@ljwei-stak/dsh-model-route-bot 0.66.4 → 0.67.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/.dsh-plugin/bot-product.mjs +81 -5
- package/.dsh-plugin/client.js +17490 -1791
- package/README.i18n.yaml +4 -4
- package/README.md +409 -364
- package/README.zh.md +409 -364
- package/apps/integration-client/README.md +31 -7
- package/apps/integration-client/api.mjs +14 -3
- package/apps/integration-client/binding-editor.jsx +308 -109
- package/apps/integration-client/binding-model.mjs +262 -0
- package/apps/integration-client/binding.css +144 -0
- package/apps/integration-client/bot-model-status.mjs +18 -0
- package/apps/integration-client/bot-profile-identity.jsx +197 -0
- package/apps/integration-client/bot-profile.css +57 -0
- package/apps/integration-client/bot-profile.mjs +97 -0
- package/apps/integration-client/bot-settings-dialog.css +18 -0
- package/apps/integration-client/bot-settings-dialog.jsx +111 -0
- package/apps/integration-client/bot-thread-header-bridge.css +20 -0
- package/apps/integration-client/bot-thread-header.jsx +278 -0
- package/apps/integration-client/bot-threads-panel.jsx +378 -0
- package/apps/integration-client/bot-threads.css +169 -0
- package/apps/integration-client/chat-design.mjs +250 -0
- package/apps/integration-client/chat-ui.jsx +175 -0
- package/apps/integration-client/chat.css +477 -0
- package/apps/integration-client/composer-draft-binding.mjs +50 -0
- package/apps/integration-client/composer-draft-bound.jsx +14 -0
- package/apps/integration-client/composer-draft-host.jsx +142 -0
- package/apps/integration-client/composer-draft-session.jsx +287 -0
- package/apps/integration-client/composer-draft-witness.mjs +115 -0
- package/apps/integration-client/composer-draft.css +4 -0
- package/apps/integration-client/composer-draft.mjs +322 -0
- package/apps/integration-client/contact-attention.jsx +54 -0
- package/apps/integration-client/data-panel.jsx +439 -0
- package/apps/integration-client/data.css +69 -0
- package/apps/integration-client/desktop-bridge.mjs +1 -0
- package/apps/integration-client/desktop-http-socket.mjs +49 -0
- package/apps/integration-client/desktop-keyboard.mjs +32 -0
- package/apps/integration-client/desktop-pane.jsx +99 -30
- package/apps/integration-client/desktop-stream.mjs +13 -3
- package/apps/integration-client/desktop-tools.jsx +19 -0
- package/apps/integration-client/desktop-touch.mjs +27 -0
- package/apps/integration-client/desktop.css +41 -0
- package/apps/integration-client/independent-chat-controller.mjs +224 -0
- package/apps/integration-client/independent-chat-entry.jsx +61 -0
- package/apps/integration-client/independent-chat-navigation.mjs +21 -0
- package/apps/integration-client/independent-chat-page.jsx +148 -0
- package/apps/integration-client/independent-chat-rich.jsx +64 -0
- package/apps/integration-client/independent-chat-rich.mjs +30 -0
- package/apps/integration-client/independent-chat.css +73 -0
- package/apps/integration-client/index.jsx +24 -16
- package/apps/integration-client/library-panel.jsx +4 -1
- package/apps/integration-client/licenses/jsqr-1.4.0-LICENSE.txt +202 -0
- package/apps/integration-client/main-route-editor.jsx +94 -0
- package/apps/integration-client/main-route-model.mjs +100 -0
- package/apps/integration-client/model-picker.css +37 -0
- package/apps/integration-client/model-picker.jsx +83 -0
- package/apps/integration-client/native-activity.css +141 -0
- package/apps/integration-client/native-activity.jsx +276 -0
- package/apps/integration-client/native-activity.mjs +384 -0
- package/apps/integration-client/notifications-panel.jsx +186 -0
- package/apps/integration-client/notifications.css +126 -0
- package/apps/integration-client/pairing-qr.jsx +79 -0
- package/apps/integration-client/panels.jsx +126 -115
- package/apps/integration-client/product-slots.jsx +46 -0
- package/apps/integration-client/routine-recorder.mjs +23 -2
- package/apps/integration-client/secure-pairing.jsx +84 -0
- package/apps/integration-client/tasks-panel.jsx +32 -10
- package/apps/integration-client/ui-icons.jsx +17 -0
- package/apps/integration-client/usage-explorer.jsx +43 -0
- package/apps/integration-client/usage-report.mjs +71 -0
- package/apps/integration-client/usage.css +34 -0
- package/apps/integration-client/web-panel-model.mjs +140 -0
- package/apps/integration-client/web-panel-page.jsx +221 -0
- package/apps/integration-client/web-panel-pins-live.jsx +28 -0
- package/apps/integration-client/web-panel.css +139 -0
- package/apps/integration-client/workspace-model.mjs +67 -0
- package/apps/integration-client/workspace-navigation.mjs +56 -0
- package/apps/integration-client/workspace-ui.jsx +173 -0
- package/apps/integration-client/workspace.css +332 -0
- package/modules/bot-runtime/SOURCE.json +176 -50
- package/modules/bot-runtime/index.mjs +169 -13
- package/modules/bot-runtime/product-overlay/OVERLAY.json +128 -2
- package/modules/bot-runtime/product-overlay/base/src/client/header.js +244 -0
- package/modules/bot-runtime/product-overlay/base/src/client/learning-card.js +202 -0
- package/modules/bot-runtime/product-overlay/base/src/client/model-menu.js +371 -0
- package/modules/bot-runtime/product-overlay/base/src/client/secret-card.js +363 -0
- package/modules/bot-runtime/product-overlay/base/src/client/sources.js +68 -0
- package/modules/bot-runtime/product-overlay/base/src/client/turn-error.js +260 -0
- package/modules/bot-runtime/product-overlay/base/src/host/channel.js +647 -0
- package/modules/bot-runtime/product-overlay/base/src/host/memory.js +2050 -0
- package/modules/bot-runtime/product-overlay/base/src/host/models.js +314 -0
- package/modules/bot-runtime/product-overlay/base/src/host/parts.js +1473 -0
- package/modules/bot-runtime/product-overlay/base/src/host/privacy.js +368 -0
- package/modules/bot-runtime/product-overlay/base/src/host/roster.js +776 -0
- package/modules/bot-runtime/product-overlay/base/src/host/store.js +512 -0
- package/modules/bot-runtime/product-overlay/base/src/host/usage.js +653 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/details-drawer.js.patch +48 -2
- package/modules/bot-runtime/product-overlay/patches/src/client/header.js.patch +41 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/index.js.patch +33 -1
- package/modules/bot-runtime/product-overlay/patches/src/client/learning-card.js.patch +20 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/model-menu.js.patch +24 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/secret-card.js.patch +25 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/sources.js.patch +52 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/turn-error.js.patch +23 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/channel.js.patch +46 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/memory.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/models.js.patch +16 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/parts.js.patch +95 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/privacy.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/roster.js.patch +145 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/store.js.patch +68 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/usage.js.patch +11 -0
- package/modules/bot-runtime/read-model.mjs +19 -4
- package/modules/bot-runtime/vendor/ds-bot/src/client/details-drawer.js +15 -5
- package/modules/bot-runtime/vendor/ds-bot/src/client/header.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/index.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/learning-card.js +2 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/model-menu.js +5 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/secret-card.js +3 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/sources.js +25 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/turn-error.js +3 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/channel.js +14 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/memory.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/models.js +7 -0
- package/modules/bot-runtime/vendor/ds-bot/src/host/parts.js +26 -9
- package/modules/bot-runtime/vendor/ds-bot/src/host/privacy.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/roster.js +112 -3
- package/modules/bot-runtime/vendor/ds-bot/src/host/store.js +34 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/usage.js +1 -1
- package/modules/independent-runtime/index.mjs +152 -62
- package/modules/independent-runtime/remote-tools.mjs +8 -5
- package/modules/openmaus-protocol/NOTICE +10 -0
- package/modules/openmaus-protocol/README.md +15 -0
- package/modules/openmaus-protocol/SOURCE.json +68 -0
- package/modules/openmaus-protocol/dist/protocol.mjs +1203 -0
- package/modules/openmaus-protocol/index.mjs +1 -0
- package/modules/openmaus-protocol/transport.mjs +77 -0
- package/modules/openmaus-protocol/vendor/LICENSE +202 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/README.md +405 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/credentials.ts +128 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/descriptor.ts +84 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/noise.ts +456 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/phone.ts +104 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/session.ts +417 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.json +2565 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.sha256 +1 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/yamux.ts +335 -0
- package/package.json +18 -6
- package/packages/bot-acp/README.md +156 -0
- package/packages/bot-acp/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-acp/licenses/NOTICE.md +27 -0
- package/packages/bot-acp/src/acp-adapter.mjs +795 -0
- package/packages/bot-acp/src/acp-host.mjs +148 -0
- package/packages/bot-acp/src/native-launch.mjs +121 -0
- package/packages/bot-acp/src/permission-policy.mjs +283 -0
- package/packages/bot-acp/src/rpc-codec.mjs +265 -0
- package/packages/bot-acp/src/session-core.mjs +581 -0
- package/packages/bot-acp/src/stdio-rpc.mjs +329 -0
- package/packages/bot-engines/README.md +281 -0
- package/packages/bot-engines/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-engines/licenses/NOTICE.md +32 -0
- package/packages/bot-engines/src/claude-engine.mjs +772 -0
- package/packages/bot-engines/src/claude-llm-adapter.mjs +797 -0
- package/packages/bot-engines/src/claude-llm-turn.mjs +292 -0
- package/packages/bot-engines/src/claude-permission-broker.mjs +130 -0
- package/packages/bot-engines/src/claude-permission-proxy.mjs +87 -0
- package/packages/bot-engines/src/claude-protocol.mjs +177 -0
- package/packages/bot-engines/src/driver-catalog.mjs +334 -0
- package/packages/bot-engines/src/host-loop-request.mjs +95 -0
- package/packages/bot-engines/src/host-registrations.mjs +105 -0
- package/packages/capability-broker/src/cli-bootstrap.mjs +86 -16
- package/packages/capability-broker/src/cli-bridge-response.mjs +80 -0
- package/packages/capability-broker/src/cli-stdio-bridge.mjs +21 -8
- package/packages/cli-runtime/src/codex-session-core.mjs +4 -0
- package/packages/cli-runtime/src/native-cli-output.mjs +57 -0
- package/packages/cli-runtime/src/native-peer.ps1 +51 -3
- package/packages/cli-runtime/src/native-process.mjs +77 -14
- package/packages/model-router-plugin/package.json +1 -1
- package/packages/orchestration/src/binding-product.mjs +19 -0
- package/packages/orchestration/src/bot-data-actions.mjs +45 -0
- package/packages/orchestration/src/bot-data-chart.mjs +109 -0
- package/packages/orchestration/src/bot-data-context.mjs +44 -0
- package/packages/orchestration/src/bot-data-engine.mjs +809 -0
- package/packages/orchestration/src/bot-data-io.mjs +186 -0
- package/packages/orchestration/src/bot-data-sql.mjs +195 -0
- package/packages/orchestration/src/bot-data-surface.mjs +144 -0
- package/packages/orchestration/src/bot-data-tools.mjs +68 -0
- package/packages/orchestration/src/bot-data-worker.mjs +228 -0
- package/packages/orchestration/src/bot-independent-execution.mjs +195 -0
- package/packages/orchestration/src/bot-independent-stream.mjs +74 -0
- package/packages/orchestration/src/bot-loop.mjs +152 -19
- package/packages/orchestration/src/bot-notifications.mjs +159 -0
- package/packages/orchestration/src/bot-policy.mjs +43 -4
- package/packages/orchestration/src/bot-profile.mjs +171 -0
- package/packages/orchestration/src/bot-routine.mjs +4 -4
- package/packages/orchestration/src/bot-runtime-assembly.mjs +255 -8
- package/packages/orchestration/src/bot-runtime-switch.mjs +13 -2
- package/packages/orchestration/src/bot-serving.mjs +164 -0
- package/packages/orchestration/src/bot-session-store.mjs +105 -9
- package/packages/orchestration/src/bot-settings.mjs +50 -11
- package/packages/orchestration/src/bot-thread-admission.mjs +84 -0
- package/packages/orchestration/src/bot-thread-host.mjs +187 -0
- package/packages/orchestration/src/bot-threads.mjs +790 -0
- package/packages/orchestration/src/data-product-access.mjs +37 -0
- package/packages/orchestration/src/data-tool-contract.mjs +291 -0
- package/packages/orchestration/src/desktop-http-carrier.mjs +120 -0
- package/packages/orchestration/src/desktop-proxy.mjs +311 -0
- package/packages/orchestration/src/desktop-sessions.mjs +43 -9
- package/packages/orchestration/src/ds-lifecycle.mjs +31 -4
- package/packages/orchestration/src/grant-refresh-queue.mjs +17 -0
- package/packages/orchestration/src/independent-chat-page.mjs +39 -0
- package/packages/orchestration/src/independent-chat-service.mjs +308 -0
- package/packages/orchestration/src/independent-context-state.mjs +44 -0
- package/packages/orchestration/src/independent-message-wire.mjs +84 -0
- package/packages/orchestration/src/independent-tool-catalog.mjs +175 -0
- package/packages/orchestration/src/independent-tool-families.mjs +73 -0
- package/packages/orchestration/src/independent-tool-host.mjs +344 -0
- package/packages/orchestration/src/main-bot-grant.mjs +15 -0
- package/packages/orchestration/src/main-model-routes.mjs +208 -0
- package/packages/orchestration/src/model-accounting.mjs +99 -35
- package/packages/orchestration/src/model-auxiliary-dispatch.mjs +187 -0
- package/packages/orchestration/src/model-loop-frame.mjs +179 -0
- package/packages/orchestration/src/model-prepared-generation.mjs +175 -0
- package/packages/orchestration/src/model-request-bounds.mjs +111 -0
- package/packages/orchestration/src/native-activity-contract.mjs +491 -0
- package/packages/orchestration/src/native-activity-service.mjs +102 -0
- package/packages/orchestration/src/native-activity-store.mjs +249 -0
- package/packages/orchestration/src/native-conversation-tools.mjs +78 -0
- package/packages/orchestration/src/native-data-tools.mjs +100 -0
- package/packages/orchestration/src/native-remote-actions.mjs +742 -0
- package/packages/orchestration/src/native-team-tools.mjs +73 -0
- package/packages/orchestration/src/pairing-invite.mjs +37 -0
- package/packages/orchestration/src/product-composition.mjs +1559 -1001
- package/packages/orchestration/src/remote-machines.mjs +82 -14
- package/packages/orchestration/src/routine-execution.mjs +5 -3
- package/packages/orchestration/src/routine-session.mjs +19 -13
- package/packages/orchestration/src/secure-pairing-host.mjs +112 -0
- package/packages/orchestration/src/task-journal.mjs +29 -0
- package/packages/orchestration/src/task-product.mjs +17 -0
- package/packages/orchestration/src/team-assignment-store.mjs +120 -0
- package/packages/orchestration/src/team-bridge.mjs +282 -0
- package/packages/orchestration/src/team-scheduler.mjs +83 -0
- package/packages/orchestration/src/team-sdk-tools.mjs +84 -0
- package/packages/orchestration/src/team-tool-contract.mjs +284 -0
- package/packages/orchestration/src/ui-host-namespace.mjs +23 -0
- package/packages/orchestration/src/web-panel-actions.mjs +103 -0
- package/packages/orchestration/src/web-panel-framing.mjs +227 -0
- package/packages/orchestration/src/web-panel-store.mjs +206 -0
- package/remote-computer-agent/README.md +39 -29
- package/remote-computer-agent/build.mjs +3 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_LICENSE +202 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_NOTICE +10 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_SOURCE.json +68 -0
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs +215 -25
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs.map +4 -4
- package/remote-computer-agent/dist/cli.mjs +1688 -168
- package/remote-computer-agent/dist/cli.mjs.map +4 -4
- package/remote-computer-agent/dist/index.mjs +1579 -122
- package/remote-computer-agent/dist/index.mjs.map +4 -4
- package/remote-computer-agent/dist/native-peer.ps1 +51 -3
- package/remote-computer-agent/package-lock.json +308 -2
- package/remote-computer-agent/package.json +2 -1
- package/remote-computer-agent/pair-device.ps1 +20 -0
- package/remote-computer-agent/setup.ps1 +38 -16
- package/remote-computer-agent/src/cli.ts +24 -3
- package/remote-computer-agent/src/desktop-session.ts +11 -3
- package/remote-computer-agent/src/e2e-server.ts +81 -0
- package/remote-computer-agent/src/pairing-page.ts +35 -0
- package/remote-computer-agent/src/protocol.ts +25 -25
- package/remote-computer-agent/src/secure-pairing.ts +105 -0
- package/remote-computer-agent/src/store.ts +20 -5
- package/remote-computer-agent/src/transport.ts +39 -15
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { createElement as h, Fragment, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react'
|
|
2
|
+
import { createPortal } from 'react-dom'
|
|
3
|
+
import { currentPart } from './text.js'
|
|
4
|
+
import { CheckIcon, SlidersIcon } from './icons.js'
|
|
5
|
+
import { BotMark } from './mark.js'
|
|
6
|
+
import { KeyIcon, MaskedInput } from './secret-card.js'
|
|
7
|
+
import { SPRING_POP, SPRING_SHAPE, spring, useLinger, useViewport, viewBottom } from './motion.js'
|
|
8
|
+
import { hostText, t } from './i18n.js'
|
|
9
|
+
|
|
10
|
+
const MENU_EXIT_MS = 120
|
|
11
|
+
|
|
12
|
+
// -------------------------------------------------------------------------
|
|
13
|
+
// A Bot's model: the picker in its details panel and settings, and the message its
|
|
14
|
+
// chat opens on while it has none. Models come from DeepSeek Harness, grouped by provider.
|
|
15
|
+
|
|
16
|
+
export function groupModels(models) {
|
|
17
|
+
const groups = new Map()
|
|
18
|
+
for (const model of models ?? []) {
|
|
19
|
+
const provider = model.provider ?? model.providerName ?? model.ref.split('/')[0]
|
|
20
|
+
if (!groups.has(provider)) groups.set(provider, [])
|
|
21
|
+
groups.get(provider).push(model)
|
|
22
|
+
}
|
|
23
|
+
const names = new Map()
|
|
24
|
+
for (const [provider, list] of groups) {
|
|
25
|
+
const name = list[0].providerName ?? provider
|
|
26
|
+
names.set(name, (names.get(name) ?? 0) + 1)
|
|
27
|
+
}
|
|
28
|
+
return [...groups].map(([provider, list]) => {
|
|
29
|
+
const name = list[0].providerName ?? provider
|
|
30
|
+
return [names.get(name) > 1 ? `${name} (${provider})` : name, list]
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const servedOf = (roster, ref) => (roster.models ?? []).find(model => model.ref === ref)
|
|
35
|
+
|
|
36
|
+
// What a Bot runs on, for display: the chosen model, else the one it last ran on.
|
|
37
|
+
export function modelOf(bot, roster) {
|
|
38
|
+
const ref = bot.model ?? bot.appliedModel
|
|
39
|
+
if (!ref) return undefined
|
|
40
|
+
const served = servedOf(roster, ref)
|
|
41
|
+
const [provider, ...rest] = ref.split('/')
|
|
42
|
+
return { ref, name: served?.name ?? rest.join('/'), provider: served?.providerName ?? provider, served: served !== undefined }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The thinking levels of one model, `{efforts, defaultEffort?}` or undefined when it
|
|
46
|
+
// has none. The catalog carries them for models Bots use; any other ref asks the host.
|
|
47
|
+
const effortCache = new Map()
|
|
48
|
+
function useEfforts(served, ref, actions) {
|
|
49
|
+
const [fetched, setFetched] = useState(undefined)
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
setFetched(undefined)
|
|
52
|
+
if (ref === undefined || served?.reasoning !== undefined || actions?.efforts === undefined) return undefined
|
|
53
|
+
let live = true
|
|
54
|
+
if (effortCache.has(ref)) setFetched(effortCache.get(ref))
|
|
55
|
+
else void Promise.resolve(actions.efforts(ref)).then((value) => {
|
|
56
|
+
const reasoning = value?.efforts ? value : null
|
|
57
|
+
effortCache.set(ref, reasoning)
|
|
58
|
+
if (live) setFetched(reasoning)
|
|
59
|
+
}).catch(() => {})
|
|
60
|
+
return () => { live = false }
|
|
61
|
+
}, [ref, served?.reasoning !== undefined, actions])
|
|
62
|
+
return served?.reasoning ?? fetched
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The rows of the Thinking section: a Default row that clears the Bot's pick, then every
|
|
66
|
+
// level the model serves.
|
|
67
|
+
export function effortRows(reasoning) {
|
|
68
|
+
if (reasoning?.efforts === undefined || reasoning.efforts.length === 0) return []
|
|
69
|
+
return [
|
|
70
|
+
{ effort: undefined, label: t('Default') },
|
|
71
|
+
...reasoning.efforts.map(effort => ({ effort: effort.id, label: effort.name })),
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function EffortRows({ reasoning, current, onPick }) {
|
|
76
|
+
return h(Fragment, null,
|
|
77
|
+
h('div', { className: 'bt-menu-sep', role: 'separator' }),
|
|
78
|
+
h('div', { className: 'bt-model-group', role: 'presentation' }, t('Thinking level')),
|
|
79
|
+
effortRows(reasoning).map(row => h('button', {
|
|
80
|
+
key: row.effort ?? 'default', type: 'button', role: 'menuitemradio', 'aria-checked': (row.effort ?? null) === (current ?? null),
|
|
81
|
+
onClick: () => onPick(row.effort),
|
|
82
|
+
},
|
|
83
|
+
h('span', { className: 'bt-menu-label' }, row.label),
|
|
84
|
+
h('span', { className: 'bt-model-tick', 'aria-hidden': true }, (row.effort ?? null) === (current ?? null) ? h(CheckIcon) : null))))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function ModelRows({ roster, current, onPick }) {
|
|
88
|
+
return groupModels(roster.models).map(([provider, list]) => h(Fragment, { key: list[0].provider ?? provider },
|
|
89
|
+
h('div', { className: 'bt-model-group', role: 'presentation' }, provider),
|
|
90
|
+
list.map(model => h('button', {
|
|
91
|
+
key: model.ref, type: 'button', role: 'menuitemradio', 'aria-checked': model.ref === current, title: model.ref,
|
|
92
|
+
onClick: () => onPick(model.ref),
|
|
93
|
+
},
|
|
94
|
+
h('span', { className: 'bt-menu-label' }, model.name),
|
|
95
|
+
model.ref === roster.defaultModel ? h('span', { className: 'bt-menu-hint' }, t('Default')) : null,
|
|
96
|
+
h('span', { className: 'bt-model-tick', 'aria-hidden': true }, model.ref === current ? h(CheckIcon) : null)))))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const Caret = () => h('svg', { className: 'bt-model-caret', width: 12, height: 12, viewBox: '0 0 20 20', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true }, h('path', { d: 'M5.5 8l4.5 4.5L14.5 8' }))
|
|
100
|
+
|
|
101
|
+
// A menu that opens under its trigger, or above when the window has no room below. A
|
|
102
|
+
// click outside, Escape, or a scroll outside it closes it.
|
|
103
|
+
export function useAnchoredMenu() {
|
|
104
|
+
const [anchor, setAnchor] = useState(null)
|
|
105
|
+
const [place, setPlace] = useState(null)
|
|
106
|
+
const [shownPlace, leaving] = useLinger(place, MENU_EXIT_MS)
|
|
107
|
+
const button = useRef(null)
|
|
108
|
+
const menu = useRef(null)
|
|
109
|
+
const tick = useViewport()
|
|
110
|
+
const close = () => { setAnchor(null); setPlace(null) }
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (place === null) return undefined
|
|
113
|
+
const onDown = (event) => {
|
|
114
|
+
if (menu.current?.contains(event.target) || button.current?.contains(event.target)) return
|
|
115
|
+
close()
|
|
116
|
+
}
|
|
117
|
+
const onKey = (event) => { if (event.key === 'Escape') { event.stopPropagation(); close(); button.current?.focus() } }
|
|
118
|
+
const onScroll = (event) => { if (!menu.current?.contains(event.target)) close() }
|
|
119
|
+
window.addEventListener('mousedown', onDown)
|
|
120
|
+
window.addEventListener('keydown', onKey, true)
|
|
121
|
+
window.addEventListener('scroll', onScroll, true)
|
|
122
|
+
return () => {
|
|
123
|
+
window.removeEventListener('mousedown', onDown)
|
|
124
|
+
window.removeEventListener('keydown', onKey, true)
|
|
125
|
+
window.removeEventListener('scroll', onScroll, true)
|
|
126
|
+
}
|
|
127
|
+
}, [place])
|
|
128
|
+
const open = () => setAnchor({})
|
|
129
|
+
// Re-reads the trigger's rect whenever the visible viewport moves: the menu
|
|
130
|
+
// tracks its anchor through the on-screen keyboard, not past it.
|
|
131
|
+
useLayoutEffect(() => {
|
|
132
|
+
if (anchor === null || !button.current) return
|
|
133
|
+
const rect = button.current.getBoundingClientRect()
|
|
134
|
+
const width = Math.max(240, rect.width)
|
|
135
|
+
const left = Math.max(8, Math.min(rect.right - width, window.innerWidth - width - 8))
|
|
136
|
+
const below = viewBottom() - rect.bottom
|
|
137
|
+
setPlace(below >= 260 || below >= rect.top
|
|
138
|
+
? { left, width, top: rect.bottom + 4, maxHeight: below - 16, '--bt-origin': 'top right' }
|
|
139
|
+
: { left, width, bottom: viewBottom() - rect.top + 4, maxHeight: rect.top - 16, '--bt-origin': 'bottom right' })
|
|
140
|
+
}, [anchor, tick])
|
|
141
|
+
return { isOpen: place !== null, shownPlace, leaving, button, menu, open, close }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// The trigger shows the model name over its provider. A Thinking section lists the
|
|
145
|
+
// levels of the shown model when it has them.
|
|
146
|
+
// `onPick`/`onPickEffort` replace saving to the Bot, for a Bot that does not exist yet.
|
|
147
|
+
// `withEfforts: false` keeps the Thinking section out, for a pick that has no level of its own.
|
|
148
|
+
export function ModelMenu({ bot, roster, actions, run, wide = false, onPick, onPickEffort, withEfforts = true }) {
|
|
149
|
+
const { isOpen, shownPlace, leaving, button, menu, open, close } = useAnchoredMenu()
|
|
150
|
+
const shown = modelOf(bot, roster)
|
|
151
|
+
const current = bot.model ?? bot.appliedModel
|
|
152
|
+
const served = servedOf(roster, current)
|
|
153
|
+
const reasoning = useEfforts(served, withEfforts ? current : undefined, actions)
|
|
154
|
+
// The level the Bot runs on: its pick when the model serves it, else the model's own.
|
|
155
|
+
const picked = reasoning?.efforts?.some(effort => effort.id === bot.effort) ? bot.effort : undefined
|
|
156
|
+
const currentEffort = picked ?? reasoning?.defaultEffort
|
|
157
|
+
const effortLabel = currentEffort === undefined
|
|
158
|
+
? (reasoning?.efforts?.length > 0 ? t('Default') : undefined)
|
|
159
|
+
: reasoning.efforts.find(effort => effort.id === currentEffort)?.name ?? currentEffort
|
|
160
|
+
const toggle = () => {
|
|
161
|
+
if (isOpen) { close(); return }
|
|
162
|
+
// DSH settings can change the list at any time, so opening asks again.
|
|
163
|
+
void actions?.refreshModels?.()
|
|
164
|
+
open()
|
|
165
|
+
}
|
|
166
|
+
const pick = (ref) => {
|
|
167
|
+
close()
|
|
168
|
+
if (onPick) onPick(ref)
|
|
169
|
+
else if (ref !== current || !bot.model) void run(actions.updateBot(bot.id, { model: ref }))
|
|
170
|
+
}
|
|
171
|
+
const pickEffort = (effort) => {
|
|
172
|
+
close()
|
|
173
|
+
if (onPickEffort) onPickEffort(effort)
|
|
174
|
+
else if (effort !== bot.effort) void run(actions.updateBot(bot.id, { effort: effort ?? null }))
|
|
175
|
+
}
|
|
176
|
+
const models = roster.models ?? []
|
|
177
|
+
const manage = manageModels(actions)
|
|
178
|
+
return h(Fragment, null,
|
|
179
|
+
h('button', {
|
|
180
|
+
ref: button, type: 'button', className: 'bt-model-trigger', 'data-wide': wide || undefined,
|
|
181
|
+
'aria-haspopup': 'menu', 'aria-expanded': isOpen, 'aria-label': t('Model: {model}', { model: shown?.name ?? t('none') }),
|
|
182
|
+
'data-missing': shown !== undefined && !shown.served && models.length > 0 ? '' : undefined, onClick: toggle,
|
|
183
|
+
},
|
|
184
|
+
h('span', { className: 'bt-model-copy' },
|
|
185
|
+
h('span', { className: 'bt-model-name' }, shown?.name ?? t('Choose a model')),
|
|
186
|
+
shown ? h('span', { className: 'bt-model-provider' }, shown.served || models.length === 0
|
|
187
|
+
? (!withEfforts || effortLabel === undefined ? shown.provider : `${shown.provider} · ${effortLabel}`)
|
|
188
|
+
: t('{provider} · not available', { provider: shown.provider })) : null),
|
|
189
|
+
h(Caret)),
|
|
190
|
+
shownPlace ? createPortal(h('div', { ref: menu, className: `bt-menu bt-model-menu${shownPlace.bottom !== undefined ? ' bt-menu-up' : ''}`, role: 'menu', 'aria-label': t('Model for {name}', { name: bot.name }), style: shownPlace, 'data-leaving': leaving || undefined },
|
|
191
|
+
models.length > 0 ? h(ModelRows, { roster, current, onPick: pick })
|
|
192
|
+
: h('div', { className: 'bt-model-none' }, t('No models are set up in DeepSeek Harness yet.')),
|
|
193
|
+
withEfforts && effortRows(reasoning).length > 0 ? h(EffortRows, { reasoning, current: picked, onPick: pickEffort }) : null,
|
|
194
|
+
manage ? h('div', { className: 'bt-menu-sep', role: 'separator' }) : null,
|
|
195
|
+
manage ? h('button', { type: 'button', role: 'menuitem', onClick: () => { close(); manage() } }, h(SlidersIcon), h('span', { className: 'bt-menu-label' }, t('Models and providers…'))) : null), document.body) : null)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// The card a Bot's chat opens on while the Bot has no model, or has one that is no
|
|
199
|
+
// longer served. The first Main Bot greets the user only after a pick.
|
|
200
|
+
export function ModelChoiceDock({ sessionId, useRoster, actions }) {
|
|
201
|
+
const roster = useRoster(value => value)
|
|
202
|
+
const bot = roster.byId[sessionId]
|
|
203
|
+
const [busy, setBusy] = useState('')
|
|
204
|
+
const [error, setError] = useState('')
|
|
205
|
+
const shown = Boolean(bot) && currentPart(bot) === sessionId && roster.ready
|
|
206
|
+
useEffect(() => { if (shown) void actions.refreshModels?.() }, [shown, sessionId])
|
|
207
|
+
if (!bot || currentPart(bot) !== sessionId || !roster.ready) return null
|
|
208
|
+
const models = roster.models ?? []
|
|
209
|
+
// A model whose provider has no API key fails every turn, so it is no choice.
|
|
210
|
+
const usable = models.filter(model => !model.needsKey)
|
|
211
|
+
const lost = bot.model ? !models.some(model => model.ref === bot.model) : false
|
|
212
|
+
// An unknown catalog (none listed) cannot tell a lost model from a slow provider.
|
|
213
|
+
if (bot.model && (!lost || models.length === 0)) return null
|
|
214
|
+
// The missing key is taken here, through the host.
|
|
215
|
+
const keyFor = usable.length === 0 && actions.setProviderKey ? models.find(model => model.needsKey) : undefined
|
|
216
|
+
const manage = manageModels(actions)
|
|
217
|
+
const pick = (ref) => {
|
|
218
|
+
setBusy(ref)
|
|
219
|
+
setError('')
|
|
220
|
+
void Promise.resolve(actions.updateBot(bot.id, { model: ref }))
|
|
221
|
+
.catch((failure) => { setError(failure?.message ?? String(failure)); void actions.refreshModels?.() })
|
|
222
|
+
.finally(() => setBusy(''))
|
|
223
|
+
}
|
|
224
|
+
// The pick reads as the Bot's own first message, with the choices as replies under it.
|
|
225
|
+
const text = lost
|
|
226
|
+
? t('{model} is not available anymore. Pick another model for me.', { model: bot.model.split('/').slice(1).join('/') })
|
|
227
|
+
: keyFor
|
|
228
|
+
? t("Hi, I'm {name}. I need a {provider} API key before I can start.", { name: bot.name, provider: keyFor.providerName ?? keyFor.provider })
|
|
229
|
+
: usable.length === 0
|
|
230
|
+
? t("Hi, I'm {name}. I don't have a model yet. How do you want to set one up?", { name: bot.name })
|
|
231
|
+
: t("Hi, I'm {name}. Pick a model for me first. You can change it later in my details.", { name: bot.name })
|
|
232
|
+
const choices = usable.length === 0
|
|
233
|
+
? setupChoices(actions).map(([label, run]) => h('button', { key: label, type: 'button', className: 'bt-model-chip', onClick: run }, label))
|
|
234
|
+
: [
|
|
235
|
+
...groupModels(usable).map(([provider, list]) => h('div', { key: list[0].provider ?? provider, className: 'bt-model-choice-group', role: 'group', 'aria-label': provider },
|
|
236
|
+
h('div', { className: 'bt-model-choice-provider', 'aria-hidden': true }, provider),
|
|
237
|
+
h('div', { className: 'bt-model-choice-chips' }, list.map(model => h('button', {
|
|
238
|
+
key: model.ref, type: 'button', className: 'bt-model-chip', disabled: busy !== '', 'aria-busy': busy === model.ref || undefined,
|
|
239
|
+
'aria-label': `${model.name} · ${provider}`,
|
|
240
|
+
title: model.ref === roster.defaultModel ? `${model.ref} · ${t('Harness default')}` : model.ref,
|
|
241
|
+
onClick: () => pick(model.ref),
|
|
242
|
+
}, model.name, busy === model.ref ? h('span', { className: 'bt-model-spin', 'aria-label': t('Saving…') }) : null))))),
|
|
243
|
+
manage ? h('button', { key: 'manage', type: 'button', className: 'bt-model-chip bt-model-chip-quiet', onClick: manage }, h(SlidersIcon), t('Manage models')) : null,
|
|
244
|
+
]
|
|
245
|
+
return h('div', { className: 'bt-model-dock' },
|
|
246
|
+
h('div', { className: 'bt-astack', 'data-lead': '' },
|
|
247
|
+
h('div', { className: 'bt-msg' },
|
|
248
|
+
h('div', { className: 'bt-msg-line' },
|
|
249
|
+
h(BotMark, { bot, size: 22 }),
|
|
250
|
+
h('div', { className: 'bt-bubble' }, text)))),
|
|
251
|
+
keyFor
|
|
252
|
+
? h(ModelKeyCard, { key: keyFor.provider, model: keyFor, actions })
|
|
253
|
+
: h('div', { className: 'bt-model-chips', role: 'group', 'aria-label': t('Choose a model') }, choices),
|
|
254
|
+
error ? h('div', { className: 'bt-secret-error bt-model-error', role: 'alert' }, error) : null)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const DEEPSEEK_KEYS_URL = 'https://platform.deepseek.com/api_keys'
|
|
258
|
+
|
|
259
|
+
// The provider's API key, saved through the host; once it is set, the provider's
|
|
260
|
+
// models become the choices. `onSaved` runs once the host took the key.
|
|
261
|
+
export function ModelKeyCard({ model, actions, onSaved }) {
|
|
262
|
+
const [value, setValue] = useState('')
|
|
263
|
+
const [busy, setBusy] = useState(false)
|
|
264
|
+
const [error, setError] = useState('')
|
|
265
|
+
const label = t('{provider} API key', { provider: model.providerName ?? model.provider })
|
|
266
|
+
const ready = value.trim() !== '' && !busy
|
|
267
|
+
const save = () => {
|
|
268
|
+
if (!ready) return
|
|
269
|
+
setBusy(true)
|
|
270
|
+
setError('')
|
|
271
|
+
void Promise.resolve(actions.setProviderKey(model.provider, value))
|
|
272
|
+
.then(() => { setValue(''); onSaved?.() }, failure => setError(hostText(failure?.message ?? String(failure))))
|
|
273
|
+
.finally(() => setBusy(false))
|
|
274
|
+
}
|
|
275
|
+
const official = model.provider === 'deepseek-official'
|
|
276
|
+
return h('div', { className: 'bt-q bt-model-key', role: 'group', 'aria-label': label },
|
|
277
|
+
h('div', { className: 'bt-model-key-title' }, h(KeyIcon), label),
|
|
278
|
+
h('label', { className: 'bt-q-field bt-secret-field' }, h(MaskedInput, { value, onChange: setValue, onEnter: save, label, autoFocus: true })),
|
|
279
|
+
official ? h('div', { className: 'bt-model-key-help' },
|
|
280
|
+
h('a', { className: 'bt-model-key-link', href: DEEPSEEK_KEYS_URL, target: '_blank', rel: 'noopener noreferrer' }, t('Create a key on the DeepSeek platform ↗')),
|
|
281
|
+
h('span', { className: 'bt-model-key-note' }, t('The API is billed by usage; the account needs a balance first.'))) : null,
|
|
282
|
+
error ? h('div', { className: 'bt-secret-error', role: 'alert' }, error) : null,
|
|
283
|
+
h('div', { className: 'bt-q-foot' },
|
|
284
|
+
actions.openModels ? h('button', { type: 'button', className: 'bt-q-btn bt-model-key-other', onClick: actions.openModels }, t('Another provider')) : null,
|
|
285
|
+
h('button', { type: 'button', className: 'bt-q-submit', disabled: !ready, onClick: save }, busy ? t('Saving…') : t('Save'))))
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Where the providers are set up: DS Bot's Models page, else DSH's own.
|
|
289
|
+
export const manageModels = actions => actions.openModels ?? actions.openHarnessModels ?? actions.openHarnessSettings
|
|
290
|
+
|
|
291
|
+
// Ways to get a first model. The DeepSeek sign-in exists only where the shell carries
|
|
292
|
+
// its account plugin; the API key uses the shell's own editor.
|
|
293
|
+
function setupChoices(actions) {
|
|
294
|
+
const custom = manageModels(actions)
|
|
295
|
+
return [
|
|
296
|
+
actions.canSignIn?.() ? [t('Sign in to DeepSeek'), () => actions.signIn()] : null,
|
|
297
|
+
actions.openHarnessOnboarding ? [t('Enter a DeepSeek API key'), () => actions.openHarnessOnboarding('deepseek-official')] : null,
|
|
298
|
+
custom ? [t('Open settings to set up a custom model'), () => custom()] : null,
|
|
299
|
+
].filter(Boolean)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// The shell's own DeepSeek sign-in dialog, borrowed from its account plugin: the
|
|
303
|
+
// plugin's model sign-in entry renders here with the plugin's own operations.
|
|
304
|
+
export function DeepSeekSignIn({ useUi, actions }) {
|
|
305
|
+
const open = useUi(state => state.signIn)
|
|
306
|
+
const entry = open ? actions.signInEntry?.() : null
|
|
307
|
+
if (!entry) return null
|
|
308
|
+
return h(SignInHost, { key: open, entry, actions })
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function SignInHost({ entry, actions }) {
|
|
312
|
+
const [ops] = useState(() => entry.inject?.() ?? {})
|
|
313
|
+
const close = () => actions.closeSignIn()
|
|
314
|
+
const useHook = source => selector => useSyncExternalStore(source.subscribe, () => selector(source.getSnapshot()))
|
|
315
|
+
const [hooks] = useState(() => ({ useAccount: useHook(ops.hooks.account), useTheme: useHook(ops.hooks.theme) }))
|
|
316
|
+
return h(entry.component, {
|
|
317
|
+
...ops,
|
|
318
|
+
...hooks,
|
|
319
|
+
t: actions.accountT(),
|
|
320
|
+
complete: close,
|
|
321
|
+
useApiKey: () => { close(); actions.openHarnessOnboarding('deepseek-official') },
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export const MODEL_CSS = `
|
|
326
|
+
.bt-model-trigger{display:inline-flex;align-items:center;gap:8px;max-width:100%;min-width:0;height:auto;min-height:32px;padding:4px 8px 4px 10px;border:0;border-radius:8px;background:var(--bt-hover);color:var(--bt-ink);font:inherit;text-align:left;cursor:pointer;transition:background-color .12s ease}
|
|
327
|
+
.bt-model-trigger:hover,.bt-model-trigger[aria-expanded=true]{background:var(--bt-active)}
|
|
328
|
+
.bt-model-trigger:focus-visible{outline:2px solid var(--bt-accent);outline-offset:1px}
|
|
329
|
+
.bt-model-trigger[data-missing] .bt-model-provider{color:var(--bt-danger)}
|
|
330
|
+
.bt-model-trigger[data-wide]{display:flex;width:100%;min-height:48px;padding:7px 12px;justify-content:space-between;border-radius:12px;background:var(--bt-card);box-shadow:inset 0 0 0 1px var(--bt-line)}
|
|
331
|
+
.bt-model-trigger[data-wide]:hover,.bt-model-trigger[data-wide][aria-expanded=true]{background:var(--bt-hover)}
|
|
332
|
+
.bt-model-copy{display:flex;flex-direction:column;min-width:0}
|
|
333
|
+
.bt-model-name{font-size:13px;line-height:18px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
334
|
+
.bt-model-provider{font-size:11px;line-height:14px;color:var(--bt-ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
335
|
+
.bt-model-caret{flex:none;color:var(--bt-ink-3);transition:${spring(SPRING_SHAPE, 'transform')}}
|
|
336
|
+
.bt-model-trigger[aria-expanded=true] .bt-model-caret{transform:rotate(180deg)}
|
|
337
|
+
.bt-model-menu{overflow-y:auto;scrollbar-width:thin}
|
|
338
|
+
.bt-model-group{flex:none;padding:8px 8px 2px;font-size:11px;line-height:14px;font-weight:500;color:var(--bt-ink-3);text-transform:none}
|
|
339
|
+
.bt-model-group:first-child{padding-top:2px}
|
|
340
|
+
.bt-model-tick{flex:none;width:16px;height:16px;display:inline-flex;color:var(--bt-ink-2)}
|
|
341
|
+
.bt-model-tick svg{animation:bt-check-pop ${SPRING_POP.duration}ms ${SPRING_POP.easing} 60ms backwards}
|
|
342
|
+
.bt-model-none{padding:8px;font-size:13px;line-height:18px;color:var(--bt-ink-2)}
|
|
343
|
+
/* DSH centers the message column inside its scroll padding, which can sit a few px off this dock's center; the side shadows keep the band over the column's edges. */
|
|
344
|
+
.bt-model-dock{width:100%;max-width:var(--dsh-chat-content-width,748px);margin:0 auto;display:flex;flex-direction:column;align-items:flex-start;gap:8px;padding:0 0 10px;box-sizing:border-box;background:var(--bt-main);box-shadow:-12px 0 0 var(--bt-main),12px 0 0 var(--bt-main);animation:bt-model-in ${SPRING_POP.duration}ms ${SPRING_POP.easing} backwards}
|
|
345
|
+
/* The scroll box clips at its padding edge, so 3px of padding keeps the chips' focus rings visible; the negative margins cancel it so the chips stay in place. */
|
|
346
|
+
.bt-model-chips{display:flex;flex-wrap:wrap;gap:6px;box-sizing:border-box;padding:3px;margin:-3px 0 -3px 27px;max-width:min(566px,calc(100% - 27px));max-height:min(246px,calc(36vh + 6px));max-height:min(246px,calc(36dvh + 6px));overflow-y:auto;scrollbar-width:thin}
|
|
347
|
+
.bt-model-choice-group{display:flex;flex-direction:column;gap:6px;width:100%;min-width:0}
|
|
348
|
+
.bt-model-choice-provider{padding-top:4px;font-size:12px;line-height:16px;font-weight:500;color:var(--bt-ink-2);overflow-wrap:anywhere}
|
|
349
|
+
.bt-model-choice-chips{display:flex;flex-wrap:wrap;gap:6px}
|
|
350
|
+
.bt-model-chip{display:inline-flex;align-items:center;gap:6px;min-height:30px;padding:5px 12px;border:.5px solid var(--bt-line-2);border-radius:999px;background:var(--bt-surface,transparent);color:var(--bt-ink);font:inherit;font-size:13px;line-height:18px;cursor:pointer;transition:background-color .12s ease,border-color .12s ease}
|
|
351
|
+
.bt-model-chip:hover:not(:disabled){background:var(--bt-hover)}
|
|
352
|
+
.bt-model-chip:focus-visible{outline:2px solid var(--bt-accent);outline-offset:1px}
|
|
353
|
+
.bt-model-chip-quiet{color:var(--bt-ink-2)}
|
|
354
|
+
.bt-model-chip svg{width:14px;height:14px}
|
|
355
|
+
.bt-model-chip:disabled{cursor:default}
|
|
356
|
+
.bt-model-chip:disabled:not([aria-busy]){opacity:.5}
|
|
357
|
+
.bt-model-error{margin-left:30px}
|
|
358
|
+
.bt-q.bt-model-key{margin:0 0 0 30px;width:min(480px,calc(100% - 30px));gap:10px}
|
|
359
|
+
.bt-model-key-title{display:flex;align-items:center;gap:6px;font-size:13px;line-height:18px;font-weight:500;color:var(--bt-ink-2)}
|
|
360
|
+
.bt-model-key-title svg{flex:none;width:16px;height:16px}
|
|
361
|
+
.bt-model-key .bt-q-foot{margin-top:2px}
|
|
362
|
+
.bt-model-key-help{display:flex;flex-direction:column;gap:2px;font-size:12px;line-height:17px;color:var(--bt-ink-2)}
|
|
363
|
+
.bt-model-key-link{align-self:flex-start;color:var(--bt-ink);text-decoration:underline;text-decoration-color:var(--bt-line-2);text-underline-offset:2px}
|
|
364
|
+
.bt-model-key-link:hover{text-decoration-color:currentColor}
|
|
365
|
+
.bt-model-key-link:focus-visible{outline:2px solid var(--bt-accent);outline-offset:1px;border-radius:4px}
|
|
366
|
+
.bt-model-key-other{margin-right:auto}
|
|
367
|
+
@keyframes bt-model-in{from{opacity:0;transform:translateY(6px)}}
|
|
368
|
+
.bt-model-spin{flex:none;width:14px;height:14px;border-radius:50%;border:1.5px solid var(--bt-line-2);border-top-color:var(--bt-ink-2);animation:bt-model-spin .7s linear infinite}
|
|
369
|
+
@keyframes bt-model-spin{to{transform:rotate(360deg)}}
|
|
370
|
+
@media (prefers-reduced-motion:reduce){.bt-model-dock{animation:none}.bt-model-spin{animation-duration:2s}.bt-model-caret{transition:none}.bt-model-tick svg{animation:none}}
|
|
371
|
+
`
|