@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
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
2
|
-
import type { ClientContext, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
|
|
3
|
-
import type { ModelDirectoryState } from '@deepseek-ai/dsh-client-ui-model-selection/client'
|
|
4
|
-
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
5
|
-
import { IconChevronDownOutline14, IconGlobeOutline14, Menu, type MenuEntry } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
6
|
-
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
7
|
-
import type {} from '@deepseek-ai/dsh-client-ui-model-selection/client'
|
|
8
|
-
import { api, type ModelRouterState, type RouterModelView } from './api'
|
|
9
|
-
import { LOCALE_NS } from './locales'
|
|
10
|
-
import './route-switcher.css'
|
|
11
|
-
|
|
12
|
-
const ROUTER_PROVIDER_ID = 'model-router'
|
|
13
|
-
|
|
14
|
-
interface RouteSwitcherInjected {
|
|
15
|
-
available: boolean
|
|
16
|
-
directory: SnapshotStore<ModelDirectoryState>
|
|
17
|
-
load: () => void
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type RouteSwitcherProps = PropsRuntime<'conversation.input.right'>
|
|
21
|
-
& PropsLocale<typeof LOCALE_NS>
|
|
22
|
-
& RouteSwitcherInjected
|
|
23
|
-
|
|
24
|
-
type RouteProvider = RouterModelView['providers'][number]
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The quick route switcher lives beside DSH's model seat in the composer row.
|
|
28
|
-
* It reads the same per-session model directory as the host model picker, but
|
|
29
|
-
* only appears when the selected model is the virtual model-router route.
|
|
30
|
-
*/
|
|
31
|
-
export function RouteSwitcher({
|
|
32
|
-
session,
|
|
33
|
-
available,
|
|
34
|
-
directory,
|
|
35
|
-
load,
|
|
36
|
-
t,
|
|
37
|
-
}: RouteSwitcherProps): JSX.Element | null {
|
|
38
|
-
const directoryState = useSyncExternalStore(
|
|
39
|
-
onChange => directory.subscribe(onChange),
|
|
40
|
-
() => directory.getSnapshot(),
|
|
41
|
-
() => directory.getSnapshot(),
|
|
42
|
-
)
|
|
43
|
-
const current = directoryState.current
|
|
44
|
-
const [routerState, setRouterState] = useState<ModelRouterState | null>(null)
|
|
45
|
-
const [routerLoading, setRouterLoading] = useState(false)
|
|
46
|
-
const [routerError, setRouterError] = useState<string | null>(null)
|
|
47
|
-
const [open, setOpen] = useState(false)
|
|
48
|
-
const [busyProvider, setBusyProvider] = useState<string | null>(null)
|
|
49
|
-
|
|
50
|
-
const isRouterModel = available && current?.provider === ROUTER_PROVIDER_ID
|
|
51
|
-
const modelId = isRouterModel ? current.model : null
|
|
52
|
-
|
|
53
|
-
const loadRouterState = useCallback(async () => {
|
|
54
|
-
setRouterLoading(true)
|
|
55
|
-
try {
|
|
56
|
-
setRouterState(await api.state())
|
|
57
|
-
setRouterError(null)
|
|
58
|
-
} catch (cause) {
|
|
59
|
-
setRouterError(cause instanceof Error ? cause.message : String(cause))
|
|
60
|
-
} finally {
|
|
61
|
-
setRouterLoading(false)
|
|
62
|
-
}
|
|
63
|
-
}, [])
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
if (!available) return
|
|
67
|
-
load()
|
|
68
|
-
}, [available, load])
|
|
69
|
-
|
|
70
|
-
// Fetch the global toggle (and the model's provider list) on mount too, so a
|
|
71
|
-
// "hide quick switcher" setting applies even before the model directory
|
|
72
|
-
// settles. The button stays visible while the value is unknown (default on).
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
if (!available) return
|
|
75
|
-
void loadRouterState()
|
|
76
|
-
}, [available])
|
|
77
|
-
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
setOpen(false)
|
|
80
|
-
setBusyProvider(null)
|
|
81
|
-
setRouterState(null)
|
|
82
|
-
setRouterError(null)
|
|
83
|
-
}, [modelId])
|
|
84
|
-
|
|
85
|
-
// The model-selection directory is refreshed when another session changes
|
|
86
|
-
// the router settings document. Re-read the global router state after that
|
|
87
|
-
// refresh so this button never shows a stale active provider. Include the
|
|
88
|
-
// model id: switching between two router models can leave the directory in
|
|
89
|
-
// the same ready state while changing the API row we need to display.
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
if (!isRouterModel || modelId === null || directoryState.status !== 'ready') return
|
|
92
|
-
void loadRouterState()
|
|
93
|
-
}, [directoryState.status, isRouterModel, loadRouterState, modelId])
|
|
94
|
-
|
|
95
|
-
const routeModel = useMemo(
|
|
96
|
-
() => modelId === null
|
|
97
|
-
? undefined
|
|
98
|
-
: routerState?.models.find(model => model.id === modelId),
|
|
99
|
-
[modelId, routerState],
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
const providers = useMemo(() => {
|
|
103
|
-
if (routeModel === undefined) return []
|
|
104
|
-
const byId = new Map(routeModel.providers.map(provider => [provider.id, provider]))
|
|
105
|
-
const ordered = routeModel.order
|
|
106
|
-
.map(id => byId.get(id))
|
|
107
|
-
.filter((provider): provider is RouteProvider => provider !== undefined)
|
|
108
|
-
const seen = new Set(ordered.map(provider => provider.id))
|
|
109
|
-
return [...ordered, ...routeModel.providers.filter(provider => !seen.has(provider.id))]
|
|
110
|
-
}, [routeModel])
|
|
111
|
-
|
|
112
|
-
const switchProvider = useCallback(async (providerId: string) => {
|
|
113
|
-
if (modelId === null || routeModel === undefined || routeModel.active === providerId) {
|
|
114
|
-
setOpen(false)
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
setBusyProvider(providerId)
|
|
118
|
-
try {
|
|
119
|
-
const next = await api.switchActive(modelId, providerId)
|
|
120
|
-
setRouterState(next)
|
|
121
|
-
setRouterError(null)
|
|
122
|
-
setOpen(false)
|
|
123
|
-
// The model directory also listens for settings updates, but an explicit
|
|
124
|
-
// refresh keeps the host model picker description in sync immediately.
|
|
125
|
-
load()
|
|
126
|
-
} catch (cause) {
|
|
127
|
-
setRouterError(t('switchFailed', { message: cause instanceof Error ? cause.message : String(cause) }))
|
|
128
|
-
} finally {
|
|
129
|
-
setBusyProvider(null)
|
|
130
|
-
}
|
|
131
|
-
}, [load, modelId, routeModel, t])
|
|
132
|
-
|
|
133
|
-
if (session.removed || !isRouterModel) return null
|
|
134
|
-
if (routerState?.showQuickSwitch === false) return null
|
|
135
|
-
|
|
136
|
-
const activeProvider = providers.find(provider => provider.id === routeModel?.active)
|
|
137
|
-
const activeName = activeProvider?.name ?? routeModel?.active ?? t('routeLoading')
|
|
138
|
-
if (routeModel !== undefined && providers.length < 2) return null
|
|
139
|
-
|
|
140
|
-
const menuItems: MenuEntry[] = routeModel === undefined
|
|
141
|
-
? [{
|
|
142
|
-
type: 'label',
|
|
143
|
-
id: 'route-status',
|
|
144
|
-
text: routerError ?? t('routeLoading'),
|
|
145
|
-
}]
|
|
146
|
-
: [
|
|
147
|
-
{
|
|
148
|
-
type: 'label',
|
|
149
|
-
id: 'route-model',
|
|
150
|
-
text: t('routeMenuTitle', { model: modelId }),
|
|
151
|
-
},
|
|
152
|
-
...providers.map(provider => ({
|
|
153
|
-
id: provider.id,
|
|
154
|
-
label: (
|
|
155
|
-
<span className="mr-route-item-copy">
|
|
156
|
-
<span className="mr-route-item-name">{provider.name}</span>
|
|
157
|
-
{!provider.credentialConfigured && (
|
|
158
|
-
<span className="mr-route-item-note">{t('noCredentialShort')}</span>
|
|
159
|
-
)}
|
|
160
|
-
</span>
|
|
161
|
-
),
|
|
162
|
-
disabled: !provider.credentialConfigured || busyProvider !== null,
|
|
163
|
-
})),
|
|
164
|
-
...(routerError === null ? [] : [{
|
|
165
|
-
id: 'route-error',
|
|
166
|
-
label: routerError,
|
|
167
|
-
disabled: true,
|
|
168
|
-
danger: true,
|
|
169
|
-
}]),
|
|
170
|
-
]
|
|
171
|
-
|
|
172
|
-
return (
|
|
173
|
-
<Menu
|
|
174
|
-
className="mr-route-anchor"
|
|
175
|
-
open={open}
|
|
176
|
-
side="top"
|
|
177
|
-
align="end"
|
|
178
|
-
portal
|
|
179
|
-
compact
|
|
180
|
-
dense
|
|
181
|
-
items={menuItems}
|
|
182
|
-
selectedId={routeModel?.active ?? undefined}
|
|
183
|
-
onSelect={id => {
|
|
184
|
-
if (id === 'route-error' || routeModel === undefined) {
|
|
185
|
-
void loadRouterState()
|
|
186
|
-
load()
|
|
187
|
-
return
|
|
188
|
-
}
|
|
189
|
-
void switchProvider(id)
|
|
190
|
-
}}
|
|
191
|
-
onClose={() => setOpen(false)}
|
|
192
|
-
footer={[
|
|
193
|
-
{
|
|
194
|
-
type: 'label',
|
|
195
|
-
id: 'route-global-hint',
|
|
196
|
-
text: t('globalRouteHint'),
|
|
197
|
-
},
|
|
198
|
-
]}
|
|
199
|
-
anchor={(
|
|
200
|
-
<button
|
|
201
|
-
type="button"
|
|
202
|
-
className="mr-route-trigger"
|
|
203
|
-
aria-label={t('routeButtonAria', { provider: activeName })}
|
|
204
|
-
aria-haspopup="menu"
|
|
205
|
-
aria-expanded={open}
|
|
206
|
-
disabled={routerLoading || busyProvider !== null}
|
|
207
|
-
onClick={() => {
|
|
208
|
-
setOpen(value => !value)
|
|
209
|
-
if (!open) {
|
|
210
|
-
void loadRouterState()
|
|
211
|
-
load()
|
|
212
|
-
}
|
|
213
|
-
}}
|
|
214
|
-
title={t('routeButtonTitle')}
|
|
215
|
-
>
|
|
216
|
-
<IconGlobeOutline14 />
|
|
217
|
-
<span className="mr-route-trigger-label">{activeName}</span>
|
|
218
|
-
<span className="mr-route-trigger-chevron" aria-hidden>
|
|
219
|
-
<IconChevronDownOutline14 />
|
|
220
|
-
</span>
|
|
221
|
-
</button>
|
|
222
|
-
)}
|
|
223
|
-
/>
|
|
224
|
-
)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** Register the quick route switcher into the composer right-side tool row. */
|
|
228
|
-
export function registerRouteSwitcher(ctx: ClientContext): void {
|
|
229
|
-
const t = ctx.locale.bind(LOCALE_NS)
|
|
230
|
-
ctx.inject(['slots', 'modelDirectories'], scope => {
|
|
231
|
-
const models = scope.modelDirectories
|
|
232
|
-
const sessions = scope.sessions
|
|
233
|
-
scope.slots.inject('conversation.input.right', () => scope.slots.register({
|
|
234
|
-
name: 'conversation.input.right',
|
|
235
|
-
id: 'model-router-route-switcher',
|
|
236
|
-
order: 100,
|
|
237
|
-
label: () => t('routeButtonLabel'),
|
|
238
|
-
locale: LOCALE_NS,
|
|
239
|
-
inject: (sessionId) => {
|
|
240
|
-
const directory = models.directoryFor(sessionId)
|
|
241
|
-
const available = sessions.subagentAddress(sessionId) === undefined
|
|
242
|
-
return {
|
|
243
|
-
available,
|
|
244
|
-
directory: directory.store,
|
|
245
|
-
load: () => {
|
|
246
|
-
if (available) directory.load().catch(() => {})
|
|
247
|
-
},
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
}, RouteSwitcher as never))
|
|
251
|
-
})
|
|
252
|
-
}
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/* Model Router settings page — theme-consistent via DSW alias variables. */
|
|
2
|
-
.mr-root {
|
|
3
|
-
display: flex;
|
|
4
|
-
flex-direction: column;
|
|
5
|
-
gap: 12px;
|
|
6
|
-
padding: 4px 0 24px;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.mr-description {
|
|
10
|
-
margin: 0;
|
|
11
|
-
color: var(--dsw-alias-label-secondary);
|
|
12
|
-
font-size: 13px;
|
|
13
|
-
line-height: 20px;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.mr-quick-switch {
|
|
17
|
-
display: flex;
|
|
18
|
-
align-items: flex-start;
|
|
19
|
-
gap: 8px;
|
|
20
|
-
padding: 8px 10px;
|
|
21
|
-
border: 1px solid var(--dsw-alias-border-inverted);
|
|
22
|
-
border-radius: 10px;
|
|
23
|
-
background: var(--dsw-alias-bg-module-platform);
|
|
24
|
-
cursor: pointer;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.mr-quick-switch input[type="checkbox"] {
|
|
28
|
-
margin-top: 2px;
|
|
29
|
-
accent-color: var(--dsw-alias-accent);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.mr-quick-switch-copy {
|
|
33
|
-
display: flex;
|
|
34
|
-
flex-direction: column;
|
|
35
|
-
gap: 2px;
|
|
36
|
-
min-width: 0;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.mr-quick-switch-label {
|
|
40
|
-
color: var(--dsw-alias-label-primary);
|
|
41
|
-
font-size: 13px;
|
|
42
|
-
line-height: 20px;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.mr-quick-switch-desc {
|
|
46
|
-
color: var(--dsw-alias-label-tertiary);
|
|
47
|
-
font-size: 12px;
|
|
48
|
-
line-height: 18px;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.mr-toolbar {
|
|
52
|
-
display: flex;
|
|
53
|
-
justify-content: space-between;
|
|
54
|
-
align-items: center;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.mr-count {
|
|
58
|
-
color: var(--dsw-alias-label-tertiary);
|
|
59
|
-
font-size: 12px;
|
|
60
|
-
line-height: 18px;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.mr-button {
|
|
64
|
-
color: var(--dsw-alias-label-primary);
|
|
65
|
-
cursor: pointer;
|
|
66
|
-
background: var(--dsw-alias-interactive-bg-hover);
|
|
67
|
-
border: 1px solid var(--dsw-alias-border-l3);
|
|
68
|
-
border-radius: 8px;
|
|
69
|
-
font-size: 12px;
|
|
70
|
-
line-height: 18px;
|
|
71
|
-
padding: 3px 10px;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.mr-button:hover:not(:disabled) {
|
|
75
|
-
background: var(--dsw-alias-interactive-bg-hover);
|
|
76
|
-
filter: brightness(1.08);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.mr-button:disabled {
|
|
80
|
-
opacity: 0.5;
|
|
81
|
-
cursor: default;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.mr-error {
|
|
85
|
-
background: var(--dsw-alias-interactive-bg-hover-danger);
|
|
86
|
-
color: var(--dsw-alias-state-error-primary);
|
|
87
|
-
border-radius: 8px;
|
|
88
|
-
padding: 7px 8px;
|
|
89
|
-
font-size: 12px;
|
|
90
|
-
line-height: 18px;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.mr-empty {
|
|
94
|
-
color: var(--dsw-alias-label-tertiary);
|
|
95
|
-
padding: 16px 0;
|
|
96
|
-
font-size: 13px;
|
|
97
|
-
line-height: 20px;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.mr-models {
|
|
101
|
-
display: flex;
|
|
102
|
-
flex-direction: column;
|
|
103
|
-
gap: 10px;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.mr-card {
|
|
107
|
-
border: 1px solid var(--dsw-alias-border-inverted);
|
|
108
|
-
border-radius: 12px;
|
|
109
|
-
padding: 10px 12px;
|
|
110
|
-
background: var(--dsw-alias-bg-module-platform);
|
|
111
|
-
transition: opacity 0.12s;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.mr-card-busy {
|
|
115
|
-
opacity: 0.6;
|
|
116
|
-
pointer-events: none;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.mr-card-header {
|
|
120
|
-
display: flex;
|
|
121
|
-
align-items: baseline;
|
|
122
|
-
gap: 8px;
|
|
123
|
-
min-width: 0;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.mr-model-name {
|
|
127
|
-
color: var(--dsw-alias-label-primary);
|
|
128
|
-
font-size: 14px;
|
|
129
|
-
font-weight: 600;
|
|
130
|
-
line-height: 20px;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.mr-model-id {
|
|
134
|
-
color: var(--dsw-alias-label-tertiary);
|
|
135
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
136
|
-
font-size: 12px;
|
|
137
|
-
line-height: 18px;
|
|
138
|
-
text-overflow: ellipsis;
|
|
139
|
-
white-space: nowrap;
|
|
140
|
-
overflow: hidden;
|
|
141
|
-
min-width: 0;
|
|
142
|
-
flex: 1;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.mr-provider-count {
|
|
146
|
-
color: var(--dsw-alias-label-tertiary);
|
|
147
|
-
font-size: 12px;
|
|
148
|
-
line-height: 18px;
|
|
149
|
-
flex: none;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.mr-drag-hint {
|
|
153
|
-
margin: 2px 0 6px;
|
|
154
|
-
color: var(--dsw-alias-label-caption);
|
|
155
|
-
font-size: 11px;
|
|
156
|
-
line-height: 16px;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.mr-providers {
|
|
160
|
-
list-style: none;
|
|
161
|
-
margin: 0;
|
|
162
|
-
padding: 0;
|
|
163
|
-
display: flex;
|
|
164
|
-
flex-direction: column;
|
|
165
|
-
gap: 4px;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.mr-provider {
|
|
169
|
-
display: flex;
|
|
170
|
-
align-items: center;
|
|
171
|
-
gap: 6px;
|
|
172
|
-
border: 1px solid transparent;
|
|
173
|
-
border-radius: 10px;
|
|
174
|
-
padding: 2px 8px 2px 4px;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.mr-provider-active {
|
|
178
|
-
background: var(--dsw-alias-interactive-bg-hover);
|
|
179
|
-
border-color: var(--dsw-alias-border-l3);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.mr-provider-nocred {
|
|
183
|
-
opacity: 0.55;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.mr-provider-drop {
|
|
187
|
-
border-color: var(--dsw-alias-border-brand);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
.mr-grip {
|
|
191
|
-
color: var(--dsw-alias-label-caption);
|
|
192
|
-
cursor: grab;
|
|
193
|
-
font-size: 12px;
|
|
194
|
-
line-height: 20px;
|
|
195
|
-
flex: none;
|
|
196
|
-
user-select: none;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
.mr-grip:active {
|
|
200
|
-
cursor: grabbing;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.mr-provider-button {
|
|
204
|
-
flex: 1;
|
|
205
|
-
min-width: 0;
|
|
206
|
-
display: flex;
|
|
207
|
-
align-items: baseline;
|
|
208
|
-
gap: 6px;
|
|
209
|
-
color: var(--dsw-alias-label-primary);
|
|
210
|
-
background: none;
|
|
211
|
-
border: none;
|
|
212
|
-
text-align: left;
|
|
213
|
-
cursor: pointer;
|
|
214
|
-
font: inherit;
|
|
215
|
-
padding: 4px 0;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
.mr-provider-button:disabled {
|
|
219
|
-
cursor: default;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.mr-provider-name {
|
|
223
|
-
font-size: 13px;
|
|
224
|
-
line-height: 20px;
|
|
225
|
-
text-overflow: ellipsis;
|
|
226
|
-
white-space: nowrap;
|
|
227
|
-
overflow: hidden;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
.mr-provider-nocred-label {
|
|
231
|
-
color: var(--dsw-alias-label-tertiary);
|
|
232
|
-
font-size: 12px;
|
|
233
|
-
line-height: 18px;
|
|
234
|
-
flex: none;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.mr-active-badge {
|
|
238
|
-
flex: none;
|
|
239
|
-
color: var(--dsw-alias-label-inverted, #fff);
|
|
240
|
-
background: var(--dsw-alias-border-brand, #4d6bfe);
|
|
241
|
-
border-radius: 999px;
|
|
242
|
-
font-size: 11px;
|
|
243
|
-
line-height: 16px;
|
|
244
|
-
padding: 0 8px;
|
|
245
|
-
}
|