@fonlan/dsh-model-router 0.3.0 → 0.3.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.md +17 -10
- package/lib/client.js +844 -239
- package/lib/client.js.map +1 -1
- package/lib/index.js +257 -30
- package/lib/types/server/adapter.d.ts +27 -1
- package/lib/types/server/service.d.ts +45 -5
- package/lib/types/shared/config.d.ts +54 -2
- package/package.json +33 -29
- package/src/client/api.ts +11 -1
- package/src/client/index.tsx +39 -16
- package/src/client/locales.ts +64 -14
- package/src/client/model-seat.css +289 -0
- package/src/client/model-seat.tsx +623 -0
- package/src/client/settings-card.css +470 -0
- package/src/client/settings-card.tsx +468 -0
- package/src/server/adapter.ts +62 -13
- package/src/server/rpc.ts +16 -0
- package/src/server/service.ts +135 -25
- package/src/shared/config.ts +142 -8
- package/src/client/route-switcher.css +0 -136
- package/src/client/route-switcher.tsx +0 -252
- package/src/client/settings-section.css +0 -245
- package/src/client/settings-section.tsx +0 -293
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonlan/dsh-model-router",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "DSH plugin: a virtual model-router provider that aggregates every configured provider and model, routes each request to the model's currently active provider, and ships a settings page for per-model provider ordering and switching.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,14 +43,6 @@
|
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
|
-
"scripts": {
|
|
47
|
-
"build": "rm -rf lib && tsc -p tsconfig.build.json && tsdown",
|
|
48
|
-
"prepare": "pnpm build",
|
|
49
|
-
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.client.json",
|
|
50
|
-
"bundle": "tsdown",
|
|
51
|
-
"watch": "tsdown --watch",
|
|
52
|
-
"test": "vitest run"
|
|
53
|
-
},
|
|
54
46
|
"dsh": {
|
|
55
47
|
"bundle": {
|
|
56
48
|
"patch": "./cordis.patch.yml"
|
|
@@ -63,6 +55,8 @@
|
|
|
63
55
|
"@deepseek-ai/dsh-client-ui-slots",
|
|
64
56
|
"@deepseek-ai/dsh-client-ui-primitives",
|
|
65
57
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
58
|
+
"@deepseek-ai/dsh-client-connection",
|
|
59
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
66
60
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
67
61
|
"@deepseek-ai/dsh-client-ui-model-selection"
|
|
68
62
|
]
|
|
@@ -71,15 +65,15 @@
|
|
|
71
65
|
"peerDependencies": {
|
|
72
66
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
73
67
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
74
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
75
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
76
|
-
"@deepseek-ai/dsh-credentials": "^0.1.
|
|
77
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.
|
|
78
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
79
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
80
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.
|
|
81
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.
|
|
82
|
-
"@deepseek-ai/dsh-client-ui-model-selection": "^0.1.
|
|
68
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-credentials": "^0.1.1-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.1-rc.2",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.2",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-model-selection": "^0.1.1-rc.2",
|
|
83
77
|
"react": "^18.2.0",
|
|
84
78
|
"react-dom": "^18.2.0"
|
|
85
79
|
},
|
|
@@ -91,16 +85,26 @@
|
|
|
91
85
|
"typescript": "^5.9.0",
|
|
92
86
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
93
87
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
94
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
95
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
96
|
-
"@deepseek-ai/dsh-credentials": "^0.1.
|
|
97
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.
|
|
98
|
-
"@deepseek-ai/dsh-
|
|
99
|
-
"@deepseek-ai/dsh-
|
|
100
|
-
"@deepseek-ai/dsh-
|
|
101
|
-
"@deepseek-ai/dsh-
|
|
102
|
-
"@deepseek-ai/dsh-client-
|
|
88
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-credentials": "^0.1.1-rc.2",
|
|
91
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-brand": "0.1.1-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-attachment": "0.1.1-rc.2",
|
|
94
|
+
"@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
|
|
95
|
+
"@deepseek-ai/dsh-timeout": "0.1.1-rc.2",
|
|
96
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
|
|
97
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.1-rc.2",
|
|
98
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
|
|
99
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.2",
|
|
100
|
+
"@deepseek-ai/dsh-client-ui-model-selection": "^0.1.1-rc.2",
|
|
103
101
|
"vitest": "^3.2.0"
|
|
104
102
|
},
|
|
105
|
-
"
|
|
106
|
-
|
|
103
|
+
"scripts": {
|
|
104
|
+
"build": "rm -rf lib && tsc -p tsconfig.build.json && tsdown",
|
|
105
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.client.json",
|
|
106
|
+
"bundle": "tsdown",
|
|
107
|
+
"watch": "tsdown --watch",
|
|
108
|
+
"test": "vitest run"
|
|
109
|
+
}
|
|
110
|
+
}
|
package/src/client/api.ts
CHANGED
|
@@ -5,6 +5,8 @@ export interface RouterProviderView {
|
|
|
5
5
|
credentialConfigured: boolean
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
export type ModelSortMode = 'custom' | 'name' | 'recent'
|
|
9
|
+
|
|
8
10
|
export interface RouterModelView {
|
|
9
11
|
id: string
|
|
10
12
|
name: string
|
|
@@ -14,10 +16,14 @@ export interface RouterModelView {
|
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface ModelRouterState {
|
|
17
|
-
/** Whether the composer
|
|
19
|
+
/** Whether provider switching is shown inside the composer model picker menu. */
|
|
18
20
|
showQuickSwitch: boolean
|
|
19
21
|
/** Whether model ids are matched with their leading vendor/ prefix ignored. */
|
|
20
22
|
ignoreModelIdPrefix: boolean
|
|
23
|
+
/** The model-list display order mode (custom | name | recent). */
|
|
24
|
+
modelSort: ModelSortMode
|
|
25
|
+
/** Explicit model id order for custom mode (display order). */
|
|
26
|
+
modelOrder: string[]
|
|
21
27
|
providers: RouterProviderView[]
|
|
22
28
|
models: RouterModelView[]
|
|
23
29
|
}
|
|
@@ -61,4 +67,8 @@ export const api = {
|
|
|
61
67
|
call<ModelRouterState>('set-show-quick-switch', { value }),
|
|
62
68
|
setIgnoreModelIdPrefix: (value: boolean) =>
|
|
63
69
|
call<ModelRouterState>('set-ignore-model-id-prefix', { value }),
|
|
70
|
+
setModelSort: (mode: ModelSortMode) =>
|
|
71
|
+
call<ModelRouterState>('set-model-sort', { mode }),
|
|
72
|
+
setModelOrder: (order: string[]) =>
|
|
73
|
+
call<ModelRouterState>('set-model-order', { order }),
|
|
64
74
|
}
|
package/src/client/index.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fonlan/dsh-model-router client half: the
|
|
3
|
-
*
|
|
2
|
+
* @fonlan/dsh-model-router client half: the plugin's own Settings Card
|
|
3
|
+
* (设置 → 插件 → 插件配置 → 模型路由) plus the model seat that merges the
|
|
4
|
+
* provider switcher into DSH's composer model picker menu.
|
|
4
5
|
*/
|
|
5
6
|
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
|
6
7
|
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
@@ -9,30 +10,52 @@ import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
|
9
10
|
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
10
11
|
import type {} from '@deepseek-ai/dsh-client-ui-model-selection/client'
|
|
11
12
|
import { LOCALE_NS, zh, en } from './locales'
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
13
|
+
import { makeSettingsCard, type SettingsScopeFace } from './settings-card'
|
|
14
|
+
import { registerModelRouterSeat } from './model-seat'
|
|
15
|
+
|
|
16
|
+
/** The settings namespace this card edits (must match the host half). */
|
|
17
|
+
const ROUTER_NS = 'model-router'
|
|
18
|
+
|
|
19
|
+
/** Slots face: the keyed `settings.plugin.item` declaration ships with the web
|
|
20
|
+
* app's ui-settings-plugins package, which external plugins do not depend on;
|
|
21
|
+
* the register call below is typed through this local face (erased at build). */
|
|
22
|
+
interface Slots {
|
|
23
|
+
inject(name: string, callback: () => unknown): unknown
|
|
24
|
+
register(def: { name: string; key: string; inject?: () => unknown }, component: unknown): unknown
|
|
25
|
+
}
|
|
14
26
|
|
|
15
27
|
/** Services required before mounting (provided by the client runtime). */
|
|
16
|
-
export const inject = ['slots', 'locale', 'modelDirectories', 'sessions']
|
|
28
|
+
export const inject = ['slots', 'locale', 'modelDirectories', 'sessions', 'settingsScope', 'connection', 'remote']
|
|
17
29
|
|
|
18
30
|
/** Client plugin body. */
|
|
19
31
|
export function apply(ctx: ClientContext): void {
|
|
20
|
-
const t = ctx.locale.bind(LOCALE_NS)
|
|
21
32
|
ctx.effect(() => {
|
|
22
33
|
const off = ctx.locale.register(LOCALE_NS, { zh, en })
|
|
23
34
|
return () => off()
|
|
24
35
|
}, 'model-router: dictionaries')
|
|
25
36
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
const SettingsCard = makeSettingsCard(ctx)
|
|
38
|
+
|
|
39
|
+
// The card registers on the `settings.plugin.item` slot keyed by the
|
|
40
|
+
// `model-router` settings namespace (the same string the host half
|
|
41
|
+
// registers), so it appears inside 设置 → 插件 → 插件配置, paired with the
|
|
42
|
+
// namespace by the tab.
|
|
43
|
+
const services = ctx as unknown as {
|
|
44
|
+
slots: Slots
|
|
45
|
+
settingsScope: { bind(spec: { namespace: string }): SettingsScopeFace }
|
|
46
|
+
}
|
|
47
|
+
const scope = services.settingsScope.bind({ namespace: ROUTER_NS })
|
|
48
|
+
|
|
49
|
+
services.slots.inject('settings.plugin.item', () =>
|
|
50
|
+
services.slots.register(
|
|
51
|
+
{
|
|
52
|
+
name: 'settings.plugin.item',
|
|
53
|
+
key: ROUTER_NS,
|
|
54
|
+
inject: () => ({ scope }),
|
|
55
|
+
},
|
|
56
|
+
SettingsCard,
|
|
57
|
+
),
|
|
35
58
|
)
|
|
36
59
|
|
|
37
|
-
|
|
60
|
+
registerModelRouterSeat(ctx)
|
|
38
61
|
}
|
package/src/client/locales.ts
CHANGED
|
@@ -6,7 +6,10 @@ export const LOCALE_NS = 'model-router'
|
|
|
6
6
|
|
|
7
7
|
export const zh = {
|
|
8
8
|
settingsTitle: '模型路由',
|
|
9
|
-
|
|
9
|
+
settingsCardDescription: '模型聚合、提供商切换与优先级顺序',
|
|
10
|
+
expand: '展开',
|
|
11
|
+
collapse: '收起',
|
|
12
|
+
readOnly: '本部署的设置为只读。',
|
|
10
13
|
loading: '加载中…',
|
|
11
14
|
loadFailed: '加载失败:{message}',
|
|
12
15
|
retry: '重试',
|
|
@@ -19,23 +22,48 @@ export const zh = {
|
|
|
19
22
|
switchFailed: '切换失败:{message}',
|
|
20
23
|
reorderFailed: '排序失败:{message}',
|
|
21
24
|
providerCount: '{count} 个提供商',
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
seatLabel: '模型选择(含提供商切换)',
|
|
26
|
+
menuAria: '模型、提供商与推理等级',
|
|
27
|
+
menuModel: '模型',
|
|
28
|
+
menuProvider: '提供商',
|
|
29
|
+
menuEffort: '推理等级',
|
|
30
|
+
triggerFallback: '选择模型',
|
|
31
|
+
triggerSelectAria: '选择模型',
|
|
32
|
+
triggerAria: '选择模型,当前 {model}',
|
|
33
|
+
triggerAriaEffort: '选择模型,当前 {model},推理等级 {effort}',
|
|
34
|
+
effortProviderDefault: 'Default',
|
|
35
|
+
statusLoading: '正在刷新模型列表…',
|
|
36
|
+
errorAction: '模型操作失败:{message}',
|
|
37
|
+
actionReload: '重新加载',
|
|
38
|
+
warningGroupLoad: '{name} 加载失败:{message}',
|
|
39
|
+
emptyModels: '没有可用的模型。',
|
|
40
|
+
emptyEfforts: '当前模型未提供推理等级。',
|
|
25
41
|
routeMenuTitle: '{model} · 选择提供商',
|
|
26
42
|
routeLoading: '加载路由…',
|
|
27
|
-
unknownProvider: '未知提供商',
|
|
28
43
|
noCredentialShort: '未配置凭据',
|
|
29
44
|
globalRouteHint: '切换后对所有会话的后续请求生效',
|
|
30
|
-
showQuickSwitchLabel: '
|
|
31
|
-
showQuickSwitchDescription: '开启后,当当前模型是 model-router
|
|
45
|
+
showQuickSwitchLabel: '在模型选择菜单中显示提供商切换',
|
|
46
|
+
showQuickSwitchDescription: '开启后,当当前模型是 model-router 路由时,模型选择弹出菜单会显示提供商列表入口,可直接切换该模型的激活提供商。',
|
|
32
47
|
ignorePrefixLabel: '匹配时忽略模型 ID 前缀',
|
|
33
48
|
ignorePrefixDescription: '开启后,deepseek/deepseek-v4-flash 与 deepseek-v4-flash 视为同一模型,统一显示不带前缀的模型 id(默认开启)。',
|
|
49
|
+
sortTitle: '模型显示顺序',
|
|
50
|
+
sortDescription: '模型选择列表中 model-router 分组内模型的排列方式。',
|
|
51
|
+
sortModeCustom: '自定义',
|
|
52
|
+
sortModeName: '按名称',
|
|
53
|
+
sortModeRecent: '最近使用',
|
|
54
|
+
sortModeDescCustom: '按下方拖拽的手动顺序排列',
|
|
55
|
+
sortModeDescName: '按模型名称排序(数字感知)',
|
|
56
|
+
sortModeDescRecent: '最近用过的模型排在最前',
|
|
57
|
+
sortCustomHint: '拖拽调整模型在模型选择列表中的显示顺序',
|
|
58
|
+
sortFailed: '排序设置失败:{message}',
|
|
34
59
|
}
|
|
35
60
|
|
|
36
61
|
export const en = {
|
|
37
62
|
settingsTitle: 'Model Router',
|
|
38
|
-
|
|
63
|
+
settingsCardDescription: 'Model aggregation, provider switching and preference order',
|
|
64
|
+
expand: 'Expand',
|
|
65
|
+
collapse: 'Collapse',
|
|
66
|
+
readOnly: 'This deployment stores settings read-only.',
|
|
39
67
|
loading: 'Loading…',
|
|
40
68
|
loadFailed: 'Load failed: {message}',
|
|
41
69
|
retry: 'Retry',
|
|
@@ -48,16 +76,38 @@ export const en = {
|
|
|
48
76
|
switchFailed: 'Switch failed: {message}',
|
|
49
77
|
reorderFailed: 'Reorder failed: {message}',
|
|
50
78
|
providerCount: '{count} providers',
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
seatLabel: 'Model selection (with provider switching)',
|
|
80
|
+
menuAria: 'Model, provider and reasoning effort',
|
|
81
|
+
menuModel: 'Model',
|
|
82
|
+
menuProvider: 'Provider',
|
|
83
|
+
menuEffort: 'Effort',
|
|
84
|
+
triggerFallback: 'Select model',
|
|
85
|
+
triggerSelectAria: 'Select model',
|
|
86
|
+
triggerAria: 'Select model, current {model}',
|
|
87
|
+
triggerAriaEffort: 'Select model, current {model}, reasoning effort {effort}',
|
|
88
|
+
effortProviderDefault: 'Default',
|
|
89
|
+
statusLoading: 'Refreshing model list…',
|
|
90
|
+
errorAction: 'Model operation failed: {message}',
|
|
91
|
+
actionReload: 'Reload',
|
|
92
|
+
warningGroupLoad: '{name} failed to load: {message}',
|
|
93
|
+
emptyModels: 'No models available.',
|
|
94
|
+
emptyEfforts: 'This model provides no reasoning effort levels.',
|
|
54
95
|
routeMenuTitle: '{model} · Choose provider',
|
|
55
96
|
routeLoading: 'Loading routes…',
|
|
56
|
-
unknownProvider: 'Unknown provider',
|
|
57
97
|
noCredentialShort: 'No credentials',
|
|
58
98
|
globalRouteHint: 'Changes apply to subsequent requests in all sessions',
|
|
59
|
-
showQuickSwitchLabel: 'Show
|
|
60
|
-
showQuickSwitchDescription: 'When enabled,
|
|
99
|
+
showQuickSwitchLabel: 'Show provider switching in the model picker',
|
|
100
|
+
showQuickSwitchDescription: 'When enabled, the model picker menu shows a provider list entry that switches the active provider, for model-router routes.',
|
|
61
101
|
ignorePrefixLabel: 'Ignore model ID prefix when matching',
|
|
62
102
|
ignorePrefixDescription: 'When enabled, deepseek/deepseek-v4-flash and deepseek-v4-flash are treated as the same model and shown without the prefix (on by default).',
|
|
103
|
+
sortTitle: 'Model display order',
|
|
104
|
+
sortDescription: 'How models inside the model-router group are ordered in the model picker.',
|
|
105
|
+
sortModeCustom: 'Custom',
|
|
106
|
+
sortModeName: 'By name',
|
|
107
|
+
sortModeRecent: 'Recently used',
|
|
108
|
+
sortModeDescCustom: 'Manual order via drag below',
|
|
109
|
+
sortModeDescName: 'Sort by model name (numeric-aware)',
|
|
110
|
+
sortModeDescRecent: 'Most recently used models first',
|
|
111
|
+
sortCustomHint: 'Drag to reorder models in the model picker',
|
|
112
|
+
sortFailed: 'Sort setting failed: {message}',
|
|
63
113
|
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Composer model seat (shadows the host ModelSelect). Styles mirror the host
|
|
3
|
+
* ModelSelect.module.css exactly (same tokens, sizes and spacing) so the
|
|
4
|
+
* replacement is visually indistinguishable, plus the provider pane's title
|
|
5
|
+
* and hint rows.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.mr-seat-root {
|
|
9
|
+
min-width: 0;
|
|
10
|
+
position: relative;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.mr-seat-trigger {
|
|
14
|
+
min-width: 0;
|
|
15
|
+
max-width: 220px;
|
|
16
|
+
height: 28px;
|
|
17
|
+
color: var(--dsw-alias-label-secondary);
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
background: transparent;
|
|
20
|
+
border: none;
|
|
21
|
+
border-radius: 24px;
|
|
22
|
+
outline: none;
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: 4px;
|
|
25
|
+
padding: 0 4px 0 8px;
|
|
26
|
+
font-size: 13px;
|
|
27
|
+
font-weight: 500;
|
|
28
|
+
line-height: 20px;
|
|
29
|
+
display: flex;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.mr-seat-trigger:hover:not(:disabled) {
|
|
33
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.mr-seat-trigger:focus-visible {
|
|
37
|
+
box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.mr-seat-trigger:disabled {
|
|
41
|
+
color: var(--dsw-alias-label-dimmed);
|
|
42
|
+
cursor: default;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.mr-seat-triggerLabel {
|
|
46
|
+
text-overflow: ellipsis;
|
|
47
|
+
white-space: nowrap;
|
|
48
|
+
min-width: 0;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.mr-seat-triggerEffort {
|
|
53
|
+
color: var(--dsw-alias-label-caption);
|
|
54
|
+
flex: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.mr-seat-chevron {
|
|
58
|
+
color: var(--dsw-alias-label-caption);
|
|
59
|
+
flex: none;
|
|
60
|
+
transition: transform 0.12s;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.mr-seat-chevronOpen {
|
|
64
|
+
transform: rotate(180deg);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.mr-seat-menu {
|
|
68
|
+
z-index: 20;
|
|
69
|
+
border: 1px solid var(--dsw-alias-border-inverted);
|
|
70
|
+
background: var(--dsw-specific-menu);
|
|
71
|
+
width: min(240px, 100vw - 32px);
|
|
72
|
+
max-height: min(360px, 100vh - 96px);
|
|
73
|
+
box-shadow: var(--dsw-shadow-lv3);
|
|
74
|
+
color: var(--dsw-alias-label-primary);
|
|
75
|
+
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
|
76
|
+
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
|
77
|
+
border-radius: 12px;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
padding: 4px;
|
|
80
|
+
display: flex;
|
|
81
|
+
position: absolute;
|
|
82
|
+
bottom: calc(100% + 8px);
|
|
83
|
+
right: 0;
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.mr-seat-status,
|
|
88
|
+
.mr-seat-empty {
|
|
89
|
+
color: var(--dsw-alias-label-tertiary);
|
|
90
|
+
padding: 10px;
|
|
91
|
+
font-size: 13px;
|
|
92
|
+
line-height: 20px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.mr-seat-error,
|
|
96
|
+
.mr-seat-warning {
|
|
97
|
+
background: var(--dsw-alias-interactive-bg-hover-danger);
|
|
98
|
+
color: var(--dsw-alias-state-error-primary);
|
|
99
|
+
border-radius: 8px;
|
|
100
|
+
justify-content: space-between;
|
|
101
|
+
align-items: flex-start;
|
|
102
|
+
gap: 8px;
|
|
103
|
+
margin-bottom: 4px;
|
|
104
|
+
padding: 7px 8px;
|
|
105
|
+
font-size: 12px;
|
|
106
|
+
line-height: 18px;
|
|
107
|
+
display: flex;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.mr-seat-warning {
|
|
111
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
112
|
+
color: var(--dsw-alias-state-warn-label);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.mr-seat-retry {
|
|
116
|
+
color: inherit;
|
|
117
|
+
font: inherit;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
background: transparent;
|
|
120
|
+
border: none;
|
|
121
|
+
flex: none;
|
|
122
|
+
padding: 0;
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.mr-seat-groups {
|
|
127
|
+
min-height: 0;
|
|
128
|
+
overflow-y: auto;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.mr-seat-group + .mr-seat-group {
|
|
132
|
+
margin-top: 4px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.mr-seat-groupTitle {
|
|
136
|
+
z-index: 1;
|
|
137
|
+
background: var(--dsw-specific-menu);
|
|
138
|
+
color: var(--dsw-alias-label-tertiary);
|
|
139
|
+
padding: 5px 8px 3px;
|
|
140
|
+
font-size: 12px;
|
|
141
|
+
font-weight: 500;
|
|
142
|
+
line-height: 18px;
|
|
143
|
+
position: sticky;
|
|
144
|
+
top: 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Provider pane: non-sticky title row above the provider list. */
|
|
148
|
+
.mr-seat-title {
|
|
149
|
+
color: var(--dsw-alias-label-tertiary);
|
|
150
|
+
padding: 5px 8px 3px;
|
|
151
|
+
font-size: 12px;
|
|
152
|
+
font-weight: 500;
|
|
153
|
+
line-height: 18px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.mr-seat-option {
|
|
157
|
+
width: 100%;
|
|
158
|
+
min-height: 38px;
|
|
159
|
+
color: inherit;
|
|
160
|
+
text-align: left;
|
|
161
|
+
cursor: pointer;
|
|
162
|
+
background: transparent;
|
|
163
|
+
border: none;
|
|
164
|
+
border-radius: 10px;
|
|
165
|
+
outline: none;
|
|
166
|
+
align-items: center;
|
|
167
|
+
gap: 8px;
|
|
168
|
+
padding: 6px 8px;
|
|
169
|
+
display: flex;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.mr-seat-option:hover:not(:disabled),
|
|
173
|
+
.mr-seat-option:focus-visible {
|
|
174
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.mr-seat-selected {
|
|
178
|
+
background: transparent;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.mr-seat-option:disabled {
|
|
182
|
+
color: var(--dsw-alias-label-dimmed);
|
|
183
|
+
cursor: default;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.mr-seat-optionCopy {
|
|
187
|
+
flex-direction: column;
|
|
188
|
+
flex: 1;
|
|
189
|
+
min-width: 0;
|
|
190
|
+
display: flex;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.mr-seat-modelName {
|
|
194
|
+
color: inherit;
|
|
195
|
+
text-overflow: ellipsis;
|
|
196
|
+
white-space: nowrap;
|
|
197
|
+
font-size: 14px;
|
|
198
|
+
font-weight: 500;
|
|
199
|
+
line-height: 20px;
|
|
200
|
+
overflow: hidden;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.mr-seat-description {
|
|
204
|
+
color: var(--dsw-alias-label-tertiary);
|
|
205
|
+
text-overflow: ellipsis;
|
|
206
|
+
white-space: nowrap;
|
|
207
|
+
font-size: 12px;
|
|
208
|
+
line-height: 18px;
|
|
209
|
+
overflow: hidden;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.mr-seat-check {
|
|
213
|
+
color: var(--dsw-alias-label-primary);
|
|
214
|
+
flex: 0 0 18px;
|
|
215
|
+
place-items: center;
|
|
216
|
+
display: grid;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.mr-seat-cell {
|
|
220
|
+
width: 100%;
|
|
221
|
+
height: 40px;
|
|
222
|
+
color: var(--dsw-alias-label-primary);
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
text-align: left;
|
|
225
|
+
background: transparent;
|
|
226
|
+
border: none;
|
|
227
|
+
border-radius: 10px;
|
|
228
|
+
align-items: center;
|
|
229
|
+
gap: 8px;
|
|
230
|
+
padding: 0 10px;
|
|
231
|
+
font-size: 14px;
|
|
232
|
+
line-height: 22px;
|
|
233
|
+
display: flex;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.mr-seat-cell:hover {
|
|
237
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.mr-seat-cellLabel {
|
|
241
|
+
text-overflow: ellipsis;
|
|
242
|
+
white-space: nowrap;
|
|
243
|
+
flex: auto;
|
|
244
|
+
min-width: 0;
|
|
245
|
+
overflow: hidden;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.mr-seat-cellValue {
|
|
249
|
+
text-overflow: ellipsis;
|
|
250
|
+
white-space: nowrap;
|
|
251
|
+
min-width: 0;
|
|
252
|
+
color: var(--dsw-alias-label-tertiary);
|
|
253
|
+
flex: 0 auto;
|
|
254
|
+
overflow: hidden;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.mr-seat-cellChevron {
|
|
258
|
+
color: var(--dsw-alias-label-tertiary);
|
|
259
|
+
flex: none;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* Global hint footer of the provider pane. */
|
|
263
|
+
.mr-seat-hint {
|
|
264
|
+
color: var(--dsw-alias-label-tertiary);
|
|
265
|
+
padding: 5px 8px 3px;
|
|
266
|
+
font-size: 12px;
|
|
267
|
+
line-height: 18px;
|
|
268
|
+
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
269
|
+
margin-top: 4px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/*
|
|
273
|
+
* Mobile portrait: the composer row is an inline-size container. With the
|
|
274
|
+
* quick route switcher gone from the right tool row, let the model seat
|
|
275
|
+
* absorb the remaining row width (same rule the old switcher enforced).
|
|
276
|
+
* The breakpoint matches DSH's own narrow-composer rule (<=460px).
|
|
277
|
+
*/
|
|
278
|
+
@container (width <= 460px) {
|
|
279
|
+
[data-slot="conversation.input.model"] > * {
|
|
280
|
+
flex: 1 1 0;
|
|
281
|
+
min-width: 0;
|
|
282
|
+
max-width: 100%;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
[data-slot="conversation.input.model"] [aria-haspopup="menu"] {
|
|
286
|
+
width: 100%;
|
|
287
|
+
max-width: 100%;
|
|
288
|
+
}
|
|
289
|
+
}
|