@fonlan/dsh-model-parameters 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +16 -4
- package/README.md +14 -4
- package/lib/client.js +379 -302
- package/lib/client.js.map +1 -1
- package/package.json +5 -2
- package/src/client/index.tsx +35 -14
- package/src/client/locales.ts +6 -2
- package/src/client/settings-card.css +330 -0
- package/src/client/settings-card.tsx +395 -0
- package/src/client/settings-section.css +0 -172
- package/src/client/settings-section.tsx +0 -310
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Model Parameters Settings Card: master + per-field toggles, catalog TTL,
|
|
3
|
+
* provider→models.dev mapping overrides, a manual refresh/fill action, the
|
|
4
|
+
* catalog freshness line, and the last fill report (filled / touched /
|
|
5
|
+
* unmatched).
|
|
6
|
+
*
|
|
7
|
+
* The card registers into the `settings.plugin.item` slot keyed by the
|
|
8
|
+
* `model-parameters` settings namespace, so it renders inside
|
|
9
|
+
* 设置 → 插件 → 插件配置, stacked with the built-in plugin cards. The chrome is
|
|
10
|
+
* self-drawn (external plugins cannot import the built-in PluginCard) with
|
|
11
|
+
* styles aligned to it: an expandable header (collapsed by default) and a body
|
|
12
|
+
* that discloses the controls in place.
|
|
13
|
+
*
|
|
14
|
+
* All reads/mutations go through the plugin's fenced API; config persists into
|
|
15
|
+
* the `model-parameters` settings namespace (settings.yaml).
|
|
16
|
+
*/
|
|
17
|
+
import { useCallback, useEffect, useState, useSyncExternalStore } from 'react'
|
|
18
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
|
19
|
+
import type { Translate } from '@deepseek-ai/dsh-client-ui-slots'
|
|
20
|
+
import { LOCALE_NS } from './locales'
|
|
21
|
+
import { api, type PluginState } from './api'
|
|
22
|
+
import type { ModelParametersConfig } from '../shared/config.js'
|
|
23
|
+
import './settings-card.css'
|
|
24
|
+
|
|
25
|
+
function useLocaleRevision(ctx: ClientContext): number {
|
|
26
|
+
const subscribe = useCallback(
|
|
27
|
+
(onChange: () => void) => {
|
|
28
|
+
try {
|
|
29
|
+
return ctx.locale.subscribe(onChange)
|
|
30
|
+
} catch {
|
|
31
|
+
return () => {}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
[ctx],
|
|
35
|
+
)
|
|
36
|
+
const getSnapshot = useCallback(() => {
|
|
37
|
+
try {
|
|
38
|
+
return ctx.locale.getLocale().revision
|
|
39
|
+
} catch {
|
|
40
|
+
return 0
|
|
41
|
+
}
|
|
42
|
+
}, [ctx])
|
|
43
|
+
return useSyncExternalStore(subscribe, getSnapshot)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatTime(epochMs: number): string {
|
|
47
|
+
try {
|
|
48
|
+
return new Date(epochMs).toLocaleString()
|
|
49
|
+
} catch {
|
|
50
|
+
return String(epochMs)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function makeSettingsCard(ctx: ClientContext): () => JSX.Element {
|
|
55
|
+
const t: Translate = (() => {
|
|
56
|
+
try {
|
|
57
|
+
return ctx.locale.bind(LOCALE_NS) as unknown as Translate
|
|
58
|
+
} catch {
|
|
59
|
+
return (key: string) => key
|
|
60
|
+
}
|
|
61
|
+
})()
|
|
62
|
+
|
|
63
|
+
return function ModelParametersCard(): JSX.Element {
|
|
64
|
+
useLocaleRevision(ctx)
|
|
65
|
+
// Card-local disclosure: collapsed by default, like the built-in plugin cards.
|
|
66
|
+
const [open, setOpen] = useState(false)
|
|
67
|
+
const [state, setState] = useState<PluginState | null>(null)
|
|
68
|
+
const [error, setError] = useState<string | null>(null)
|
|
69
|
+
const [loading, setLoading] = useState(true)
|
|
70
|
+
const [busy, setBusy] = useState(false)
|
|
71
|
+
const [saving, setSaving] = useState(false)
|
|
72
|
+
const [mappingRows, setMappingRows] = useState<Array<{ from: string; to: string }>>([])
|
|
73
|
+
const [newFrom, setNewFrom] = useState('')
|
|
74
|
+
const [newTo, setNewTo] = useState('')
|
|
75
|
+
|
|
76
|
+
const load = useCallback(async () => {
|
|
77
|
+
setLoading(true)
|
|
78
|
+
try {
|
|
79
|
+
const next = await api.state()
|
|
80
|
+
setState(next)
|
|
81
|
+
setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({ from, to })))
|
|
82
|
+
setError(null)
|
|
83
|
+
} catch (cause) {
|
|
84
|
+
setError(cause instanceof Error ? cause.message : String(cause))
|
|
85
|
+
} finally {
|
|
86
|
+
setLoading(false)
|
|
87
|
+
}
|
|
88
|
+
}, [])
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
void load()
|
|
92
|
+
}, [load])
|
|
93
|
+
|
|
94
|
+
const patchConfig = useCallback(async (patch: Partial<ModelParametersConfig>) => {
|
|
95
|
+
setSaving(true)
|
|
96
|
+
try {
|
|
97
|
+
const next = await api.setConfig(patch)
|
|
98
|
+
setState(next)
|
|
99
|
+
setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({ from, to })))
|
|
100
|
+
setError(null)
|
|
101
|
+
} catch (cause) {
|
|
102
|
+
setError(cause instanceof Error ? cause.message : String(cause))
|
|
103
|
+
} finally {
|
|
104
|
+
setSaving(false)
|
|
105
|
+
}
|
|
106
|
+
}, [])
|
|
107
|
+
|
|
108
|
+
const refresh = useCallback(async () => {
|
|
109
|
+
setBusy(true)
|
|
110
|
+
try {
|
|
111
|
+
setState(await api.refresh())
|
|
112
|
+
setError(null)
|
|
113
|
+
} catch (cause) {
|
|
114
|
+
setError(cause instanceof Error ? cause.message : String(cause))
|
|
115
|
+
} finally {
|
|
116
|
+
setBusy(false)
|
|
117
|
+
}
|
|
118
|
+
}, [])
|
|
119
|
+
|
|
120
|
+
const reconcileNow = useCallback(async () => {
|
|
121
|
+
setBusy(true)
|
|
122
|
+
try {
|
|
123
|
+
setState(await api.reconcile())
|
|
124
|
+
setError(null)
|
|
125
|
+
} catch (cause) {
|
|
126
|
+
setError(cause instanceof Error ? cause.message : String(cause))
|
|
127
|
+
} finally {
|
|
128
|
+
setBusy(false)
|
|
129
|
+
}
|
|
130
|
+
}, [])
|
|
131
|
+
|
|
132
|
+
const applyMappings = useCallback(async () => {
|
|
133
|
+
const providerMap: Record<string, string> = {}
|
|
134
|
+
for (const row of mappingRows) {
|
|
135
|
+
const from = row.from.trim()
|
|
136
|
+
const to = row.to.trim()
|
|
137
|
+
if (from.length > 0 && to.length > 0) providerMap[from] = to
|
|
138
|
+
}
|
|
139
|
+
await patchConfig({ providerMap })
|
|
140
|
+
}, [mappingRows, patchConfig])
|
|
141
|
+
|
|
142
|
+
const title = t('settingsTitle')
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<li className="mp-card" data-open={open ? '' : undefined}>
|
|
146
|
+
<button type="button" className="mp-card-head" aria-expanded={open}
|
|
147
|
+
aria-label={(open ? t('collapse') : t('expand')) + ':' + title}
|
|
148
|
+
onClick={() => setOpen(!open)}>
|
|
149
|
+
<span className="mp-card-headText">
|
|
150
|
+
<span className="mp-card-title">{title}</span>
|
|
151
|
+
<span className="mp-card-sub">{t('cardSub')}</span>
|
|
152
|
+
</span>
|
|
153
|
+
<span className="mp-card-chevron" data-open={open ? '' : undefined} aria-hidden="true">
|
|
154
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
|
155
|
+
<path d="M3.5 5.25L7 8.75L10.5 5.25" stroke="currentColor" strokeWidth="1.5"
|
|
156
|
+
strokeLinecap="round" strokeLinejoin="round" />
|
|
157
|
+
</svg>
|
|
158
|
+
</span>
|
|
159
|
+
</button>
|
|
160
|
+
{open && (
|
|
161
|
+
<div className="mp-card-body">
|
|
162
|
+
{error !== null && state === null ? (
|
|
163
|
+
<p className="mp-error">{t('error')}: {error}</p>
|
|
164
|
+
) : loading || state === null ? (
|
|
165
|
+
<p className="mp-muted">{t('saving')}</p>
|
|
166
|
+
) : (
|
|
167
|
+
<ModelParametersCardBody
|
|
168
|
+
t={t}
|
|
169
|
+
state={state}
|
|
170
|
+
error={error}
|
|
171
|
+
saving={saving}
|
|
172
|
+
busy={busy}
|
|
173
|
+
mappingRows={mappingRows}
|
|
174
|
+
newFrom={newFrom}
|
|
175
|
+
newTo={newTo}
|
|
176
|
+
setNewFrom={setNewFrom}
|
|
177
|
+
setNewTo={setNewTo}
|
|
178
|
+
setMappingRows={setMappingRows}
|
|
179
|
+
onPatchConfig={patchConfig}
|
|
180
|
+
onRefresh={refresh}
|
|
181
|
+
onReconcile={reconcileNow}
|
|
182
|
+
onApplyMappings={applyMappings}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
</li>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The card body: divider-separated control groups, styled like the built-in cards. */
|
|
193
|
+
function ModelParametersCardBody(props: {
|
|
194
|
+
t: Translate
|
|
195
|
+
state: PluginState
|
|
196
|
+
error: string | null
|
|
197
|
+
saving: boolean
|
|
198
|
+
busy: boolean
|
|
199
|
+
mappingRows: Array<{ from: string; to: string }>
|
|
200
|
+
newFrom: string
|
|
201
|
+
newTo: string
|
|
202
|
+
setNewFrom: (value: string) => void
|
|
203
|
+
setNewTo: (value: string) => void
|
|
204
|
+
setMappingRows: (rows: Array<{ from: string; to: string }>) => void
|
|
205
|
+
onPatchConfig: (patch: Partial<ModelParametersConfig>) => Promise<void>
|
|
206
|
+
onRefresh: () => Promise<void>
|
|
207
|
+
onReconcile: () => Promise<void>
|
|
208
|
+
onApplyMappings: () => Promise<void>
|
|
209
|
+
}): JSX.Element {
|
|
210
|
+
const {
|
|
211
|
+
t, state, error, saving, busy,
|
|
212
|
+
mappingRows, newFrom, newTo,
|
|
213
|
+
setNewFrom, setNewTo, setMappingRows,
|
|
214
|
+
onPatchConfig, onRefresh, onReconcile, onApplyMappings,
|
|
215
|
+
} = props
|
|
216
|
+
|
|
217
|
+
const config = state.config
|
|
218
|
+
const catalog = state.catalog
|
|
219
|
+
const report = state.report
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div className="mp-body">
|
|
223
|
+
<section className="mp-group">
|
|
224
|
+
<h4 className="mp-group-title">{t('fields')}</h4>
|
|
225
|
+
<label className="mp-toggle">
|
|
226
|
+
<input
|
|
227
|
+
type="checkbox"
|
|
228
|
+
checked={config.enabled}
|
|
229
|
+
disabled={saving}
|
|
230
|
+
onChange={(event) => void onPatchConfig({ enabled: event.target.checked })}
|
|
231
|
+
/>
|
|
232
|
+
<span className="mp-toggle-body">
|
|
233
|
+
<span className="mp-toggle-label">{t('master')}</span>
|
|
234
|
+
<span className="mp-toggle-desc">{t('masterDesc')}</span>
|
|
235
|
+
</span>
|
|
236
|
+
</label>
|
|
237
|
+
{([
|
|
238
|
+
['fillName', t('fillName'), t('fillNameDesc')],
|
|
239
|
+
['fillContext', t('fillContext'), t('fillContextDesc')],
|
|
240
|
+
['fillMaxTokens', t('fillMaxTokens'), t('fillMaxTokensDesc')],
|
|
241
|
+
['fillReasoning', t('fillReasoning'), t('fillReasoningDesc')],
|
|
242
|
+
['fillInput', t('fillInput'), t('fillInputDesc')],
|
|
243
|
+
] as const).map(([key, label, desc]) => (
|
|
244
|
+
<label className="mp-toggle mp-toggle-sub" key={key}>
|
|
245
|
+
<input
|
|
246
|
+
type="checkbox"
|
|
247
|
+
checked={config[key]}
|
|
248
|
+
disabled={saving || !config.enabled}
|
|
249
|
+
onChange={(event) => void onPatchConfig({ [key]: event.target.checked })}
|
|
250
|
+
/>
|
|
251
|
+
<span className="mp-toggle-body">
|
|
252
|
+
<span className="mp-toggle-label">{label}</span>
|
|
253
|
+
<span className="mp-toggle-desc">{desc}</span>
|
|
254
|
+
</span>
|
|
255
|
+
</label>
|
|
256
|
+
))}
|
|
257
|
+
</section>
|
|
258
|
+
|
|
259
|
+
<hr className="mp-divider" />
|
|
260
|
+
|
|
261
|
+
<section className="mp-group">
|
|
262
|
+
<h4 className="mp-group-title">{t('catalog')}</h4>
|
|
263
|
+
<div className="mp-meta">
|
|
264
|
+
<span>{t('catalogEntries')}: <strong>{catalog.entries.toLocaleString()}</strong></span>
|
|
265
|
+
<span>{t('catalogProviders')}: <strong>{catalog.providers.toLocaleString()}</strong></span>
|
|
266
|
+
<span>
|
|
267
|
+
{t('catalogFetched')}:{' '}
|
|
268
|
+
<strong>{catalog.fetchedAt > 0 ? formatTime(catalog.fetchedAt) : t('catalogNever')}</strong>{' '}
|
|
269
|
+
{catalog.fetchedAt > 0 && (
|
|
270
|
+
<span className={catalog.fresh ? 'mp-badge mp-badge-ok' : 'mp-badge mp-badge-warn'}>
|
|
271
|
+
{catalog.fresh ? t('catalogFresh') : t('catalogStale')}
|
|
272
|
+
</span>
|
|
273
|
+
)}
|
|
274
|
+
</span>
|
|
275
|
+
<label className="mp-ttl">
|
|
276
|
+
{t('ttl')}
|
|
277
|
+
<input
|
|
278
|
+
className="mp-input mp-input-narrow"
|
|
279
|
+
type="number"
|
|
280
|
+
min={1}
|
|
281
|
+
max={90}
|
|
282
|
+
value={config.ttlDays}
|
|
283
|
+
disabled={saving}
|
|
284
|
+
onChange={(event) => {
|
|
285
|
+
const days = Number(event.target.value)
|
|
286
|
+
if (Number.isFinite(days) && days >= 1 && days <= 90) void onPatchConfig({ ttlDays: Math.round(days) })
|
|
287
|
+
}}
|
|
288
|
+
/>
|
|
289
|
+
</label>
|
|
290
|
+
</div>
|
|
291
|
+
<div className="mp-actions">
|
|
292
|
+
<button className="mp-button" disabled={busy} onClick={() => void onRefresh()}>
|
|
293
|
+
{busy ? t('refreshBusy') : t('refresh')}
|
|
294
|
+
</button>
|
|
295
|
+
<button className="mp-button mp-button-ghost" disabled={busy} onClick={() => void onReconcile()}>
|
|
296
|
+
{t('reconcileNow')}
|
|
297
|
+
</button>
|
|
298
|
+
</div>
|
|
299
|
+
</section>
|
|
300
|
+
|
|
301
|
+
<hr className="mp-divider" />
|
|
302
|
+
|
|
303
|
+
<section className="mp-group">
|
|
304
|
+
<h4 className="mp-group-title">{t('providerMap')}</h4>
|
|
305
|
+
<p className="mp-desc">{t('providerMapDesc')}</p>
|
|
306
|
+
{mappingRows.map((row, index) => (
|
|
307
|
+
<div className="mp-map-row" key={index}>
|
|
308
|
+
<input
|
|
309
|
+
className="mp-input"
|
|
310
|
+
value={row.from}
|
|
311
|
+
disabled={saving}
|
|
312
|
+
onChange={(event) => {
|
|
313
|
+
const next = [...mappingRows]
|
|
314
|
+
next[index] = { ...row, from: event.target.value }
|
|
315
|
+
setMappingRows(next)
|
|
316
|
+
}}
|
|
317
|
+
/>
|
|
318
|
+
<span className="mp-map-arrow">→</span>
|
|
319
|
+
<input
|
|
320
|
+
className="mp-input"
|
|
321
|
+
value={row.to}
|
|
322
|
+
disabled={saving}
|
|
323
|
+
onChange={(event) => {
|
|
324
|
+
const next = [...mappingRows]
|
|
325
|
+
next[index] = { ...row, to: event.target.value }
|
|
326
|
+
setMappingRows(next)
|
|
327
|
+
}}
|
|
328
|
+
/>
|
|
329
|
+
<button
|
|
330
|
+
className="mp-button mp-button-ghost"
|
|
331
|
+
disabled={saving}
|
|
332
|
+
onClick={() => setMappingRows(mappingRows.filter((_, i) => i !== index))}
|
|
333
|
+
>
|
|
334
|
+
{t('removeMapping')}
|
|
335
|
+
</button>
|
|
336
|
+
</div>
|
|
337
|
+
))}
|
|
338
|
+
<div className="mp-map-row">
|
|
339
|
+
<input
|
|
340
|
+
className="mp-input"
|
|
341
|
+
placeholder="dsh provider"
|
|
342
|
+
value={newFrom}
|
|
343
|
+
onChange={(event) => setNewFrom(event.target.value)}
|
|
344
|
+
/>
|
|
345
|
+
<span className="mp-map-arrow">→</span>
|
|
346
|
+
<input
|
|
347
|
+
className="mp-input"
|
|
348
|
+
placeholder="models.dev provider"
|
|
349
|
+
value={newTo}
|
|
350
|
+
onChange={(event) => setNewTo(event.target.value)}
|
|
351
|
+
/>
|
|
352
|
+
<button
|
|
353
|
+
className="mp-button mp-button-ghost"
|
|
354
|
+
disabled={newFrom.trim().length === 0 || newTo.trim().length === 0}
|
|
355
|
+
onClick={() => {
|
|
356
|
+
setMappingRows([...mappingRows, { from: newFrom.trim(), to: newTo.trim() }])
|
|
357
|
+
setNewFrom('')
|
|
358
|
+
setNewTo('')
|
|
359
|
+
}}
|
|
360
|
+
>
|
|
361
|
+
{t('addMapping')}
|
|
362
|
+
</button>
|
|
363
|
+
</div>
|
|
364
|
+
<div className="mp-actions">
|
|
365
|
+
<button className="mp-button" disabled={saving} onClick={() => void onApplyMappings()}>
|
|
366
|
+
{saving ? t('saving') : '✓ ' + t('providerMap')}
|
|
367
|
+
</button>
|
|
368
|
+
</div>
|
|
369
|
+
</section>
|
|
370
|
+
|
|
371
|
+
<hr className="mp-divider" />
|
|
372
|
+
|
|
373
|
+
<section className="mp-group">
|
|
374
|
+
<h4 className="mp-group-title">{t('report')}</h4>
|
|
375
|
+
{report === null ? (
|
|
376
|
+
<p className="mp-muted">{t('reportNever')}</p>
|
|
377
|
+
) : (
|
|
378
|
+
<ul className="mp-report">
|
|
379
|
+
<li>{t('reportAt')}: {formatTime(report.at)}</li>
|
|
380
|
+
<li>{t('reportFilled')}: <strong>{report.filled}</strong></li>
|
|
381
|
+
<li>{t('reportTouched')}: <strong>{report.touched}</strong></li>
|
|
382
|
+
<li>
|
|
383
|
+
{t('reportUnmatched')}:{' '}
|
|
384
|
+
{report.unmatched.length === 0
|
|
385
|
+
? t('reportEmpty')
|
|
386
|
+
: <code className="mp-code">{report.unmatched.join(', ')}</code>}
|
|
387
|
+
</li>
|
|
388
|
+
</ul>
|
|
389
|
+
)}
|
|
390
|
+
</section>
|
|
391
|
+
|
|
392
|
+
{error !== null && <p className="mp-error">{error}</p>}
|
|
393
|
+
</div>
|
|
394
|
+
)
|
|
395
|
+
}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
.mp-root {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
|
-
gap: 14px;
|
|
5
|
-
max-width: 720px;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.mp-desc {
|
|
9
|
-
margin: 0;
|
|
10
|
-
opacity: 0.85;
|
|
11
|
-
font-size: 13px;
|
|
12
|
-
line-height: 1.5;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.mp-card {
|
|
16
|
-
border: 1px solid var(--dsh-border-color, rgba(128, 128, 128, 0.25));
|
|
17
|
-
border-radius: 10px;
|
|
18
|
-
padding: 12px 14px;
|
|
19
|
-
display: flex;
|
|
20
|
-
flex-direction: column;
|
|
21
|
-
gap: 10px;
|
|
22
|
-
background: var(--dsh-surface-color, transparent);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.mp-card-title {
|
|
26
|
-
margin: 0;
|
|
27
|
-
font-size: 13px;
|
|
28
|
-
font-weight: 600;
|
|
29
|
-
opacity: 0.9;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.mp-toggle {
|
|
33
|
-
display: flex;
|
|
34
|
-
align-items: flex-start;
|
|
35
|
-
gap: 10px;
|
|
36
|
-
cursor: pointer;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.mp-toggle input[type='checkbox'] {
|
|
40
|
-
margin-top: 3px;
|
|
41
|
-
accent-color: var(--dsh-accent-color, #4f8cff);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.mp-toggle-sub {
|
|
45
|
-
padding-left: 22px;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.mp-toggle-body {
|
|
49
|
-
display: flex;
|
|
50
|
-
flex-direction: column;
|
|
51
|
-
gap: 2px;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.mp-toggle-label {
|
|
55
|
-
font-size: 13px;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.mp-toggle-desc {
|
|
59
|
-
font-size: 12px;
|
|
60
|
-
opacity: 0.65;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.mp-meta {
|
|
64
|
-
display: flex;
|
|
65
|
-
flex-wrap: wrap;
|
|
66
|
-
gap: 6px 16px;
|
|
67
|
-
font-size: 13px;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.mp-ttl {
|
|
71
|
-
display: inline-flex;
|
|
72
|
-
align-items: center;
|
|
73
|
-
gap: 6px;
|
|
74
|
-
font-size: 13px;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.mp-ttl input {
|
|
78
|
-
width: 64px;
|
|
79
|
-
padding: 3px 6px;
|
|
80
|
-
border-radius: 6px;
|
|
81
|
-
border: 1px solid var(--dsh-border-color, rgba(128, 128, 128, 0.35));
|
|
82
|
-
background: var(--dsh-input-bg, transparent);
|
|
83
|
-
color: inherit;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.mp-actions {
|
|
87
|
-
display: flex;
|
|
88
|
-
gap: 8px;
|
|
89
|
-
flex-wrap: wrap;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.mp-button {
|
|
93
|
-
padding: 6px 12px;
|
|
94
|
-
border-radius: 8px;
|
|
95
|
-
border: 1px solid var(--dsh-border-color, rgba(128, 128, 128, 0.35));
|
|
96
|
-
background: var(--dsh-accent-color, #4f8cff);
|
|
97
|
-
color: #fff;
|
|
98
|
-
font-size: 13px;
|
|
99
|
-
cursor: pointer;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.mp-button:disabled {
|
|
103
|
-
opacity: 0.5;
|
|
104
|
-
cursor: default;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.mp-button-ghost {
|
|
108
|
-
background: transparent;
|
|
109
|
-
color: inherit;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.mp-map-row {
|
|
113
|
-
display: flex;
|
|
114
|
-
align-items: center;
|
|
115
|
-
gap: 6px;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.mp-map-row input {
|
|
119
|
-
flex: 1;
|
|
120
|
-
padding: 4px 8px;
|
|
121
|
-
border-radius: 6px;
|
|
122
|
-
border: 1px solid var(--dsh-border-color, rgba(128, 128, 128, 0.35));
|
|
123
|
-
background: var(--dsh-input-bg, transparent);
|
|
124
|
-
color: inherit;
|
|
125
|
-
font-size: 13px;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.mp-map-arrow {
|
|
129
|
-
opacity: 0.6;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.mp-report {
|
|
133
|
-
margin: 0;
|
|
134
|
-
padding-left: 18px;
|
|
135
|
-
font-size: 13px;
|
|
136
|
-
line-height: 1.7;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.mp-code {
|
|
140
|
-
background: var(--dsh-code-bg, rgba(128, 128, 128, 0.15));
|
|
141
|
-
border-radius: 4px;
|
|
142
|
-
padding: 1px 5px;
|
|
143
|
-
font-size: 12px;
|
|
144
|
-
word-break: break-all;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.mp-badge {
|
|
148
|
-
font-size: 11px;
|
|
149
|
-
padding: 1px 7px;
|
|
150
|
-
border-radius: 999px;
|
|
151
|
-
margin-left: 4px;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.mp-badge-ok {
|
|
155
|
-
background: rgba(52, 199, 89, 0.18);
|
|
156
|
-
color: #34c759;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.mp-badge-warn {
|
|
160
|
-
background: rgba(255, 149, 0, 0.18);
|
|
161
|
-
color: #ff9500;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.mp-muted {
|
|
165
|
-
font-size: 13px;
|
|
166
|
-
opacity: 0.6;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.mp-error {
|
|
170
|
-
font-size: 13px;
|
|
171
|
-
color: #ff3b30;
|
|
172
|
-
}
|