dsh-daruma 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SISCHOI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # dsh-daruma
2
+
3
+ Daruma resilience plugin for DeepSeek Harness — detects model-request failures
4
+ and fails over to another channel to keep long tasks alive.
5
+
6
+ ## How it works
7
+
8
+ dsh-daruma is a native Cordis plugin. It sits **downstream of the in-box
9
+ `dsh-llm-retry`** on the `agent/request-error` waterfall: retry owns the
10
+ same-channel retry budget, and when it gives up it delegates (via `next()`) to
11
+ daruma. daruma then:
12
+
13
+ 1. records the failure against the current channel's circuit-breaker state;
14
+ 2. when the channel trips (`failureBudget` consecutive failures, or a terminal
15
+ code like `QUOTA` / `INVALID_CREDENTIAL` / `CONTEXT_WINDOW_EXCEEDED`), it
16
+ arms the next routable channel and returns `{ kind: 'retry' }`;
17
+ 3. on the retry turn, the `agent/request` waterfall swaps the request config
18
+ onto the armed channel.
19
+
20
+ Channel health persists to `~/.dsh/daruma/channel-health.json`, so a tripped
21
+ channel stays cooled-down across restarts.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ dsh plugin --profile web add github:<you>/daruma # or a published dsh-daruma
27
+ ```
28
+
29
+ ## Configure
30
+
31
+ Add a `dsh-daruma` entry to your profile's `cordis.patch.yml` (or rely on the
32
+ bundle defaults) with an ordered failover chain:
33
+
34
+ ```yaml
35
+ - id: dsh-daruma
36
+ name: dsh-daruma
37
+ config:
38
+ channels:
39
+ - provider: mt
40
+ model: deepseek-v4-pro
41
+ - provider: mt
42
+ model: glm-5.2
43
+ failureBudget: 3
44
+ cooldownMs: 30000
45
+ giveUpBudget: 8
46
+ ```
47
+
48
+ Each channel is a `{ provider, model }` pair reachable by the LLM adapter
49
+ registry (same names you use in the settings model selector).
50
+
51
+ ## Development
52
+
53
+ ```bash
54
+ pnpm --filter dsh-daruma build
55
+ pnpm --filter dsh-daruma test
56
+ ```
@@ -0,0 +1,24 @@
1
+ # dsh-daruma bundle patch: inserts the Daruma resilience plugin into a
2
+ # profile's layer stack. This config is overridden by the profile's own
3
+ # cordis.patch.yml.
4
+ - insert:
5
+ - id: dsh-daruma
6
+ name: 'dsh-daruma'
7
+ config:
8
+ # Failover chain, highest priority first. Each entry is a
9
+ # { provider, model } pair reachable by the LLM adapter registry.
10
+ # When the current channel trips, daruma switches to the next
11
+ # routable entry.
12
+ channels:
13
+ # - provider: mt
14
+ # model: deepseek-v4-pro
15
+ # - provider: mt
16
+ # model: glm-5.2
17
+ # Circuit breaker thresholds.
18
+ failureBudget: 3
19
+ cooldownMs: 30000
20
+ # Maximum failovers per session before giving up entirely.
21
+ giveUpBudget: 8
22
+ # Where channel health is persisted (defaults to
23
+ # ~/.dsh/daruma/channel-health.json).
24
+ # stateFile: ''
@@ -0,0 +1,16 @@
1
+ /**
2
+ * BackupChannelPanel: list candidate models for a provider and pick one as
3
+ * the failover backup. Rendered inside a primitives Modal.
4
+ *
5
+ * No probing happens here anymore — the panel is a plain picker. Channel
6
+ * health still updates from real traffic via the recovery engine.
7
+ */
8
+ import type { DarumaApi, StatusView } from './api.ts';
9
+ type Translate = (key: string) => string;
10
+ export declare function BackupPanel(props: {
11
+ api: DarumaApi;
12
+ t: Translate;
13
+ status: StatusView | null;
14
+ onClose: () => void;
15
+ }): React.JSX.Element;
16
+ export {};
@@ -0,0 +1,132 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * BackupChannelPanel: list candidate models for a provider and pick one as
4
+ * the failover backup. Rendered inside a primitives Modal.
5
+ *
6
+ * No probing happens here anymore — the panel is a plain picker. Channel
7
+ * health still updates from real traffic via the recovery engine.
8
+ */
9
+ import { memo, useCallback, useEffect, useMemo, useState } from 'react';
10
+ import { Button, Modal, Pill, } from '@deepseek-ai/dsh-client-ui-primitives';
11
+ function deriveProvider(status) {
12
+ if (status === null)
13
+ return '';
14
+ if (status.current !== null)
15
+ return status.current.split('::')[0] ?? '';
16
+ if (status.backup !== null)
17
+ return status.backup.provider;
18
+ return status.providers[0] ?? '';
19
+ }
20
+ const sectionStyle = {
21
+ display: 'flex',
22
+ flexDirection: 'column',
23
+ gap: '8px',
24
+ padding: '12px 0',
25
+ fontSize: '13px',
26
+ };
27
+ const rowStyle = {
28
+ display: 'flex',
29
+ alignItems: 'center',
30
+ gap: '8px',
31
+ };
32
+ const noteStyle = { color: 'var(--dsw-text-tertiary, #999)', fontSize: '12px' };
33
+ /** Clamp a long provider/model name so the row never wraps. */
34
+ const backupPillStyle = {
35
+ flex: '1 1 auto',
36
+ minWidth: 0,
37
+ overflow: 'hidden',
38
+ };
39
+ const ellipsisStyle = {
40
+ minWidth: 0,
41
+ overflow: 'hidden',
42
+ textOverflow: 'ellipsis',
43
+ whiteSpace: 'nowrap',
44
+ };
45
+ const listStyle = {
46
+ maxHeight: 300,
47
+ overflowY: 'auto',
48
+ display: 'flex',
49
+ flexDirection: 'column',
50
+ gap: 4,
51
+ contain: 'content',
52
+ willChange: 'scroll-position',
53
+ overscrollBehavior: 'contain',
54
+ };
55
+ /** Skip rendering rows outside the viewport while scrolling. */
56
+ const candidateRowStyle = {
57
+ ...rowStyle,
58
+ contentVisibility: 'auto',
59
+ containIntrinsicSize: '28px',
60
+ };
61
+ /** One candidate row, memoized so selection changes only re-render it. */
62
+ const CandidateRow = memo(function CandidateRow(props) {
63
+ const { candidate, isBackup, t, onSetBackup } = props;
64
+ return (_jsxs("div", { style: candidateRowStyle, children: [_jsx("span", { style: { width: 14, flexShrink: 0 } }), _jsx("span", { style: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: candidate.model }), isBackup && _jsx(Pill, { active: true, children: t('currentBackupMark') }), _jsx(Button, { size: "sm", variant: "ghost", disabled: isBackup, onClick: () => onSetBackup(candidate.model), children: t('setBackup') })] }));
65
+ });
66
+ export function BackupPanel(props) {
67
+ const { api, t, status, onClose } = props;
68
+ const [provider, setProvider] = useState(deriveProvider(status));
69
+ const [candidates, setCandidates] = useState(null);
70
+ const [loading, setLoading] = useState(false);
71
+ const [busy, setBusy] = useState(null);
72
+ const [error, setError] = useState(null);
73
+ // Local overlay on the status-prop backup: applied optimistically after
74
+ // set/clear so the row reflects the change without waiting for the parent
75
+ // to re-fetch status. Cleared whenever a fresh status arrives.
76
+ const [backupOverride, setBackupOverride] = useState(undefined);
77
+ // The effective backup shown in the panel: local override → status prop.
78
+ const backupShown = backupOverride !== undefined ? backupOverride : status?.backup ?? null;
79
+ // O(1) lookup for the current backup model; re-built only when the effective backup changes.
80
+ const backupModel = useMemo(() => (backupShown !== null && backupShown.provider === provider ? backupShown.model : null), [backupShown, provider]);
81
+ const loadCandidates = async () => {
82
+ if (provider === '')
83
+ return;
84
+ setLoading(true);
85
+ setError(null);
86
+ try {
87
+ setCandidates(await api.listCandidates(provider));
88
+ }
89
+ catch (cause) {
90
+ setError(cause instanceof Error ? cause.message : String(cause));
91
+ }
92
+ finally {
93
+ setLoading(false);
94
+ }
95
+ };
96
+ useEffect(() => {
97
+ if (provider !== '')
98
+ void loadCandidates();
99
+ }, [provider]);
100
+ // A fresh status from the parent supersedes the local override.
101
+ useEffect(() => {
102
+ setBackupOverride(undefined);
103
+ }, [status]);
104
+ const setBackup = async (model) => {
105
+ setBusy(model);
106
+ setError(null);
107
+ try {
108
+ await api.setBackup(provider, model);
109
+ setBackupOverride({ provider, model });
110
+ }
111
+ catch (cause) {
112
+ setError(cause instanceof Error ? cause.message : String(cause));
113
+ }
114
+ finally {
115
+ setBusy(null);
116
+ }
117
+ };
118
+ const clearBackup = async () => {
119
+ setError(null);
120
+ try {
121
+ await api.clearBackup();
122
+ setBackupOverride(null);
123
+ }
124
+ catch (cause) {
125
+ setError(cause instanceof Error ? cause.message : String(cause));
126
+ }
127
+ };
128
+ // Stable callback so memoized CandidateRow props don't change every render.
129
+ const handleSetBackup = useCallback((model) => void setBackup(model), [provider]);
130
+ return (_jsx(Modal, { open: true, onClose: onClose, title: t('backupManage'), className: "daruma-wide-dialog", closeLabel: t('close'), description: t('backupManageHint'), footer: _jsx(Button, { variant: "primary", onClick: onClose, children: t('close') }), children: _jsxs("div", { style: sectionStyle, children: [status !== null && status.failoverCount > 0 && (_jsxs("div", { style: noteStyle, children: [t('failoverCount'), ": ", status.failoverCount, status.failoverHistory.length > 0 && (_jsxs(_Fragment, { children: [' · ', t('lastFailover'), ": ", status.failoverHistory.map((h) => `${h.from} → ${h.to}`).join(' · ')] }))] })), backupShown !== null && (_jsxs("div", { style: rowStyle, children: [_jsxs("span", { style: { flexShrink: 0 }, children: [t('currentBackup'), ":"] }), _jsx(Pill, { active: true, style: backupPillStyle, title: `${backupShown.provider}/${backupShown.model}`, children: _jsxs("span", { style: ellipsisStyle, children: [backupShown.provider, "/", backupShown.model] }) }), _jsx(Button, { size: "sm", variant: "ghost", style: { flexShrink: 0 }, onClick: () => void clearBackup(), children: t('clearBackup') })] })), _jsxs("div", { style: rowStyle, children: [(status?.providers ?? []).length > 0 ? (_jsx("select", { value: provider, disabled: loading, onChange: (event) => setProvider(event.currentTarget.value), style: { flex: 1, padding: '4px 8px', fontSize: 13 }, children: status?.providers.map((name) => (_jsx("option", { value: name, children: name }, name))) })) : (_jsx("input", { value: provider, placeholder: t('providerPlaceholder'), onChange: (event) => setProvider(event.currentTarget.value), style: { flex: 1, padding: '4px 8px', fontSize: 13 } })), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void loadCandidates(), disabled: provider === '' || loading, children: loading ? t('loading') : t('loadCandidates') })] }), error !== null && _jsx("div", { style: { color: 'var(--dsw-text-danger, #e5484d)' }, children: error }), candidates !== null && (_jsxs(_Fragment, { children: [_jsx("div", { style: rowStyle, children: _jsxs("span", { children: [t('candidates'), " (", candidates.length, ")"] }) }), candidates.length === 0 && _jsx("div", { style: noteStyle, children: t('noCandidates') }), _jsx("div", { style: listStyle, children: candidates.map((candidate) => (_jsx(CandidateRow, { candidate: candidate, isBackup: candidate.model === backupModel, t: t, onSetBackup: handleSetBackup }, candidate.model))) })] }))] }) }));
131
+ }
132
+ //# sourceMappingURL=BackupPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackupPanel.js","sourceRoot":"","sources":["../../src/client/BackupPanel.tsx"],"names":[],"mappings":";AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACvE,OAAO,EACL,MAAM,EACN,KAAK,EACL,IAAI,GACL,MAAM,uCAAuC,CAAA;AAK9C,SAAS,cAAc,CAAC,MAAyB;IAC/C,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,CAAA;IAC9B,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IACvE,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAA;IACzD,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;AAClC,CAAC;AAED,MAAM,YAAY,GAAwB;IACxC,OAAO,EAAE,MAAM;IACf,aAAa,EAAE,QAAQ;IACvB,GAAG,EAAE,KAAK;IACV,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,MAAM;CACjB,CAAA;AAED,MAAM,QAAQ,GAAwB;IACpC,OAAO,EAAE,MAAM;IACf,UAAU,EAAE,QAAQ;IACpB,GAAG,EAAE,KAAK;CACX,CAAA;AAED,MAAM,SAAS,GAAwB,EAAE,KAAK,EAAE,gCAAgC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;AAEpG,+DAA+D;AAC/D,MAAM,eAAe,GAAwB;IAC3C,IAAI,EAAE,UAAU;IAChB,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,QAAQ;CACnB,CAAA;AAED,MAAM,aAAa,GAAwB;IACzC,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,QAAQ;IAClB,YAAY,EAAE,UAAU;IACxB,UAAU,EAAE,QAAQ;CACrB,CAAA;AAED,MAAM,SAAS,GAAwB;IACrC,SAAS,EAAE,GAAG;IACd,SAAS,EAAE,MAAM;IACjB,OAAO,EAAE,MAAM;IACf,aAAa,EAAE,QAAQ;IACvB,GAAG,EAAE,CAAC;IACN,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,iBAAiB;IAC7B,kBAAkB,EAAE,SAAS;CAC9B,CAAA;AAED,gEAAgE;AAChE,MAAM,iBAAiB,GAAwB;IAC7C,GAAG,QAAQ;IACX,iBAAiB,EAAE,MAAM;IACzB,oBAAoB,EAAE,MAAM;CAC7B,CAAA;AAED,0EAA0E;AAC1E,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,YAAY,CAAC,KAK/C;IACC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,KAAK,CAAA;IACrD,OAAO,CACL,eAAK,KAAK,EAAE,iBAAiB,aAC3B,eAAM,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,GAAI,EAC7C,eAAM,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAG,SAAS,CAAC,KAAK,GAAQ,EAClI,QAAQ,IAAI,KAAC,IAAI,IAAC,MAAM,kBAAE,CAAC,CAAC,mBAAmB,CAAC,GAAQ,EACzD,KAAC,MAAM,IAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,YAC9F,CAAC,CAAC,WAAW,CAAC,GACR,IACL,CACP,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,UAAU,WAAW,CAAC,KAK3B;IACC,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,CAAA;IACzC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAA;IAChE,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAyB,IAAI,CAAC,CAAA;IAC1E,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACrD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,wEAAwE;IACxE,0EAA0E;IAC1E,+DAA+D;IAC/D,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAyD,SAAS,CAAC,CAAA;IAEvH,yEAAyE;IACzE,MAAM,WAAW,GAAG,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,IAAI,IAAI,CAAA;IAE1F,6FAA6F;IAC7F,MAAM,WAAW,GAAG,OAAO,CACzB,GAAG,EAAE,CAAC,CAAC,WAAW,KAAK,IAAI,IAAI,WAAW,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAC5F,CAAC,WAAW,EAAE,QAAQ,CAAC,CACxB,CAAA;IAED,MAAM,cAAc,GAAG,KAAK,IAAmB,EAAE;QAC/C,IAAI,QAAQ,KAAK,EAAE;YAAE,OAAM;QAC3B,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,IAAI,CAAC;YACH,aAAa,CAAC,MAAM,GAAG,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAA;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAClE,CAAC;gBAAS,CAAC;YACT,UAAU,CAAC,KAAK,CAAC,CAAA;QACnB,CAAC;IACH,CAAC,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,QAAQ,KAAK,EAAE;YAAE,KAAK,cAAc,EAAE,CAAA;IAC5C,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEd,gEAAgE;IAChE,SAAS,CAAC,GAAG,EAAE;QACb,iBAAiB,CAAC,SAAS,CAAC,CAAA;IAC9B,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,MAAM,SAAS,GAAG,KAAK,EAAE,KAAa,EAAiB,EAAE;QACvD,OAAO,CAAC,KAAK,CAAC,CAAA;QACd,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;YACpC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;QACxC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAClE,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,IAAI,CAAC,CAAA;QACf,CAAC;IACH,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,KAAK,IAAmB,EAAE;QAC5C,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,WAAW,EAAE,CAAA;YACvB,iBAAiB,CAAC,IAAI,CAAC,CAAA;QACzB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAClE,CAAC;IACH,CAAC,CAAA;IAED,4EAA4E;IAC5E,MAAM,eAAe,GAAG,WAAW,CAAC,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEzF,OAAO,CACL,KAAC,KAAK,IACJ,IAAI,QACJ,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,CAAC,CAAC,cAAc,CAAC,EACxB,SAAS,EAAC,oBAAoB,EAC9B,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,EACtB,WAAW,EAAE,CAAC,CAAC,kBAAkB,CAAC,EAClC,MAAM,EAAE,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,OAAO,EAAE,OAAO,YAAG,CAAC,CAAC,OAAO,CAAC,GAAU,YAEzE,eAAK,KAAK,EAAE,YAAY,aACrB,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,aAAa,GAAG,CAAC,IAAI,CAC9C,eAAK,KAAK,EAAE,SAAS,aAClB,CAAC,CAAC,eAAe,CAAC,QAAI,MAAM,CAAC,aAAa,EAC1C,MAAM,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,IAAI,CACpC,8BACG,KAAK,EACL,CAAC,CAAC,cAAc,CAAC,QAAI,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IACzF,CACJ,IACG,CACP,EAEA,WAAW,KAAK,IAAI,IAAI,CACvB,eAAK,KAAK,EAAE,QAAQ,aAClB,gBAAM,KAAK,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,aAAG,CAAC,CAAC,eAAe,CAAC,SAAS,EAC5D,KAAC,IAAI,IAAC,MAAM,QAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC,QAAQ,IAAI,WAAW,CAAC,KAAK,EAAE,YACxF,gBAAM,KAAK,EAAE,aAAa,aACvB,WAAW,CAAC,QAAQ,OAAG,WAAW,CAAC,KAAK,IACpC,GACF,EACP,KAAC,MAAM,IAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,KAAK,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,WAAW,EAAE,YAAG,CAAC,CAAC,aAAa,CAAC,GAAU,IACtH,CACP,EAED,eAAK,KAAK,EAAE,QAAQ,aACjB,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACtC,iBACE,KAAK,EAAE,QAAQ,EACf,QAAQ,EAAE,OAAO,EACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,EAC3D,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,YAEnD,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAC/B,iBAAmB,KAAK,EAAE,IAAI,YAAG,IAAI,IAAxB,IAAI,CAA8B,CAChD,CAAC,GACK,CACV,CAAC,CAAC,CAAC,CACF,gBACE,KAAK,EAAE,QAAQ,EACf,WAAW,EAAE,CAAC,CAAC,qBAAqB,CAAC,EACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,EAC3D,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,GACpD,CACH,EACD,KAAC,MAAM,IAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,cAAc,EAAE,EAAE,QAAQ,EAAE,QAAQ,KAAK,EAAE,IAAI,OAAO,YAC3G,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,GACtC,IACL,EAEL,KAAK,KAAK,IAAI,IAAI,cAAK,KAAK,EAAE,EAAE,KAAK,EAAE,iCAAiC,EAAE,YAAG,KAAK,GAAO,EAEzF,UAAU,KAAK,IAAI,IAAI,CACtB,8BACE,cAAK,KAAK,EAAE,QAAQ,YAClB,2BAAO,CAAC,CAAC,YAAY,CAAC,QAAI,UAAU,CAAC,MAAM,SAAS,GAChD,EACL,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,cAAK,KAAK,EAAE,SAAS,YAAG,CAAC,CAAC,cAAc,CAAC,GAAO,EAC5E,cAAK,KAAK,EAAE,SAAS,YAClB,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAC7B,KAAC,YAAY,IAEX,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,SAAS,CAAC,KAAK,KAAK,WAAW,EACzC,CAAC,EAAE,CAAC,EACJ,WAAW,EAAE,eAAe,IAJvB,SAAS,CAAC,KAAK,CAKpB,CACH,CAAC,GACE,IACL,CACJ,IACG,GACA,CACT,CAAA;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * DarumaStatus: a compact control at the right end of the composer tool row,
3
+ * next to the model selector. Shows overall channel health and the backup
4
+ * selection; clicking it opens the backup-channel panel.
5
+ */
6
+ import type { DarumaApi } from './api.ts';
7
+ type Translate = (key: string) => string;
8
+ export declare function StatusDock(props: {
9
+ api: DarumaApi;
10
+ t: Translate;
11
+ }): React.JSX.Element;
12
+ export {};
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * DarumaStatus: a compact control at the right end of the composer tool row,
4
+ * next to the model selector. Shows overall channel health and the backup
5
+ * selection; clicking it opens the backup-channel panel.
6
+ */
7
+ import { useCallback, useEffect, useState } from 'react';
8
+ import { Button, IconCheckOutline16, IconWarningOutline16 } from '@deepseek-ai/dsh-client-ui-primitives';
9
+ import { BackupPanel } from "./BackupPanel.js";
10
+ const controlStyle = {
11
+ display: 'inline-flex',
12
+ alignItems: 'center',
13
+ gap: 6,
14
+ maxWidth: 220,
15
+ overflow: 'hidden',
16
+ textOverflow: 'ellipsis',
17
+ whiteSpace: 'nowrap',
18
+ };
19
+ /** Overall health: any cooling channel → error, any probe → warning, else done. */
20
+ function overallState(status) {
21
+ if (status === null)
22
+ return 'ongoing';
23
+ if (status.channels.some((c) => c.state === 'COOLDOWN'))
24
+ return 'error';
25
+ if (status.channels.some((c) => c.state === 'PROBE'))
26
+ return 'warning';
27
+ return 'done';
28
+ }
29
+ const iconColor = {
30
+ done: 'var(--dsw-text-success, #30a46c)',
31
+ warning: 'var(--dsw-text-warning, #f5a524)',
32
+ error: 'var(--dsw-text-danger, #e5484d)',
33
+ ongoing: 'var(--dsw-text-secondary, #888)',
34
+ };
35
+ /** Status glyph: a check when healthy, a warning otherwise. */
36
+ function StatusIcon(props) {
37
+ return (_jsx("span", { style: { display: 'inline-flex', color: iconColor[props.state], flexShrink: 0 }, children: props.state === 'done'
38
+ ? _jsx(IconCheckOutline16, { size: 14 })
39
+ : _jsx(IconWarningOutline16, { size: 14 }) }));
40
+ }
41
+ export function StatusDock(props) {
42
+ const [status, setStatus] = useState(null);
43
+ const [panelOpen, setPanelOpen] = useState(false);
44
+ const load = useCallback(async () => {
45
+ try {
46
+ const value = await props.api.status();
47
+ setStatus(value);
48
+ }
49
+ catch {
50
+ // keep last good snapshot; the control stays quiet on RPC failure
51
+ }
52
+ }, [props.api]);
53
+ useEffect(() => {
54
+ void load();
55
+ const timer = setInterval(() => void load(), 30_000);
56
+ return () => clearInterval(timer);
57
+ }, [load]);
58
+ const t = props.t;
59
+ const backupLabel = status?.backup !== null && status?.backup !== undefined
60
+ ? `${status.backup.provider}/${status.backup.model}`
61
+ : t('backupNone');
62
+ return (_jsxs(_Fragment, { children: [_jsxs(Button, { size: "sm", variant: "ghost", title: t('backupManageHint'), onClick: () => setPanelOpen(true), style: controlStyle, children: [_jsx(StatusIcon, { state: overallState(status) }), _jsxs("span", { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: [t('backup'), ": ", backupLabel] })] }), panelOpen && (_jsx(BackupPanel, { api: props.api, t: t, status: status, onClose: () => {
63
+ setPanelOpen(false);
64
+ void load();
65
+ } }))] }));
66
+ }
67
+ //# sourceMappingURL=StatusDock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatusDock.js","sourceRoot":"","sources":["../../src/client/StatusDock.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Client-side wire types and RPC wrapper for the `/dsh-daruma` channel.
3
+ * Structural types only — no monorepo-internal imports.
4
+ */
5
+ export type ChannelState = 'HEALTHY' | 'COOLDOWN' | 'PROBE';
6
+ export interface ChannelHealthView {
7
+ channel: string;
8
+ state: ChannelState;
9
+ failures: number;
10
+ }
11
+ export interface StatusView {
12
+ current: string | null;
13
+ backup: {
14
+ provider: string;
15
+ model: string;
16
+ } | null;
17
+ channels: ChannelHealthView[];
18
+ failoverCount: number;
19
+ failoverHistory: Array<{
20
+ from: string;
21
+ to: string;
22
+ reason: string;
23
+ at: number;
24
+ }>;
25
+ providers: string[];
26
+ }
27
+ export interface CandidateView {
28
+ provider: string;
29
+ model: string;
30
+ }
31
+ export interface RpcResult<T = unknown> {
32
+ ok: boolean;
33
+ value?: T;
34
+ error?: {
35
+ code: string;
36
+ message: string;
37
+ };
38
+ }
39
+ /** One unary call on the daruma channel. */
40
+ export type Rpc = (endpoint: string, payload?: unknown) => Promise<RpcResult>;
41
+ export interface DarumaApi {
42
+ status(): Promise<StatusView>;
43
+ listCandidates(provider: string): Promise<CandidateView[]>;
44
+ setBackup(provider: string, model: string): Promise<void>;
45
+ clearBackup(): Promise<void>;
46
+ }
47
+ export declare function createApi(rpc: Rpc): DarumaApi;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Client-side wire types and RPC wrapper for the `/dsh-daruma` channel.
3
+ * Structural types only — no monorepo-internal imports.
4
+ */
5
+ export function createApi(rpc) {
6
+ const call = async (endpoint, payload) => {
7
+ const result = (await rpc(endpoint, payload));
8
+ if (!result.ok) {
9
+ throw new Error(result.error?.message ?? `daruma rpc ${endpoint} failed`);
10
+ }
11
+ return result.value;
12
+ };
13
+ return {
14
+ status: () => call('status'),
15
+ listCandidates: (provider) => call('listCandidates', { provider }),
16
+ setBackup: async (provider, model) => {
17
+ await call('setBackup', { provider, model });
18
+ },
19
+ clearBackup: async () => {
20
+ await call('clearBackup');
21
+ },
22
+ };
23
+ }
24
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/client/api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAwCH,MAAM,UAAU,SAAS,CAAC,GAAQ;IAChC,MAAM,IAAI,GAAG,KAAK,EAAK,QAAgB,EAAE,OAAiB,EAAc,EAAE;QACxE,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAiB,CAAA;QAC7D,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,IAAI,cAAc,QAAQ,SAAS,CAAC,CAAA;QAC3E,CAAC;QACD,OAAO,MAAM,CAAC,KAAU,CAAA;IAC1B,CAAC,CAAA;IAED,OAAO;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAa,QAAQ,CAAC;QACxC,cAAc,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAkB,gBAAgB,EAAE,EAAE,QAAQ,EAAE,CAAC;QACnF,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;YACnC,MAAM,IAAI,CAAkB,WAAW,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;QAC/D,CAAC;QACD,WAAW,EAAE,KAAK,IAAI,EAAE;YACtB,MAAM,IAAI,CAAkB,aAAa,CAAC,CAAA;QAC5C,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * dsh-daruma client: registers a compact channel-status dock above the
3
+ * composer (conversation.input.dock) with a "backup channel" panel that
4
+ * lists candidate models and picks a backup. Built by tsdown into the
5
+ * __ModuleLoader__ factory bundle at lib/client.js.
6
+ */
7
+ type Translate = (key: string) => string;
8
+ /** Structural client context (host provides the real cordis context). */
9
+ interface ClientContext {
10
+ effect(callback: () => unknown, label?: string): void;
11
+ locale: {
12
+ register(namespace: string, dicts: {
13
+ zh: Record<string, string>;
14
+ en: Record<string, string>;
15
+ }): unknown;
16
+ bind(namespace: string): Translate;
17
+ };
18
+ slots: {
19
+ inject(slot: string, register: () => unknown): void;
20
+ register(meta: Record<string, unknown>, component: () => unknown): unknown;
21
+ };
22
+ get(service: string): unknown;
23
+ }
24
+ export declare const name = "dsh-daruma";
25
+ export declare const inject: readonly ["slots", "locale"];
26
+ export declare function apply(ctx: ClientContext): void;
27
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * dsh-daruma client: registers a compact channel-status dock above the
3
+ * composer (conversation.input.dock) with a "backup channel" panel that
4
+ * lists candidate models and picks a backup. Built by tsdown into the
5
+ * __ModuleLoader__ factory bundle at lib/client.js.
6
+ */
7
+ import { createElement as h } from 'react';
8
+ import { en, zh } from "./locales.js";
9
+ import { createApi } from "./api.js";
10
+ import { StatusDock } from "./StatusDock.js";
11
+ const NS = 'daruma';
12
+ export const name = 'dsh-daruma';
13
+ export const inject = ['slots', 'locale'];
14
+ export function apply(ctx) {
15
+ // Widen the backup-panel dialog: the primitives Modal card is fixed at
16
+ // min(380px, 100%), which truncates long provider/model names. A global
17
+ // class (not a CSS module) outranks the module-scoped `.dialog` rule.
18
+ if (typeof document !== 'undefined') {
19
+ const tagId = 'daruma-panel-style';
20
+ if (document.querySelector(`style[data-daruma="${tagId}"]`) === null) {
21
+ const style = document.createElement('style');
22
+ style.dataset.daruma = tagId;
23
+ style.textContent = '.daruma-wide-dialog.daruma-wide-dialog { width: min(560px, 100%); }';
24
+ document.head.appendChild(style);
25
+ }
26
+ }
27
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-daruma: dictionaries');
28
+ const t = ctx.locale.bind(NS);
29
+ const connection = ctx.get('connection');
30
+ if (connection === undefined) {
31
+ console.warn('[dsh-daruma] web transport unavailable — client UI disabled');
32
+ return;
33
+ }
34
+ const rpc = (endpoint, payload) => connection.rpc.call('/dsh-daruma', endpoint, payload ?? {});
35
+ const api = createApi(rpc);
36
+ ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
37
+ name: 'conversation.input.right',
38
+ id: 'daruma-status',
39
+ order: 0,
40
+ inject: () => ({ api, t }),
41
+ }, () => h(StatusDock, { api, t })));
42
+ }
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,IAAI,CAAC,EAAE,MAAM,OAAO,CAAA;AAC1C,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,SAAS,EAA4B,MAAM,UAAU,CAAA;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAkB,CAAA;AAE7C,MAAM,EAAE,GAAG,QAAQ,CAAA;AAyBnB,MAAM,CAAC,MAAM,IAAI,GAAG,YAAY,CAAA;AAChC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAU,CAAA;AAElD,MAAM,UAAU,KAAK,CAAC,GAAkB;IACtC,uEAAuE;IACvE,wEAAwE;IACxE,sEAAsE;IACtE,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,oBAAoB,CAAA;QAClC,IAAI,QAAQ,CAAC,aAAa,CAAC,sBAAsB,KAAK,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACrE,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;YAC7C,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,KAAK,CAAA;YAC5B,KAAK,CAAC,WAAW,GAAG,qEAAqE,CAAA;YACzF,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QAClC,CAAC;IACH,CAAC;IAED,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,0BAA0B,CAAC,CAAA;IACjF,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC7B,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,YAAY,CAA+B,CAAA;IACtE,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAA;QAC3E,OAAM;IACR,CAAC;IACD,MAAM,GAAG,GAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CACrC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,EAAE,OAAO,IAAI,EAAE,CAAuB,CAAA;IACnF,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,CAAA;IAE1B,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,0BAA0B,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;QACpE,IAAI,EAAE,0BAA0B;QAChC,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;KAC3B,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AACtC,CAAC"}
@@ -0,0 +1,53 @@
1
+ /** zh/en dictionaries for the daruma client UI. */
2
+ export declare const zh: {
3
+ status: string;
4
+ current: string;
5
+ backup: string;
6
+ backupNone: string;
7
+ backupManage: string;
8
+ backupManageHint: string;
9
+ failoverCount: string;
10
+ lastFailover: string;
11
+ healthy: string;
12
+ cooldown: string;
13
+ probe: string;
14
+ refresh: string;
15
+ close: string;
16
+ clearBackup: string;
17
+ provider: string;
18
+ providerPlaceholder: string;
19
+ loadCandidates: string;
20
+ loading: string;
21
+ candidates: string;
22
+ setBackup: string;
23
+ currentBackup: string;
24
+ currentBackupMark: string;
25
+ noCandidates: string;
26
+ empty: string;
27
+ };
28
+ export declare const en: {
29
+ status: string;
30
+ current: string;
31
+ backup: string;
32
+ backupNone: string;
33
+ backupManage: string;
34
+ backupManageHint: string;
35
+ failoverCount: string;
36
+ lastFailover: string;
37
+ healthy: string;
38
+ cooldown: string;
39
+ probe: string;
40
+ refresh: string;
41
+ close: string;
42
+ clearBackup: string;
43
+ provider: string;
44
+ providerPlaceholder: string;
45
+ loadCandidates: string;
46
+ loading: string;
47
+ candidates: string;
48
+ setBackup: string;
49
+ currentBackup: string;
50
+ currentBackupMark: string;
51
+ noCandidates: string;
52
+ empty: string;
53
+ };
@@ -0,0 +1,54 @@
1
+ /** zh/en dictionaries for the daruma client UI. */
2
+ export const zh = {
3
+ status: '渠道状态',
4
+ current: '当前',
5
+ backup: '备用',
6
+ backupNone: '未设置',
7
+ backupManage: '备用渠道',
8
+ backupManageHint: '从候选列表选择备用渠道',
9
+ failoverCount: '已切换',
10
+ lastFailover: '上次切换',
11
+ healthy: '健康',
12
+ cooldown: '冷却中',
13
+ probe: '探测中',
14
+ refresh: '刷新',
15
+ close: '关闭',
16
+ clearBackup: '清除备用',
17
+ provider: '提供方',
18
+ providerPlaceholder: 'provider,如 mt',
19
+ loadCandidates: '加载候选',
20
+ loading: '加载中…',
21
+ candidates: '候选模型',
22
+ setBackup: '设为备用',
23
+ currentBackup: '当前备用',
24
+ currentBackupMark: '备用',
25
+ noCandidates: '未加载到候选模型',
26
+ empty: '暂无渠道数据',
27
+ };
28
+ export const en = {
29
+ status: 'Channel status',
30
+ current: 'Current',
31
+ backup: 'Backup',
32
+ backupNone: 'none',
33
+ backupManage: 'Backup channel',
34
+ backupManageHint: 'Pick a backup channel from the candidate list',
35
+ failoverCount: 'failovers',
36
+ lastFailover: 'last failover',
37
+ healthy: 'healthy',
38
+ cooldown: 'cooling',
39
+ probe: 'probing',
40
+ refresh: 'Refresh',
41
+ close: 'Close',
42
+ clearBackup: 'Clear backup',
43
+ provider: 'Provider',
44
+ providerPlaceholder: 'provider, e.g. mt',
45
+ loadCandidates: 'Load candidates',
46
+ loading: 'Loading…',
47
+ candidates: 'Candidate models',
48
+ setBackup: 'Set as backup',
49
+ currentBackup: 'Current backup',
50
+ currentBackupMark: 'backup',
51
+ noCandidates: 'No candidates loaded',
52
+ empty: 'No channel data yet',
53
+ };
54
+ //# sourceMappingURL=locales.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locales.js","sourceRoot":"","sources":["../../src/client/locales.ts"],"names":[],"mappings":"AAAA,mDAAmD;AAEnD,MAAM,CAAC,MAAM,EAAE,GAAG;IAChB,MAAM,EAAE,MAAM;IACd,OAAO,EAAE,IAAI;IACb,MAAM,EAAE,IAAI;IACZ,UAAU,EAAE,KAAK;IACjB,YAAY,EAAE,MAAM;IACpB,gBAAgB,EAAE,aAAa;IAC/B,aAAa,EAAE,KAAK;IACpB,YAAY,EAAE,MAAM;IACpB,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE,KAAK;IACf,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE,IAAI;IACb,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,MAAM;IACnB,QAAQ,EAAE,KAAK;IACf,mBAAmB,EAAE,eAAe;IACpC,cAAc,EAAE,MAAM;IACtB,OAAO,EAAE,MAAM;IACf,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,MAAM;IACjB,aAAa,EAAE,MAAM;IACrB,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,UAAU;IACxB,KAAK,EAAE,QAAQ;CAChB,CAAA;AAED,MAAM,CAAC,MAAM,EAAE,GAAG;IAChB,MAAM,EAAE,gBAAgB;IACxB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,MAAM;IAClB,YAAY,EAAE,gBAAgB;IAC9B,gBAAgB,EAAE,+CAA+C;IACjE,aAAa,EAAE,WAAW;IAC1B,YAAY,EAAE,eAAe;IAC7B,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,SAAS;IACnB,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,OAAO;IACd,WAAW,EAAE,cAAc;IAC3B,QAAQ,EAAE,UAAU;IACpB,mBAAmB,EAAE,mBAAmB;IACxC,cAAc,EAAE,iBAAiB;IACjC,OAAO,EAAE,UAAU;IACnB,UAAU,EAAE,kBAAkB;IAC9B,SAAS,EAAE,eAAe;IAC1B,aAAa,EAAE,gBAAgB;IAC/B,iBAAiB,EAAE,QAAQ;IAC3B,YAAY,EAAE,sBAAsB;IACpC,KAAK,EAAE,qBAAqB;CAC7B,CAAA"}