@fonlan/dsh-model-router 0.1.0 → 0.2.0
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.md +2 -0
- package/lib/client.js +286 -9
- package/lib/client.js.map +1 -1
- package/lib/index.js +50 -11
- package/lib/types/server/service.d.ts +4 -0
- package/lib/types/shared/config.d.ts +6 -0
- package/package.json +8 -2
- package/src/client/api.ts +4 -0
- package/src/client/index.tsx +7 -4
- package/src/client/locales.ts +20 -0
- package/src/client/route-switcher.css +136 -0
- package/src/client/route-switcher.tsx +252 -0
- package/src/client/settings-section.css +35 -0
- package/src/client/settings-section.tsx +29 -0
- package/src/server/rpc.ts +8 -0
- package/src/server/service.ts +19 -3
- package/src/shared/config.ts +12 -4
|
@@ -58,6 +58,7 @@ export function makeSettingsSection(ctx: ClientContext): () => JSX.Element {
|
|
|
58
58
|
const [loading, setLoading] = useState(true)
|
|
59
59
|
const [busyModel, setBusyModel] = useState<string | null>(null)
|
|
60
60
|
const [drag, setDrag] = useState<{ modelId: string; index: number } | null>(null)
|
|
61
|
+
const [quickSwitchBusy, setQuickSwitchBusy] = useState(false)
|
|
61
62
|
|
|
62
63
|
const load = useCallback(async () => {
|
|
63
64
|
setLoading(true)
|
|
@@ -110,11 +111,39 @@ export function makeSettingsSection(ctx: ClientContext): () => JSX.Element {
|
|
|
110
111
|
}
|
|
111
112
|
}, [t])
|
|
112
113
|
|
|
114
|
+
const toggleQuickSwitch = useCallback(async (value: boolean) => {
|
|
115
|
+
if (state === null) return
|
|
116
|
+
const previous = state.showQuickSwitch
|
|
117
|
+
setState(prev => prev === null ? prev : { ...prev, showQuickSwitch: value })
|
|
118
|
+
setQuickSwitchBusy(true)
|
|
119
|
+
try {
|
|
120
|
+
setState(await api.setShowQuickSwitch(value))
|
|
121
|
+
setError(null)
|
|
122
|
+
} catch (cause) {
|
|
123
|
+
setState(prev => prev === null ? prev : { ...prev, showQuickSwitch: previous })
|
|
124
|
+
setError(cause instanceof Error ? cause.message : String(cause))
|
|
125
|
+
} finally {
|
|
126
|
+
setQuickSwitchBusy(false)
|
|
127
|
+
}
|
|
128
|
+
}, [state])
|
|
129
|
+
|
|
113
130
|
const models = useMemo(() => state?.models ?? [], [state])
|
|
114
131
|
|
|
115
132
|
return (
|
|
116
133
|
<div className="mr-root">
|
|
117
134
|
<p className="mr-description">{t('settingsDescription')}</p>
|
|
135
|
+
<label className="mr-quick-switch">
|
|
136
|
+
<input
|
|
137
|
+
type="checkbox"
|
|
138
|
+
checked={state?.showQuickSwitch ?? true}
|
|
139
|
+
disabled={loading || quickSwitchBusy}
|
|
140
|
+
onChange={(event) => void toggleQuickSwitch(event.target.checked)}
|
|
141
|
+
/>
|
|
142
|
+
<span className="mr-quick-switch-copy">
|
|
143
|
+
<span className="mr-quick-switch-label">{t('showQuickSwitchLabel')}</span>
|
|
144
|
+
<span className="mr-quick-switch-desc">{t('showQuickSwitchDescription')}</span>
|
|
145
|
+
</span>
|
|
146
|
+
</label>
|
|
118
147
|
<div className="mr-toolbar">
|
|
119
148
|
<span className="mr-count">{t('modelsCount', { count: models.length })}</span>
|
|
120
149
|
<button type="button" className="mr-button" onClick={() => void load()} disabled={loading}>
|
package/src/server/rpc.ts
CHANGED
|
@@ -134,6 +134,14 @@ export function registerApiRoutes(ctx: Context, service: ModelRouterService): ()
|
|
|
134
134
|
return service.state()
|
|
135
135
|
})
|
|
136
136
|
|
|
137
|
+
route('set-show-quick-switch', async (p) => {
|
|
138
|
+
if (typeof p.value !== 'boolean') {
|
|
139
|
+
throw new Error('value 必须是布尔值')
|
|
140
|
+
}
|
|
141
|
+
await service.setShowQuickSwitch(p.value)
|
|
142
|
+
return service.state()
|
|
143
|
+
})
|
|
144
|
+
|
|
137
145
|
return () => {
|
|
138
146
|
for (const dispose of disposers) dispose()
|
|
139
147
|
}
|
package/src/server/service.ts
CHANGED
|
@@ -29,12 +29,17 @@ export const RouterConfigSchema: z<RouterConfigShape> = z.object({
|
|
|
29
29
|
order: z.array(z.string()),
|
|
30
30
|
active: z.string(),
|
|
31
31
|
})),
|
|
32
|
+
// Older documents predate the toggle; normalizeConfig defaults to true.
|
|
33
|
+
// Schemastery object keys are input-optional, so absent is valid here.
|
|
34
|
+
showQuickSwitch: z.boolean(),
|
|
32
35
|
})
|
|
33
36
|
|
|
34
|
-
const EMPTY_CONFIG: RouterConfigShape = { models: {} }
|
|
37
|
+
const EMPTY_CONFIG: RouterConfigShape = { models: {}, showQuickSwitch: true }
|
|
35
38
|
|
|
36
39
|
/** The settings-page state view served by the API. */
|
|
37
40
|
export interface ModelRouterState {
|
|
41
|
+
/** Whether the composer quick route-switcher button is enabled. */
|
|
42
|
+
showQuickSwitch: boolean
|
|
38
43
|
providers: Array<{ id: string; name: string; credentialConfigured: boolean }>
|
|
39
44
|
models: Array<{
|
|
40
45
|
id: string
|
|
@@ -159,7 +164,7 @@ export class ModelRouterService implements RouterFacts {
|
|
|
159
164
|
}
|
|
160
165
|
if (!changed) return
|
|
161
166
|
try {
|
|
162
|
-
await this.scope.replace({ models: next })
|
|
167
|
+
await this.scope.replace({ models: next, showQuickSwitch: this.config().showQuickSwitch })
|
|
163
168
|
} catch (error) {
|
|
164
169
|
this.ctx.logger.warn(`model-router: reconcile persist failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
165
170
|
}
|
|
@@ -181,6 +186,7 @@ export class ModelRouterService implements RouterFacts {
|
|
|
181
186
|
const names = catalog.providerNames
|
|
182
187
|
const creds = catalog.credentialConfigured
|
|
183
188
|
return {
|
|
189
|
+
showQuickSwitch: config.showQuickSwitch,
|
|
184
190
|
providers: catalog.providers.map(provider => ({
|
|
185
191
|
id: provider.id,
|
|
186
192
|
name: names.get(provider.id) ?? provider.id,
|
|
@@ -230,10 +236,20 @@ export class ModelRouterService implements RouterFacts {
|
|
|
230
236
|
await this.persist(models)
|
|
231
237
|
}
|
|
232
238
|
|
|
239
|
+
/** Toggle the composer quick route-switcher button (global, persisted). */
|
|
240
|
+
async setShowQuickSwitch(value: boolean): Promise<void> {
|
|
241
|
+
if (this.scope === undefined) {
|
|
242
|
+
throw new Error('model-router: settings service is not available in this profile')
|
|
243
|
+
}
|
|
244
|
+
const current = this.config()
|
|
245
|
+
if (current.showQuickSwitch === value) return
|
|
246
|
+
await this.scope.replace({ models: current.models, showQuickSwitch: value })
|
|
247
|
+
}
|
|
248
|
+
|
|
233
249
|
private async persist(models: Record<string, { order: string[]; active: string }>): Promise<void> {
|
|
234
250
|
if (this.scope === undefined) {
|
|
235
251
|
throw new Error('model-router: settings service is not available in this profile')
|
|
236
252
|
}
|
|
237
|
-
await this.scope.replace({ models })
|
|
253
|
+
await this.scope.replace({ models, showQuickSwitch: this.config().showQuickSwitch })
|
|
238
254
|
}
|
|
239
255
|
}
|
package/src/shared/config.ts
CHANGED
|
@@ -56,6 +56,12 @@ export interface RouterModelConfig {
|
|
|
56
56
|
/** The whole router config document. */
|
|
57
57
|
export interface RouterConfigShape {
|
|
58
58
|
models: Record<string, RouterModelConfig>
|
|
59
|
+
/**
|
|
60
|
+
* Whether the quick route-switcher button is shown beside the composer
|
|
61
|
+
* model selector (settings → 模型路由 → 显示快速路由切换按钮). Stored
|
|
62
|
+
* globally; absent/unknown documents normalize to `true`.
|
|
63
|
+
*/
|
|
64
|
+
showQuickSwitch: boolean
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
export const ROUTER_PROVIDER_ID = 'model-router'
|
|
@@ -216,9 +222,11 @@ export function setOrder(
|
|
|
216
222
|
/** Shape-validate an unknown value as a RouterConfigShape (lenient). */
|
|
217
223
|
export function normalizeConfig(value: unknown): RouterConfigShape {
|
|
218
224
|
const models: Record<string, RouterModelConfig> = {}
|
|
219
|
-
if (typeof value !== 'object' || value === null) return { models }
|
|
220
|
-
const
|
|
221
|
-
|
|
225
|
+
if (typeof value !== 'object' || value === null) return { models, showQuickSwitch: true }
|
|
226
|
+
const root = value as { models?: unknown; showQuickSwitch?: unknown }
|
|
227
|
+
const showQuickSwitch = root.showQuickSwitch !== false
|
|
228
|
+
const raw = root.models
|
|
229
|
+
if (typeof raw !== 'object' || raw === null) return { models, showQuickSwitch }
|
|
222
230
|
for (const [id, entry] of Object.entries(raw as Record<string, unknown>)) {
|
|
223
231
|
if (typeof entry !== 'object' || entry === null) continue
|
|
224
232
|
const e = entry as { order?: unknown; active?: unknown }
|
|
@@ -227,5 +235,5 @@ export function normalizeConfig(value: unknown): RouterConfigShape {
|
|
|
227
235
|
if (active === '') continue
|
|
228
236
|
models[id] = { order, active }
|
|
229
237
|
}
|
|
230
|
-
return { models }
|
|
238
|
+
return { models, showQuickSwitch }
|
|
231
239
|
}
|