@changfenhuang/dsh-genui 0.9.6 → 0.9.8
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 +15 -0
- package/README.md +7 -18
- package/README.zh-CN.md +7 -18
- package/SKILL.md +4 -2
- package/lib/assets/three.js +1 -1
- package/lib/client.js +3 -3
- package/lib/index.js +273 -61
- package/lib/types/client/GenuiBlock.d.ts +5 -2
- package/lib/types/client/blocks/advanced.d.ts +1 -1
- package/lib/types/client/blocks/image.d.ts +6 -0
- package/lib/types/client/blocks/state.d.ts +2 -0
- package/lib/types/client/fence-render.d.ts +3 -0
- package/lib/types/client/gallery.d.ts +3 -5
- package/lib/types/client/guard.d.ts +10 -5
- package/lib/types/client/parse-partial.d.ts +29 -3
- package/lib/types/client/spec.d.ts +8 -1
- package/lib/types/plugin/chart-contract.d.ts +7 -0
- package/lib/types/plugin/index.d.ts +1 -1
- package/package.json +18 -17
- package/src/client/GenuiBlock.module.css +1 -3
- package/src/client/GenuiBlock.tsx +37 -24
- package/src/client/PlotBlock.tsx +25 -10
- package/src/client/blocks/advanced.tsx +34 -25
- package/src/client/blocks/charts.tsx +4 -4
- package/src/client/blocks/image.tsx +25 -0
- package/src/client/blocks/render-node.tsx +3 -0
- package/src/client/blocks/state.ts +2 -0
- package/src/client/dom-fence.tsx +12 -5
- package/src/client/fence-render.tsx +35 -11
- package/src/client/gallery.ts +4 -5
- package/src/client/guard.ts +125 -29
- package/src/client/panel-command.ts +14 -5
- package/src/client/parse-partial.ts +19 -25
- package/src/client/safe-math.ts +29 -1
- package/src/client/scene3d-core.ts +5 -2
- package/src/client/spec.ts +9 -1
- package/src/plugin/chart-contract.ts +103 -0
- package/src/plugin/index.ts +48 -2
- package/src/plugin/tool.ts +25 -4
package/src/plugin/index.ts
CHANGED
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
import { Context } from '@deepseek-ai/cordis'
|
|
14
14
|
import type {} from '@deepseek-ai/dsh-system-prompt'
|
|
15
15
|
import type {} from '@deepseek-ai/dsh-tools'
|
|
16
|
+
import type { SkillProvider } from '@deepseek-ai/dsh-skill'
|
|
16
17
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
18
|
+
import { readFileSync } from 'node:fs'
|
|
17
19
|
import { readFile } from 'node:fs/promises'
|
|
18
|
-
import { join } from 'node:path'
|
|
20
|
+
import { basename, dirname, join, resolve } from 'node:path'
|
|
19
21
|
import { fileURLToPath } from 'node:url'
|
|
20
22
|
import { createRenderUiTool, createValidateDshUiTool } from './tool.ts'
|
|
21
23
|
|
|
@@ -98,7 +100,7 @@ The spec is a white-listed component tree rendered inline where the fence sits.
|
|
|
98
100
|
|
|
99
101
|
- 布局: text · row · col · grid · card · divider · spacer
|
|
100
102
|
- 展示: badge · stat · progress · list · table · keyvalue · avatar · audio · video · timeline · file-tree · breadcrumb · callout · steps · diff · json · code · copy
|
|
101
|
-
- 图表: chart
|
|
103
|
+
- 图表: chart {"type":"chart","kind":"bars|line|donut","data":[{"label":"...","value":n,"color":"#hex?"}],"series":[...]?}(series 仅 bars;未知扩展字段可通过校验,但原生 chart repair/render 会忽略) · echart (preset|option) · plot (函数图)
|
|
102
104
|
- 交互: button · input · textarea · select · checkbox · switch · slider · radio · submit · quiz · link · tabs · accordion
|
|
103
105
|
- 高级: mermaid (flowchart/sequence/class/gantt/pie/er/state/journey) · diagram (编辑级架构/流程图,27 种 kind) · scene3d (3D WebGL)
|
|
104
106
|
|
|
@@ -123,6 +125,46 @@ Rules:
|
|
|
123
125
|
// instead (see apply).
|
|
124
126
|
export const inject = ['systemPrompt']
|
|
125
127
|
|
|
128
|
+
const BUNDLED_SKILL_RANK = 600
|
|
129
|
+
const BUNDLED_SKILL_PROVIDER = 'dsh-genui'
|
|
130
|
+
const BUNDLED_SKILL_DESCRIPTION = 'GenUI 完整组件与字段规范,用于生成 dsh-ui 结构化交互界面。'
|
|
131
|
+
const BUNDLED_SKILL_INVOCATION = { modelInvocable: true, userInvocable: true } as const
|
|
132
|
+
|
|
133
|
+
/** Register through the provider path so source=bundled also gets bundled precedence. */
|
|
134
|
+
function bundledSkillProvider(): SkillProvider {
|
|
135
|
+
const moduleDirectory = dirname(fileURLToPath(new URL(import.meta.url)))
|
|
136
|
+
const path = basename(moduleDirectory) === 'plugin'
|
|
137
|
+
? resolve(moduleDirectory, '../../SKILL.md')
|
|
138
|
+
: resolve(moduleDirectory, '../SKILL.md')
|
|
139
|
+
const raw = readFileSync(path, 'utf8')
|
|
140
|
+
const end = raw.indexOf('\n---\n', 4)
|
|
141
|
+
if (!raw.startsWith('---\n') || end < 0) throw new Error('genui SKILL.md has invalid frontmatter')
|
|
142
|
+
return {
|
|
143
|
+
name: BUNDLED_SKILL_PROVIDER,
|
|
144
|
+
list: () => Promise.resolve([{
|
|
145
|
+
name: 'genui',
|
|
146
|
+
description: BUNDLED_SKILL_DESCRIPTION,
|
|
147
|
+
invocation: BUNDLED_SKILL_INVOCATION,
|
|
148
|
+
source: 'bundled',
|
|
149
|
+
provider: BUNDLED_SKILL_PROVIDER,
|
|
150
|
+
path,
|
|
151
|
+
resourceBase: { kind: 'directory', path: dirname(path) },
|
|
152
|
+
rank: BUNDLED_SKILL_RANK,
|
|
153
|
+
locator: path,
|
|
154
|
+
}]),
|
|
155
|
+
get: () => Promise.resolve({
|
|
156
|
+
name: 'genui',
|
|
157
|
+
description: BUNDLED_SKILL_DESCRIPTION,
|
|
158
|
+
invocation: BUNDLED_SKILL_INVOCATION,
|
|
159
|
+
source: 'bundled',
|
|
160
|
+
provider: BUNDLED_SKILL_PROVIDER,
|
|
161
|
+
path,
|
|
162
|
+
resourceBase: { kind: 'directory', path: dirname(path) },
|
|
163
|
+
content: raw.slice(end + 5),
|
|
164
|
+
}),
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
126
168
|
export function apply(ctx: Context): void {
|
|
127
169
|
ctx.systemPrompt.section({
|
|
128
170
|
name: 'genui:fence',
|
|
@@ -156,6 +198,10 @@ export function apply(ctx: Context): void {
|
|
|
156
198
|
if (name === 'tools') tryRegister(value as { register(tool: unknown): unknown })
|
|
157
199
|
})
|
|
158
200
|
|
|
201
|
+
ctx.inject(['skills'], (skillCtx) => {
|
|
202
|
+
skillCtx.skills.registerProvider(() => bundledSkillProvider())
|
|
203
|
+
})
|
|
204
|
+
|
|
159
205
|
// Lazy-engine asset route: same optional-probe pattern as the tools
|
|
160
206
|
// registry — the webserver service may bind after this plugin starts.
|
|
161
207
|
let assetsRegistered = false
|
package/src/plugin/tool.ts
CHANGED
|
@@ -22,8 +22,11 @@ import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
|
22
22
|
import type { JsonValue } from '@deepseek-ai/dsh-session'
|
|
23
23
|
import type { GenericCallView, GenericResultView, JsonSchemaNode, ToolDefinition } from '@deepseek-ai/dsh-tools'
|
|
24
24
|
import type { GenuiSpec } from '../client/spec.ts'
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
GENUI_LIMITS, countDeclaredGenuiNodes, countGenuiNodes, repairGenuiSpec,
|
|
27
|
+
} from '../client/guard.ts'
|
|
26
28
|
import { completeFenceJson } from '../shared/fence-repair.ts'
|
|
29
|
+
import { validateRenderableChartSemantics } from './chart-contract.ts'
|
|
27
30
|
|
|
28
31
|
/**
|
|
29
32
|
* Arguments schema: an open `spec` slot. The schema must NOT reject anything
|
|
@@ -172,7 +175,12 @@ export function createRenderUiTool(): ToolDefinition {
|
|
|
172
175
|
},
|
|
173
176
|
},
|
|
174
177
|
async execute(args: unknown): Promise<JsonValue> {
|
|
175
|
-
const
|
|
178
|
+
const input = specOf(args)
|
|
179
|
+
const chartErrors = validateRenderableChartSemantics(input)
|
|
180
|
+
if (chartErrors.length > 0) {
|
|
181
|
+
throw new Error('render_ui spec invalid: ' + chartErrors.join('; '))
|
|
182
|
+
}
|
|
183
|
+
const spec = repairGenuiSpec(input)
|
|
176
184
|
if (spec === null) {
|
|
177
185
|
return 'render_ui:spec 无效 —— 根对象需要 "items" 数组(组件树白名单见系统提示词),请修正后重试。'
|
|
178
186
|
}
|
|
@@ -277,6 +285,12 @@ function bracketDiagnostic(raw: string): string {
|
|
|
277
285
|
const COMMON_CAUSES =
|
|
278
286
|
'常见原因:① 收尾括号错位/缺失({ 与 }、[ 与 ] 数量不相等)② 字符串值内用了半角引号 "(中文引语请用 “” 或 「」)③ 尾随逗号 ④ 字符串未闭合'
|
|
279
287
|
|
|
288
|
+
/** Return the model-facing chart field failure for one parsed fence body. */
|
|
289
|
+
function chartValidationFailure(value: unknown): string | undefined {
|
|
290
|
+
const errors = validateRenderableChartSemantics(value)
|
|
291
|
+
return errors.length === 0 ? undefined : `❌ chart 字段验证失败:\n- ${errors.join('\n- ')}`
|
|
292
|
+
}
|
|
293
|
+
|
|
280
294
|
/** Build the validate_dsh_ui tool definition (registered alongside render_ui). */
|
|
281
295
|
export function createValidateDshUiTool(): ToolDefinition {
|
|
282
296
|
return {
|
|
@@ -304,11 +318,18 @@ export function createValidateDshUiTool(): ToolDefinition {
|
|
|
304
318
|
// FIXED JSON instead of asking it to re-author the fix — re-writing
|
|
305
319
|
// the whole fence by hand is where the next typo comes from.
|
|
306
320
|
const repaired = completeFenceJson(raw)
|
|
307
|
-
if (repaired !== null
|
|
308
|
-
|
|
321
|
+
if (repaired !== null) {
|
|
322
|
+
const repairedValue = JSON.parse(repaired.text) as unknown
|
|
323
|
+
const chartFailure = chartValidationFailure(repairedValue)
|
|
324
|
+
if (chartFailure !== undefined) return chartFailure
|
|
325
|
+
if (repairGenuiSpec(repairedValue) !== null) {
|
|
326
|
+
return `❌ dsh-ui 围栏 JSON 解析失败:${detail}。\n${bracketDiagnostic(raw)} 已自动修复 ${repaired.repairs} 处,下面是修复后的 JSON,直接作为围栏正文发出即可(无需再验证):\n\`\`\`\n${repaired.text}\n\`\`\``
|
|
327
|
+
}
|
|
309
328
|
}
|
|
310
329
|
return `❌ dsh-ui 围栏 JSON 解析失败:${detail}。\n${bracketDiagnostic(raw)} 自动修复未能恢复(结构损坏),请按错误信息修正后重新调用本工具验证,通过后再发出围栏。\n${COMMON_CAUSES}`
|
|
311
330
|
}
|
|
331
|
+
const chartFailure = chartValidationFailure(parsed)
|
|
332
|
+
if (chartFailure !== undefined) return chartFailure
|
|
312
333
|
const spec = repairGenuiSpec(parsed)
|
|
313
334
|
if (spec === null) {
|
|
314
335
|
return '❌ 不是合法 GenUI spec:根对象需要 "items" 数组,且每个节点 type 必须在白名单内(见系统提示词)。请修正后重新验证。'
|