dsh-motion-ui 0.7.0 → 0.7.1
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 +22 -0
- package/README.md +19 -1
- package/VERIFICATION.md +10 -0
- package/client.js +391 -222
- package/index.js +31 -16
- package/lib/author-expression.js +7 -7
- package/lib/authoring-guide.js +3 -17
- package/lib/build-info.js +1 -1
- package/lib/canvas-design.js +2 -2
- package/lib/canvas-system.js +1 -1
- package/lib/catalog.js +1 -2
- package/lib/delivery.js +1 -1
- package/lib/export-runtime-bundle.js +1 -1
- package/lib/fence-feedback.js +8 -2
- package/lib/freeform.js +2 -8
- package/lib/inspection.js +11 -6
- package/lib/motion-library.js +2 -3
- package/lib/normalize-authoring.js +9 -0
- package/lib/prompt-contract.js +22 -0
- package/lib/protocol.js +3 -3
- package/lib/refinement.js +3 -2
- package/lib/scene-entry.js +41 -0
- package/lib/style-system.js +2 -2
- package/lib/text-guide.js +9 -13
- package/lib/third-party/taste-skill-NOTICE.md +10 -0
- package/lib/tool-schema.js +7 -7
- package/lib/ui-intent.js +4 -15
- package/lib/ui-skill.js +19 -26
- package/lib/visual-audit.js +15 -4
- package/lib/visual-styles.js +1 -1
- package/package.json +1 -1
- package/skills/intelligent-ui/SKILL.md +14 -107
package/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {sceneDocument,SCENE_PARAMETERS,sceneReference} from './lib/scene-entry.js';
|
|
2
|
+
import {TEXT_GUIDE} from './lib/text-guide.js';
|
|
3
|
+
import {CANVAS_CONTRACT,TOOL_DIRECTORY} from './lib/prompt-contract.js';
|
|
1
4
|
import {mechanismDocument} from './lib/mechanism-entry.js';
|
|
2
5
|
import {mechanismCatalog} from './lib/mechanism-catalog.js';
|
|
3
6
|
import {motionCatalog} from './lib/motion-library.js';
|
|
@@ -7,11 +10,12 @@ import { validateDocument, fallbackText } from './lib/protocol.js';
|
|
|
7
10
|
import { UiStore, stateRoute, documentRoute, historyRoute } from './lib/store.js';
|
|
8
11
|
import { evaluate } from './lib/protocol.js';
|
|
9
12
|
import { componentCatalog } from './lib/components.js';
|
|
13
|
+
import {CANVAS_SYSTEM_GUIDE} from './lib/canvas-system.js';
|
|
10
14
|
import { CANVAS_DESIGN_GUIDE } from './lib/canvas-design.js';
|
|
11
15
|
import { architectureExample, scatterExample } from './lib/visual-examples.js';
|
|
12
16
|
import { BUILD_INFO } from './lib/build-info.js';
|
|
13
17
|
import { UI_SKILL_SECTIONS, promptSections } from './lib/ui-skill.js';
|
|
14
|
-
import {DEFAULT_VISUAL_STYLE,VISUAL_STYLE_IDS} from './lib/visual-styles.js';
|
|
18
|
+
import {stylePrompt,styleExamples,DEFAULT_VISUAL_STYLE,VISUAL_STYLE_IDS} from './lib/visual-styles.js';
|
|
15
19
|
import { canvasDocument } from './lib/freeform.js';
|
|
16
20
|
import z from '@deepseek-ai/schemastery';
|
|
17
21
|
import { toolJson } from './lib/tool-json.js';
|
|
@@ -25,7 +29,7 @@ import {VISUAL_GUIDE,VISUAL_EXAMPLES,visualCatalog} from './lib/visual.js';
|
|
|
25
29
|
import {registerVisualAssets} from './lib/visual-assets.js';
|
|
26
30
|
import {diagnosticsRoute} from './lib/diagnostics-route.js';
|
|
27
31
|
import {fenceRoute} from './lib/fence-route.js';
|
|
28
|
-
import {sceneCatalog
|
|
32
|
+
import {sceneCatalog} from './lib/scene-library.js';
|
|
29
33
|
import {registerFenceFeedback} from './lib/fence-feedback.js';
|
|
30
34
|
import {diagnoseDelivery,deliveryDiagnostics} from './lib/delivery.js';
|
|
31
35
|
|
|
@@ -49,14 +53,14 @@ export function apply(ctx,config={}) {
|
|
|
49
53
|
const feedback=registerFenceFeedback(ctx,()=>settings().autoRepair!==false);
|
|
50
54
|
const placement=value=>`\nPlace this exact fenced marker in your answer at the intended UI position:\n\`\`\`dsh-ui\n${value.documentId}\n\`\`\``;
|
|
51
55
|
ctx.effect(()=>ctx.tools.register(defineRenderTool({
|
|
52
|
-
name:'render_text',description:
|
|
56
|
+
name:'render_text',description:"Format native text, comparisons, steps, copyable text/code and link buttons without HTML. blocks is a direct array of strings/native nodes with optional IDs. State/formulas require render_ui; custom graphics require render_canvas. Several calls create independent documents.",
|
|
53
57
|
parameters:{title:{type:'string'},preset:{type:'string',enum:TEXT_PRESETS},accent:{type:'string',enum:['blue','teal','violet','amber','rose']},blocks:TEXT_BLOCKS_PARAMETER},
|
|
54
58
|
output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:v.document.fallback+`\nUI documentId=${v.documentId}; documentRevision=${v.documentRevision??0}.`+placement(v)}],presentationMeta:(_a,v)=>({dshGenerativeUi:v})},
|
|
55
59
|
async execute(args,exec){exec.signal.throwIfAborted();for(const key of Object.keys(args))if(!['title','preset','accent','blocks'].includes(key))throw Error(`DSH UI: unknown render_text argument ${key}; put all content/styling inside blocks or children`);const document=textDocument({...args,blocks:toolJson(args.blocks,'blocks'),preset:args.preset??'reading'});fallbackText(document);const record=await store.create(document,{sessionId:exec.agent?.id??'unscoped',callId:exec.callId});exec.signal.throwIfAborted();return record;}
|
|
56
60
|
})));
|
|
57
61
|
ctx.effect(() => ctx.tools.register(defineRenderTool({
|
|
58
62
|
name: 'render_ui',
|
|
59
|
-
description:
|
|
63
|
+
description: "Render one complete native v1 {version:1,title,fallback,nodes,state?,derived?} OR extended {title,items:[components]} document. Do not mix root languages. Use for useful static/interactive explanations; native formulas stay local. Direct call only. ui_catalog supplies optional fields/examples.",
|
|
60
64
|
parameters: { document: DOCUMENT_PARAMETER },
|
|
61
65
|
output: {
|
|
62
66
|
schema: { type: 'json' },
|
|
@@ -71,29 +75,40 @@ export function apply(ctx,config={}) {
|
|
|
71
75
|
}
|
|
72
76
|
})));
|
|
73
77
|
ctx.effect(()=>ctx.tools.register(defineTool({
|
|
74
|
-
name:'render_canvas',description:
|
|
78
|
+
name:'render_canvas',description:"Render custom HTML/SVG/CSS/JS in an isolated canvas, with optional persisted state. Separate html/css/script fields; no external resources or host APIs. Use the injected canvas contract; libraries:[\"mechanism\"] enables the optional local helper.",
|
|
75
79
|
parameters:{title:{type:'string',required:true},fallback:{type:'string',required:true},state:{type:'json'},data:{type:'json'},derived:{type:'json'},libraries:{type:'array',items:{type:'string',enum:['mechanism']}},height:{type:'number'},css:{type:'string',description:'Canvas CSS; use an empty string when no custom styles are needed. Styled static previews appear progressively; interaction activates after the assistant turn ends.'},html:{type:'string',required:true},script:{type:'string'}},
|
|
76
80
|
output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:fallbackText(v.document)+`\nUI documentId=${v.documentId}; documentRevision=${v.documentRevision??0}; canvas nodeId=canvas.`+placement(v)}],presentationMeta:(_a,v)=>({dshGenerativeUi:v})},
|
|
77
81
|
async execute(args,exec){exec.signal.throwIfAborted();const record=await store.create(canvasDocument(args),{sessionId:exec.agent?.id??'unscoped',callId:exec.callId});exec.signal.throwIfAborted();return record;}
|
|
78
82
|
})));
|
|
83
|
+
ctx.effect(()=>ctx.tools.register(defineTool({name:'render_scene',description:'Deliver a complete flow/comparison/allocation/budget UI from short task data; Host creates nodes, controls and formulas. No source or catalog needed. values/items must be complete; never invent prices. Custom UI remains render_ui/render_canvas.',parameters:SCENE_PARAMETERS,output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:fallbackText(v.document)+'\nUI documentId='+v.documentId+'; documentRevision='+v.documentRevision+'. To revise this saved scene, use inspect_ui then patch_ui/update_ui with this documentId; calling render_scene again creates an extra UI.'+placement(v)}],presentationMeta:(_a,v)=>({dshGenerativeUi:v})},async execute(args,exec){exec.signal.throwIfAborted();const parsed={...args};for(const key of['items','values'])if(parsed[key]!==undefined)parsed[key]=toolJson(parsed[key],key);const record=await store.create(sceneDocument(parsed),{sessionId:exec.agent?.id??'unscoped',callId:exec.callId});exec.signal.throwIfAborted();return record;}})));
|
|
79
84
|
ctx.effect(()=>ctx.tools.register(defineTool({
|
|
80
|
-
name:'render_mechanism',description:
|
|
85
|
+
name:'render_mechanism',description:"Create an adjustable assembly/gears/crank diagram from parameters, no source or catalog needed. Gear driver16 teeth; crank rod70. Educational geometry/kinematics. For other structures use render_canvas libraries:[\"mechanism\"] and ui_catalog topic=mechanism.",
|
|
81
86
|
parameters:{scene:{type:'string',required:true,enum:['assembly','gears','crank']},title:{type:'string'},rpm:{type:'number',description:'gears/crank: 0..90, default20'},teeth:{type:'number',description:'gears only: driven integer16..48, default32'},radius:{type:'number',description:'crank only: integer10..28, default20'},running:{type:'boolean',description:'gears/crank: defaultfalse'},angle:{type:'number',description:'gears/crank: integer0..360 degrees, default0'},explode:{type:'number',description:'0..1 in steps0.01; assembly default0.45, others0'}},
|
|
82
87
|
output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:fallbackText(v.document)+'\nUI documentId='+v.documentId+'; documentRevision='+v.documentRevision+'.'+placement(v)}],presentationMeta:(_a,v)=>({dshGenerativeUi:v})},
|
|
83
88
|
async execute(args,exec){exec.signal.throwIfAborted();const record=await store.create(mechanismDocument(args),{sessionId:exec.agent?.id??'unscoped',callId:exec.callId});exec.signal.throwIfAborted();return record;}
|
|
84
89
|
})));
|
|
85
90
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
86
|
-
name: 'ui_catalog', description:
|
|
87
|
-
parameters: { topic: { type: 'string', enum: ['overview',
|
|
91
|
+
name: 'ui_catalog', description: "Read one optional reference topic or scene. text=native typography; native=v1 fields/formulas; visual=extended components; canvas=API/construction/examples; style=selected guided style/examples; templates/motion/mechanism=targeted recipes. overview/quick/data/scenes/all remain available; avoid all unless needed.",
|
|
92
|
+
parameters: { topic: { type: 'string', enum: ['overview','quick','text','native','style','visual','motion','mechanism','templates','canvas','data','scenes','all'], description:'Optional topic; default overview.' },scene:{type:'string',description:'Scene for canvas/templates/motion/mechanism, or explicit style ID for style. Omit to get summaries/current guided style.'} }, output: { schema: { type: 'json' }, render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }] },
|
|
88
93
|
isConcurrencySafe: () => true,
|
|
89
94
|
async execute({ topic = 'overview',scene } = {}) {
|
|
95
|
+
if(topic==='text')return {topic,guide:TEXT_GUIDE};
|
|
96
|
+
if(topic==='native')return {topic,protocol:PROTOCOL_GUIDE,components:componentCatalog(),example:calculatorExample};
|
|
97
|
+
if(topic==='style'){if(settings().designMode==='free'&&!scene)return{topic,mode:'free',note:'No style is selected in free mode. An explicit scene equal to a style ID requests reference guidance only.',styles:VISUAL_STYLE_IDS};const id=scene??settings().visualStyle??DEFAULT_VISUAL_STYLE;return{topic,style:id,guide:stylePrompt(id),examples:styleExamples(id)}}
|
|
98
|
+
if(topic==='canvas'){
|
|
99
|
+
const docs={bicycle:bicycleExample,oscillator:oscillatorExample,architecture:architectureExample,scatter:scatterExample};
|
|
100
|
+
if(scene&&!Object.hasOwn(docs,scene))throw Error('Unknown canvas scene; use bicycle, oscillator, architecture or scatter');
|
|
101
|
+
return {topic,interface:CANVAS_CONTRACT,construction:CANVAS_SYSTEM_GUIDE,...(settings().designMode==='free'?{}:{design:CANVAS_DESIGN_GUIDE}),examples:scene?[docs[scene]]:Object.entries(docs).map(([scene,d])=>({scene,title:d.title,fallback:d.fallback})),usage:'Add scene to retrieve one complete example, not all four sources.'};
|
|
102
|
+
}
|
|
103
|
+
if(topic==='overview')return{topic,guide:TOOL_DIRECTORY,examples:QUICK_EXAMPLES.slice(0,2)};
|
|
104
|
+
if(topic==='quick')return{...sceneReference(),design:QUICK_GUIDE,fullDocuments:QUICK_EXAMPLES};
|
|
90
105
|
if(topic==='templates')return sceneCatalog(scene);
|
|
91
106
|
if(topic==='mechanism')return mechanismCatalog(scene);
|
|
92
107
|
if(topic==='motion')return motionCatalog(scene);
|
|
93
108
|
const groups = { overview: [calculatorExample, oscillatorExample], canvas: [bicycleExample, oscillatorExample, architectureExample, scatterExample], data: [calculatorExample, comparisonExample, budgetExample, curveExample, scatterExample], scenes: sceneExamples, all: [calculatorExample, comparisonExample, budgetExample, curveExample, ...sceneExamples, bicycleExample, oscillatorExample, architectureExample, scatterExample] };
|
|
94
109
|
groups.quick=QUICK_EXAMPLES;groups.overview=QUICK_EXAMPLES.slice(0,2);
|
|
95
110
|
groups.visual=VISUAL_EXAMPLES;
|
|
96
|
-
if (!Object.hasOwn(groups, topic)) throw new Error('Unknown ui_catalog topic; use templates, overview, quick, visual, canvas, data, scenes or all');
|
|
111
|
+
if (!Object.hasOwn(groups, topic)) throw new Error('Unknown ui_catalog topic; use text, native, style, templates, overview, quick, visual, canvas, data, scenes, motion, mechanism or all');
|
|
97
112
|
if(topic==='visual')return{build:BUILD_INFO,topic,protocol:VISUAL_GUIDE,components:visualCatalog(),examples:VISUAL_EXAMPLES};
|
|
98
113
|
return { build: BUILD_INFO, topic, protocol: PROTOCOL_GUIDE, components: componentCatalog(), design: topic === 'quick' ? QUICK_GUIDE : topic === 'canvas' || topic === 'all' ? CANVAS_DESIGN_GUIDE : 'Choose composition by the question: editorial reading, technical relationships, analytical data, exploratory objects, aligned comparisons or ordered instruction. Keep theme, accessible labels and readable Chinese. Use topic=canvas for the full visual design guide.', examples: groups[topic] };
|
|
99
114
|
}
|
|
@@ -104,16 +119,16 @@ export function apply(ctx,config={}) {
|
|
|
104
119
|
registerVisualAssets(ctx);
|
|
105
120
|
ctx.effect(()=>ctx.connection.fetch.register({path:'/api/plugins/dsh-generative-ui/diagnostics',methods:['GET'],requestBody:'buffered',fetch:diagnosticsRoute(feedback)}));
|
|
106
121
|
ctx.effect(()=>ctx.connection.fetch.register({path:'/api/plugins/dsh-generative-ui/fence',methods:['POST'],requestBody:'buffered',fetch:fenceRoute(store)}));
|
|
107
|
-
ctx.effect(()=>ctx.tools.register(defineTool({name:'validate_ui',description:
|
|
122
|
+
ctx.effect(()=>ctx.tools.register(defineTool({name:'validate_ui',description:"Validate native or extended UI without saving/displaying. valid with complete=false means rejected components. Returns canonical document/diagnostics; a rendering tool still delivers the UI.",parameters:{document:{type:'json',required:true}},output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:JSON.stringify(v)}]},isConcurrencySafe:()=>true,async execute({document}){try{const report=diagnoseDelivery(toolJson(document,'document'));return {...report,...(report.valid?{fallback:fallbackText(report.document)}:{error:report.diagnostics[0].message})}}catch(e){return{valid:false,complete:false,error:e.message}}}})));
|
|
108
123
|
ctx.effect(()=>ctx.tools.register(defineTool({
|
|
109
|
-
name:'inspect_ui',description:
|
|
110
|
-
parameters:{documentId:{type:'string',required:true},detail:{type:'string',enum:['summary','nodes','full']},nodeIds:{type:'json',description:'For nodes detail: up to 16 existing node IDs.'}},
|
|
124
|
+
name:'inspect_ui',description:"Read current revision/state/derived and heuristic observations. Summary includes node paths/positions/editable fields. query finds text/labels/types (max200 chars); detail=nodes with query OR nodeIds reads matching source. Full only when needed. Missing observations mean unobserved; do not poll. Inspect before editing.",
|
|
125
|
+
parameters:{documentId:{type:'string',required:true},detail:{type:'string',enum:['summary','nodes','full']},query:{type:'string',description:'Find by text/label/title/type; mutually exclusive with nodeIds. Up to16 matches; blank means all.'},nodeIds:{type:'json',description:'For nodes detail: up to 16 existing node IDs.'}},
|
|
111
126
|
output:{schema:{type:'json'},render:(_args,value)=>[{type:'text',text:JSON.stringify(value)}]},isConcurrencySafe:()=>true,
|
|
112
|
-
async execute({documentId,detail='summary',nodeIds=[]},exec){const record=await store.owned(documentId,exec.agent?.id);const saved=await store.readState(documentId,record.sessionId);return inspectRecord(record,saved.state,evaluate(record.document,saved.state).derived,await store.audits(documentId),{detail,nodeIds:toolJson(nodeIds,'nodeIds')});}
|
|
127
|
+
async execute({documentId,detail='summary',nodeIds=[],query=''},exec){const record=await store.owned(documentId,exec.agent?.id);const saved=await store.readState(documentId,record.sessionId);return inspectRecord(record,saved.state,evaluate(record.document,saved.state).derived,await store.audits(documentId),{detail,nodeIds:toolJson(nodeIds,'nodeIds'),query});}
|
|
113
128
|
})));
|
|
114
|
-
ctx.effect(()=>ctx.tools.register(defineTool({name:'patch_ui',description:
|
|
115
|
-
ctx.effect(()=>ctx.tools.register(defineTool({name:'update_ui',description:
|
|
116
|
-
for
|
|
129
|
+
ctx.effect(()=>ctx.tools.register(defineTool({name:'patch_ui',description:"Edit existing scalar/source properties using the inspected documentRevision. changes:[{id,props}]. IDs/structure/bindings/actions stay unchanged; canvas source replacement restarts that frame. Saved parameters persist. Use update_ui for structure changes.",parameters:{documentId:{type:'string',required:true},documentRevision:{type:'number',required:true},changes:{type:'json',required:true}},output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:`Updated ${v.document.title}; documentId=${v.documentId}; documentRevision=${v.documentRevision}. Saved parameters retained.`}],presentationMeta:(_a,v)=>({dshGenerativeUiPatch:v})},async execute({documentId,documentRevision,changes},exec){exec.signal.throwIfAborted();return store.refine(documentId,exec.agent?.id,documentRevision,toolJson(changes,'changes'));}})));
|
|
130
|
+
ctx.effect(()=>ctx.tools.register(defineTool({name:'update_ui',description:"Append/replace existing content at the inspected revision, keeping documentId and compatible state. targetId selects a node; an extended fragment appends to the sole visual when unambiguous. Supply a stable operationId; reuse only for an identical retry (latest64 replay IDs). Preserve state/derived definitions; new inputs get defaults.",parameters:{documentId:{type:'string',required:true},documentRevision:{type:'number',required:true},operationId:{type:'string',required:true},mode:{type:'string',enum:['append','replace'],required:true},targetId:{type:'string'},document:{type:'json',required:true}},output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:'Updated existing UI '+v.documentId+'; documentRevision='+v.documentRevision+'. Keep its existing inline marker; do not duplicate it.'}],presentationMeta:(_a,v)=>({dshGenerativeUiPatch:v})},async execute({documentId,documentRevision,operationId,mode,targetId,document},exec){exec.signal.throwIfAborted();return store.update(documentId,exec.agent?.id,documentRevision,operationId,{mode,...(targetId?{targetId}:{}),document:toolJson(document,'document')})}})));
|
|
131
|
+
for(const[i,section]of UI_SKILL_SECTIONS.entries())ctx.effect(()=>ctx.systemPrompt.section({...section,text:()=>promptSections(settings().designMode??'guided',settings().visualStyle??DEFAULT_VISUAL_STYLE)[i].text}));
|
|
117
132
|
}
|
|
118
133
|
|
|
119
134
|
|
package/lib/author-expression.js
CHANGED
|
@@ -41,21 +41,21 @@ export function compileExpression(source) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export function expandExpressions(doc) {
|
|
44
|
-
const expression = e => {
|
|
44
|
+
const expression = (e, at) => {
|
|
45
45
|
if (!e || typeof e !== 'object' || Array.isArray(e)) return e;
|
|
46
46
|
if (Object.hasOwn(e, 'expr')) {
|
|
47
47
|
if (Object.keys(e).length !== 1) throw new Error('DSH UI: expr must be the only formula field');
|
|
48
|
-
return compileExpression(e.expr);
|
|
48
|
+
try{return compileExpression(e.expr)}catch(error){error.message='DSH UI: '+at+': '+error.message.replace(/^DSH UI: /,'');error.path=at;throw error}
|
|
49
49
|
}
|
|
50
|
-
if (Array.isArray(e.args)) e.args = e.args.map(expression);
|
|
51
|
-
if (e.map?.fields) for (const k of Object.keys(e.map.fields)) e.map.fields[k] = expression(e.map.fields[k]);
|
|
50
|
+
if (Array.isArray(e.args)) e.args = e.args.map((v,i)=>expression(v,at+'.args['+i+']'));
|
|
51
|
+
if (e.map?.fields) for (const k of Object.keys(e.map.fields)) e.map.fields[k] = expression(e.map.fields[k],at+'.map.fields.'+k);
|
|
52
52
|
return e;
|
|
53
53
|
};
|
|
54
|
-
if (doc.derived && typeof doc.derived === 'object') for (const k of Object.keys(doc.derived)) doc.derived[k] = expression(doc.derived[k]);
|
|
54
|
+
if (doc.derived && typeof doc.derived === 'object') for (const k of Object.keys(doc.derived)) doc.derived[k] = expression(doc.derived[k],'derived.'+k);
|
|
55
55
|
const walk = nodes => { if (!Array.isArray(nodes)) return; for (const node of nodes) {
|
|
56
56
|
if (!node || typeof node !== 'object') continue;
|
|
57
|
-
for (const field of ['text', 'value', 'max', 'rows']) if (Object.hasOwn(node, field)) node[field] = expression(node[field]);
|
|
58
|
-
if (['table','chart'].includes(node.type)&&Array.isArray(node.rows)) for(const row of node.rows) if(row&&typeof row==='object'&&!Array.isArray(row)) for(const k of Object.keys(row)) row[k]=expression(row[k]);
|
|
57
|
+
for (const field of ['text', 'value', 'max', 'rows']) if (Object.hasOwn(node, field)) node[field] = expression(node[field],'node.'+node.id+'.'+field);
|
|
58
|
+
if (['table','chart'].includes(node.type)&&Array.isArray(node.rows)) for(const [rowIndex,row] of node.rows.entries()) if(row&&typeof row==='object'&&!Array.isArray(row)) for(const k of Object.keys(row)) row[k]=expression(row[k],'node.'+node.id+'.rows['+rowIndex+'].'+k);
|
|
59
59
|
walk(node.children);
|
|
60
60
|
} };
|
|
61
61
|
walk(doc.nodes); return doc;
|
package/lib/authoring-guide.js
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
- JSON object field order never matters. Every node, including each sandbox, must include a unique id. sandbox title/fallback default to the document's only when omitted; do not send null or empty values.
|
|
5
|
-
- Declaring state does NOT create controls. For each parameter the user should edit, add a native slider/input/select/toggle bound to state.key, or actual canvas HTML controls wired to DSHUI.setState with that key in stateKeys. A canvas subscribe callback only updates display; it does not create an input. Read-only state and static UI are valid; do not invent controls where the task does not need them. Check intended controls exist before calling the tool.
|
|
6
|
-
- Use render_ui/render_canvas directly for UI construction. This contract and ui_catalog are the protocol reference; installation source inspection is not a prerequisite. For unclear component/formula details consult ui_catalog, rather than grep/glob/shell searches of DSH checkouts or app.asar. File/search tools needed for the user's actual task or data remain appropriate.
|
|
7
|
-
- Formula round(x) takes ONE argument and uses JavaScript Math.round: nearest integer, ties toward positive infinity (round(2.5)=3, round(-2.5)=-2). Native ceil(x) rounds up and floor(x) rounds down, each with ONE argument. Use {expr:"ceil(state.people / 2)"} for room counts directly, without a canvas or lookup chain. Display decimals formats numbers without changing calculation precision.
|
|
8
|
-
- Pass tool JSON fields as objects, not hand-escaped JSON strings. For custom HTML/SVG use render_canvas({title,fallback,html,css,script}) to avoid nesting source in document JSON.
|
|
9
|
-
- Start with the smallest useful static UI. State/derived/actions are optional. A table can put its object rows directly in rows; reuse data/derived references only when useful. Example: {id:"t",type:"table",label:"方案比较",columns:[{key:"方案",label:"方案"}],rows:[{"方案":"A"}]}.
|
|
10
|
-
- For a short ordered process, use steps with a DIRECT items array: {id:"flow",type:"steps",items:[{title:"整理文件",description:"保留源码和说明,排除密钥。"},{title:"提交代码",description:"检查变更后提交。"}]}. Do not wrap items in {item:[...]}. status is optional: omit it for explanatory guides; use pending/active/done/error only for known task states. Keep one sentence per requested step; avoid extra command disclosures unless asked.
|
|
11
|
-
- A short dynamic chart/table can use inline formula cells: rows:[{label:"餐饮",value:{ref:"derived.food"}},{label:"住宿",value:{expr:"ceil(state.people / 2) * state.room"}}]. Chart labelKey:"label",valueKey:"value". No map/if chain is required for a few categories. Submit complete nodes/state/derived together on every render_ui retry; it creates a document, not a partial patch.
|
|
12
|
-
- Numeric controls need complete typed state: state:{people:{type:"number",default:2,min:1,max:12,step:1},days:{type:"number",default:5,min:2,max:15,step:1}}. Use numbers without quotes or units. Prefer derived:{food:{expr:"state.people * state.days * 100"}} to a deep op/args tree. children, options, columns and expression args are direct arrays. The host normalizes plain numeric strings only in explicitly numeric fields and exact {item:[...]} wrappers only at declared array positions; ordinary text/data retain their types.
|
|
13
|
-
- Host defaults use readable editorial typography, quiet backgrounds, light borders and restrained accents. For guided canvas designs prefer 12..18px radii and flat surfaces; gradients are optional and must not replace information hierarchy. Let content determine height: no empty spacer sections, min-height:100vh or large fixed min-heights. A canvas auto-fits its rendered content after commit, including shrinking after filtering; height is only the initial placeholder. Use SVG viewBox bounds close to actual graphics plus reasonable padding.
|
|
14
|
-
- metric.value accepts a finite number or display string/boolean. Numbers use decimals and unit; strings display verbatim ("约 12 GiB", "待确认"). Calculations and chart series still require real numbers, never formatted strings.
|
|
15
|
-
- IDs, data/state/derived keys, column keys and formula references support Chinese letters and underscores. Use 1..64 letters/numbers/underscores, start with a letter/underscore, no spaces/dots/hyphens; never constructor/prototype/__proto__. Labels/text can be natural language.
|
|
16
|
-
- Persist a canvas checklist/list/object with state:{checklist:{type:"json",default:[]}} and DSHUI.setState({checklist:nextArray}). json state allows bounded arrays/objects, no options; native input/select cannot edit it. A native checklist uses separate boolean states and toggle nodes. Static lists belong in nodes/data, not state. String state with an array/object default and no options is canonicalized to json without losing its structure.
|
|
17
|
-
- Do not guess capabilities. On validation failure, fix the named field in the original document; do not deliver tiny probe/test UIs or search the DSH installation with shell tools. ui_catalog is the protocol reference. Missing bounds/operands need a real correction, not arbitrary guesses. Unknown properties are not discarded; unsupported code is not executed. Never claim successful delivery before the tool succeeds. A canvas with undeclared private DOM state does not have host persistence or state/derived followup snapshots.`;
|
|
1
|
+
export const AUTHORING_GUIDE=`Authoring fast path:
|
|
2
|
+
Pass complete JSON objects, not serialized strings or partial documents. Object field order never matters. children/items/options/columns are direct arrays, not {item:[...]}. Every native node has a unique id (render_text auto-fills omitted IDs). Sandbox title/fallback may inherit only when omitted; do not send null/empty values.
|
|
3
|
+
Before submitting v1, include its REQUIRED nodes array with all visible content and matching controls; state/derived alone is incomplete. Native attributes are registered fields, not HTML/style/className/callbacks. UI source belongs in canvas html/css/script fields. Omit unused state/data/derived/actions. On validation failure fix the named field in the original document, preserving needed nodes/state/derived.`;
|
package/lib/build-info.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by npm run build.
|
|
2
|
-
export const BUILD_INFO = Object.freeze({"version":"0.7.
|
|
2
|
+
export const BUILD_INFO = Object.freeze({"version":"0.7.1","id":"0.7.1+d92c9bf2bf64","sourceHash":"d92c9bf2bf6407506977357f099a9c7935c744d97606dc631267a62be10735eb"});
|
package/lib/canvas-design.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export const CANVAS_DESIGN_GUIDE = `Design
|
|
1
|
+
export const CANVAS_DESIGN_GUIDE = `Design the explanation around its content. Follow the selected guided style for palette, typography, materials and surfaces; these functional principles do not replace it. Examples illustrate capabilities, not templates. Static readability and purposeful motion can coexist.
|
|
2
2
|
1. Identify the user's question, the visual relationship; add a primary interaction only if it helps. Reserve the largest region for the evidence or object being explored. Use a heading, a compact explanation and purposeful supporting details; don't repeat titles or put every paragraph inside another card.
|
|
3
3
|
2. Choose composition by task: mechanical explorer -> large SVG subject, optional disassembly slider and part focus; parameter experiment -> visualization plus compact controls and exact values; architecture/process -> role-based nodes and clear labelled relationships; data analysis -> chart and exact table with units/source; comparison -> aligned criteria, normally static; reading/guide -> editorial text with a single supporting illustration. Combine native components and canvases where useful.
|
|
4
|
-
3. Choose visual treatment by content: editorial (reading, minimal borders), technical (geometry, restrained grid, annotations), analytical (quiet surface, aligned numerals), exploratory (large interactive object), comparative (aligned criteria), instructional (ordered explanation). Variation must change hierarchy and composition, not randomly swap accent colors. Keep host theme and Chinese readability.
|
|
4
|
+
3. Choose visual treatment by content: editorial (reading, minimal borders), technical (geometry, restrained grid, annotations), analytical (quiet surface, aligned numerals), exploratory (large interactive object), comparative (aligned criteria), instructional (ordered explanation). Variation must change hierarchy and composition, not randomly swap accent colors. Keep host theme and Chinese readability. Accent treatment follows the selected style; semantic warning/success colors describe established meaning. Check foreground/background contrast in both themes; pale dark-theme accents need dark foregrounds.
|
|
5
5
|
4. Diagram grammar: distinguish hierarchy, flow, sequence, dependency and spatial assembly. Give each node one clear idea. Reserve space for labels before routing lines; avoid crossing text, ambiguity and clipped arrows. Prefer a focused overview plus details over dense spaghetti. Don't force every relation into identical cards. SVG labels and units must remain readable on mobile; use viewBox and responsive width. A diagram's numbers must not imply engineering or physical accuracy unless established by evidence.
|
|
6
6
|
5. Interaction: show an informative initial state; updates need visible feedback and matching explanation. Important inputs use document.state. Read the shared DSHUI snapshot instead of independently recalculating a host-derived result. Use DSHUI.subscribe to drive views, not to echo the same state back. Prefer a few labelled controls. Don't duplicate every native input inside the canvas. Make SVG hotspots keyboard-operable or provide equivalent labelled buttons.
|
|
7
7
|
6. Motion: animate the explanation, not every decoration. Prefer DSHUI.animate(({time,delta,reducedMotion,visible})=>{...}) and retain its cleanup callback. time/delta are seconds; integrate delta for pause/resume without jumps. The helper pauses offscreen/background, and supplies a static delta=0 frame for reduced motion. Keep reading and controls usable without animation; never save each animation frame to host state. Raw requestAnimationFrame remains available, but you own its lifecycle and reduced-motion handling.
|
package/lib/canvas-system.js
CHANGED
|
@@ -14,5 +14,5 @@ export const CANVAS_SYSTEM_GUIDE = `The canvas includes optional .ui-stack, .ui-
|
|
|
14
14
|
For a small static diagram, .ui-flow arranges .ui-step nodes separated by .ui-arrow; each step uses strong + span. .ui-callout emphasizes one sentence. These work with no custom CSS or JavaScript and adapt to narrow screens.
|
|
15
15
|
SVG construction: reserve label space first; center box labels with text-anchor=middle and dominant-baseline=central. Measure Chinese characters at roughly one font-size each, Latin letters about .6 font-size; mixed text needs combined width plus 24px padding on each side. These are estimates, not browser measurements. Give same-role nodes aligned dimensions. Route connectors around text and unrelated boxes, with explicit fill=none and visible arrowheads. Compute viewBox bounds from the furthest shape, label and arrow plus padding. A 680-unit diagram shown at 340px halves its fonts: reflow or move captions into HTML instead of shrinking important labels. Keep PC chat-column labels readable at actual rendered size. Use overview + focus detail for dense systems. Do not force this grammar on spatial/mechanical drawings.
|
|
16
16
|
For calculators validate blanks, finite values, ranges and zero denominators; show an actionable error, never a stale result as valid. Use one state source for filtering/sorting/reset and one animation loop with cleanup. A reset restores inputs and outputs together. For explanation show relationships and one useful variable; for comparison expose criteria/units/assumptions; for a tool provide working inputs, calculation, outputs and reset.
|
|
17
|
-
Keep CSS small and put a useful initial view in html. Source field order is arbitrary.
|
|
17
|
+
Keep CSS small and put a useful initial view in html. Source field order is arbitrary. Completed static content previews progressively as read-only during generation; scripts/controls activate only after the whole assistant turn ends and state restoration. Do not claim a saved document has been visually checked without evidence.
|
|
18
18
|
Small building example (adapt relationships and dimensions to the actual task, not a compulsory template): <svg viewBox="0 0 420 120" role="img" aria-label="输入转为输出"><path class="ui-edge" d="M160 60H260"/><path class="ui-edge" d="M250 54L260 60L250 66"/><rect class="ui-node" x="20" y="36" width="140" height="48" rx="8"/><text class="ui-label" x="90" y="60" text-anchor="middle" dominant-baseline="central">输入</text><rect class="ui-node ui-accent" x="260" y="36" width="140" height="48" rx="8"/><text class="ui-label" x="330" y="60" text-anchor="middle" dominant-baseline="central">输出</text></svg>. Use SVG label text only; explanatory paragraphs belong in responsive HTML. For a reset button connect addEventListener('click',()=>DSHUI.setState({yourDeclaredKey:yourDefault})); render results from DSHUI.subscribe(s=>...) rather than independently duplicating host-derived formulas.`;
|
package/lib/catalog.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { UI_ICONS, UI_TONES } from './presentation.js';
|
|
2
2
|
import { componentGuide } from './components.js';
|
|
3
3
|
import { AUTHORING_GUIDE } from './authoring-guide.js';
|
|
4
|
-
import {VISUAL_GUIDE} from './visual.js';
|
|
5
4
|
export { sceneExamples } from './scenes.js';
|
|
6
5
|
export { bicycleExample, oscillatorExample } from './canvas-example.js';
|
|
7
|
-
export const PROTOCOL_GUIDE =
|
|
6
|
+
export const PROTOCOL_GUIDE = AUTHORING_GUIDE+`\n\nDSH UI v1. Call render_ui({document: ...}) directly when visualization helps. Do not call it inside run_code/PTC in this version.
|
|
8
7
|
Formula shorthand: {expr:"state.V * state.V / state.R"} compiles to the same validated interpreter. Supports explicit state.x/data.x/derived.x/item.x references, + - * /, == != < <= > >=, parentheses, condition ? yes : no, min/max/round/ceil/floor/abs, double-quoted strings and booleans. Use it in derived, node text/value/max/rows or map.fields. No arbitrary functions, property chains, statements or side effects; complex animation belongs in render_canvas. Example derived:{power:{expr:"state.V * state.V / state.R"}}, metric.value:{ref:"derived.power"}.
|
|
9
8
|
Document: {version:1,title:string,fallback:string,presentation?:{layout,accent,density,preset},state?:{},data?:{},derived?:{},nodes:[],actions?:{}}.
|
|
10
9
|
Scene layouts: dashboard (metrics/trends), workbench (parameter controls/results), comparison (parallel choice cards), guide (ordered instructions), status (observed task snapshot), report (reading and evidence). Accent: blue/teal/violet/amber/rose; density: comfortable/compact. Omit to keep the standard design. Choose based on the task, not at random. Card surface: outline/soft/featured. Featured indicates discussion focus, not an automatic recommendation.
|
package/lib/delivery.js
CHANGED
|
@@ -6,7 +6,7 @@ export function deliveryDiagnostics(document){
|
|
|
6
6
|
return diagnostics;
|
|
7
7
|
}
|
|
8
8
|
export function diagnoseDelivery(input){
|
|
9
|
-
try{const document=validateDocument(input),diagnostics=deliveryDiagnostics(document);return {valid:true,complete:diagnostics.length===0,document,diagnostics}}catch(e){return {valid:false,complete:false,diagnostics:[{severity:'error',path:'document',message:e.message}]}}
|
|
9
|
+
try{const document=validateDocument(input),diagnostics=deliveryDiagnostics(document);return {valid:true,complete:diagnostics.length===0,document,diagnostics}}catch(e){return {valid:false,complete:false,diagnostics:[{severity:'error',path:e.path??e.message.match(/(?:^DSH UI: |unknown field )((?:node|nodes|derived|state|document|presentation)(?:\.[\p{L}\p{N}_]+|\[\d+\])+)/u)?.[1]??'document',message:e.message}]}}
|
|
10
10
|
}
|
|
11
11
|
export function resolveSourceFence(raw,{svg=false}={}){
|
|
12
12
|
if(typeof raw!=='string'||raw.length>131072)return {valid:false,complete:false,diagnostics:[{severity:'error',path:'source',message:'Source exceeds 128 KiB'}]};
|