a2ui-render-in-dsh 0.1.1 → 0.2.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/lib/index.js CHANGED
@@ -5878,66 +5878,121 @@ var KNOWN_COMPONENTS = /* @__PURE__ */ new Set([
5878
5878
  "Card",
5879
5879
  "List",
5880
5880
  "Divider",
5881
+ "Tabs",
5882
+ "When",
5883
+ "Wizard",
5881
5884
  "Text",
5885
+ "Markdown",
5882
5886
  "Image",
5887
+ "Icon",
5883
5888
  "Tag",
5884
5889
  "Math",
5885
5890
  "Mermaid",
5886
5891
  "Chart",
5892
+ "Map",
5887
5893
  "Video",
5894
+ "Audio",
5888
5895
  "Anim",
5896
+ "Table",
5897
+ "Stat",
5898
+ "Steps",
5899
+ "Progress",
5900
+ "Timeline",
5901
+ "CodeBlock",
5902
+ "Flashcard",
5903
+ "Countdown",
5904
+ "ImageCompare",
5889
5905
  "Button",
5890
5906
  "MultipleChoice",
5891
5907
  "Select",
5908
+ "Rate",
5909
+ "Slider",
5892
5910
  "CheckBox",
5893
- "TextField"
5911
+ "TextField",
5912
+ "Calc",
5913
+ "Upload",
5914
+ "Suggestions",
5915
+ "Calendar",
5916
+ "RankList",
5917
+ "Signature",
5918
+ "EditableTable"
5894
5919
  ]);
