@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonlan/dsh-model-router",
3
- "version": "0.3.0",
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.0-rc.6",
75
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
76
- "@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
77
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
78
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
79
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
80
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
81
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
82
- "@deepseek-ai/dsh-client-ui-model-selection": "^0.1.0-rc.6",
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.0-rc.6",
95
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
96
- "@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
97
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
98
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
99
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
100
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
101
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
102
- "@deepseek-ai/dsh-client-ui-model-selection": "^0.1.0-rc.6",
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
- "packageManager": "pnpm@11.21.0"
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 quick route-switcher button is enabled. */
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
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
- * @fonlan/dsh-model-router client half: the Model Router settings page plus
3
- * a quick provider switcher beside DSH's composer model selector.
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 { makeSettingsSection } from './settings-section'
13
- import { registerRouteSwitcher } from './route-switcher'
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 SettingsSection = makeSettingsSection(ctx)
27
- ctx.slots.inject('settings.section', () =>
28
- ctx.slots.register({
29
- name: 'settings.section',
30
- id: 'model-router',
31
- order: 300,
32
- label: () => t('settingsTitle'),
33
- locale: LOCALE_NS,
34
- }, SettingsSection as never),
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
- registerRouteSwitcher(ctx)
60
+ registerModelRouterSeat(ctx)
38
61
  }
@@ -6,7 +6,10 @@ export const LOCALE_NS = 'model-router'
6
6
 
7
7
  export const zh = {
8
8
  settingsTitle: '模型路由',
9
- settingsDescription: '每个模型按 model id 聚合所有提供商;点击提供商切换该模型当前使用的路由,拖拽调整优先级顺序(供后续自动切换使用)。',
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
- routeButtonLabel: '快速切换模型路由',
23
- routeButtonTitle: '切换当前模型的提供商',
24
- routeButtonAria: '切换当前模型提供商,当前为 {provider}',
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 路由时,在输入框模型选择器右侧显示 provider 快速切换按钮。',
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
- settingsDescription: 'Each model aggregates its providers by exact model id. Click a provider to switch where this model currently routes; drag to adjust the preference order (used by automatic failover later).',
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
- routeButtonLabel: 'Quick model route switcher',
52
- routeButtonTitle: 'Switch the current model provider',
53
- routeButtonAria: 'Switch current model provider, currently {provider}',
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 quick route switcher in the composer',
60
- showQuickSwitchDescription: 'When enabled, a provider quick-switch button appears next to the model selector when the current model is a model-router route.',
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
+ }