dsh-draw 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/CHANGELOG.md +29 -0
- package/LICENSE +201 -0
- package/README.es.md +194 -0
- package/README.hi.md +194 -0
- package/README.md +194 -0
- package/README.pt.md +194 -0
- package/README.zh.md +194 -0
- package/SECURITY.md +39 -0
- package/THIRD_PARTY_NOTICES.md +21 -0
- package/cordis.patch.yml +48 -0
- package/lib/client.js +4787 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +1429 -0
- package/lib/typert.host.js +26 -0
- package/lib/types/client/DrawResultCard.d.ts +58 -0
- package/lib/types/client/DrawResultCard.d.ts.map +1 -0
- package/lib/types/client/DrawResultCard.js +48 -0
- package/lib/types/client/DrawSettingsTab.d.ts +31 -0
- package/lib/types/client/DrawSettingsTab.d.ts.map +1 -0
- package/lib/types/client/DrawSettingsTab.js +129 -0
- package/lib/types/client/index.d.ts +35 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/index.js +98 -0
- package/lib/types/client/locales.d.ts +14 -0
- package/lib/types/client/locales.d.ts.map +1 -0
- package/lib/types/client/locales.js +57 -0
- package/lib/types/client/present.d.ts +80 -0
- package/lib/types/client/present.d.ts.map +1 -0
- package/lib/types/client/present.js +86 -0
- package/lib/types/client/remote.d.ts +268 -0
- package/lib/types/client/remote.d.ts.map +1 -0
- package/lib/types/client/remote.js +15 -0
- package/lib/types/client/styles.d.ts +11 -0
- package/lib/types/client/styles.d.ts.map +1 -0
- package/lib/types/client/styles.js +43 -0
- package/lib/types/config.d.ts +160 -0
- package/lib/types/config.d.ts.map +1 -0
- package/lib/types/config.js +230 -0
- package/lib/types/drawer.d.ts +114 -0
- package/lib/types/drawer.d.ts.map +1 -0
- package/lib/types/drawer.js +138 -0
- package/lib/types/engine.d.ts +58 -0
- package/lib/types/engine.d.ts.map +1 -0
- package/lib/types/engine.js +135 -0
- package/lib/types/http.d.ts +89 -0
- package/lib/types/http.d.ts.map +1 -0
- package/lib/types/http.js +127 -0
- package/lib/types/index.d.ts +43 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +78 -0
- package/lib/types/quota.d.ts +69 -0
- package/lib/types/quota.d.ts.map +1 -0
- package/lib/types/quota.js +56 -0
- package/lib/types/router.d.ts +141 -0
- package/lib/types/router.d.ts.map +1 -0
- package/lib/types/router.js +207 -0
- package/lib/types/sanitize.d.ts +40 -0
- package/lib/types/sanitize.d.ts.map +1 -0
- package/lib/types/sanitize.js +103 -0
- package/lib/types/service.d.ts +59 -0
- package/lib/types/service.d.ts.map +1 -0
- package/lib/types/service.js +131 -0
- package/lib/types/session-events.d.ts +66 -0
- package/lib/types/session-events.d.ts.map +1 -0
- package/lib/types/session-events.js +32 -0
- package/lib/types/tool.d.ts +30 -0
- package/lib/types/tool.d.ts.map +1 -0
- package/lib/types/tool.js +131 -0
- package/lib/types/translate.d.ts +64 -0
- package/lib/types/translate.d.ts.map +1 -0
- package/lib/types/translate.js +56 -0
- package/lib/types/typert.host.d.ts +250 -0
- package/lib/types/typert.host.d.ts.map +1 -0
- package/lib/types/typert.host.js +23 -0
- package/lib/types/version.d.ts +10 -0
- package/lib/types/version.d.ts.map +1 -0
- package/lib/types/version.js +9 -0
- package/lib/types/wire.d.ts +699 -0
- package/lib/types/wire.d.ts.map +1 -0
- package/lib/types/wire.js +273 -0
- package/lib/wire-Cc4JZ3jR.js +4370 -0
- package/package.json +179 -0
- package/src/client/DrawResultCard.tsx +100 -0
- package/src/client/DrawSettingsTab.tsx +159 -0
- package/src/client/index.ts +123 -0
- package/src/client/locales.ts +84 -0
- package/src/client/present.ts +137 -0
- package/src/client/remote.ts +44 -0
- package/src/client/styles.ts +44 -0
- package/src/config.ts +358 -0
- package/src/drawer.ts +234 -0
- package/src/engine.ts +182 -0
- package/src/http.ts +161 -0
- package/src/index.ts +93 -0
- package/src/quota.ts +98 -0
- package/src/router.ts +309 -0
- package/src/sanitize.ts +113 -0
- package/src/service.ts +169 -0
- package/src/session-events.ts +70 -0
- package/src/tool.ts +145 -0
- package/src/translate.ts +101 -0
- package/src/typert.host.ts +25 -0
- package/src/version.ts +10 -0
- package/src/wire.ts +417 -0
package/package.json
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-draw",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Unified static-image generation router for DeepSeek Harness: one image_generate tool with standard parameters, config-driven OpenAI-compatible engine routing (OpenAI Images, Zhipu CogView, and any compatible endpoint) with health-aware fallback, durable workspace attachment results, per-session quota accounting, an in-conversation result card, and a Plugins settings panel that stores API keys as credential references.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/PerryLink/dsh-draw.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/PerryLink/dsh-draw#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/PerryLink/dsh-draw/issues"
|
|
12
|
+
},
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "PerryLink"
|
|
15
|
+
},
|
|
16
|
+
"private": false,
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./lib/index.js",
|
|
22
|
+
"types": "./lib/types/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./lib/types/index.d.ts",
|
|
26
|
+
"default": "./lib/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./typert": {
|
|
29
|
+
"types": "./lib/types/typert.host.d.ts",
|
|
30
|
+
"default": "./lib/typert.host.js"
|
|
31
|
+
},
|
|
32
|
+
"./client": {
|
|
33
|
+
"types": "./lib/types/client/index.d.ts",
|
|
34
|
+
"default": "./lib/client.js"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"lib",
|
|
40
|
+
"src",
|
|
41
|
+
"cordis.patch.yml",
|
|
42
|
+
"CHANGELOG.md",
|
|
43
|
+
"SECURITY.md",
|
|
44
|
+
"THIRD_PARTY_NOTICES.md",
|
|
45
|
+
"README.md",
|
|
46
|
+
"README.zh.md",
|
|
47
|
+
"README.es.md",
|
|
48
|
+
"README.pt.md",
|
|
49
|
+
"README.hi.md",
|
|
50
|
+
"LICENSE"
|
|
51
|
+
],
|
|
52
|
+
"dsh": {
|
|
53
|
+
"bundle": {
|
|
54
|
+
"patch": "./cordis.patch.yml"
|
|
55
|
+
},
|
|
56
|
+
"client": {
|
|
57
|
+
"platform": "web",
|
|
58
|
+
"inject": [
|
|
59
|
+
"@deepseek-ai/dsh-client-connection",
|
|
60
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
61
|
+
"@deepseek-ai/dsh-client-locale",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-tool"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"dshWorkshop": {
|
|
69
|
+
"schema": "omdsh-workshop-package/v1",
|
|
70
|
+
"type": "plugin",
|
|
71
|
+
"integration": {
|
|
72
|
+
"protocol": "harness-profile",
|
|
73
|
+
"artifact": "cordis.patch.yml"
|
|
74
|
+
},
|
|
75
|
+
"install": {
|
|
76
|
+
"mode": "transactional",
|
|
77
|
+
"adapter": "profile-bundle",
|
|
78
|
+
"failurePolicy": "generation-rollback",
|
|
79
|
+
"touchesCurrentBeforeActivation": false
|
|
80
|
+
},
|
|
81
|
+
"lifecycle": {
|
|
82
|
+
"activation": "hot-reload",
|
|
83
|
+
"dispose": "supported"
|
|
84
|
+
},
|
|
85
|
+
"permissions": [
|
|
86
|
+
"network:outbound",
|
|
87
|
+
"session:append",
|
|
88
|
+
"tools:register",
|
|
89
|
+
"attachments:write"
|
|
90
|
+
],
|
|
91
|
+
"compatibility": {
|
|
92
|
+
"dshVersions": [
|
|
93
|
+
"0.1.0-rc.6"
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"capability": {
|
|
97
|
+
"id": "image-generate-tool",
|
|
98
|
+
"kind": "tool",
|
|
99
|
+
"invocation": "image_generate",
|
|
100
|
+
"expected": "generates 1..4 static images from a prompt through the first healthy configured engine, saves them as durable attachments, and reports engine, size, quota usage, and per-image references; on engine failure it falls back down the configured chain"
|
|
101
|
+
},
|
|
102
|
+
"evidence": {
|
|
103
|
+
"install": null,
|
|
104
|
+
"failureIsolation": null,
|
|
105
|
+
"hotReload": null,
|
|
106
|
+
"remove": null
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"keywords": [
|
|
110
|
+
"dsh",
|
|
111
|
+
"dsh-plugin",
|
|
112
|
+
"deepseek-harness",
|
|
113
|
+
"deepseek",
|
|
114
|
+
"cordis",
|
|
115
|
+
"image-generation",
|
|
116
|
+
"openai-images",
|
|
117
|
+
"cogview",
|
|
118
|
+
"zhipu",
|
|
119
|
+
"text-to-image"
|
|
120
|
+
],
|
|
121
|
+
"engines": {
|
|
122
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
123
|
+
},
|
|
124
|
+
"packageManager": "pnpm@11.7.0",
|
|
125
|
+
"peerDependencies": {
|
|
126
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
127
|
+
"@deepseek-ai/schemastery": "^3.18.0",
|
|
128
|
+
"@deepseek-ai/dsh-attachment": "0.1.0-rc.6",
|
|
129
|
+
"@deepseek-ai/dsh-brand": "0.1.0-rc.6",
|
|
130
|
+
"@deepseek-ai/dsh-credentials": "0.1.0-rc.6",
|
|
131
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.6",
|
|
132
|
+
"@deepseek-ai/dsh-session": "0.1.0-rc.6",
|
|
133
|
+
"@deepseek-ai/dsh-tools": "0.1.0-rc.6",
|
|
134
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.0-rc.6"
|
|
135
|
+
},
|
|
136
|
+
"dependencies": {
|
|
137
|
+
"tsdown": "^0.22.14",
|
|
138
|
+
"typescript": "^7.0.2",
|
|
139
|
+
"zod": "^4.4.3"
|
|
140
|
+
},
|
|
141
|
+
"devDependencies": {
|
|
142
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
143
|
+
"@deepseek-ai/schemastery": "^3.18.0",
|
|
144
|
+
"@deepseek-ai/dsh-agent": "0.1.0-rc.6",
|
|
145
|
+
"@deepseek-ai/dsh-api-remotes": "0.1.0-rc.6",
|
|
146
|
+
"@deepseek-ai/dsh-attachment": "0.1.0-rc.6",
|
|
147
|
+
"@deepseek-ai/dsh-attachment-local": "0.1.0-rc.6",
|
|
148
|
+
"@deepseek-ai/dsh-brand": "0.1.0-rc.6",
|
|
149
|
+
"@deepseek-ai/dsh-client-connection": "0.1.0-rc.6",
|
|
150
|
+
"@deepseek-ai/dsh-client-locale": "0.1.0-rc.6",
|
|
151
|
+
"@deepseek-ai/dsh-client-runtime": "0.1.0-rc.6",
|
|
152
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.6",
|
|
153
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6",
|
|
154
|
+
"@deepseek-ai/dsh-client-ui-tool": "0.1.0-rc.6",
|
|
155
|
+
"@deepseek-ai/dsh-credentials": "0.1.0-rc.6",
|
|
156
|
+
"@deepseek-ai/dsh-credentials-local": "0.1.0-rc.6",
|
|
157
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.6",
|
|
158
|
+
"@deepseek-ai/dsh-session": "0.1.0-rc.6",
|
|
159
|
+
"@deepseek-ai/dsh-tools": "0.1.0-rc.6",
|
|
160
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.0-rc.6",
|
|
161
|
+
"@types/node": "^22.19.0",
|
|
162
|
+
"@types/react": "18.3.31",
|
|
163
|
+
"jsdom": "^30.0.1",
|
|
164
|
+
"react": "18.3.1",
|
|
165
|
+
"react-dom": "18.3.1",
|
|
166
|
+
"vitest": "^4.1.10"
|
|
167
|
+
},
|
|
168
|
+
"scripts": {
|
|
169
|
+
"build": "node scripts/prepare.mjs",
|
|
170
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
171
|
+
"typecheck:ci": "tsc -p tsconfig.ci.json --noEmit",
|
|
172
|
+
"test": "vitest run",
|
|
173
|
+
"test:watch": "vitest",
|
|
174
|
+
"prepare": "node scripts/prepare.mjs",
|
|
175
|
+
"verify:self-contained": "node scripts/verify-self-contained.mjs",
|
|
176
|
+
"verify:artifacts": "node scripts/verify-artifacts.mjs"
|
|
177
|
+
},
|
|
178
|
+
"license": "Apache-2.0"
|
|
179
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The keyed `image_generate` tool result card: engine/quota facts and the
|
|
3
|
+
* regenerate action. The images themselves are the attachment content blocks
|
|
4
|
+
* the shell already renders from the tool result, so the card only adds the
|
|
5
|
+
* accounting line and the action — it never duplicates image transport.
|
|
6
|
+
*
|
|
7
|
+
* The `tool.call.toolview` SlotMap member is declared locally (mirroring the
|
|
8
|
+
* harness's own ui-tool contract declaration, which its package index does not
|
|
9
|
+
* re-export): when both declarations land in one program they merge.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-draw/client/DrawResultCard
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createElement as h, useState, type ReactElement } from 'react'
|
|
15
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
16
|
+
import type { ToolCallBlock } from '@deepseek-ai/dsh-client-runtime/client'
|
|
17
|
+
import { presentDrawResult } from './present.ts'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Standard owner currency for one keyed tool view (mirror of the harness
|
|
21
|
+
* ui-tool contract `ToolCallOwnerProps`, which the package index does not
|
|
22
|
+
* re-export — keep the shape identical so the declarations merge).
|
|
23
|
+
*/
|
|
24
|
+
export interface ToolCallOwnerProps {
|
|
25
|
+
/** Tool call identity, stable across running and settled forms. */
|
|
26
|
+
callId: string
|
|
27
|
+
/** Wire tool name and keyed dispatch value. */
|
|
28
|
+
toolName: string
|
|
29
|
+
/** Frozen running call or settled result node. */
|
|
30
|
+
block: ToolCallBlock
|
|
31
|
+
/** Session workspace root for relative summaries. */
|
|
32
|
+
cwd?: string
|
|
33
|
+
/** Open a tool argument path through the host. */
|
|
34
|
+
openFile: (path: string) => void
|
|
35
|
+
/** Inspect this call in the trajectory view when available. */
|
|
36
|
+
inspect?: () => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
40
|
+
interface SlotMap {
|
|
41
|
+
/** Keyed atomic tool-call view, dispatched by the wire tool name. */
|
|
42
|
+
'tool.call.toolview': { kind: 'keyed'; scope: 'session'; owner: ToolCallOwnerProps }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Registration-side injected face: locale binding and the regenerate RPC. */
|
|
47
|
+
export interface DrawResultCardInjected {
|
|
48
|
+
/** Re-run the generation through the host drawer; rejects on failure. */
|
|
49
|
+
regenerate: (args: Record<string, unknown>) => Promise<void>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Full component props assembled by the tool-view slot renderer. */
|
|
53
|
+
export type DrawResultCardProps =
|
|
54
|
+
PropsRuntime<'tool.call.toolview'>
|
|
55
|
+
& PropsLocale<'draw'>
|
|
56
|
+
& InjectFace<DrawResultCardInjected>
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The keyed tool result card.
|
|
60
|
+
* @param props - owner currency, locale, and injected bindings.
|
|
61
|
+
* @returns the card element.
|
|
62
|
+
*/
|
|
63
|
+
export function DrawResultCard(props: DrawResultCardProps): ReactElement {
|
|
64
|
+
const { block, regenerate, t } = props
|
|
65
|
+
const presented = presentDrawResult(block)
|
|
66
|
+
const [busy, setBusy] = useState(false)
|
|
67
|
+
const [failed, setFailed] = useState(false)
|
|
68
|
+
|
|
69
|
+
if (presented === undefined) return h('div', null)
|
|
70
|
+
|
|
71
|
+
const runRegenerate = async (): Promise<void> => {
|
|
72
|
+
if (presented.args === undefined) return
|
|
73
|
+
setBusy(true)
|
|
74
|
+
setFailed(false)
|
|
75
|
+
try {
|
|
76
|
+
await regenerate(presented.args)
|
|
77
|
+
} catch {
|
|
78
|
+
setFailed(true)
|
|
79
|
+
} finally {
|
|
80
|
+
setBusy(false)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return h('div', { className: 'dshdraw-card' },
|
|
85
|
+
h('div', { className: 'dshdraw-meta' },
|
|
86
|
+
h('span', null, `${t('result.engine')}: ${presented.engine} (${presented.model})`),
|
|
87
|
+
...(presented.fallbackUsed ? [h('span', null, t('result.fallback'))] : []),
|
|
88
|
+
h('span', null, `${t('result.quota')}: ${presented.quota.generations}/${presented.limits.maxGenerations} · ${presented.quota.bytes}/${presented.limits.maxBytes}`),
|
|
89
|
+
),
|
|
90
|
+
h('div', { className: 'dshdraw-actions' },
|
|
91
|
+
h('button', {
|
|
92
|
+
className: 'dshdraw-button',
|
|
93
|
+
type: 'button',
|
|
94
|
+
disabled: busy || presented.args === undefined,
|
|
95
|
+
onClick: () => { void runRegenerate() },
|
|
96
|
+
}, busy ? t('result.regenerating') : t('result.regenerate')),
|
|
97
|
+
...(failed ? [h('span', { className: 'dshdraw-badge warn' }, t('result.failed'))] : []),
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Plugins settings tab for dsh-draw: engine chain, credential references
|
|
3
|
+
* (set/remove through the host's credential seam), probe button, and quota
|
|
4
|
+
* limits. Read-only except the credential actions; all data arrives through
|
|
5
|
+
* the `draw` Remote namespace.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-draw/client/DrawSettingsTab
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createElement as h, useEffect, useState, type ReactElement, type ReactNode } from 'react'
|
|
11
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
12
|
+
import type { PresentedDrawPanel } from './present.ts'
|
|
13
|
+
|
|
14
|
+
/** Registration-side injected face: the panel RPCs (RemoteResult already unwrapped). */
|
|
15
|
+
export interface DrawSettingsTabInjected {
|
|
16
|
+
/** Read the panel snapshot. */
|
|
17
|
+
status: () => Promise<PresentedDrawPanel>
|
|
18
|
+
/** Probe one engine's connectivity. */
|
|
19
|
+
probe: (engineId: string) => Promise<string>
|
|
20
|
+
/** Store one API key under the engine's credential reference. */
|
|
21
|
+
setCredential: (engineId: string, value: string) => Promise<string>
|
|
22
|
+
/** Remove a stored API key. */
|
|
23
|
+
unsetCredential: (engineId: string) => Promise<string>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Full component props assembled by the Settings slot renderer. */
|
|
27
|
+
export type DrawSettingsTabProps =
|
|
28
|
+
PropsRuntime<'settings.plugins.tab'>
|
|
29
|
+
& PropsLocale<'draw'>
|
|
30
|
+
& InjectFace<DrawSettingsTabInjected>
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The settings tab element.
|
|
34
|
+
* @param props - locale and injected bindings.
|
|
35
|
+
* @returns the tab element.
|
|
36
|
+
*/
|
|
37
|
+
export function DrawSettingsTab(props: DrawSettingsTabProps): ReactElement {
|
|
38
|
+
const { t, status, probe, setCredential, unsetCredential } = props
|
|
39
|
+
const [panel, setPanel] = useState<PresentedDrawPanel | undefined>(undefined)
|
|
40
|
+
const [notes, setNotes] = useState<Record<string, string>>({})
|
|
41
|
+
const [keys, setKeys] = useState<Record<string, string>>({})
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
let alive = true
|
|
45
|
+
const load = async (): Promise<void> => {
|
|
46
|
+
try {
|
|
47
|
+
const snapshot = await status()
|
|
48
|
+
if (alive) setPanel(snapshot)
|
|
49
|
+
} catch {
|
|
50
|
+
// Panel load failure keeps the previous snapshot; the rows stay usable.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
void load()
|
|
54
|
+
return () => {
|
|
55
|
+
alive = false
|
|
56
|
+
}
|
|
57
|
+
}, [status])
|
|
58
|
+
|
|
59
|
+
const act = async (engineId: string, run: () => Promise<string>): Promise<void> => {
|
|
60
|
+
setNotes(current => ({ ...current, [engineId]: '' }))
|
|
61
|
+
try {
|
|
62
|
+
const note = await run()
|
|
63
|
+
setNotes(current => ({ ...current, [engineId]: note }))
|
|
64
|
+
} catch (error) {
|
|
65
|
+
setNotes(current => ({ ...current, [engineId]: `failed: ${error instanceof Error ? error.message : String(error)}` }))
|
|
66
|
+
} finally {
|
|
67
|
+
try {
|
|
68
|
+
const snapshot = await status()
|
|
69
|
+
setPanel(snapshot)
|
|
70
|
+
} catch {
|
|
71
|
+
// Refresh failure keeps the previous snapshot.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (panel === undefined) return h('div', { className: 'dshdraw-panel' }, t('tab.engines'))
|
|
77
|
+
|
|
78
|
+
const engineCards: ReactNode[] = []
|
|
79
|
+
for (const engine of panel.engines) {
|
|
80
|
+
const headerChildren: ReactNode[] = [engine.id]
|
|
81
|
+
if (engine.preferred) headerChildren.push(h('span', { key: 'p', className: 'dshdraw-badge ok' }, t('tab.preferred')))
|
|
82
|
+
if (!engine.enabled) headerChildren.push(h('span', { key: 'd', className: 'dshdraw-badge warn' }, t('tab.disabled')))
|
|
83
|
+
|
|
84
|
+
const keyActions: ReactNode[] = []
|
|
85
|
+
if (engine.credentialWritable) {
|
|
86
|
+
keyActions.push(h('input', {
|
|
87
|
+
key: 'input',
|
|
88
|
+
className: 'dshdraw-input',
|
|
89
|
+
type: 'password',
|
|
90
|
+
placeholder: t('tab.setKey'),
|
|
91
|
+
value: keys[engine.id] ?? '',
|
|
92
|
+
onChange: (event: { target: { value: string } }) => setKeys(current => ({ ...current, [engine.id]: event.target.value })),
|
|
93
|
+
}))
|
|
94
|
+
keyActions.push(h('button', {
|
|
95
|
+
key: 'set',
|
|
96
|
+
className: 'dshdraw-button',
|
|
97
|
+
type: 'button',
|
|
98
|
+
onClick: () => {
|
|
99
|
+
void act(engine.id, async () => {
|
|
100
|
+
const note = await setCredential(engine.id, keys[engine.id] ?? '')
|
|
101
|
+
setKeys(current => ({ ...current, [engine.id]: '' }))
|
|
102
|
+
return note
|
|
103
|
+
})
|
|
104
|
+
},
|
|
105
|
+
}, t('tab.setKey')))
|
|
106
|
+
if (engine.credentialConfigured) {
|
|
107
|
+
keyActions.push(h('button', {
|
|
108
|
+
key: 'remove',
|
|
109
|
+
className: 'dshdraw-button',
|
|
110
|
+
type: 'button',
|
|
111
|
+
onClick: () => { void act(engine.id, () => unsetCredential(engine.id)) },
|
|
112
|
+
}, t('tab.removeKey')))
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const healthChildren: ReactNode[] = []
|
|
117
|
+
healthChildren.push(h('label', null, t('tab.health')))
|
|
118
|
+
if (engine.coolingDown) {
|
|
119
|
+
healthChildren.push(h('span', { className: 'dshdraw-badge warn' }, `cooldown (${engine.consecutiveFailures})`))
|
|
120
|
+
} else if (engine.consecutiveFailures === 0) {
|
|
121
|
+
healthChildren.push(h('span', { className: 'dshdraw-badge ok' }, t('tab.healthOk')))
|
|
122
|
+
} else {
|
|
123
|
+
healthChildren.push(h('span', { className: 'dshdraw-badge warn' }, `${t('tab.healthError')} (${engine.consecutiveFailures})`))
|
|
124
|
+
}
|
|
125
|
+
if (engine.lastError !== null) healthChildren.push(h('span', { key: 'err' }, engine.lastError))
|
|
126
|
+
healthChildren.push(h('button', {
|
|
127
|
+
key: 'probe',
|
|
128
|
+
className: 'dshdraw-button',
|
|
129
|
+
type: 'button',
|
|
130
|
+
onClick: () => { void act(engine.id, () => probe(engine.id)) },
|
|
131
|
+
}, t('tab.probe')))
|
|
132
|
+
|
|
133
|
+
const engineChildren: ReactNode[] = [
|
|
134
|
+
h('h4', null, ...headerChildren),
|
|
135
|
+
h('div', { className: 'dshdraw-row' }, h('label', null, t('tab.engines')), h('span', null, `${engine.model} @ ${engine.baseUrl}`)),
|
|
136
|
+
h('div', { className: 'dshdraw-row' },
|
|
137
|
+
h('label', null, t('tab.keyRef')),
|
|
138
|
+
h('span', null, engine.apiKeyRef),
|
|
139
|
+
h('span', { className: engine.credentialConfigured ? 'dshdraw-badge ok' : 'dshdraw-badge warn' },
|
|
140
|
+
engine.credentialConfigured ? t('tab.credentialSet') : t('tab.credentialMissing')),
|
|
141
|
+
),
|
|
142
|
+
]
|
|
143
|
+
if (keyActions.length > 0) engineChildren.push(h('div', { className: 'dshdraw-row' }, ...keyActions))
|
|
144
|
+
engineChildren.push(h('div', { className: 'dshdraw-row' }, ...healthChildren))
|
|
145
|
+
const note = notes[engine.id]
|
|
146
|
+
if (note !== undefined && note !== '') engineChildren.push(h('div', { className: 'dshdraw-row' }, note))
|
|
147
|
+
engineCards.push(h('div', { key: engine.id, className: 'dshdraw-engine' }, ...engineChildren))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return h('div', { className: 'dshdraw-panel' },
|
|
151
|
+
h('div', { className: 'dshdraw-row' },
|
|
152
|
+
h('span', null, `${t('tab.quotaCalls')}: ${panel.quota.maxGenerationsPerSession}`),
|
|
153
|
+
h('span', null, `${t('tab.quotaBytes')}: ${panel.quota.maxBytesPerSession}`),
|
|
154
|
+
h('span', null, `timeout: ${panel.requestTimeoutMs} ms`),
|
|
155
|
+
h('span', null, `max: ${panel.maxImagesPerCall}`),
|
|
156
|
+
),
|
|
157
|
+
...engineCards,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dsh-draw`, browser half: mounts the `draw` Remote contribution, registers
|
|
3
|
+
* the keyed `image_generate` result card (`tool.call.toolview`, key
|
|
4
|
+
* `image_generate`), and registers the Plugins settings tab (`settings.plugins.tab`,
|
|
5
|
+
* id `draw`). All data arrives through the `remote.draw` namespace; the card
|
|
6
|
+
* and the tab hold no state beyond their local forms.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-draw/client
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
|
12
|
+
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
13
|
+
// Type-only: pulls the 'settings.plugins.tab' SlotMap declaration into this
|
|
14
|
+
// program so the tab registration typechecks against the real declaration.
|
|
15
|
+
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
16
|
+
import type {} from '@deepseek-ai/dsh-client-ui-tool'
|
|
17
|
+
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol'
|
|
18
|
+
import { DrawResultCard, type DrawResultCardInjected } from './DrawResultCard.tsx'
|
|
19
|
+
import { DrawSettingsTab, type DrawSettingsTabInjected } from './DrawSettingsTab.tsx'
|
|
20
|
+
import { en, zh, type DrawLocaleKey } from './locales.ts'
|
|
21
|
+
import { presentDrawPanel } from './present.ts'
|
|
22
|
+
import { DRAW_REMOTE } from './remote.ts'
|
|
23
|
+
import { installDrawStyles } from './styles.ts'
|
|
24
|
+
import type { CredentialActionResult, DrawProbeResult, DrawRegenerateResult, DrawStatusSnapshot } from '../wire.ts'
|
|
25
|
+
|
|
26
|
+
export type { DrawResultCardInjected, DrawResultCardProps } from './DrawResultCard.tsx'
|
|
27
|
+
export type { DrawSettingsTabInjected, DrawSettingsTabProps } from './DrawSettingsTab.tsx'
|
|
28
|
+
export type { DrawLocaleKey } from './locales.ts'
|
|
29
|
+
export { presentDrawPanel, presentDrawResult, type PresentedDrawPanel, type PresentedDrawResult, type PresentedEngineRow } from './present.ts'
|
|
30
|
+
|
|
31
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
32
|
+
interface LocaleNamespaceMap {
|
|
33
|
+
/** Image-generation card and panel copy. */
|
|
34
|
+
'draw': DrawLocaleKey
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Dictionary namespace owned by this plugin. */
|
|
39
|
+
export const NS = 'draw'
|
|
40
|
+
|
|
41
|
+
/** Plugin name: matches the package name, the graph row id, and the bundle id. */
|
|
42
|
+
export const name = 'dsh-draw'
|
|
43
|
+
|
|
44
|
+
/** Services the browser half reads; `remote.draw` appears once the contribution mounts. */
|
|
45
|
+
export const inject = ['slots', 'locale', 'remote', 'sessions']
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Browser plugin body: dictionaries, the scoped stylesheet, the Remote
|
|
49
|
+
* contribution mount, the result card, and the settings tab registration.
|
|
50
|
+
*
|
|
51
|
+
* @param ctx - client root context.
|
|
52
|
+
*/
|
|
53
|
+
export async function apply(ctx: ClientContext): Promise<void> {
|
|
54
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-draw: dictionaries')
|
|
55
|
+
ctx.effect(() => installDrawStyles(), 'dsh-draw: stylesheet')
|
|
56
|
+
|
|
57
|
+
// $mount registers the 'remote.draw' namespace service and owns its
|
|
58
|
+
// removal for this fiber's lifetime.
|
|
59
|
+
await ctx.remote.$mount(DRAW_REMOTE)
|
|
60
|
+
|
|
61
|
+
ctx.inject(['remote.draw'], (scope) => {
|
|
62
|
+
const t = scope.locale.bind(NS)
|
|
63
|
+
const unwrap = <T>(result: RemoteResult<T>, method: string): T => {
|
|
64
|
+
if (!result.ok) {
|
|
65
|
+
throw new Error(`draw.${method} failed: ${result.error.code}: ${result.error.message}`)
|
|
66
|
+
}
|
|
67
|
+
return result.value
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
scope.slots.inject('tool.call.toolview', () => scope.slots.register({
|
|
71
|
+
name: 'tool.call.toolview',
|
|
72
|
+
key: 'image_generate',
|
|
73
|
+
locale: NS,
|
|
74
|
+
inject: (): DrawResultCardInjected => ({
|
|
75
|
+
regenerate: async (args) => {
|
|
76
|
+
const sessionId = currentSessionId(scope.get('sessions'))
|
|
77
|
+
if (sessionId === undefined) throw new Error('no active session — cannot regenerate')
|
|
78
|
+
unwrap<DrawRegenerateResult>(await scope.remote.draw.regenerate(sessionId, args), 'regenerate')
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
}, DrawResultCard))
|
|
82
|
+
|
|
83
|
+
scope.slots.inject('settings.plugins.tab', () => scope.slots.register({
|
|
84
|
+
name: 'settings.plugins.tab',
|
|
85
|
+
id: 'draw',
|
|
86
|
+
order: 40,
|
|
87
|
+
label: () => t('tab'),
|
|
88
|
+
locale: NS,
|
|
89
|
+
inject: (): DrawSettingsTabInjected => ({
|
|
90
|
+
status: async () => presentDrawPanel(unwrap<DrawStatusSnapshot>(await scope.remote.draw.status(), 'status')),
|
|
91
|
+
probe: async (engineId) => {
|
|
92
|
+
const result = unwrap<DrawProbeResult>(await scope.remote.draw.probe(engineId), 'probe')
|
|
93
|
+
return result.note
|
|
94
|
+
},
|
|
95
|
+
setCredential: async (engineId, value) => {
|
|
96
|
+
const result = unwrap<CredentialActionResult>(await scope.remote.draw.setCredential(engineId, value), 'setCredential')
|
|
97
|
+
return result.note
|
|
98
|
+
},
|
|
99
|
+
unsetCredential: async (engineId) => {
|
|
100
|
+
const result = unwrap<CredentialActionResult>(await scope.remote.draw.unsetCredential(engineId), 'unsetCredential')
|
|
101
|
+
return result.note
|
|
102
|
+
},
|
|
103
|
+
}),
|
|
104
|
+
}, DrawSettingsTab))
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Read the current session id from the sessions store face (structural:
|
|
110
|
+
* the store shape differs across harness lines, so only the leaf is read).
|
|
111
|
+
*/
|
|
112
|
+
function currentSessionId(sessions: unknown): string | undefined {
|
|
113
|
+
try {
|
|
114
|
+
const list = (sessions as { list?: unknown } | null)?.list
|
|
115
|
+
if (typeof list !== 'object' || list === null) return undefined
|
|
116
|
+
const getSnapshot = (list as { getSnapshot?: unknown }).getSnapshot
|
|
117
|
+
if (typeof getSnapshot !== 'function') return undefined
|
|
118
|
+
const current = (getSnapshot as () => { current?: unknown })().current
|
|
119
|
+
return typeof current === 'string' ? current : undefined
|
|
120
|
+
} catch {
|
|
121
|
+
return undefined
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI dictionary for the dsh-draw browser half. The harness locale registry
|
|
3
|
+
* accepts only the 'en' | 'zh' UI language codes today (its LocaleDictOf
|
|
4
|
+
* face), so the client ships those two dictionaries.
|
|
5
|
+
*
|
|
6
|
+
* @module dsh-draw/client/locales
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Dictionary key union. */
|
|
10
|
+
export type DrawLocaleKey =
|
|
11
|
+
| 'result.title'
|
|
12
|
+
| 'result.fallback'
|
|
13
|
+
| 'result.quota'
|
|
14
|
+
| 'result.engine'
|
|
15
|
+
| 'result.regenerate'
|
|
16
|
+
| 'result.regenerating'
|
|
17
|
+
| 'result.failed'
|
|
18
|
+
| 'tab'
|
|
19
|
+
| 'tab.engines'
|
|
20
|
+
| 'tab.preferred'
|
|
21
|
+
| 'tab.disabled'
|
|
22
|
+
| 'tab.keyRef'
|
|
23
|
+
| 'tab.credentialSet'
|
|
24
|
+
| 'tab.credentialMissing'
|
|
25
|
+
| 'tab.setKey'
|
|
26
|
+
| 'tab.removeKey'
|
|
27
|
+
| 'tab.probe'
|
|
28
|
+
| 'tab.quotaCalls'
|
|
29
|
+
| 'tab.quotaBytes'
|
|
30
|
+
| 'tab.health'
|
|
31
|
+
| 'tab.healthOk'
|
|
32
|
+
| 'tab.healthError'
|
|
33
|
+
|
|
34
|
+
/** English dictionary. */
|
|
35
|
+
export const en: Record<DrawLocaleKey, string> = {
|
|
36
|
+
'result.title': 'Generated image',
|
|
37
|
+
'result.fallback': 'fallback used',
|
|
38
|
+
'result.quota': 'Quota',
|
|
39
|
+
'result.engine': 'Engine',
|
|
40
|
+
'result.regenerate': 'Regenerate',
|
|
41
|
+
'result.regenerating': 'Regenerating…',
|
|
42
|
+
'result.failed': 'Regenerate failed',
|
|
43
|
+
'tab': 'Image generation',
|
|
44
|
+
'tab.engines': 'Engines',
|
|
45
|
+
'tab.preferred': 'preferred',
|
|
46
|
+
'tab.disabled': 'disabled',
|
|
47
|
+
'tab.keyRef': 'API key reference',
|
|
48
|
+
'tab.credentialSet': 'configured',
|
|
49
|
+
'tab.credentialMissing': 'not configured',
|
|
50
|
+
'tab.setKey': 'Set key',
|
|
51
|
+
'tab.removeKey': 'Remove key',
|
|
52
|
+
'tab.probe': 'Probe',
|
|
53
|
+
'tab.quotaCalls': 'Per-session call cap',
|
|
54
|
+
'tab.quotaBytes': 'Per-session byte cap',
|
|
55
|
+
'tab.health': 'Health',
|
|
56
|
+
'tab.healthOk': 'healthy',
|
|
57
|
+
'tab.healthError': 'last error',
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Chinese dictionary. */
|
|
61
|
+
export const zh: Record<DrawLocaleKey, string> = {
|
|
62
|
+
'result.title': '生成的图片',
|
|
63
|
+
'result.fallback': '已使用回退引擎',
|
|
64
|
+
'result.quota': '配额',
|
|
65
|
+
'result.engine': '引擎',
|
|
66
|
+
'result.regenerate': '重新生成',
|
|
67
|
+
'result.regenerating': '正在重新生成…',
|
|
68
|
+
'result.failed': '重新生成失败',
|
|
69
|
+
'tab': '图像生成',
|
|
70
|
+
'tab.engines': '引擎',
|
|
71
|
+
'tab.preferred': '首选',
|
|
72
|
+
'tab.disabled': '已禁用',
|
|
73
|
+
'tab.keyRef': 'API 密钥引用',
|
|
74
|
+
'tab.credentialSet': '已配置',
|
|
75
|
+
'tab.credentialMissing': '未配置',
|
|
76
|
+
'tab.setKey': '设置密钥',
|
|
77
|
+
'tab.removeKey': '移除密钥',
|
|
78
|
+
'tab.probe': '探测',
|
|
79
|
+
'tab.quotaCalls': '每会话调用上限',
|
|
80
|
+
'tab.quotaBytes': '每会话字节上限',
|
|
81
|
+
'tab.health': '健康状态',
|
|
82
|
+
'tab.healthOk': '正常',
|
|
83
|
+
'tab.healthError': '最近错误',
|
|
84
|
+
}
|