dsh-motion-ui 0.8.7 → 0.8.9

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/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.8.7","id":"0.8.7+b49a94e536b0","sourceHash":"b49a94e536b010c02561479d3e8daffea6997fe97b0c711057cdffa554998761"});
2
+ export const BUILD_INFO = Object.freeze({"version":"0.8.9","id":"0.8.9+ee236a68197c","sourceHash":"ee236a68197cf7a6992976fd1cafa7f5f9890a36fa3dca3ef4855b11c4189e0c"});
package/lib/catalog.js CHANGED
@@ -1,94 +1,94 @@
1
- import { UI_ICONS, UI_TONES } from './presentation.js';
2
- import { componentGuide } from './components.js';
3
- import { AUTHORING_GUIDE } from './authoring-guide.js';
4
- export { sceneExamples } from './scenes.js';
5
- export { bicycleExample, oscillatorExample } from './canvas-example.js';
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.
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"}.
8
- Document: {version:1,title:string,fallback:string,presentation?:{layout,accent,density,preset},state?:{},data?:{},derived?:{},nodes:[],actions?:{}}.
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.
10
- Additional nodes: badge {id,type,text,icon?,tone?}; progress {id,type,label,value,max,unit?,icon?,tone?} where values may be bindings and 0 <= value <= positive max; steps {id,type,items:[{title,description?,status?:"pending"|"active"|"done"|"error"}]} (direct array of 1..12 items, not {item:[...]}; omit status for static guides); details {id,type,title,children,open?:boolean,icon?,tone?}. Progress and steps are declared snapshots, not a live host task subscription. Only claim real progress/completion from actual tool evidence; mark demo data visibly. Details keeps the content available in text fallback.
11
- Display lifecycle: submit native render_text/render_ui/render_canvas directly. Several independent UI documents may be created in one answer. Complete validated native blocks appear progressively as read-only previews. Keep previews visible through later reasoning and text; durable turn/end activates controls and scripts. Incomplete/failed tools never become interactive. Use small meaningful documents; no separate ui.begin/append calls.
12
- All IDs and dictionary keys: Unicode letters (including Chinese) or underscore, followed by letters/marks/digits/underscore, max64; unique node IDs. Standard components reject HTML, JS, arbitrary styles and callbacks. link/button href explicitly supports http/https URLs; path uses the host file opener on user click. The sandbox component below is the explicit exception; unlisted fields are always rejected.
13
- Freeform canvas: {id,type:"sandbox",title,html,css?:string,script?:string,height?:160..1000,fallback,stateKeys?:string[],dataKeys?:string[],derivedKeys?:string[],libraries?:["mechanism"]}. For internal assemblies and mechanical relations, ui_catalog topic=mechanism provides an opt-in local drawing/kinematics helper and three complete examples (assembly/gears/crank). Use it for custom SVG diagrams, animation, simulations and bespoke interfaces that fixed components cannot express. HTML is a body fragment. Put CSS in css and ALL JavaScript in script (not inline handlers, script tags or remote imports). Inline SVG is supported. Each source field allows up to 49152 characters; the total document remains 128KiB. The iframe has no same-origin privilege, remote resources/network APIs are blocked by CSP, and no host tools are exposed. Never put credentials or private unrelated data in a canvas. It is browser origin isolation, not a CPU/memory hard sandbox.
14
- Canvas API: window.DSHUI.snapshot -> {state,data,derived,disabled,theme,visible}; DSHUI.animate(fn) returns cleanup and calls fn({time,delta,reducedMotion,visible}), time/delta in seconds, delta capped at 0.05. It pauses offscreen/background and supplies a static delta=0 callback for reduced motion; integrate delta for pause/resume without jumps. DSHUI.subscribe(snapshot=>{...}) immediately supplies current selected values and returns unsubscribe; DSHUI.setState({key:value,...}) requests an atomic Host-validated update of declared stateKeys. Use bare dictionary keys in stateKeys/dataKeys/derivedKeys. Only declared keys are exposed; state types/ranges/options remain enforced. Subscribe to drive DOM/SVG from host state; do not send the same state back from subscribe (feedback loop). Native controls and canvas share the saved state. Local animation may use requestAnimationFrame without saving every frame. No Agent/tool action API in this release. theme exposes bg,soft,ink,muted,line,accent,scheme; CSS variables --canvas-bg/soft/ink/muted/line/accent update automatically. Honor reduced motion, provide accessible labels and responsive layout. Canvas scripts activate only after successful tool commit and state restoration; previews show inert completed HTML/CSS. Use a complete useful fallback per canvas. Code or reset changes restart the frame, state changes do not. All transient internal animation state is reset on reload; declare important parameters in document.state.
15
- state: key -> {type:"number",default,min,max,step} or {type:"string",default,options?:scalar[]} or {type:"boolean",default} or {type:"json",default:array/object} (canvas structured state, max16KiB per value). select requires scalar options. Numeric defaults follow step from min.
16
- derived: key -> expression: {const:scalar}, {ref:"state.key"|"data.key"|"derived.key"}, or {op,args:expression[]}. Allowed ops add,mul,min,max (1..8 args); sub,div,eq,neq,lt,lte,gt,gte (2); round,ceil,floor,abs (1); if (3, boolean condition). No cycles. Arithmetic requires finite numbers: add does NOT concatenate strings. For formatted labels use a metric's label/unit/decimals, or format text inside a sandbox; omit unused derived fields. if can return strings: {op:"if",args:[{op:"gt",args:[{ref:"derived.weightGiB"},{const:20}]},{const:"超过理论预算"},{const:"在理论预算内"}]} -> bind a text node to this derived result. No separate conditional-rendering field is needed.
17
- Text/metric values are literal scalars or {ref:...}. Table/chart rows accept inline arrays of objects, {ref:"data.rows"}, {ref:"derived.rows"}, or the shorthand string "data.rows"/"derived.rows". Table label is an optional caption. Static table example: data:{rows:[{name:"A",note:"示例"}]}, node:{id:"t",type:"table",rows:{ref:"data.rows"},columns:[{key:"name",label:"方案"},{key:"note",label:"说明"}]}. For a short live breakdown prefer inline formula cells: rows:[{label:"餐饮",value:{ref:"derived.mealTotal"}},{label:"住宿",value:{expr:"ceil(state.people / 2) * state.room"}}], labelKey:"label",valueKey:"value". These cells recalculate locally; data remains literal and never executes formulas. Dynamic rows for many samples: a derived root may be {map:{source:"data.samples",fields:{contextK:{op:"mul",args:[{ref:"state.contextK"},{ref:"item.fraction"}]},value:{ref:"derived.total"}}}}. source must be the string "data.samples", not an inline array; declare those object rows in document.data.samples. Internal field keys support Chinese identifiers such as 功率 and ASCII identifiers such as powerW; spaces/dots/hyphens belong in display labels. item.key is available only inside map fields. Each field is a normal scalar expression. No nested maps/callbacks; 1..16 fields, max500 generated rows across all maps; normal expression and calculation budgets apply. Derived output max128KiB. DSHUI.formatNumber(value, decimals=2) formats finite numbers with 0..6 decimal places and comma grouping, exactly matching native metrics and fallback; use it for shared readouts instead of mixing toFixed rounding. Use this for live charts/tables, never model calls on slider changes.
18
- Registered component fields (some optional; required values and types follow the rules above/below): ${componentGuide()}. Controls bind "state.key"; slider numeric, toggle boolean, select requires state options. Metric decimals 0..6. Table columns: [{key,label,unit?}]. Chart kind bar/line, finite numeric values, rows as a data/derived binding; line xScale linear positions numeric labelKey samples by their actual coordinate, category uses equal spacing. Use ascending samples for a curve.
19
- actions: key -> {type:"agent.followup",intent:string,stateKeys:string[]}. Button submits current selected parameters as a new conversation request; it does not perform a host action itself. All host actions still use ordinary DSH permissions.
20
- Presentation: heading/text/card/metric accept optional tone (${UI_TONES.join(', ')}); heading/text/card/metric/button accept optional icon (${UI_ICONS.join(', ')}). Fixed tokens only, no SVG/HTML/CSS/color strings. Defaults already include icons and themed spacing. Use accent for primary information, warning for assumptions/caveats, success/danger only for explicitly established facts. Tones are static, not conditional; use derived string text to report changing verdicts. Use row for 2-3 related metrics, card for a meaningful section, avoid repeated headings and deep decorative nesting. Do not use color as the only explanation.
21
- Always include a useful fallback, units, assumptions and data provenance in visible text. No invented measurements. Use local derived values rather than requesting inference on slider changes.
22
- Limits: 128KiB document, 160 nodes, 12 node nesting levels, 64 state/derived/data/action keys, 128 expression nodes, 4096 calculation steps, 8192 characters/string. Charts include an accessible data table. Unknown fields/components are rejected.`;
23
- export const calculatorExample = {
24
- version: 1, title: '理论权重体积计算器',
25
- fallback: '27B 参数、4 bit 时纯权重理论体积约 12.57 GiB。这是理论估算,不是实测文件大小或运行显存;不含量化元数据、混合精度层、KV cache 和运行缓冲区。',
26
- state: { parametersB: { type: 'number', default: 27, min: 1, max: 200, step: 1 }, bits: { type: 'number', default: 4, min: 2, max: 16, step: 1 } },
27
- data: {},
28
- derived: { weightGiB: { op: 'div', args: [{ op: 'mul', args: [{ ref: 'state.parametersB' }, { const: 1000000000 }, { ref: 'state.bits' }] }, { const: 8589934592 }] } },
29
- nodes: [
30
- { id: 'parameters', type: 'input', label: '参数量 B(十亿)', bind: 'state.parametersB' },
31
- { id: 'bits', type: 'slider', label: '假设有效位宽 bit', bind: 'state.bits' },
32
- { id: 'weight', type: 'metric', label: '纯权重理论体积', value: { ref: 'derived.weightGiB' }, unit: 'GiB', decimals: 2, icon: 'database', tone: 'accent' },
33
- { id: 'note', type: 'text', text: '理论估算,不是实际 GGUF 大小或推理显存。', tone: 'warning' },
34
- { id: 'followup', type: 'button', text: '按当前参数继续分析', action: 'analyze' }
35
- ],
36
- actions: { analyze: { type: 'agent.followup', intent: '分析当前参数下的量化选择,区分理论权重体积与真实运行开销;缺少信息时说明不确定性。', stateKeys: ['parametersB', 'bits'] } }
37
- };
38
- export const comparisonExample = {
39
- version: 1, title: '示例数据比较', fallback: '仅用于演示:A 为 12,B 为 18,C 为 15;不是实测性能。',
40
- data: { results: [{ name: 'A', value: 12 }, { name: 'B', value: 18 }, { name: 'C', value: 15 }] },
41
- nodes: [
42
- { id: 'note', type: 'text', text: '以下为演示数据,不是实测性能。', tone: 'warning' },
43
- { id: 'chart', type: 'chart', kind: 'bar', rows: { ref: 'data.results' }, labelKey: 'name', valueKey: 'value', label: '示例分数', unit: '分' },
44
- { id: 'table', type: 'table', rows: { ref: 'data.results' }, columns: [{ key: 'name', label: '方案' }, { key: 'value', label: '示例分数', unit: '分' }] }
45
- ]
46
- };
47
- export const budgetExample = {
48
- ...calculatorExample, title: '理论权重预算比较',
49
- fallback: '这是纯权重理论预算比较,不包含实际推理运行开销。默认 27B、4 bit、20 GiB 理论预算,权重约 12.57 GiB。',
50
- state: { ...calculatorExample.state, budgetGiB: { type: 'number', default: 20, min: 1, max: 96, step: 1 } },
51
- derived: { ...calculatorExample.derived,
52
- verdict: { op: 'if', args: [{ op: 'gt', args: [{ ref: 'derived.weightGiB' }, { ref: 'state.budgetGiB' }] }, { const: '超过纯权重理论预算;请降低位宽或提高预算。' }, { const: '在纯权重理论预算内;仍需另计运行开销。' }] }
53
- },
54
- nodes: [
55
- { id: 'assumptions', type: 'text', tone: 'warning', text: '以下均为理论估算,不能直接据此判断模型能否在显卡上运行。' },
56
- { id: 'settings', type: 'card', title: '调整参数', icon: 'sliders', children: [calculatorExample.nodes[0], calculatorExample.nodes[1], { id: 'budget', type: 'slider', label: '纯权重理论预算 GiB', bind: 'state.budgetGiB' }] },
57
- { id: 'results', type: 'row', children: [calculatorExample.nodes[2], { id: 'budgetMetric', type: 'metric', label: '理论预算', icon: 'target', tone: 'neutral', value: { ref: 'state.budgetGiB' }, unit: 'GiB', decimals: 0 }] },
58
- { id: 'verdictText', type: 'text', icon: 'info', text: { ref: 'derived.verdict' } }
59
- ], actions: {}
60
- };
61
- export const curveExample = {
62
- version: 1, title: '上下文与理论内存曲线',
63
- fallback: '理论估算,只计算纯权重与 KV cache,不包含运行缓冲、量化元数据或多卡分配。默认 27B、4.25 bpw、32K 上下文、48 KiB/token KV,权重约 13.36 GiB,权重加 KV 约 14.86 GiB。',
64
- state: {
65
- parametersB: { type: 'number', default: 27, min: 1, max: 200, step: 1 },
66
- bits: { type: 'number', default: 4.25, min: 2, max: 8, step: 0.25 },
67
- contextK: { type: 'number', default: 32, min: 8, max: 128, step: 8 },
68
- kvKib: { type: 'number', default: 48, min: 8, max: 192, step: 8 }
69
- },
70
- data: { samples: [{ fraction: 0.25 }, { fraction: 0.5 }, { fraction: 0.75 }, { fraction: 1 }] },
71
- derived: {
72
- weightGiB: calculatorExample.derived.weightGiB,
73
- totalGiB: { op: 'add', args: [{ ref: 'derived.weightGiB' }, { op: 'div', args: [{ op: 'mul', args: [{ ref: 'state.contextK' }, { ref: 'state.kvKib' }] }, { const: 1024 }] }] },
74
- curve: { map: { source: 'data.samples', fields: {
75
- contextK: { op: 'mul', args: [{ ref: 'state.contextK' }, { ref: 'item.fraction' }] },
76
- totalGiB: { op: 'add', args: [{ ref: 'derived.weightGiB' }, { op: 'div', args: [{ op: 'mul', args: [{ ref: 'state.contextK' }, { ref: 'item.fraction' }, { ref: 'state.kvKib' }] }, { const: 1024 }] }] }
77
- } } }
78
- },
79
- nodes: [
80
- { id: 'note', type: 'text', tone: 'warning', text: '理论值,不是实测推理显存。仅计纯权重 + KV cache,不含运行缓冲、量化元数据或多卡分配。' },
81
- { id: 'controls', type: 'card', title: '参数调整', icon: 'sliders', children: [
82
- calculatorExample.nodes[0], calculatorExample.nodes[1],
83
- { id: 'context', type: 'slider', label: '最大上下文 K tokens', bind: 'state.contextK' },
84
- { id: 'kv', type: 'slider', label: '每 token KV 开销 KiB', bind: 'state.kvKib' }
85
- ] },
86
- { id: 'metric', type: 'metric', label: '最大上下文下的理论权重 + KV', icon: 'database', value: { ref: 'derived.totalGiB' }, unit: 'GiB', decimals: 2 },
87
- { id: 'curveChart', type: 'chart', kind: 'line', xScale: 'linear', label: '上下文 K tokens → 权重 + KV', unit: 'GiB', rows: { ref: 'derived.curve' }, labelKey: 'contextK', valueKey: 'totalGiB' },
88
- { id: 'curveTable', type: 'table', rows: { ref: 'derived.curve' }, columns: [{ key: 'contextK', label: '上下文', unit: 'K tokens' }, { key: 'totalGiB', label: '理论内存', unit: 'GiB' }] },
89
- { id: 'followup', type: 'button', text: '按当前曲线继续分析', action: 'analyze' }
90
- ],
91
- actions: { analyze: { type: 'agent.followup', intent: '依据当前参数与 derived.curve 理论曲线分析上下文和内存取舍,说明尚未计入的实际运行开销。', stateKeys: ['parametersB', 'bits', 'contextK', 'kvKib'] } }
92
- };
93
-
94
-
1
+ import { UI_ICONS, UI_TONES } from './presentation.js';
2
+ import { componentGuide } from './components.js';
3
+ import { AUTHORING_GUIDE } from './authoring-guide.js';
4
+ export { sceneExamples } from './scenes.js';
5
+ export { bicycleExample, oscillatorExample } from './canvas-example.js';
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.
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"}.
8
+ Document: {version:1,title:string,fallback:string,presentation?:{layout,accent,density,preset},state?:{},data?:{},derived?:{},nodes:[],actions?:{}}.
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.
10
+ Additional nodes: badge {id,type,text,icon?,tone?}; progress {id,type,label,value,max,unit?,icon?,tone?} where values may be bindings and 0 <= value <= positive max; steps {id,type,items:[{title,description?,status?:"pending"|"active"|"done"|"error"}]} (direct array of 1..12 items, not {item:[...]}; omit status for static guides); details {id,type,title,children,open?:boolean,icon?,tone?}. Progress and steps are declared snapshots, not a live host task subscription. Only claim real progress/completion from actual tool evidence; mark demo data visibly. Details keeps the content available in text fallback.
11
+ Display lifecycle: submit native render_text/render_ui/render_canvas directly. Several independent UI documents may be created in one answer. Complete validated native blocks appear progressively as read-only previews. Keep previews visible through later reasoning and text; durable turn/end activates controls and scripts. Incomplete/failed tools never become interactive. Use small meaningful documents; no separate ui.begin/append calls.
12
+ All IDs and dictionary keys: Unicode letters (including Chinese) or underscore, followed by letters/marks/digits/underscore, max64; unique node IDs. Standard components reject HTML, JS, arbitrary styles and callbacks. link/button href explicitly supports http/https URLs; path uses the host file opener on user click. The sandbox component below is the explicit exception; unlisted fields are always rejected.
13
+ Freeform canvas: {id,type:"sandbox",title,html,css?:string,script?:string,height?:160..1000,fallback,stateKeys?:string[],dataKeys?:string[],derivedKeys?:string[],libraries?:["mechanism"]}. For internal assemblies and mechanical relations, ui_catalog topic=mechanism provides an opt-in local drawing/kinematics helper and three complete examples (assembly/gears/crank). Use it for custom SVG diagrams, animation, simulations and bespoke interfaces that fixed components cannot express. HTML is a body fragment. Put CSS in css and ALL JavaScript in script (not inline handlers, script tags or remote imports). Inline SVG is supported. Each source field allows up to 49152 characters; the total document remains 128KiB. The iframe has no same-origin privilege, remote resources/network APIs are blocked by CSP, and no host tools are exposed. Never put credentials or private unrelated data in a canvas. It is browser origin isolation, not a CPU/memory hard sandbox.
14
+ Canvas API: window.DSHUI.snapshot -> {state,data,derived,disabled,theme,visible}; DSHUI.animate(fn) returns cleanup and calls fn({time,delta,reducedMotion,visible}), time/delta in seconds, delta capped at 0.05. It pauses offscreen/background and supplies a static delta=0 callback for reduced motion; integrate delta for pause/resume without jumps. DSHUI.subscribe(snapshot=>{...}) immediately supplies current selected values and returns unsubscribe; DSHUI.setState({key:value,...}) requests an atomic Host-validated update of declared stateKeys. Use bare dictionary keys in stateKeys/dataKeys/derivedKeys. Only declared keys are exposed; state types/ranges/options remain enforced. Subscribe to drive DOM/SVG from host state; do not send the same state back from subscribe (feedback loop). Native controls and canvas share the saved state. Local animation may use requestAnimationFrame without saving every frame. No Agent/tool action API in this release. theme exposes bg,soft,ink,muted,line,accent,scheme; CSS variables --canvas-bg/soft/ink/muted/line/accent update automatically. Honor reduced motion, provide accessible labels and responsive layout. Canvas scripts activate only after successful tool commit and state restoration; previews show inert completed HTML/CSS. Use a complete useful fallback per canvas. Code or reset changes restart the frame, state changes do not. All transient internal animation state is reset on reload; declare important parameters in document.state.
15
+ state: key -> {type:"number",default,min,max,step} or {type:"string",default,options?:scalar[]} or {type:"boolean",default} or {type:"json",default:array/object} (canvas structured state, max16KiB per value). select requires scalar options. Numeric defaults follow step from min.
16
+ derived: key -> expression: {const:scalar}, {ref:"state.key"|"data.key"|"derived.key"}, or {op,args:expression[]}. Allowed ops add,mul,min,max (1..8 args); sub,div,eq,neq,lt,lte,gt,gte (2); round,ceil,floor,abs (1); if (3, boolean condition). No cycles. Arithmetic requires finite numbers: add does NOT concatenate strings. For formatted labels use a metric's label/unit/decimals, or format text inside a sandbox; omit unused derived fields. if can return strings: {op:"if",args:[{op:"gt",args:[{ref:"derived.weightGiB"},{const:20}]},{const:"超过理论预算"},{const:"在理论预算内"}]} -> bind a text node to this derived result. No separate conditional-rendering field is needed.
17
+ Text/metric values are literal scalars or {ref:...}. Table/chart rows accept inline arrays of objects, {ref:"data.rows"}, {ref:"derived.rows"}, or the shorthand string "data.rows"/"derived.rows". Table label is an optional caption. Static table example: data:{rows:[{name:"A",note:"示例"}]}, node:{id:"t",type:"table",rows:{ref:"data.rows"},columns:[{key:"name",label:"方案"},{key:"note",label:"说明"}]}. For a short live breakdown prefer inline formula cells: rows:[{label:"餐饮",value:{ref:"derived.mealTotal"}},{label:"住宿",value:{expr:"ceil(state.people / 2) * state.room"}}], labelKey:"label",valueKey:"value". These cells recalculate locally; data remains literal and never executes formulas. Dynamic rows for many samples: a derived root may be {map:{source:"data.samples",fields:{contextK:{op:"mul",args:[{ref:"state.contextK"},{ref:"item.fraction"}]},value:{ref:"derived.total"}}}}. source must be the string "data.samples", not an inline array; declare those object rows in document.data.samples. Internal field keys support Chinese identifiers such as 功率 and ASCII identifiers such as powerW; spaces/dots/hyphens belong in display labels. item.key is available only inside map fields. Each field is a normal scalar expression. No nested maps/callbacks; 1..16 fields, max500 generated rows across all maps; normal expression and calculation budgets apply. Derived output max128KiB. DSHUI.formatNumber(value, decimals=2) formats finite numbers with 0..6 decimal places and comma grouping, exactly matching native metrics and fallback; use it for shared readouts instead of mixing toFixed rounding. Use this for live charts/tables, never model calls on slider changes.
18
+ Registered component fields (some optional; required values and types follow the rules above/below): ${componentGuide()}. Controls bind "state.key"; slider numeric, toggle boolean, select requires state options. Metric decimals 0..6. Table columns: [{key,label,unit?}]. Chart kind bar/line, finite numeric values, rows as a data/derived binding; line xScale linear positions numeric labelKey samples by their actual coordinate, category uses equal spacing. Use ascending samples for a curve.
19
+ actions: key -> {type:"agent.followup",intent:string,stateKeys:string[]}. Button submits current selected parameters as a new conversation request; it does not perform a host action itself. All host actions still use ordinary DSH permissions.
20
+ Presentation: heading/text/card/metric accept optional tone (${UI_TONES.join(', ')}); heading/text/card/metric/button accept optional icon (${UI_ICONS.join(', ')}). Fixed tokens only, no SVG/HTML/CSS/color strings. Defaults already include icons and themed spacing. Use accent for primary information, warning for assumptions/caveats, success/danger only for explicitly established facts. Tones are static, not conditional; use derived string text to report changing verdicts. Use row for 2-3 related metrics, card for a meaningful section, avoid repeated headings and deep decorative nesting. Do not use color as the only explanation.
21
+ Always include a useful fallback, units, assumptions and data provenance in visible text. No invented measurements. Use local derived values rather than requesting inference on slider changes.
22
+ Limits: 128KiB document, 160 nodes, 12 node nesting levels, 64 state/derived/data/action keys, 128 expression nodes, 4096 calculation steps, 8192 characters/string. Charts include an accessible data table. Unknown fields/components are rejected.`;
23
+ export const calculatorExample = {
24
+ version: 1, title: '理论权重体积计算器',
25
+ fallback: '27B 参数、4 bit 时纯权重理论体积约 12.57 GiB。这是理论估算,不是实测文件大小或运行显存;不含量化元数据、混合精度层、KV cache 和运行缓冲区。',
26
+ state: { parametersB: { type: 'number', default: 27, min: 1, max: 200, step: 1 }, bits: { type: 'number', default: 4, min: 2, max: 16, step: 1 } },
27
+ data: {},
28
+ derived: { weightGiB: { op: 'div', args: [{ op: 'mul', args: [{ ref: 'state.parametersB' }, { const: 1000000000 }, { ref: 'state.bits' }] }, { const: 8589934592 }] } },
29
+ nodes: [
30
+ { id: 'parameters', type: 'input', label: '参数量 B(十亿)', bind: 'state.parametersB' },
31
+ { id: 'bits', type: 'slider', label: '假设有效位宽 bit', bind: 'state.bits' },
32
+ { id: 'weight', type: 'metric', label: '纯权重理论体积', value: { ref: 'derived.weightGiB' }, unit: 'GiB', decimals: 2, icon: 'database', tone: 'accent' },
33
+ { id: 'note', type: 'text', text: '理论估算,不是实际 GGUF 大小或推理显存。', tone: 'warning' },
34
+ { id: 'followup', type: 'button', text: '按当前参数继续分析', action: 'analyze' }
35
+ ],
36
+ actions: { analyze: { type: 'agent.followup', intent: '分析当前参数下的量化选择,区分理论权重体积与真实运行开销;缺少信息时说明不确定性。', stateKeys: ['parametersB', 'bits'] } }
37
+ };
38
+ export const comparisonExample = {
39
+ version: 1, title: '示例数据比较', fallback: '仅用于演示:A 为 12,B 为 18,C 为 15;不是实测性能。',
40
+ data: { results: [{ name: 'A', value: 12 }, { name: 'B', value: 18 }, { name: 'C', value: 15 }] },
41
+ nodes: [
42
+ { id: 'note', type: 'text', text: '以下为演示数据,不是实测性能。', tone: 'warning' },
43
+ { id: 'chart', type: 'chart', kind: 'bar', rows: { ref: 'data.results' }, labelKey: 'name', valueKey: 'value', label: '示例分数', unit: '分' },
44
+ { id: 'table', type: 'table', rows: { ref: 'data.results' }, columns: [{ key: 'name', label: '方案' }, { key: 'value', label: '示例分数', unit: '分' }] }
45
+ ]
46
+ };
47
+ export const budgetExample = {
48
+ ...calculatorExample, title: '理论权重预算比较',
49
+ fallback: '这是纯权重理论预算比较,不包含实际推理运行开销。默认 27B、4 bit、20 GiB 理论预算,权重约 12.57 GiB。',
50
+ state: { ...calculatorExample.state, budgetGiB: { type: 'number', default: 20, min: 1, max: 96, step: 1 } },
51
+ derived: { ...calculatorExample.derived,
52
+ verdict: { op: 'if', args: [{ op: 'gt', args: [{ ref: 'derived.weightGiB' }, { ref: 'state.budgetGiB' }] }, { const: '超过纯权重理论预算;请降低位宽或提高预算。' }, { const: '在纯权重理论预算内;仍需另计运行开销。' }] }
53
+ },
54
+ nodes: [
55
+ { id: 'assumptions', type: 'text', tone: 'warning', text: '以下均为理论估算,不能直接据此判断模型能否在显卡上运行。' },
56
+ { id: 'settings', type: 'card', title: '调整参数', icon: 'sliders', children: [calculatorExample.nodes[0], calculatorExample.nodes[1], { id: 'budget', type: 'slider', label: '纯权重理论预算 GiB', bind: 'state.budgetGiB' }] },
57
+ { id: 'results', type: 'row', children: [calculatorExample.nodes[2], { id: 'budgetMetric', type: 'metric', label: '理论预算', icon: 'target', tone: 'neutral', value: { ref: 'state.budgetGiB' }, unit: 'GiB', decimals: 0 }] },
58
+ { id: 'verdictText', type: 'text', icon: 'info', text: { ref: 'derived.verdict' } }
59
+ ], actions: {}
60
+ };
61
+ export const curveExample = {
62
+ version: 1, title: '上下文与理论内存曲线',
63
+ fallback: '理论估算,只计算纯权重与 KV cache,不包含运行缓冲、量化元数据或多卡分配。默认 27B、4.25 bpw、32K 上下文、48 KiB/token KV,权重约 13.36 GiB,权重加 KV 约 14.86 GiB。',
64
+ state: {
65
+ parametersB: { type: 'number', default: 27, min: 1, max: 200, step: 1 },
66
+ bits: { type: 'number', default: 4.25, min: 2, max: 8, step: 0.25 },
67
+ contextK: { type: 'number', default: 32, min: 8, max: 128, step: 8 },
68
+ kvKib: { type: 'number', default: 48, min: 8, max: 192, step: 8 }
69
+ },
70
+ data: { samples: [{ fraction: 0.25 }, { fraction: 0.5 }, { fraction: 0.75 }, { fraction: 1 }] },
71
+ derived: {
72
+ weightGiB: calculatorExample.derived.weightGiB,
73
+ totalGiB: { op: 'add', args: [{ ref: 'derived.weightGiB' }, { op: 'div', args: [{ op: 'mul', args: [{ ref: 'state.contextK' }, { ref: 'state.kvKib' }] }, { const: 1024 }] }] },
74
+ curve: { map: { source: 'data.samples', fields: {
75
+ contextK: { op: 'mul', args: [{ ref: 'state.contextK' }, { ref: 'item.fraction' }] },
76
+ totalGiB: { op: 'add', args: [{ ref: 'derived.weightGiB' }, { op: 'div', args: [{ op: 'mul', args: [{ ref: 'state.contextK' }, { ref: 'item.fraction' }, { ref: 'state.kvKib' }] }, { const: 1024 }] }] }
77
+ } } }
78
+ },
79
+ nodes: [
80
+ { id: 'note', type: 'text', tone: 'warning', text: '理论值,不是实测推理显存。仅计纯权重 + KV cache,不含运行缓冲、量化元数据或多卡分配。' },
81
+ { id: 'controls', type: 'card', title: '参数调整', icon: 'sliders', children: [
82
+ calculatorExample.nodes[0], calculatorExample.nodes[1],
83
+ { id: 'context', type: 'slider', label: '最大上下文 K tokens', bind: 'state.contextK' },
84
+ { id: 'kv', type: 'slider', label: '每 token KV 开销 KiB', bind: 'state.kvKib' }
85
+ ] },
86
+ { id: 'metric', type: 'metric', label: '最大上下文下的理论权重 + KV', icon: 'database', value: { ref: 'derived.totalGiB' }, unit: 'GiB', decimals: 2 },
87
+ { id: 'curveChart', type: 'chart', kind: 'line', xScale: 'linear', label: '上下文 K tokens → 权重 + KV', unit: 'GiB', rows: { ref: 'derived.curve' }, labelKey: 'contextK', valueKey: 'totalGiB' },
88
+ { id: 'curveTable', type: 'table', rows: { ref: 'derived.curve' }, columns: [{ key: 'contextK', label: '上下文', unit: 'K tokens' }, { key: 'totalGiB', label: '理论内存', unit: 'GiB' }] },
89
+ { id: 'followup', type: 'button', text: '按当前曲线继续分析', action: 'analyze' }
90
+ ],
91
+ actions: { analyze: { type: 'agent.followup', intent: '依据当前参数与 derived.curve 理论曲线分析上下文和内存取舍,说明尚未计入的实际运行开销。', stateKeys: ['parametersB', 'bits', 'contextK', 'kvKib'] } }
92
+ };
93
+
94
+
@@ -10,4 +10,4 @@ export const MECHANISM_CSS=`
10
10
  @media(max-width:500px){.mui-mechanism{padding:14px;border-radius:20px}.mui-mechanism h2{font-size:20px}.mui-mechanism-controls{padding:10px}.mui-mechanism-nav{gap:5px}.mui-mechanism button{padding:6px 10px;font-size:14px}}
11
11
  @media(prefers-reduced-motion:reduce){.mui-mechanism *{animation:none!important;transition:none!important}}
12
12
  @media(forced-colors:active){.mui-mechanism-svg path{fill:Canvas;stroke:CanvasText}.mui-mechanism button[aria-pressed=true]{background:Highlight;color:HighlightText}}
13
- `;
13
+ `;
@@ -58,5 +58,5 @@ export function partGeometry(p){
58
58
  export function sceneBounds(spec){if(spec.bounds){const[a,b]=spec.bounds;return Array.from({length:8},(_,i)=>[i&1?b[0]:a[0],i&2?b[1]:a[1],i&4?b[2]:a[2]])}return spec.parts.flatMap(p=>{const geometry=partGeometry(p);return geometry.points.flatMap(v=>[v,add(v,p.explode??[0,0,0])])})}
59
59
  export function gearPair({driverTeeth=16,drivenTeeth=32,rpm=30,angle=0,module=2}={}){for(const[k,v]of Object.entries({driverTeeth,drivenTeeth}))if(!Number.isInteger(v)||v<6||v>64)fail(k+' must be integer 6..64');num(rpm,'rpm',-600,600);num(angle,'angle',-1e9,1e9);num(module,'module',.1,10);return {driverRadius:driverTeeth*module/2,drivenRadius:drivenTeeth*module/2,centerDistance:(driverTeeth+drivenTeeth)*module/2,driverAngle:angle,drivenAngle:-angle*driverTeeth/drivenTeeth+Math.PI-Math.PI/drivenTeeth,driverRpm:rpm,drivenRpm:-rpm*driverTeeth/drivenTeeth,ratio:drivenTeeth/driverTeeth}}
60
60
  export function sliderCrank({angle=0,radius=20,rod=70,z=20}={}){num(angle,'angle',-1e9,1e9);num(radius,'radius',.1,200);num(rod,'rod',.1,1000);num(z,'z');if(rod<=radius)fail('connecting rod length must exceed crank radius');const crank=[radius*Math.cos(angle),radius*Math.sin(angle),z],x=crank[0]+Math.sqrt(rod*rod-crank[1]*crank[1]);return {crank,piston:[x,0,z],position:x,stroke:radius*2,angle}}
61
- export function follow(value,target,delta,tau=.16){num(value,'value',-1e9,1e9);num(target,'target',-1e9,1e9);num(delta,'delta',0,.1);num(tau,'tau',.01,10);return value+(target-value)*(1-Math.exp(-delta/tau))}
62
-
61
+ export function follow(value,target,delta,tau=.16){num(value,'value',-1e9,1e9);num(target,'target',-1e9,1e9);num(delta,'delta',0,.1);num(tau,'tau',.01,10);return value+(target-value)*(1-Math.exp(-delta/tau))}
62
+
@@ -17,4 +17,4 @@ export function crankScene({radius=20,rod=70,angle=0}={}){const k=sliderCrank({r
17
17
  box('base','安装底座',[35,0,-6],[175,80,10],'frame',{round:6,material:'metal'}),...bolts(35,0,1,'frame',3,30),round('bearing','曲轴轴承',[0,0,7],11,15,'frame',{inner:4,material:'dark'}),round('axle','曲轴',[0,0,15],3.8,26,'crank',{material:'metal',explode:[0,-18,18]}),round('flywheel','曲柄盘',[0,0,20],radius+6,6,'crank',{inner:4,material:'accent',explode:[0,-18,18]}),
18
18
  box('railA','导轨',[74,-16,13],[85,5,6],'frame',{round:1,material:'dark'}),box('railB','导轨',[74,16,13],[85,5,6],'frame',{round:1,material:'dark'}),
19
19
  round('crankPin','曲柄销',k.crank,3,12,'crank',{material:'warm',explode:[0,-18,18]}),{id:'link',type:'rod',label:'连杆',from:k.crank,to:k.piston,radius:2.4,group:'rod',material:'metal',explode:[0,0,45]},
20
- box('piston','滑块',[k.position,0,22],[18,24,16],'piston',{round:3,material:'cool',explode:[20,0,18]}),round('pistonPin','滑块销',k.piston,3,10,'piston',{material:'warm',explode:[20,0,18]})]}}
20
+ box('piston','滑块',[k.position,0,22],[18,24,16],'piston',{round:3,material:'cool',explode:[20,0,18]}),round('pistonPin','滑块销',k.piston,3,10,'piston',{material:'warm',explode:[20,0,18]})]}}