5895
5920
  var CATALOG_DOC = `# a2ui_render authoring guide
5896
5921
 
5897
- Adaptive use: render a card ONLY when it clearly beats prose \u2014 (a) interaction: quiz/multiple-choice, short forms, option or confirm pickers, product cards; (b) visualization: charts, math function plots, flowcharts, mind maps, diagrams, LaTeX formulas. Otherwise answer in plain text. One call = one card; display-only cards (no Button) are fine. To show several items or a chart dashboard, make ONE call with a Grid \u2014 never several calls.
5922
+ Forms: each question = one field; options -> MultipleChoice/Select; free answers -> TextField (multiline for long); offering suggestions? ALSO add a TextField for a custom answer; several questions = ONE card, ONE submit Button labeled with the action ("\u63D0\u4EA4", "\u786E\u8BA4\u5EFA\u6863"). Multiple items or several charts = ONE call with a Grid. Display-only cards (no Button) are fine. Pair every card with 1-3 sentences of prose (takeaway/caveats) \u2014 never restate its data in text.
5898
5923
 
5899
- ## Components ("components" is an A2UI v0.9 adjacency list; MUST include a node with id "root"; only these names render)
5924
+ ## Components (adjacency list; MUST include id "root"; only these names render)
5900
5925
  Layout:
5901
5926
  - Column / Row {children, gap?}
5902
- - Grid {children, columns?, gap?, minWidth?} \u2014 side-by-side cards & 2x2 dashboards; columns 2\u20134, or omit for auto-fit at minWidth px (default 180)
5903
- - Card {children, title?}
5904
- - List {children, direction?: "vertical"|"horizontal"}
5905
- - Divider {}
5906
- Content (Chart/Mermaid/Image have built-in fullscreen zoom \u2014 do not oversize them):
5927
+ - Grid {children, columns?, gap?, minWidth?} \u2014 side-by-side cards, 2x2 dashboards (columns 2-4, omit for auto-fit)
5928
+ - Card {children, title?} \xB7 List {children, direction?} \xB7 Divider {}
5929
+ - Tabs {tabs: ["\u540D"\u2026], children, bind?} \u2014 one child per tab; dataset switching / grouping
5930
+ - When {value: {"path": "/x"}, equals?, includes?, notEmpty?, children} \u2014 children render only when the bound value matches; use for follow-up fields (e.g. reason box when choice includes "\u5176\u4ED6")
5931
+ - Wizard {steps: ["\u6B65\u9AA4\u540D"\u2026], children, submitLabel?} \u2014 multi-step form: one child (Column of fields) per step, built-in prev/next/progress, last step submits ALL fields; use for 5+ questions
5932
+ Content (Chart/Mermaid/Image auto-get fullscreen zoom; don't oversize):
5907
5933
  - Text {text, variant?: "h1"|"h2"|"h3"|"body"|"caption"|"strong"}
5908
- - Image {url, alt?, width?, height?} \u2014 GIF ok
5909
- - Tag {text, color?: "blue"|"green"|"red"|"orange"|"gray"}
5910
- - Math {tex, block?} \u2014 KaTeX LaTeX, e.g. {"tex": "\\\\int_0^1 x^2\\\\,dx = \\\\frac{1}{3}", "block": true}. ALWAYS use Math for mathematical notation \u2014 matrices and vectors included: NEVER write raw nested arrays like [[2,1],[0,3]] inside Text; render them as a formula instead, e.g. {"tex": "\\\\begin{pmatrix}2&1\\\\\\\\0&3\\\\end{pmatrix}\\\\times\\\\begin{pmatrix}1&4\\\\\\\\5&2\\\\end{pmatrix}=\\\\begin{pmatrix}7&10\\\\\\\\15&6\\\\end{pmatrix}", "block": true} (row separator is \\\\\\\\). When animating matrix math with Anim, put the overall equation in a Math block above it.
5911
- - Mermaid {code, caption?} \u2014 flowchart ("graph TD; A[\u5F00\u59CB]-->B{\u5224\u65AD}"), mindmap ("mindmap\\n root((\u4E3B\u9898))\\n \u5206\u652F"), sequenceDiagram, gantt, pie, stateDiagram
5912
- - Chart {option, height?, functions?, xMin?, xMax?, samples?, yClip?} \u2014 ECharts. Data mode: standard ECharts option verbatim (line/bar/pie/scatter/radar/heatmap\u2026). Function mode: functions=[{"expr":"tan(x)","name"?:"y=tan(x)"}] with xMin/xMax (default -10..10) and yClip (default 10) \u2014 ~400 points sampled, asymptotes break automatically; NEVER hand-enumerate data points for a math curve. Expressions: + - * / ^ %, sin cos tan cot sec csc asin acos atan sinh cosh tanh sqrt cbrt abs exp ln log2 log10 floor ceil round sign min max pow atan2, constants pi/e, variable x.
5913
- - Video {url, poster?, loop?, muted?, autoplay?} \u2014 mp4/webm
5914
- - Anim {frames, interval?, height?, autoplay?, labels?} \u2014 step-by-step ALGORITHM animation; auto-plays through ONCE, then the user replays/steps via the built-in controls (do not loop). The form is auto-detected from the frame shape. Simulate the algorithm yourself, one frame per step, each with a clear "note" caption. Two forms:
5915
- (1) Array/bars (sorting, searching, heaps; 8\u201312 small numbers): frames=[{"data":[5,3,8,1],"highlight":[0,1],"sorted":[3],"note":"\u6BD4\u8F83 5 \u548C 3"},\u2026] \u2014 data is the FULL array state, highlight = indices being operated on (orange), sorted = finalized (green).
5916
- (2) Grid/matrix (matrix ops, DP tables, 2D grids): frames=[{"grids":[{"title":"A","data":[[1,2],[3,4]],"highlight":[[0,0],[0,1]]},{"title":"B","data":[[5,7],[6,8]],"highlight":[[0,0],[1,0]]},{"title":"C=A\xD7B","data":[[17,null],[null,null]],"accent":[[0,0]]}],"note":"C[0][0]=1\xD75+2\xD76=17"},\u2026] \u2014 grids render side by side; highlight = cells being read (orange), accent = cells being written (green), null cells render empty. A single grid may use {"grid": [[...]], "highlight": [[r,c]], "accent": [[r,c]]} directly.
5934
+ - Markdown {text} \u2014 rich long-form (headings, bold/italic, links, lists, quotes, fenced code, $...$ math); use for any multi-paragraph text instead of stacked Texts
5935
+ - Image {url, alt?, width?, height?} \xB7 Tag {text, color?: "blue"|"green"|"red"|"orange"|"gray"}
5936
+ - Icon {name, size?, color?} \u2014 check x plus minus warning info star heart calendar clock location user search settings mail phone home file link download upload play music image cart tag gift trophy fire bolt sun moon cloud thumbs-up
5937
+ - Math {tex, block?} \u2014 KaTeX. ALL math notation goes here, matrices included: never raw arrays like [[2,1],[0,3]] in Text \u2014 write {"tex": "\\\\begin{pmatrix}2&1\\\\\\\\0&3\\\\end{pmatrix}", "block": true} (row sep \\\\\\\\). Equation above its Anim when animating.
5938
+ - Mermaid {code, caption?} \u2014 flowchart ("graph TD; A[\u5F00\u59CB]-->B{\u5224\u65AD}"), mindmap, sequenceDiagram, gantt, pie, stateDiagram
5939
+ - Chart {option, height?, functions?, params?, xMin?, xMax?, yClip?} \u2014 ECharts (viewers get a PNG-download button). Data mode: standard option verbatim. Function mode: functions=[{"expr":"tan(x)","name"?}], xMin/xMax (\xB110), yClip (10); sampled automatically, asymptotes break \u2014 NEVER hand-enumerate curve points. Ops: + - * / ^ %, sin cos tan cot sec csc asin acos atan sinh cosh tanh sqrt cbrt abs exp ln log2 log10 floor ceil round sign min max pow atan2, pi/e, x. params: {"mu":{"path":"/mu"}} injects bound values as constants \u2014 pair with Slider for live curves.
5940
+ - Table {columns, rows, caption?, sortable?, filter?, pageSize?} \u2014 for any 2+ item comparison; sort/filter/pagination/copy/CSV are automatic. Dict binding: rows={"source":{"k1":[[\u2026]],\u2026},"pick":{"path":"/k"}} + a Select/Tabs on that path switches datasets live.
5941
+ - Stat {label, value, unit?, trend?, hint?} \u2014 KPI tile ("+12%" green / "-3%" red); several in a Grid
5942
+ - Steps {items: [{title, description?, status?: "done"|"current"|"pending"}]} \u2014 linear procedures; Mermaid flowchart only when it BRANCHES
5943
+ - Progress {value, max?, label?} \xB7 Timeline {items: [{time?, title, description?}]} (past events; Steps = to-dos)
5944
+ - CodeBlock {code, language?, title?} \u2014 ALWAYS for code, including quiz/form stems: compose Text(\u9898\u5E72) + CodeBlock(\u4EE3\u7801) + inputs; NEVER inline code in a Text prop
5945
+ - Video {url, poster?\u2026} \xB7 Audio {url, title?} \xB7 Flashcard {front, back} \xB7 Countdown {to?: "2026-09-01 10:00", seconds?, label?}
5946
+ - Map {data: [{name: "\u5E7F\u4E1C", value: 100}\u2026], title?, unit?, height?} \u2014 China province choropleth (regional sales/distribution); province names in Chinese
5947
+ - ImageCompare {before, after} \u2014 drag-divider image comparison (before/after, A/B)
5948
+ - Anim {frames, interval?, height?, autoplay?} \u2014 algorithm animation; plays ONCE, user replays via controls. Form auto-detected from frame shape; simulate the algorithm yourself, one frame per step with a "note" caption, FULL state each frame:
5949
+ bars (sorting/searching, 8-12 numbers): {"data":[5,3,8,1],"highlight":[0,1],"sorted":[3],"note":"\u2026"} \u2014 highlight=orange, sorted=green
5950
+ grid (matrix/DP): {"grids":[{"title":"A","data":[[1,2],[3,4]],"highlight":[[0,0]]},{"title":"C","data":[[17,null]],"accent":[[0,0]]}],"note":"\u2026"} \u2014 highlight=read(orange), accent=write(green), null=empty; single grid: {"grid":[[\u2026]],\u2026}
5951
+ graph (BFS/DFS/trees): {"graph":{"nodes":[{"id":"A","label"?,"x"?,"y"?,"state"?:"active"|"seen"|"done"}],"edges":[["A","B","active"|"done"?]]},"note":"\u2026"} \u2014 no x/y (0-100) = auto circle; active=orange, seen=blue, done=green
5917
5952
  Interaction:
5918
5953
  - Button {label, variant?: "primary"|"default"|"danger", submit?, action: {event: {name, context?}}}
5919
- - MultipleChoice {options: [{label, value, description?, disabled?}], bind, maxAllowedSelections?, disabled?} \u2014 flat option list; stores an array of chosen values at bind; maxAllowedSelections 1 = single-select
5920
- - Select {options: [{label, value, description?, disabled?}], bind, label?, placeholder?, multiple?, maxAllowedSelections?, disabled?} \u2014 dropdown select, better for long option lists or compact forms; single-select stores the chosen value at bind, multiple: true stores an array (maxAllowedSelections caps it)
5921
- - CheckBox {label, bind, disabled?} \u2014 boolean at bind
5922
- - TextField {label?, placeholder?, multiline?, bind, disabled?} \u2014 string at bind
5923
- Input states: preselect by seeding dataModel at the bind path (e.g. dataModel={"city":"beijing"} or {"skills":["go","sql"]}); disable a whole control with disabled: true, or one option via disabled on that option.
5954
+ - MultipleChoice {options: [{label, value, description?, disabled?}], bind, maxAllowedSelections?, disabled?} \u2014 array at bind; max 1 = single-select
5955
+ - Select {options, bind, label?, placeholder?, multiple?, maxAllowedSelections?, disabled?} \u2014 dropdown; single stores value, multiple stores array
5956
+ - Rate {bind, label?, max?, disabled?} \u2014 stars 1..max (5)
5957
+ - Slider {bind, label?, min?, max?, step?, unit?, disabled?} \u2014 numbers in a range; parameter exploration with Chart params
5958
+ - Calc {expr, inputs: {name: {"path": "/x"}\u2026}, out, digits?} \u2014 invisible derived number written at out; chainable. digits rounds display only \u2014 omit on intermediates to keep precision
5959
+ - CheckBox {label, bind, disabled?} \xB7 TextField {label?, placeholder?, multiline?, kind?: "text"|"number"|"date"|"time", bind, disabled?}
5960
+ - Upload {bind?, label?, max?} \u2014 image picker; chosen images are SENT TO YOU with the submission (use whenever seeing an image helps)
5961
+ - Calendar {bind, label?, min?, max?, range?} \u2014 month-view date picker; range: true picks start+end (value ["\u8D77","\u6B62"])
5962
+ - RankList {items: ["\u9009\u9879"\u2026], bind, label?} \u2014 user reorders by priority; submits the ordered list
5963
+ - Signature {label?} \u2014 handwritten signature pad; the drawing is sent to you as an image
5964
+ - EditableTable {columns, rows, bind, label?} \u2014 user edits cells, the whole grid submits at bind
5965
+ - Suggestions {items: ["\u8FFD\u95EE1"\u2026]} \u2014 tappable follow-ups; a tap sends that question as the user's message. ADD 2-4 to display-only cards
5966
+ Preselect by seeding dataModel at bind paths; disable via disabled on a control or option. Any input takes required: true \u2014 submit is blocked (field highlighted) until filled; give required fields labels.
5967
+
5968
+ ## Reactivity
5969
+ Bindings are live: inputs write bind paths, every {"path"} display binding updates instantly.
5970
+ - Follow-ups: MultipleChoice bind:"a" + When includes:"\u5176\u4ED6" wrapping a TextField
5971
+ - Exploration: Slider bind:"mu" + Chart params:{"mu":{"path":"/mu"}}
5972
+ - Calculators: Sliders -> Calc {expr, inputs, out:"monthly"} -> Stat value:{"path":"/monthly"}
5973
+ - Dataset switch: Tabs panes, or Table dict binding
5974
+ Seed every bound path in dataModel.
5975
+
5976
+ ## Scenario map
5977
+ Input collection -> form (TextField/kind date|time|number, MultipleChoice 2-7 options, Select for long lists, CheckBox, Rate). Decisions -> Grid Cards + Table; lone confirm Button gets submit:true. Data -> Chart (trend/rank/share) + Stat tiles + Table; regional -> Map. Learning -> MultipleChoice quizzes (options may be formulas), Math, Chart functions (+Slider), Anim, mindmap, Flashcard; \u8BFB\u4EE3\u7801\u9898 = Text(\u9898\u5E72) + CodeBlock(\u4EE3\u7801) + TextField. Procedures -> Steps (Timeline for history, Progress for completion, gantt/Table for schedules). Entertainment -> Grid recommendation cards, Rate, polls via MultipleChoice.
5978
+
5979
+ ## Inline math
5980
+ Any text prop (Text, option labels/descriptions, Table cells, Steps, Flashcard, Anim notes) embeds formulas with $...$, e.g. option {"label": "$\\\\frac{x^2}{2}+C$"}. Math component = display equations; $...$ = math inside sentences/choices. In math quizzes, option labels with fractions/roots/integrals/exponents SHOULD be $...$ formulas (e.g. {"label": "$3x^2$"}), not Unicode approximations.
5924
5981
 
5925
5982
  ## Data binding
5926
- - "bind" is a dataModel key path WITHOUT a leading slash ("answer", "form/name").
5927
- - Display props may read live values with {"path": "/answer"} (WITH leading slash).
5928
- - "dataModel" seeds initial values. Avoid literal string props that start with "/".
5983
+ "bind" = write path WITHOUT leading slash ("answer", "form/name"). Display reads use {"path": "/answer"} (WITH slash). Avoid literal string props starting with "/".
5929
5984
 
5930
5985
  ## Submission
5931
- - A Button click sends a plain-language user message: button label + chosen values ("\u63D0\u4EA4\u7B54\u6848\uFF1AB\u3001C"; multi-line for forms). MultipleChoice values arrive as their option LABELS \u2014 map back to values yourself if they differ.
5932
- - action.event.context is optional (touched fields are included automatically); entries may be literals or {"path": "/x", "label": "\u5B57\u6BB5\u540D"}.
5933
- - Locking: under submitMode "once" (default), a click locks the card ONLY when the card has input components (MultipleChoice/CheckBox/TextField) \u2014 a form/quiz submits once and stays recorded. Buttons on cards WITHOUT inputs (product \u67E5\u770B\u8BE6\u60C5, menus) are query buttons and stay clickable. Override per Button with submit: true (always lock \u2014 e.g. a lone confirm button) or submit: false (never lock). submitMode "multi" disables locking entirely.
5934
- - After rendering an interactive card, end your turn with one short line telling the user to use the card. If the user types a plain reply instead, accept it as the answer.
5986
+ Button click sends a plain-language user message: button label + chosen values ("\u63D0\u4EA4\u7B54\u6848\uFF1AB\u3001C"; multi-line for forms); MultipleChoice arrives as option LABELS. action.event.context optional (touched fields auto-included); entries may be literals or {"path": "/x", "label": "\u540D"}. Locking (submitMode "once", default): cards WITH inputs lock after submit and stay recorded; input-less cards' buttons are query buttons, always clickable; per-Button submit: true|false overrides; "multi" never locks. A plain typed reply also counts as the answer. Uploads/signatures arrive attached to the submission. \u91CD\u65B0\u586B\u5199 unlocks a locked card; corrections arrive prefixed \uFF08\u4FEE\u6B63\uFF09.
5935
5987
 
5936
- ## Example (single-select quiz)
5937
- components=[{"id":"root","component":"Column","children":["q","opts","submit"]},{"id":"q","component":"Text","variant":"h3","text":"TCP \u4E09\u6B21\u63E1\u624B\u7684\u7B2C\u4E8C\u6B65\u662F\u4EC0\u4E48\uFF1F"},{"id":"opts","component":"MultipleChoice","bind":"answer","maxAllowedSelections":1,"options":[{"label":"SYN","value":"SYN"},{"label":"SYN-ACK","value":"SYN-ACK"}]},{"id":"submit","component":"Button","label":"\u63D0\u4EA4\u7B54\u6848","variant":"primary","action":{"event":{"name":"submit"}}}]
5988
+ ## Updating a rendered card
5989
+ a2ui_render returns a surfaceId. Call a2ui_update {surfaceId, components?, dataModel?} to change that card IN PLACE \u2014 replace/add components by id, overwrite dataModel keys. Use for long-task progress, corrections, appended results \u2014 never render a duplicate card. Cards stream progressively; don't shrink them.
5990
+
5991
+ ## Example (quiz)
5992
+ components=[{"id":"root","component":"Column","children":["q","opts","btn"]},{"id":"q","component":"Text","variant":"h3","text":"\u2026\u9898\u76EE\u2026"},{"id":"opts","component":"MultipleChoice","bind":"answer","maxAllowedSelections":1,"options":[{"label":"A\u2026","value":"A"},{"label":"B\u2026","value":"B"}]},{"id":"btn","component":"Button","label":"\u63D0\u4EA4\u7B54\u6848","variant":"primary","action":{"event":{"name":"submit"}}}]
5938
5993
  dataModel={"answer":[]}`;
