@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.
@@ -1,310 +0,0 @@
1
- /**
2
- * The Model Parameters settings page: master + per-field toggles, catalog TTL,
3
- * provider→models.dev mapping overrides, a manual refresh/fill action, the
4
- * catalog freshness card, and the last fill report (filled / touched /
5
- * unmatched). All reads/mutations go through the plugin's fenced API; config
6
- * persists into the `model-parameters` settings namespace (settings.yaml).
7
- */
8
- import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
9
- import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
10
- import type { Translate } from '@deepseek-ai/dsh-client-ui-slots'
11
- import { LOCALE_NS } from './locales'
12
- import { api, type PluginState } from './api'
13
- import type { ModelParametersConfig } from '../shared/config.js'
14
- import './settings-section.css'
15
-
16
- function useLocaleRevision(ctx: ClientContext): number {
17
- const subscribe = useCallback(
18
- (onChange: () => void) => {
19
- try {
20
- return ctx.locale.subscribe(onChange)
21
- } catch {
22
- return () => {}
23
- }
24
- },
25
- [ctx],
26
- )
27
- const getSnapshot = useCallback(() => {
28
- try {
29
- return ctx.locale.getLocale().revision
30
- } catch {
31
- return 0
32
- }
33
- }, [ctx])
34
- return useSyncExternalStore(subscribe, getSnapshot)
35
- }
36
-
37
- function formatTime(epochMs: number): string {
38
- try {
39
- return new Date(epochMs).toLocaleString()
40
- } catch {
41
- return String(epochMs)
42
- }
43
- }
44
-
45
- export function makeSettingsSection(ctx: ClientContext): () => JSX.Element {
46
- const t: Translate = (() => {
47
- try {
48
- return ctx.locale.bind(LOCALE_NS) as unknown as Translate
49
- } catch {
50
- return (key: string) => key
51
- }
52
- })()
53
-
54
- return function ModelParametersSettingsSection(): JSX.Element {
55
- useLocaleRevision(ctx)
56
- const [state, setState] = useState<PluginState | null>(null)
57
- const [error, setError] = useState<string | null>(null)
58
- const [loading, setLoading] = useState(true)
59
- const [busy, setBusy] = useState(false)
60
- const [saving, setSaving] = useState(false)
61
- const [mappingRows, setMappingRows] = useState<Array<{ from: string; to: string }>>([])
62
- const [newFrom, setNewFrom] = useState('')
63
- const [newTo, setNewTo] = useState('')
64
-
65
- const load = useCallback(async () => {
66
- setLoading(true)
67
- try {
68
- const next = await api.state()
69
- setState(next)
70
- setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({ from, to })))
71
- setError(null)
72
- } catch (cause) {
73
- setError(cause instanceof Error ? cause.message : String(cause))
74
- } finally {
75
- setLoading(false)
76
- }
77
- }, [])
78
-
79
- useEffect(() => {
80
- void load()
81
- }, [load])
82
-
83
- const patchConfig = useCallback(async (patch: Partial<ModelParametersConfig>) => {
84
- setSaving(true)
85
- try {
86
- const next = await api.setConfig(patch)
87
- setState(next)
88
- setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({ from, to })))
89
- setError(null)
90
- } catch (cause) {
91
- setError(cause instanceof Error ? cause.message : String(cause))
92
- } finally {
93
- setSaving(false)
94
- }
95
- }, [])
96
-
97
- const refresh = useCallback(async () => {
98
- setBusy(true)
99
- try {
100
- setState(await api.refresh())
101
- setError(null)
102
- } catch (cause) {
103
- setError(cause instanceof Error ? cause.message : String(cause))
104
- } finally {
105
- setBusy(false)
106
- }
107
- }, [])
108
-
109
- const reconcileNow = useCallback(async () => {
110
- setBusy(true)
111
- try {
112
- setState(await api.reconcile())
113
- setError(null)
114
- } catch (cause) {
115
- setError(cause instanceof Error ? cause.message : String(cause))
116
- } finally {
117
- setBusy(false)
118
- }
119
- }, [])
120
-
121
- const applyMappings = useCallback(async () => {
122
- const providerMap: Record<string, string> = {}
123
- for (const row of mappingRows) {
124
- const from = row.from.trim()
125
- const to = row.to.trim()
126
- if (from.length > 0 && to.length > 0) providerMap[from] = to
127
- }
128
- await patchConfig({ providerMap })
129
- }, [mappingRows, patchConfig])
130
-
131
- if (error !== null && state === null) {
132
- return (
133
- <p className="mp-error">{t('error')}: {error}</p>
134
- )
135
- }
136
- if (loading || state === null) {
137
- return <p className="mp-muted">{t('saving')}</p>
138
- }
139
-
140
- const config = state.config
141
- const catalog = state.catalog
142
- const report = state.report
143
-
144
- return (
145
- <div className="mp-root">
146
- <p className="mp-desc">{t('description')}</p>
147
-
148
- <section className="mp-card">
149
- <h4 className="mp-card-title">{t('fields')}</h4>
150
- <label className="mp-toggle">
151
- <input
152
- type="checkbox"
153
- checked={config.enabled}
154
- disabled={saving}
155
- onChange={(event) => void patchConfig({ enabled: event.target.checked })}
156
- />
157
- <span className="mp-toggle-body">
158
- <span className="mp-toggle-label">{t('master')}</span>
159
- <span className="mp-toggle-desc">{t('masterDesc')}</span>
160
- </span>
161
- </label>
162
- {([
163
- ['fillName', t('fillName'), t('fillNameDesc')],
164
- ['fillContext', t('fillContext'), t('fillContextDesc')],
165
- ['fillMaxTokens', t('fillMaxTokens'), t('fillMaxTokensDesc')],
166
- ['fillReasoning', t('fillReasoning'), t('fillReasoningDesc')],
167
- ['fillInput', t('fillInput'), t('fillInputDesc')],
168
- ] as const).map(([key, label, desc]) => (
169
- <label className="mp-toggle mp-toggle-sub" key={key}>
170
- <input
171
- type="checkbox"
172
- checked={config[key]}
173
- disabled={saving || !config.enabled}
174
- onChange={(event) => void patchConfig({ [key]: event.target.checked })}
175
- />
176
- <span className="mp-toggle-body">
177
- <span className="mp-toggle-label">{label}</span>
178
- <span className="mp-toggle-desc">{desc}</span>
179
- </span>
180
- </label>
181
- ))}
182
- </section>
183
-
184
- <section className="mp-card">
185
- <h4 className="mp-card-title">{t('catalog')}</h4>
186
- <div className="mp-meta">
187
- <span>{t('catalogEntries')}: <strong>{catalog.entries.toLocaleString()}</strong></span>
188
- <span>{t('catalogProviders')}: <strong>{catalog.providers.toLocaleString()}</strong></span>
189
- <span>
190
- {t('catalogFetched')}:{' '}
191
- <strong>{catalog.fetchedAt > 0 ? formatTime(catalog.fetchedAt) : t('catalogNever')}</strong>{' '}
192
- {catalog.fetchedAt > 0 && (
193
- <span className={catalog.fresh ? 'mp-badge mp-badge-ok' : 'mp-badge mp-badge-warn'}>
194
- {catalog.fresh ? t('catalogFresh') : t('catalogStale')}
195
- </span>
196
- )}
197
- </span>
198
- <label className="mp-ttl">
199
- {t('ttl')}
200
- <input
201
- type="number"
202
- min={1}
203
- max={90}
204
- value={config.ttlDays}
205
- disabled={saving}
206
- onChange={(event) => {
207
- const days = Number(event.target.value)
208
- if (Number.isFinite(days) && days >= 1 && days <= 90) void patchConfig({ ttlDays: Math.round(days) })
209
- }}
210
- />
211
- </label>
212
- </div>
213
- <div className="mp-actions">
214
- <button className="mp-button" disabled={busy} onClick={() => void refresh()}>
215
- {busy ? t('refreshBusy') : t('refresh')}
216
- </button>
217
- <button className="mp-button mp-button-ghost" disabled={busy} onClick={() => void reconcileNow()}>
218
- {t('reconcileNow')}
219
- </button>
220
- </div>
221
- </section>
222
-
223
- <section className="mp-card">
224
- <h4 className="mp-card-title">{t('providerMap')}</h4>
225
- <p className="mp-toggle-desc">{t('providerMapDesc')}</p>
226
- {mappingRows.map((row, index) => (
227
- <div className="mp-map-row" key={index}>
228
- <input
229
- value={row.from}
230
- disabled={saving}
231
- onChange={(event) => {
232
- const next = [...mappingRows]
233
- next[index] = { ...row, from: event.target.value }
234
- setMappingRows(next)
235
- }}
236
- />
237
- <span className="mp-map-arrow">→</span>
238
- <input
239
- value={row.to}
240
- disabled={saving}
241
- onChange={(event) => {
242
- const next = [...mappingRows]
243
- next[index] = { ...row, to: event.target.value }
244
- setMappingRows(next)
245
- }}
246
- />
247
- <button
248
- className="mp-button mp-button-ghost"
249
- disabled={saving}
250
- onClick={() => setMappingRows(mappingRows.filter((_, i) => i !== index))}
251
- >
252
- {t('removeMapping')}
253
- </button>
254
- </div>
255
- ))}
256
- <div className="mp-map-row">
257
- <input
258
- placeholder="dsh provider"
259
- value={newFrom}
260
- onChange={(event) => setNewFrom(event.target.value)}
261
- />
262
- <span className="mp-map-arrow">→</span>
263
- <input
264
- placeholder="models.dev provider"
265
- value={newTo}
266
- onChange={(event) => setNewTo(event.target.value)}
267
- />
268
- <button
269
- className="mp-button"
270
- disabled={newFrom.trim().length === 0 || newTo.trim().length === 0}
271
- onClick={() => {
272
- setMappingRows([...mappingRows, { from: newFrom.trim(), to: newTo.trim() }])
273
- setNewFrom('')
274
- setNewTo('')
275
- }}
276
- >
277
- {t('addMapping')}
278
- </button>
279
- </div>
280
- <div className="mp-actions">
281
- <button className="mp-button mp-button-ghost" disabled={saving} onClick={() => void applyMappings()}>
282
- {saving ? t('saving') : '✓ ' + t('providerMap')}
283
- </button>
284
- </div>
285
- </section>
286
-
287
- <section className="mp-card">
288
- <h4 className="mp-card-title">{t('report')}</h4>
289
- {report === null ? (
290
- <p className="mp-muted">{t('reportNever')}</p>
291
- ) : (
292
- <ul className="mp-report">
293
- <li>{t('reportAt')}: {formatTime(report.at)}</li>
294
- <li>{t('reportFilled')}: <strong>{report.filled}</strong></li>
295
- <li>{t('reportTouched')}: <strong>{report.touched}</strong></li>
296
- <li>
297
- {t('reportUnmatched')}:{' '}
298
- {report.unmatched.length === 0
299
- ? t('reportEmpty')
300
- : <code className="mp-code">{report.unmatched.join(', ')}</code>}
301
- </li>
302
- </ul>
303
- )}
304
- </section>
305
-
306
- {error !== null && <p className="mp-error">{error}</p>}
307
- </div>
308
- )
309
- }
310
- }