5939
- var RENDER_DESCRIPTION = "Render a UI card (A2UI) inline in the chat: quiz/multiple-choice questions, short forms, option/confirm pickers, product cards, ECharts charts and math function plots, Mermaid flowcharts/mind maps, LaTeX formulas, images, video, step-by-step algorithm animations (sorting etc.). Use ONLY when a card clearly beats prose; otherwise answer in plain text. IMPORTANT: before your FIRST call in a conversation, call a2ui_catalog and follow its authoring guide \u2014 do not guess component names or props. After rendering an interactive card, end your turn; the user's submission arrives as a plain-language user message.";
5940
- var CATALOG_DESCRIPTION = "Returns the A2UI authoring guide for a2ui_render: the full component catalog with props, data-binding rules, submission format, and an example. Call it ONCE before your first a2ui_render in a conversation; the result stays in context \u2014 call again only if the guide is no longer visible.";
5994
+ var RENDER_DESCRIPTION = "Render a UI card (A2UI) inline in the chat. Judge EVERY reply from user intent (they never say \u5361\u7247/\u56FE\u8868): would a card serve any of UI's FOUR purposes better than prose? ACT \u64CD\u4F5C: user must answer/choose/fill/adjust/confirm -> form card. BROWSE \u6D4F\u89C8: user wants to see/scan data or items \u2014 statistics, rankings, distributions, trends, comparisons, multi-item recommendations -> chart/table/map/Grid of item cards; \u770B/\u67E5/\u5BF9\u6BD4/\u63A8\u8350\u51E0\u4E2A IS a card ask, and dated or approximate data never cancels the card \u2014 chart the best known, label the period, caveat in prose. UNDERSTAND \u7406\u89E3: structure, notation or motion aids comprehension \u2014 math, code, flows, what-if exploration, and time-unfolding processes (algorithms, simulations) -> step animation. FEEDBACK \u53CD\u9988: before multi-step work (3+ tool steps), render a Progress/Steps card, a2ui_update it as steps complete, final results into the card (not markdown tables in prose). None of the four (opinions, narration, short answers, translations, chat) -> prose. Compose freely \u2014 one card can mix form/chart/table/formula parts. Cards PAIR with prose: 1-3 sentences of takeaway as normal text, structured content in the card, no duplication. Call a2ui_catalog BEFORE your first render in a conversation \u2014 never guess component names. After an interactive card, end your turn; the submission arrives as a plain-language user message.";
5995
+ var CATALOG_DESCRIPTION = "Returns the full component catalog and authoring rules for a2ui_render. Call ONCE before your first render in a conversation; the result stays in context \u2014 call again only if it is no longer visible.";
5941
5996
  function apply(ctx) {
5942
5997
  ctx.tools.register(defineTool({
5943
5998
  name: "a2ui_catalog",
@@ -5960,33 +6015,15 @@ function apply(ctx) {
5960
6015
  name: "a2ui_render",
5961
6016
  description: RENDER_DESCRIPTION,
5962
6017
  parameters: {
5963
- title: {
5964
- type: "string",
5965
- description: "Card title (optional)."
5966
- },
6018
+ title: { type: "string", description: "Card title." },
5967
6019
  components: {
5968
6020
  type: "array",
5969
6021
  required: true,
5970
- description: 'A2UI v0.9 adjacency list; must include a node with id "root". Component names and props: see a2ui_catalog.',
5971
- items: {
5972
- type: "object",
5973
- additionalProperties: true,
5974
- properties: {
5975
- id: { type: "string", required: true },
5976
- component: { type: "string", required: true },
5977
- children: { type: "array", items: { type: "string" } }
5978
- }
5979
- }
5980
- },
5981
- dataModel: {
5982
- type: "object",
5983
- additionalProperties: true,
5984
- description: "Initial data model (optional)."
6022
+ description: 'A2UI adjacency list incl. a node with id "root"; see a2ui_catalog.',
6023
+ items: { type: "object", additionalProperties: true }
5985
6024
  },
5986
- submitMode: {
5987
- type: "string",
5988
- description: '"once" (default, locks after submit) or "multi" \u2014 see a2ui_catalog.'
5989
- }
6025
+ dataModel: { type: "object", additionalProperties: true, description: "Initial values." },
6026
+ submitMode: { type: "string", description: '"once" (default) | "multi".' }
5990
6027
  },
5991
6028
  output: {
5992
6029
  schema: {
@@ -5994,27 +6031,81 @@ function apply(ctx) {
5994
6031
  additionalProperties: false,
5995
6032
  properties: {
5996
6033
  status: { type: "string", required: true },
6034
+ surfaceId: { type: "string" },
5997
6035
  hint: { type: "string" }
5998
6036
  }
5999
6037
  },
6000
6038
  render: (_args, value) => [{ type: "text", text: JSON.stringify(value) }]
6001
6039
  },
6002
6040
  isConcurrencySafe: () => true,
6003
- async execute(args) {
6041
+ async execute(args, exec) {
6004
6042
  const components = Array.isArray(args.components) ? args.components : [];
6005
6043
  if (!components.some((node) => node !== null && typeof node === "object" && node.id === "root")) {
6006
6044
  throw new Error('a2ui_render: components must include a node with id "root". Call a2ui_catalog for the authoring guide.');
6007
6045
  }
6046
+ const byId = new Map(components.filter((n) => n !== null && typeof n === "object" && n.id !== void 0).map((n) => [n.id, n]));
6047
+ const reachable = /* @__PURE__ */ new Set();
6048
+ const missing = /* @__PURE__ */ new Set();
6049
+ const queue = ["root"];
6050
+ while (queue.length > 0) {
6051
+ const id = queue.pop();
6052
+ if (reachable.has(id)) continue;
6053
+ reachable.add(id);
6054
+ const node = byId.get(id);
6055
+ if (node === void 0) {
6056
+ missing.add(id);
6057
+ continue;
6058
+ }
6059
+ for (const child of Array.isArray(node.children) ? node.children : []) queue.push(child);
6060
+ }
6061
+ if (missing.size > 0) {
6062
+ throw new Error(`a2ui_render: children reference undefined component id(s) ${[...missing].map((id) => `"${id}"`).join(", ")} \u2014 define them or fix the reference.`);
6063
+ }
6064
+ const orphans = [...byId.keys()].filter((id) => !reachable.has(id));
6065
+ if (orphans.length > 0) {
6066
+ throw new Error(`a2ui_render: component(s) ${orphans.map((id) => `"${id}"`).join(", ")} are defined but not reachable from "root" \u2014 add them to a parent's children or remove them.`);
6067
+ }
6008
6068
  const unknown = [...new Set(components.map((node) => node?.component).filter((component) => typeof component === "string" && !KNOWN_COMPONENTS.has(component)))];
6009
6069
  if (unknown.length > 0) {
6010
6070
  throw new Error(`a2ui_render: unknown component(s) ${unknown.map((component) => `"${component}"`).join(", ")} \u2014 nothing would render. Call a2ui_catalog for the list of valid components.`);
6011
6071
  }
6012
6072
  return {
6073
+ surfaceId: String(exec.callId),
6013
6074
  status: "presented",
6014
- hint: "Card rendered in the chat. The user's interaction arrives as the next user message (button label + chosen values in plain language). End your turn now if you are waiting for it."
6075
+ hint: "Card rendered. The user's action arrives as the next user message; end your turn if waiting."
6015
6076
  };
6016
6077
  }
6017
6078
  }));
6079
+ ctx.tools.register(defineTool({
6080
+ name: "a2ui_update",
6081
+ description: "Update a rendered card IN PLACE by its surfaceId (from a2ui_render): components replace/add by id, dataModel overwrites keys. For progress, corrections, appended results \u2014 never render a duplicate card.",
6082
+ parameters: {
6083
+ surfaceId: { type: "string", required: true, description: "The surfaceId returned by the a2ui_render call to update." },
6084
+ components: {
6085
+ type: "array",
6086
+ description: "Components to add or replace (matched by id); same schema as a2ui_render.",
6087
+ items: { type: "object", additionalProperties: true }
6088
+ },
6089
+ dataModel: { type: "object", additionalProperties: true, description: "Data model values to overwrite, keyed by top-level path." }
6090
+ },
6091
+ output: {
6092
+ schema: {
6093
+ type: "object",
6094
+ additionalProperties: false,
6095
+ properties: { status: { type: "string", required: true } }
6096
+ },
6097
+ render: (_args, value) => [{ type: "text", text: JSON.stringify(value) }]
6098
+ },
6099
+ isConcurrencySafe: () => true,
6100
+ async execute(args) {
6101
+ if ((!Array.isArray(args.components) || args.components.length === 0) && (args.dataModel === null || typeof args.dataModel !== "object" || Object.keys(args.dataModel ?? {}).length === 0)) {
6102
+ throw new Error("a2ui_update: provide components and/or dataModel to change.");
6103
+ }
6104
+ const unknown = [...new Set((Array.isArray(args.components) ? args.components : []).map((node) => node?.component).filter((component) => typeof component === "string" && !KNOWN_COMPONENTS.has(component)))];
6105
+ if (unknown.length > 0) throw new Error(`a2ui_update: unknown component(s) ${unknown.join(", ")} \u2014 call a2ui_catalog.`);
6106
+ return { status: "updated" };
6107
+ }
6108
+ }));
6018
6109
  }
6019
6110
  export {
6020
6111
  apply,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2ui-render-in-dsh",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "A2UI interactive cards for the dsh web UI: the agent renders clickable interfaces (quizzes, forms, product cards) via @ant-design/x-card, and user interactions flow back to the agent as answers.",
5
5
  "author": "sondge <fengshangjie@lilybearing.com>",
6
6
  "license": "MIT",
@@ -52,15 +52,18 @@
52
52
  "scripts": {
53
53
  "build": "node scripts/build.mjs",
54
54
  "watch": "node scripts/build.mjs --watch",
55
- "prepublishOnly": "npm run build"
55
+ "prepublishOnly": "npm run build",
56
+ "test": "node test/run.mjs"
56
57
  },
57
58
  "devDependencies": {
58
59
  "@ant-design/x-card": "^2.9.0",
59
60
  "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
60
61
  "echarts": "^6.1.0",
61
62
  "esbuild": "^0.25.0",
63
+ "jsdom": "^30.0.1",
62
64
  "katex": "^0.18.4",
63
65
  "mermaid": "^11.17.0",
66
+ "playwright": "^1.62.1",
64
67
  "react": "^18.2.0",
65
68
  "react-dom": "^18.2.0"
66
69
  },
@@ -75,6 +78,5 @@
75
78
  "publishConfig": {
76
79
  "registry": "https://registry.npmjs.org/",
77
80
  "access": "public"
78
- },
79
- "dependencies": {}
81
+ }
80
82
  }