@webskill/sdk 0.4.0 → 0.6.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/dist/agent.d.ts +2 -0
- package/dist/agent.js +909 -0
- package/dist/browser.d.ts +233 -4
- package/dist/browser.js +869 -19
- package/dist/{catalogComponents-KsujmL4b-Clx1kCnU.js → catalogComponents-Dr5dFMAb-Dacibl1e.js} +372 -126
- package/dist/{dist-D9Lcn5Pp.js → dist-DnYG2-eY.js} +642 -39
- package/dist/{dist-C-Sh0MDU.js → dist-DusANsrn.js} +1035 -99
- package/dist/{env--jJB-TSX-04klhTYi.js → env-8cY40DXB-CGnEVZby.js} +7 -6
- package/dist/{env-BPUBZCwJ-4jat_SVG.d.ts → env-AK3cSMEA-Dli6QU5E.d.ts} +4 -3
- package/dist/governance.d.ts +46 -4
- package/dist/governance.js +45 -2
- package/dist/{index-CHXxDccV.d.ts → index-BMocOEi0.d.ts} +106 -10
- package/dist/index-BuTpBMzr.d.ts +474 -0
- package/dist/{index-DLfR2Y6I.d.ts → index-C-KFAZoF.d.ts} +337 -18
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +39 -8
- package/dist/mcp.js +53 -19
- package/dist/{memoryArtifactStore-BtOeB_hm-tj3fC5ip.js → memoryArtifactStore-52Zn9npI-BMPYwvoy.js} +10 -2
- package/dist/node.d.ts +4 -4
- package/dist/node.js +9 -2
- package/dist/{openUiLibrary-YLS-cxyT-C96jWDQq.js → openUiLibrary-Bdrji9qK-DzAxRlTY.js} +3 -3
- package/dist/{skillVersionStore-uyefLPR1-DXOzbksv.d.ts → skillVersionStore-BzLbzFOL-CxdAFWO2.d.ts} +4 -3
- package/dist/{testing-DDCJWvgA.js → testing-CYTFqkDm.js} +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +3 -3
- package/dist/{types-7Wcg--Vh-1YlQ4jF9.d.ts → types-4pg-qp_I-Gq63X8Oa.d.ts} +55 -5
- package/dist/ui-react.d.ts +26 -5
- package/dist/ui-react.js +147 -29
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +30 -6
- package/dist/ui.d.ts +4 -4
- package/dist/ui.js +3 -3
- package/dist/{webskillLitCatalog-CSTbhBe_-CYIs5BX8.js → webskillLitCatalog-_mugzRHx-B_54vxum.js} +88 -2
- package/package.json +6 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { m as WebSkillError } from "./dist-8oQRa8Xz.js";
|
|
2
2
|
import { toJSONSchema, z } from "zod";
|
|
3
3
|
|
|
4
|
-
//#region ../ui/dist/webskillCatalog-
|
|
4
|
+
//#region ../ui/dist/webskillCatalog-B-IQxOKs.js
|
|
5
5
|
/**
|
|
6
6
|
* 最小子集 markdown → DOM:标题(#..###)、无序列表、代码块(```)、加粗、链接。
|
|
7
7
|
* 全部内容经 textContent 写入,用户内容天然转义防 HTML 注入。
|
|
@@ -305,6 +305,7 @@ function validateNode(catalog, byName, node, path, issues) {
|
|
|
305
305
|
return;
|
|
306
306
|
}
|
|
307
307
|
const allowed = def.children;
|
|
308
|
+
const singletons = /* @__PURE__ */ new Map();
|
|
308
309
|
children.forEach((child, index) => {
|
|
309
310
|
const childPath = `${path}.children[${index}]`;
|
|
310
311
|
if (allowed !== "any" && isRecord$1(child) && typeof child["component"] === "string") {
|
|
@@ -316,6 +317,17 @@ function validateNode(catalog, byName, node, path, issues) {
|
|
|
316
317
|
return;
|
|
317
318
|
}
|
|
318
319
|
}
|
|
320
|
+
if (isRecord$1(child) && typeof child["component"] === "string") {
|
|
321
|
+
const childName = child["component"];
|
|
322
|
+
if (byName.get(childName)?.singletonPerContainer === true) {
|
|
323
|
+
const seen = (singletons.get(childName) ?? 0) + 1;
|
|
324
|
+
singletons.set(childName, seen);
|
|
325
|
+
if (seen > 1) issues.push({
|
|
326
|
+
path: childPath,
|
|
327
|
+
message: `At most one "${childName}" per container; "${name}" already contains one`
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
319
331
|
validateNode(catalog, byName, child, childPath, issues);
|
|
320
332
|
});
|
|
321
333
|
}
|
|
@@ -379,6 +391,7 @@ const DATA = [
|
|
|
379
391
|
"Metric",
|
|
380
392
|
"Table",
|
|
381
393
|
"Chart",
|
|
394
|
+
"Timeline",
|
|
382
395
|
"FileLink"
|
|
383
396
|
];
|
|
384
397
|
const INPUT = [
|
|
@@ -386,6 +399,8 @@ const INPUT = [
|
|
|
386
399
|
"Field",
|
|
387
400
|
"Button"
|
|
388
401
|
];
|
|
402
|
+
/** Tabs 的面板与 Grid 的格子必须自成容器,否则「每容器至多一个表单」就没有落脚点 */
|
|
403
|
+
const PANEL = ["Card", "Stack"];
|
|
389
404
|
/**
|
|
390
405
|
* v3 首发 catalog:覆盖旧六类 surface(metric / chart / table / form / file / custom)的全部表达力,先窄后宽。
|
|
391
406
|
* 只依赖 zod —— 不 import 任何 UI framework(21 号文档 §5.1)。
|
|
@@ -401,8 +416,13 @@ const uiCatalog = defineUiCatalog({
|
|
|
401
416
|
"- Ask for user input with `Form` + `Field`; never describe an input in `Text`.",
|
|
402
417
|
"- A `Button`'s `action` must be one of the catalog actions.",
|
|
403
418
|
"- Never emit HTML, scripts or remote images.",
|
|
404
|
-
"- Prefer `Table` for many rows, `Chart` for trends, `Metric` for a single number
|
|
405
|
-
"
|
|
419
|
+
"- Prefer `Table` for many rows, `Chart` for trends, `Metric` for a single number,",
|
|
420
|
+
" `Timeline` for ordered steps and `Progress` for how far a long task has come.",
|
|
421
|
+
"- Match `Field.type` to the question instead of using `text` for everything:",
|
|
422
|
+
" `select` / `multi-select` for a fixed set of options, `date` for dates,",
|
|
423
|
+
" `toggle` for yes/no, `number` for quantities, `textarea` for several sentences.",
|
|
424
|
+
"- Group independent sections with `Tabs` or `Grid` instead of one long column.",
|
|
425
|
+
"- At most one `Form` per container; a `Tabs` panel and a `Grid` cell are separate containers."
|
|
406
426
|
].join("\n"),
|
|
407
427
|
components: [
|
|
408
428
|
{
|
|
@@ -464,6 +484,67 @@ const uiCatalog = defineUiCatalog({
|
|
|
464
484
|
props: z.object({}),
|
|
465
485
|
example: { component: "Separator" }
|
|
466
486
|
},
|
|
487
|
+
{
|
|
488
|
+
name: "Tabs",
|
|
489
|
+
group: "layout",
|
|
490
|
+
description: "Switchable panels. Each child is one panel and its own form container.",
|
|
491
|
+
props: z.object({ labels: z.array(z.string()).min(2) }),
|
|
492
|
+
children: PANEL,
|
|
493
|
+
constraints: ["One child per label, in the same order"],
|
|
494
|
+
example: {
|
|
495
|
+
component: "Tabs",
|
|
496
|
+
props: { labels: ["Filters", "Export"] },
|
|
497
|
+
children: [{
|
|
498
|
+
component: "Stack",
|
|
499
|
+
children: [{
|
|
500
|
+
component: "Text",
|
|
501
|
+
props: { text: "Pick a date range." }
|
|
502
|
+
}]
|
|
503
|
+
}, {
|
|
504
|
+
component: "Stack",
|
|
505
|
+
children: [{
|
|
506
|
+
component: "Text",
|
|
507
|
+
props: { text: "Choose a file format." }
|
|
508
|
+
}]
|
|
509
|
+
}]
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: "Grid",
|
|
514
|
+
group: "layout",
|
|
515
|
+
description: "Dashboard grid. Each child is one cell and its own form container.",
|
|
516
|
+
props: z.object({ columns: z.union([
|
|
517
|
+
z.literal(2),
|
|
518
|
+
z.literal(3),
|
|
519
|
+
z.literal(4)
|
|
520
|
+
]).default(2) }),
|
|
521
|
+
children: PANEL,
|
|
522
|
+
example: {
|
|
523
|
+
component: "Grid",
|
|
524
|
+
props: { columns: 2 },
|
|
525
|
+
children: [{
|
|
526
|
+
component: "Card",
|
|
527
|
+
props: { title: "Revenue" },
|
|
528
|
+
children: [{
|
|
529
|
+
component: "Metric",
|
|
530
|
+
props: {
|
|
531
|
+
label: "MRR",
|
|
532
|
+
value: "12,400"
|
|
533
|
+
}
|
|
534
|
+
}]
|
|
535
|
+
}, {
|
|
536
|
+
component: "Card",
|
|
537
|
+
props: { title: "Churn" },
|
|
538
|
+
children: [{
|
|
539
|
+
component: "Metric",
|
|
540
|
+
props: {
|
|
541
|
+
label: "Rate",
|
|
542
|
+
value: "2.1%"
|
|
543
|
+
}
|
|
544
|
+
}]
|
|
545
|
+
}]
|
|
546
|
+
}
|
|
547
|
+
},
|
|
467
548
|
{
|
|
468
549
|
name: "Heading",
|
|
469
550
|
group: "content",
|
|
@@ -599,6 +680,33 @@ const uiCatalog = defineUiCatalog({
|
|
|
599
680
|
}
|
|
600
681
|
}
|
|
601
682
|
},
|
|
683
|
+
{
|
|
684
|
+
name: "Timeline",
|
|
685
|
+
group: "data",
|
|
686
|
+
description: "Ordered steps or events, earliest first.",
|
|
687
|
+
props: z.object({ items: z.array(z.object({
|
|
688
|
+
title: z.string(),
|
|
689
|
+
time: z.string().optional(),
|
|
690
|
+
description: z.string().optional(),
|
|
691
|
+
state: z.enum([
|
|
692
|
+
"done",
|
|
693
|
+
"active",
|
|
694
|
+
"pending"
|
|
695
|
+
]).default("pending")
|
|
696
|
+
})).min(1) }),
|
|
697
|
+
example: {
|
|
698
|
+
component: "Timeline",
|
|
699
|
+
props: { items: [{
|
|
700
|
+
title: "Plan",
|
|
701
|
+
time: "Mon",
|
|
702
|
+
state: "done"
|
|
703
|
+
}, {
|
|
704
|
+
title: "Build",
|
|
705
|
+
time: "Tue",
|
|
706
|
+
state: "active"
|
|
707
|
+
}] }
|
|
708
|
+
}
|
|
709
|
+
},
|
|
602
710
|
{
|
|
603
711
|
name: "FileLink",
|
|
604
712
|
group: "data",
|
|
@@ -631,7 +739,8 @@ const uiCatalog = defineUiCatalog({
|
|
|
631
739
|
cancelLabel: z.string().optional()
|
|
632
740
|
}),
|
|
633
741
|
children: ["Field"],
|
|
634
|
-
|
|
742
|
+
singletonPerContainer: true,
|
|
743
|
+
constraints: ["At most one Form per container", "Every Field name must be unique inside the Form"],
|
|
635
744
|
example: {
|
|
636
745
|
component: "Form",
|
|
637
746
|
props: { title: "Schedule report" },
|
|
@@ -695,6 +804,27 @@ const uiCatalog = defineUiCatalog({
|
|
|
695
804
|
}
|
|
696
805
|
}
|
|
697
806
|
},
|
|
807
|
+
{
|
|
808
|
+
name: "Progress",
|
|
809
|
+
group: "feedback",
|
|
810
|
+
description: "How far a long-running task has come, in percent.",
|
|
811
|
+
props: z.object({
|
|
812
|
+
label: z.string().optional(),
|
|
813
|
+
value: z.number().min(0).max(100),
|
|
814
|
+
tone: z.enum([
|
|
815
|
+
"neutral",
|
|
816
|
+
"success",
|
|
817
|
+
"warning"
|
|
818
|
+
]).default("neutral")
|
|
819
|
+
}),
|
|
820
|
+
example: {
|
|
821
|
+
component: "Progress",
|
|
822
|
+
props: {
|
|
823
|
+
label: "Indexing",
|
|
824
|
+
value: 42
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
},
|
|
698
828
|
{
|
|
699
829
|
name: "Alert",
|
|
700
830
|
group: "feedback",
|
|
@@ -763,6 +893,12 @@ const UI_CATALOG_GROUPS = {
|
|
|
763
893
|
data: DATA,
|
|
764
894
|
input: INPUT
|
|
765
895
|
};
|
|
896
|
+
/**
|
|
897
|
+
* catalog 系统提示的体积上限(UTF-8 字节,FR-6.6)。
|
|
898
|
+
* 描述每次请求都要重发,所以上限必须是可判定的数字而不是「尽量精简」。
|
|
899
|
+
* 要改这个数字,先想清楚是否值得让每次请求都多付这些 token。
|
|
900
|
+
*/
|
|
901
|
+
const UI_CATALOG_PROMPT_BUDGET_BYTES = 20480;
|
|
766
902
|
/** A2UI 协议公共类型(与 @a2ui/web_core 的 common-types 同一套定义) */
|
|
767
903
|
const A2UI_COMMON_TYPES = "https://a2ui.org/specification/v0_9/common_types.json";
|
|
768
904
|
const CHILD_LIST_REF = `${A2UI_COMMON_TYPES}#/$defs/ChildList`;
|
|
@@ -856,6 +992,7 @@ function shapeInteractionValue(model, values) {
|
|
|
856
992
|
case "confirm": return values["confirmed"] === true;
|
|
857
993
|
case "select": return values["selected"];
|
|
858
994
|
case "authorize": return true;
|
|
995
|
+
case "file-pick": return values["file"];
|
|
859
996
|
case "form": return values;
|
|
860
997
|
}
|
|
861
998
|
}
|
|
@@ -869,7 +1006,8 @@ function interactionToFormModel(request) {
|
|
|
869
1006
|
name: "answer",
|
|
870
1007
|
label: request.message,
|
|
871
1008
|
control: "text",
|
|
872
|
-
required: true
|
|
1009
|
+
required: true,
|
|
1010
|
+
...request.suggestion ? { suggestion: request.suggestion } : {}
|
|
873
1011
|
}],
|
|
874
1012
|
submitLabel: "Submit",
|
|
875
1013
|
cancelLabel: "Cancel"
|
|
@@ -896,6 +1034,7 @@ function interactionToFormModel(request) {
|
|
|
896
1034
|
...f.required ? { required: true } : {},
|
|
897
1035
|
...f.description ? { description: f.description } : {},
|
|
898
1036
|
...f.defaultValue !== void 0 ? { defaultValue: f.defaultValue } : {},
|
|
1037
|
+
...f.suggestion ? { suggestion: f.suggestion } : {},
|
|
899
1038
|
...f.options ? { options: f.options } : {}
|
|
900
1039
|
})),
|
|
901
1040
|
submitLabel: "Submit",
|
|
@@ -909,7 +1048,8 @@ function interactionToFormModel(request) {
|
|
|
909
1048
|
label: request.message,
|
|
910
1049
|
control: "select",
|
|
911
1050
|
required: true,
|
|
912
|
-
options: request.options
|
|
1051
|
+
options: request.options,
|
|
1052
|
+
...request.suggestion ? { suggestion: request.suggestion } : {}
|
|
913
1053
|
}],
|
|
914
1054
|
submitLabel: "Select",
|
|
915
1055
|
cancelLabel: "Cancel"
|
|
@@ -922,6 +1062,21 @@ function interactionToFormModel(request) {
|
|
|
922
1062
|
submitLabel: "Allow",
|
|
923
1063
|
cancelLabel: "Deny"
|
|
924
1064
|
};
|
|
1065
|
+
case "file-pick": return {
|
|
1066
|
+
kind: "file-pick",
|
|
1067
|
+
title: "File requested",
|
|
1068
|
+
message: request.message,
|
|
1069
|
+
controls: [{
|
|
1070
|
+
name: "file",
|
|
1071
|
+
label: request.field ?? "file",
|
|
1072
|
+
control: "file",
|
|
1073
|
+
required: true,
|
|
1074
|
+
...request.accept ? { accept: request.accept } : {},
|
|
1075
|
+
...request.multiple ? { multiple: true } : {}
|
|
1076
|
+
}],
|
|
1077
|
+
submitLabel: "Choose file",
|
|
1078
|
+
cancelLabel: "Decline"
|
|
1079
|
+
};
|
|
925
1080
|
}
|
|
926
1081
|
}
|
|
927
1082
|
const isEmpty = (v) => v === void 0 || v === "";
|
|
@@ -971,11 +1126,35 @@ function collectValues(controls, container) {
|
|
|
971
1126
|
};
|
|
972
1127
|
}
|
|
973
1128
|
/**
|
|
1129
|
+
* 采纳建议值(FR-5.9):把 `control.suggestion` 写进已渲染的控件。
|
|
1130
|
+
* 与 `collectValues` 共用同一套选择器与编码约定——两边分开实现的话,
|
|
1131
|
+
* select 的 JSON 编码值迟早会对不上。
|
|
1132
|
+
* @experimental
|
|
1133
|
+
*/
|
|
1134
|
+
function applySuggestion(control, container) {
|
|
1135
|
+
if (control.suggestion === void 0) return;
|
|
1136
|
+
const el = container.querySelector(controlSelector(control.name));
|
|
1137
|
+
if (el === null) return;
|
|
1138
|
+
const { value } = control.suggestion;
|
|
1139
|
+
if (control.control === "boolean") {
|
|
1140
|
+
el.checked = value === true;
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
if (control.control === "select") {
|
|
1144
|
+
el.value = JSON.stringify(value);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
el.value = value === void 0 || value === null ? "" : String(value);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
974
1150
|
* 轻量 SVG 图表自绘(bar/line/pie,零依赖,三端复用单一来源)。
|
|
975
1151
|
* 全部经 doc.createElementNS 构建(内容来自技能输出,不拼 innerHTML)。
|
|
976
1152
|
*/
|
|
977
1153
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
978
|
-
/**
|
|
1154
|
+
/**
|
|
1155
|
+
* 固定 8 色循环调色板。这是**数据编码**(区分序列),不是主题色;
|
|
1156
|
+
* 图表的轴线与文字等“外壳”颜色走 currentColor,跟随宿主容器的主题。
|
|
1157
|
+
*/
|
|
979
1158
|
const CHART_PALETTE = [
|
|
980
1159
|
"#4e79a7",
|
|
981
1160
|
"#f28e2b",
|
|
@@ -998,7 +1177,8 @@ function text(doc, x, y, content, attrs = {}) {
|
|
|
998
1177
|
x: String(x),
|
|
999
1178
|
y: String(y),
|
|
1000
1179
|
"font-size": "9",
|
|
1001
|
-
fill: "
|
|
1180
|
+
fill: "currentColor",
|
|
1181
|
+
"fill-opacity": "0.65",
|
|
1002
1182
|
...attrs
|
|
1003
1183
|
});
|
|
1004
1184
|
node.textContent = content;
|
|
@@ -1023,14 +1203,16 @@ function renderBar(chart, svg, doc) {
|
|
|
1023
1203
|
y1: String(plot.y0),
|
|
1024
1204
|
x2: String(plot.x0),
|
|
1025
1205
|
y2: String(plot.y1),
|
|
1026
|
-
stroke: "
|
|
1206
|
+
stroke: "currentColor",
|
|
1207
|
+
"stroke-opacity": "0.35"
|
|
1027
1208
|
}));
|
|
1028
1209
|
svg.appendChild(el(doc, "line", {
|
|
1029
1210
|
x1: String(plot.x0),
|
|
1030
1211
|
y1: String(plot.y1),
|
|
1031
1212
|
x2: String(plot.x1),
|
|
1032
1213
|
y2: String(plot.y1),
|
|
1033
|
-
stroke: "
|
|
1214
|
+
stroke: "currentColor",
|
|
1215
|
+
"stroke-opacity": "0.35"
|
|
1034
1216
|
}));
|
|
1035
1217
|
if (max <= 0 || chart.labels.length === 0 || chart.series.length === 0) return;
|
|
1036
1218
|
const groupWidth = (plot.x1 - plot.x0) / chart.labels.length;
|
|
@@ -1070,14 +1252,16 @@ function renderLine(chart, svg, doc) {
|
|
|
1070
1252
|
y1: String(plot.y0),
|
|
1071
1253
|
x2: String(plot.x0),
|
|
1072
1254
|
y2: String(plot.y1),
|
|
1073
|
-
stroke: "
|
|
1255
|
+
stroke: "currentColor",
|
|
1256
|
+
"stroke-opacity": "0.35"
|
|
1074
1257
|
}));
|
|
1075
1258
|
svg.appendChild(el(doc, "line", {
|
|
1076
1259
|
x1: String(plot.x0),
|
|
1077
1260
|
y1: String(plot.y1),
|
|
1078
1261
|
x2: String(plot.x1),
|
|
1079
1262
|
y2: String(plot.y1),
|
|
1080
|
-
stroke: "
|
|
1263
|
+
stroke: "currentColor",
|
|
1264
|
+
"stroke-opacity": "0.35"
|
|
1081
1265
|
}));
|
|
1082
1266
|
if (max <= 0 || chart.labels.length === 0 || chart.series.length === 0) return;
|
|
1083
1267
|
const step = chart.labels.length > 1 ? (plot.x1 - plot.x0) / (chart.labels.length - 1) : 0;
|
|
@@ -1157,7 +1341,8 @@ function renderMiniChart(chart, doc) {
|
|
|
1157
1341
|
if (chart.title) svg.appendChild(text(doc, WIDTH / 2, 14, chart.title, {
|
|
1158
1342
|
"text-anchor": "middle",
|
|
1159
1343
|
"font-size": "11",
|
|
1160
|
-
fill: "
|
|
1344
|
+
fill: "currentColor",
|
|
1345
|
+
"fill-opacity": "1"
|
|
1161
1346
|
}));
|
|
1162
1347
|
switch (chart.kind) {
|
|
1163
1348
|
case "bar":
|
|
@@ -1270,6 +1455,8 @@ const WEBSKILL_STYLES_CSS = `
|
|
|
1270
1455
|
.webskill-form__error { font-size: 12px; color: #d33; display: none; }
|
|
1271
1456
|
.webskill-form__control--invalid .webskill-form__error { display: block; }
|
|
1272
1457
|
.webskill-form__input { padding: 6px 8px; border: 1px solid #bbb; border-radius: 4px; font-size: 14px; font-family: inherit; }
|
|
1458
|
+
.webskill-form__suggestion { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #666; }
|
|
1459
|
+
.webskill-form__suggestion-use { padding: 1px 8px; border-radius: 4px; border: 1px solid #bbb; background: #fff; cursor: pointer; font-size: 12px; }
|
|
1273
1460
|
.webskill-form__actions { display: flex; gap: 8px; margin-top: 12px; }
|
|
1274
1461
|
.webskill-form__button { padding: 6px 14px; border-radius: 4px; border: 1px solid #bbb; background: #fff; cursor: pointer; font-size: 14px; }
|
|
1275
1462
|
.webskill-form__button--primary { background: #2563eb; border-color: #2563eb; color: #fff; }
|
|
@@ -1463,6 +1650,7 @@ var WebFormBridge = class {
|
|
|
1463
1650
|
input.id = controlId;
|
|
1464
1651
|
input.setAttribute("aria-describedby", errorId);
|
|
1465
1652
|
wrapper.appendChild(input);
|
|
1653
|
+
if (control.suggestion !== void 0) wrapper.appendChild(this.#renderSuggestion(control, wrapper));
|
|
1466
1654
|
const error = doc.createElement("div");
|
|
1467
1655
|
error.className = "webskill-form__error";
|
|
1468
1656
|
error.id = errorId;
|
|
@@ -1471,7 +1659,369 @@ var WebFormBridge = class {
|
|
|
1471
1659
|
wrapper.appendChild(error);
|
|
1472
1660
|
return wrapper;
|
|
1473
1661
|
}
|
|
1662
|
+
/** 建议值以**建议**形态出现(FR-19.4):控件初始值仍为空,点「使用」才写入 */
|
|
1663
|
+
#renderSuggestion(control, container) {
|
|
1664
|
+
const doc = this.#doc;
|
|
1665
|
+
const row = doc.createElement("div");
|
|
1666
|
+
row.className = "webskill-form__suggestion";
|
|
1667
|
+
row.setAttribute("data-webskill-suggestion", control.name);
|
|
1668
|
+
const preview = doc.createElement("span");
|
|
1669
|
+
preview.className = "webskill-form__suggestion-value";
|
|
1670
|
+
preview.textContent = `Suggested: ${String(control.suggestion?.value)}`;
|
|
1671
|
+
row.appendChild(preview);
|
|
1672
|
+
const reason = control.suggestion?.reason;
|
|
1673
|
+
if (reason !== void 0 && reason !== "") {
|
|
1674
|
+
const why = doc.createElement("span");
|
|
1675
|
+
why.className = "webskill-form__suggestion-reason";
|
|
1676
|
+
why.textContent = `(${reason})`;
|
|
1677
|
+
row.appendChild(why);
|
|
1678
|
+
}
|
|
1679
|
+
const use = doc.createElement("button");
|
|
1680
|
+
use.type = "button";
|
|
1681
|
+
use.className = "webskill-form__suggestion-use";
|
|
1682
|
+
use.textContent = "Use";
|
|
1683
|
+
use.setAttribute("data-webskill-suggestion-use", control.name);
|
|
1684
|
+
use.addEventListener("click", () => applySuggestion(control, container));
|
|
1685
|
+
row.appendChild(use);
|
|
1686
|
+
return row;
|
|
1687
|
+
}
|
|
1474
1688
|
};
|
|
1689
|
+
function fieldNamesOf(form) {
|
|
1690
|
+
const names = [];
|
|
1691
|
+
const walk = (node) => {
|
|
1692
|
+
const name = node.props?.["name"];
|
|
1693
|
+
if (node.component === "Field" && typeof name === "string") names.push(name);
|
|
1694
|
+
for (const child of node.children ?? []) walk(child);
|
|
1695
|
+
};
|
|
1696
|
+
walk(form);
|
|
1697
|
+
return names;
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* 按出现序列出声明树里的每个 `Form`。
|
|
1701
|
+
*
|
|
1702
|
+
* 单表单时 action id 保持 `submit` / `cancel`,回传形状与容器级放宽前逐字节一致——
|
|
1703
|
+
* 向后兼容是 FR-6.3 的硬约束,不能让既有单表单卡片跟着改。
|
|
1704
|
+
* 多表单时每个表单拿到独立的 action id 与 `scopeId`,渲染层据此只收集本表单子树内的字段。
|
|
1705
|
+
*/
|
|
1706
|
+
function collectFormScopes(spec) {
|
|
1707
|
+
const forms = [];
|
|
1708
|
+
const walk = (node) => {
|
|
1709
|
+
if (node.component === "Form") forms.push(node);
|
|
1710
|
+
for (const child of node.children ?? []) walk(child);
|
|
1711
|
+
};
|
|
1712
|
+
walk(spec);
|
|
1713
|
+
if (forms.length === 1) {
|
|
1714
|
+
const form = forms[0];
|
|
1715
|
+
return [{
|
|
1716
|
+
scopeId: form.id ?? "form",
|
|
1717
|
+
submitActionId: "submit",
|
|
1718
|
+
cancelActionId: "cancel",
|
|
1719
|
+
fieldNames: fieldNamesOf(form),
|
|
1720
|
+
form
|
|
1721
|
+
}];
|
|
1722
|
+
}
|
|
1723
|
+
return forms.map((form, index) => {
|
|
1724
|
+
const scopeId = form.id ?? `form-${index}`;
|
|
1725
|
+
return {
|
|
1726
|
+
scopeId,
|
|
1727
|
+
submitActionId: `${scopeId}:submit`,
|
|
1728
|
+
cancelActionId: `${scopeId}:cancel`,
|
|
1729
|
+
fieldNames: fieldNamesOf(form),
|
|
1730
|
+
form
|
|
1731
|
+
};
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* 多表单时字段值在渲染层的存储键。
|
|
1736
|
+
*
|
|
1737
|
+
* 跨表单重名过去不可能发生,容器级放宽后它变成合法输入:两个表单各有一个 `email`,
|
|
1738
|
+
* 共用一张扁平表就是后写的覆盖先写的,用户在 A 里输入会串到 B。
|
|
1739
|
+
* 因此多表单时按作用域限定键;单表单不加前缀,存储与草稿形状保持不变。
|
|
1740
|
+
*/
|
|
1741
|
+
function qualifyFieldName(name, scopeId) {
|
|
1742
|
+
return scopeId === void 0 ? name : `${scopeId}.${name}`;
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* 提交时回传的字段值。
|
|
1746
|
+
*
|
|
1747
|
+
* 不给 `scope` → 整张 surface 的扁平值,与容器级放宽前完全一致(FR-6.3 第 3 条)。
|
|
1748
|
+
* 给了 `scope` → 只取该表单子树内的字段,键回落成裸字段名,
|
|
1749
|
+
* 因此接收端看到的载荷形状与单表单时相同。
|
|
1750
|
+
*/
|
|
1751
|
+
function collectScopedValues(values, scope) {
|
|
1752
|
+
if (!scope) return { ...values };
|
|
1753
|
+
const out = {};
|
|
1754
|
+
for (const name of scope.fieldNames) {
|
|
1755
|
+
const key = qualifyFieldName(name, scope.scopeId);
|
|
1756
|
+
if (key in values) out[name] = values[key];
|
|
1757
|
+
else if (name in values) out[name] = values[name];
|
|
1758
|
+
}
|
|
1759
|
+
return out;
|
|
1760
|
+
}
|
|
1761
|
+
/** 五类场景预设(FR-6.4)。@experimental */
|
|
1762
|
+
const UI_PRESETS = [
|
|
1763
|
+
{
|
|
1764
|
+
name: "charts",
|
|
1765
|
+
summary: "One question, one chart, with the takeaway spelled out above it.",
|
|
1766
|
+
guidance: [
|
|
1767
|
+
"Layout: `Card` → `Heading` (the takeaway, not \"Chart\") → `Chart` → optional `Text` for the caveat.",
|
|
1768
|
+
"Pick the chart type from the question: `line` / `area` for time, `bar` for comparison, `pie` for shares.",
|
|
1769
|
+
"Every series needs a name; labels and values must be the same length.",
|
|
1770
|
+
"Never repeat the same numbers in a `Table` right under the chart."
|
|
1771
|
+
].join("\n"),
|
|
1772
|
+
example: {
|
|
1773
|
+
component: "Card",
|
|
1774
|
+
props: { title: "Revenue" },
|
|
1775
|
+
children: [
|
|
1776
|
+
{
|
|
1777
|
+
component: "Heading",
|
|
1778
|
+
props: {
|
|
1779
|
+
level: 3,
|
|
1780
|
+
text: "Revenue grew 27% in Q4"
|
|
1781
|
+
}
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
component: "Chart",
|
|
1785
|
+
props: {
|
|
1786
|
+
type: "line",
|
|
1787
|
+
labels: [
|
|
1788
|
+
"Q1",
|
|
1789
|
+
"Q2",
|
|
1790
|
+
"Q3",
|
|
1791
|
+
"Q4"
|
|
1792
|
+
],
|
|
1793
|
+
series: [{
|
|
1794
|
+
name: "Revenue",
|
|
1795
|
+
values: [
|
|
1796
|
+
8,
|
|
1797
|
+
9,
|
|
1798
|
+
9.8,
|
|
1799
|
+
12.4
|
|
1800
|
+
]
|
|
1801
|
+
}]
|
|
1802
|
+
}
|
|
1803
|
+
},
|
|
1804
|
+
{
|
|
1805
|
+
component: "Text",
|
|
1806
|
+
props: {
|
|
1807
|
+
text: "Figures in millions, unaudited.",
|
|
1808
|
+
tone: "muted"
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
]
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
name: "cards",
|
|
1816
|
+
summary: "A short list of comparable items, one `Card` each.",
|
|
1817
|
+
guidance: [
|
|
1818
|
+
"Layout: `Grid` (2 or 3 columns) → one `Card` per item.",
|
|
1819
|
+
"Keep every card the same shape: title, one `Metric` or `Text`, at most one `Badge` for status.",
|
|
1820
|
+
"Use `Grid` rather than a vertical `Stack` as soon as there are three or more items.",
|
|
1821
|
+
"Put an action on a card only when the user can act on that single item."
|
|
1822
|
+
].join("\n"),
|
|
1823
|
+
example: {
|
|
1824
|
+
component: "Grid",
|
|
1825
|
+
props: { columns: 2 },
|
|
1826
|
+
children: [{
|
|
1827
|
+
component: "Card",
|
|
1828
|
+
props: { title: "Checkout" },
|
|
1829
|
+
children: [{
|
|
1830
|
+
component: "Metric",
|
|
1831
|
+
props: {
|
|
1832
|
+
label: "Errors",
|
|
1833
|
+
value: 12,
|
|
1834
|
+
trend: "down"
|
|
1835
|
+
}
|
|
1836
|
+
}, {
|
|
1837
|
+
component: "Badge",
|
|
1838
|
+
props: {
|
|
1839
|
+
text: "healthy",
|
|
1840
|
+
tone: "success"
|
|
1841
|
+
}
|
|
1842
|
+
}]
|
|
1843
|
+
}, {
|
|
1844
|
+
component: "Card",
|
|
1845
|
+
props: { title: "Search" },
|
|
1846
|
+
children: [{
|
|
1847
|
+
component: "Metric",
|
|
1848
|
+
props: {
|
|
1849
|
+
label: "Errors",
|
|
1850
|
+
value: 143,
|
|
1851
|
+
trend: "up"
|
|
1852
|
+
}
|
|
1853
|
+
}, {
|
|
1854
|
+
component: "Badge",
|
|
1855
|
+
props: {
|
|
1856
|
+
text: "degraded",
|
|
1857
|
+
tone: "warning"
|
|
1858
|
+
}
|
|
1859
|
+
}]
|
|
1860
|
+
}]
|
|
1861
|
+
}
|
|
1862
|
+
},
|
|
1863
|
+
{
|
|
1864
|
+
name: "dashboards",
|
|
1865
|
+
summary: "Headline numbers first, then detail, optionally split across `Tabs`.",
|
|
1866
|
+
guidance: [
|
|
1867
|
+
"Layout: `Stack` → `Grid` of `Metric` cards → `Chart` or `Table` for the detail.",
|
|
1868
|
+
"Use `Tabs` when the detail splits into independent groups (filters, exports, alerts).",
|
|
1869
|
+
"Each `Tabs` panel and each `Grid` cell is its own form container: at most one `Form` inside.",
|
|
1870
|
+
"Show at most four headline metrics; anything more belongs in a `Table`."
|
|
1871
|
+
].join("\n"),
|
|
1872
|
+
example: {
|
|
1873
|
+
component: "Stack",
|
|
1874
|
+
children: [{
|
|
1875
|
+
component: "Grid",
|
|
1876
|
+
props: { columns: 2 },
|
|
1877
|
+
children: [{
|
|
1878
|
+
component: "Card",
|
|
1879
|
+
props: { title: "MRR" },
|
|
1880
|
+
children: [{
|
|
1881
|
+
component: "Metric",
|
|
1882
|
+
props: {
|
|
1883
|
+
label: "This month",
|
|
1884
|
+
value: "12,400",
|
|
1885
|
+
trend: "up"
|
|
1886
|
+
}
|
|
1887
|
+
}]
|
|
1888
|
+
}, {
|
|
1889
|
+
component: "Card",
|
|
1890
|
+
props: { title: "Churn" },
|
|
1891
|
+
children: [{
|
|
1892
|
+
component: "Metric",
|
|
1893
|
+
props: {
|
|
1894
|
+
label: "This month",
|
|
1895
|
+
value: "2.1%",
|
|
1896
|
+
trend: "down"
|
|
1897
|
+
}
|
|
1898
|
+
}]
|
|
1899
|
+
}]
|
|
1900
|
+
}, {
|
|
1901
|
+
component: "Tabs",
|
|
1902
|
+
props: { labels: ["Trend", "Breakdown"] },
|
|
1903
|
+
children: [{
|
|
1904
|
+
component: "Stack",
|
|
1905
|
+
children: [{
|
|
1906
|
+
component: "Chart",
|
|
1907
|
+
props: {
|
|
1908
|
+
type: "bar",
|
|
1909
|
+
labels: ["Q3", "Q4"],
|
|
1910
|
+
series: [{
|
|
1911
|
+
name: "Revenue",
|
|
1912
|
+
values: [9800, 12400]
|
|
1913
|
+
}]
|
|
1914
|
+
}
|
|
1915
|
+
}]
|
|
1916
|
+
}, {
|
|
1917
|
+
component: "Stack",
|
|
1918
|
+
children: [{
|
|
1919
|
+
component: "Table",
|
|
1920
|
+
props: {
|
|
1921
|
+
columns: ["Region", "Revenue"],
|
|
1922
|
+
rows: [["EMEA", 5400], ["AMER", 7e3]]
|
|
1923
|
+
}
|
|
1924
|
+
}]
|
|
1925
|
+
}]
|
|
1926
|
+
}]
|
|
1927
|
+
}
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
name: "slides",
|
|
1931
|
+
summary: "One idea per `Tabs` panel, headline plus at most three supporting lines.",
|
|
1932
|
+
guidance: [
|
|
1933
|
+
"Layout: `Tabs` → one `Stack` per slide, labelled with the slide title.",
|
|
1934
|
+
"Each slide: one `Heading` (level 2) stating the point, then a `Chart`, `Metric` or short `Markdown` list.",
|
|
1935
|
+
"Never put more than one idea on a slide; add a panel instead.",
|
|
1936
|
+
"No `Form` inside slides — a deck is for reading, not for input."
|
|
1937
|
+
].join("\n"),
|
|
1938
|
+
example: {
|
|
1939
|
+
component: "Tabs",
|
|
1940
|
+
props: { labels: ["Where we are", "What we do next"] },
|
|
1941
|
+
children: [{
|
|
1942
|
+
component: "Stack",
|
|
1943
|
+
children: [{
|
|
1944
|
+
component: "Heading",
|
|
1945
|
+
props: {
|
|
1946
|
+
level: 2,
|
|
1947
|
+
text: "Revenue grew 27% in Q4"
|
|
1948
|
+
}
|
|
1949
|
+
}, {
|
|
1950
|
+
component: "Metric",
|
|
1951
|
+
props: {
|
|
1952
|
+
label: "Q4 revenue",
|
|
1953
|
+
value: "12,400",
|
|
1954
|
+
trend: "up"
|
|
1955
|
+
}
|
|
1956
|
+
}]
|
|
1957
|
+
}, {
|
|
1958
|
+
component: "Stack",
|
|
1959
|
+
children: [{
|
|
1960
|
+
component: "Heading",
|
|
1961
|
+
props: {
|
|
1962
|
+
level: 2,
|
|
1963
|
+
text: "Double down on EMEA"
|
|
1964
|
+
}
|
|
1965
|
+
}, {
|
|
1966
|
+
component: "Markdown",
|
|
1967
|
+
props: { text: "- hire two AEs\n- localise pricing" }
|
|
1968
|
+
}]
|
|
1969
|
+
}]
|
|
1970
|
+
}
|
|
1971
|
+
},
|
|
1972
|
+
{
|
|
1973
|
+
name: "reports",
|
|
1974
|
+
summary: "A long-form document: sections, tables and an optional `Timeline`.",
|
|
1975
|
+
guidance: [
|
|
1976
|
+
"Layout: `Stack` → `Heading` (level 2) → body → `Separator` between sections.",
|
|
1977
|
+
"Use `Table` for figures, `Timeline` for what happened when, `Markdown` for prose.",
|
|
1978
|
+
"Open with a one-paragraph summary before any detail.",
|
|
1979
|
+
"Attach generated files with `FileLink` at the end rather than inlining raw data."
|
|
1980
|
+
].join("\n"),
|
|
1981
|
+
example: {
|
|
1982
|
+
component: "Stack",
|
|
1983
|
+
children: [
|
|
1984
|
+
{
|
|
1985
|
+
component: "Heading",
|
|
1986
|
+
props: {
|
|
1987
|
+
level: 2,
|
|
1988
|
+
text: "Q4 incident review"
|
|
1989
|
+
}
|
|
1990
|
+
},
|
|
1991
|
+
{
|
|
1992
|
+
component: "Text",
|
|
1993
|
+
props: { text: "Three incidents, all recovered within the hour." }
|
|
1994
|
+
},
|
|
1995
|
+
{ component: "Separator" },
|
|
1996
|
+
{
|
|
1997
|
+
component: "Timeline",
|
|
1998
|
+
props: { items: [{
|
|
1999
|
+
title: "Search latency spike",
|
|
2000
|
+
time: "12 Nov",
|
|
2001
|
+
state: "done"
|
|
2002
|
+
}, {
|
|
2003
|
+
title: "Checkout timeouts",
|
|
2004
|
+
time: "03 Dec",
|
|
2005
|
+
state: "done"
|
|
2006
|
+
}] }
|
|
2007
|
+
},
|
|
2008
|
+
{
|
|
2009
|
+
component: "FileLink",
|
|
2010
|
+
props: {
|
|
2011
|
+
path: "q4-incidents.csv",
|
|
2012
|
+
label: "Full log",
|
|
2013
|
+
size: 24117
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
]
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
];
|
|
2020
|
+
const UI_PRESET_NAMES = UI_PRESETS.map((preset) => preset.name);
|
|
2021
|
+
/** Returns undefined for names outside the catalog presets. @experimental */
|
|
2022
|
+
function uiPreset(name) {
|
|
2023
|
+
return UI_PRESETS.find((preset) => preset.name === name);
|
|
2024
|
+
}
|
|
1475
2025
|
/**
|
|
1476
2026
|
* catalog 声明树 → json-render 扁平 spec。纯数据变换,不 import json-render
|
|
1477
2027
|
* (`@webskill/ui` 保持环境无关)。
|
|
@@ -1500,7 +2050,8 @@ const FIELD_TYPE = {
|
|
|
1500
2050
|
number: "number",
|
|
1501
2051
|
boolean: "toggle",
|
|
1502
2052
|
select: "select",
|
|
1503
|
-
textarea: "textarea"
|
|
2053
|
+
textarea: "textarea",
|
|
2054
|
+
file: "file"
|
|
1504
2055
|
};
|
|
1505
2056
|
function toOptions(options) {
|
|
1506
2057
|
return (options ?? []).map((option) => ({
|
|
@@ -1518,6 +2069,9 @@ function toField(control) {
|
|
|
1518
2069
|
...control.required ? { required: true } : {},
|
|
1519
2070
|
...control.description ? { description: control.description } : {},
|
|
1520
2071
|
...control.defaultValue !== void 0 ? { defaultValue: control.defaultValue } : {},
|
|
2072
|
+
...control.suggestion ? { suggestion: control.suggestion } : {},
|
|
2073
|
+
...control.accept ? { accept: [...control.accept] } : {},
|
|
2074
|
+
...control.multiple ? { multiple: true } : {},
|
|
1521
2075
|
...control.options ? { options: toOptions(control.options) } : {}
|
|
1522
2076
|
}
|
|
1523
2077
|
};
|
|
@@ -1551,6 +2105,7 @@ function interactionToUiSpec(request, labels = {}) {
|
|
|
1551
2105
|
};
|
|
1552
2106
|
}
|
|
1553
2107
|
const RENDER_UI_TOOL = "render_ui";
|
|
2108
|
+
const DESCRIBE_UI_PRESET_TOOL = "describe_ui_preset";
|
|
1554
2109
|
const defaultId = () => `ui-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1555
2110
|
/**
|
|
1556
2111
|
* 工具描述只留一句指针:catalog 本体走 system 消息(C4)。
|
|
@@ -1569,6 +2124,8 @@ const INTENTS = /* @__PURE__ */ new Set([
|
|
|
1569
2124
|
"download",
|
|
1570
2125
|
"refresh"
|
|
1571
2126
|
]);
|
|
2127
|
+
/** 去重靠指纹兜底,但重复调用本身也浪费一轮,所以先在提示词里劝阻(FR-21.5) */
|
|
2128
|
+
const NO_DUPLICATE_RENDER = "If a skill has already rendered a surface for this request, do not call render_ui again with the same content.";
|
|
1572
2129
|
function isIntent(value) {
|
|
1573
2130
|
return typeof value === "string" && INTENTS.has(value);
|
|
1574
2131
|
}
|
|
@@ -1576,8 +2133,9 @@ function isIntent(value) {
|
|
|
1576
2133
|
* 从节点树抽出 action 能力表。能力表走事件外壳而非树内:
|
|
1577
2134
|
* 树是模型可写的,nonce 放进去就等于让模型自己签发能力。
|
|
1578
2135
|
* action id 取节点 id;Button 未声明 id 时退回 intent 名(同 intent 只留一条)。
|
|
2136
|
+
* 表单的提交/取消取自作用域表:容器级放宽后一张 surface 可能有多个表单。
|
|
1579
2137
|
*/
|
|
1580
|
-
function collectActions(node, out) {
|
|
2138
|
+
function collectActions(node, out, scopes) {
|
|
1581
2139
|
if (node.component === "Button" && isIntent(node.props?.["action"])) {
|
|
1582
2140
|
const intent = node.props["action"];
|
|
1583
2141
|
const id = node.id ?? intent;
|
|
@@ -1587,23 +2145,24 @@ function collectActions(node, out) {
|
|
|
1587
2145
|
...intent === "submit" ? { awaitResponse: true } : {}
|
|
1588
2146
|
});
|
|
1589
2147
|
}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
2148
|
+
const scope = scopes.get(node);
|
|
2149
|
+
if (scope) {
|
|
2150
|
+
if (!out.has(scope.submitActionId)) out.set(scope.submitActionId, {
|
|
2151
|
+
id: scope.submitActionId,
|
|
1593
2152
|
intent: "submit",
|
|
1594
2153
|
awaitResponse: true
|
|
1595
2154
|
});
|
|
1596
|
-
if (typeof node.props?.["cancelLabel"] === "string" && !out.has(
|
|
1597
|
-
id:
|
|
2155
|
+
if (typeof node.props?.["cancelLabel"] === "string" && !out.has(scope.cancelActionId)) out.set(scope.cancelActionId, {
|
|
2156
|
+
id: scope.cancelActionId,
|
|
1598
2157
|
intent: "cancel"
|
|
1599
2158
|
});
|
|
1600
2159
|
}
|
|
1601
|
-
for (const child of node.children ?? []) collectActions(child, out);
|
|
2160
|
+
for (const child of node.children ?? []) collectActions(child, out, scopes);
|
|
1602
2161
|
}
|
|
1603
2162
|
/** 节点树声明的 action 能力表(renderer 据此生成按钮,runtime 据此签发 nonce)。@experimental */
|
|
1604
2163
|
function collectSpecActions(spec) {
|
|
1605
2164
|
const actions = /* @__PURE__ */ new Map();
|
|
1606
|
-
collectActions(spec, actions);
|
|
2165
|
+
collectActions(spec, actions, new Map(collectFormScopes(spec).map((scope) => [scope.form, scope])));
|
|
1607
2166
|
return [...actions.values()];
|
|
1608
2167
|
}
|
|
1609
2168
|
/**
|
|
@@ -1613,21 +2172,63 @@ function collectSpecActions(spec) {
|
|
|
1613
2172
|
function createUiCatalogToolSource(options = {}) {
|
|
1614
2173
|
const catalog = options.catalog ?? uiCatalog;
|
|
1615
2174
|
const newSurfaceId = options.newSurfaceId ?? defaultId;
|
|
2175
|
+
const presets = options.presets ? options.presets.flatMap((name) => uiPreset(name) ?? []) : UI_PRESETS;
|
|
2176
|
+
const presetChoices = presets.map((preset) => `${preset.name} — ${preset.summary}`).join("\n");
|
|
2177
|
+
const presetTool = () => ({
|
|
2178
|
+
name: DESCRIBE_UI_PRESET_TOOL,
|
|
2179
|
+
description: `Fetch the layout guidance for one scenario preset before calling ${RENDER_UI_TOOL}.\n${presetChoices}`,
|
|
2180
|
+
inputSchema: {
|
|
2181
|
+
type: "object",
|
|
2182
|
+
properties: { preset: {
|
|
2183
|
+
type: "string",
|
|
2184
|
+
enum: presets.map((preset) => preset.name)
|
|
2185
|
+
} },
|
|
2186
|
+
required: ["preset"],
|
|
2187
|
+
additionalProperties: false
|
|
2188
|
+
}
|
|
2189
|
+
});
|
|
2190
|
+
const renderTool = () => ({
|
|
2191
|
+
name: RENDER_UI_TOOL,
|
|
2192
|
+
description: RENDER_UI_DESCRIPTION,
|
|
2193
|
+
inputSchema: {
|
|
2194
|
+
type: "object",
|
|
2195
|
+
properties: {
|
|
2196
|
+
spec: catalog.toJsonSchema(),
|
|
2197
|
+
...presets.length > 0 ? { preset: {
|
|
2198
|
+
type: "string",
|
|
2199
|
+
enum: presets.map((preset) => preset.name),
|
|
2200
|
+
description: `Scenario preset this surface follows; call ${DESCRIBE_UI_PRESET_TOOL} first for its layout guidance.`
|
|
2201
|
+
} } : {}
|
|
2202
|
+
},
|
|
2203
|
+
required: ["spec"],
|
|
2204
|
+
additionalProperties: false
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
1616
2207
|
return {
|
|
1617
2208
|
kind: "ui-catalog",
|
|
1618
|
-
systemPrompt: () => Promise.resolve(catalog.toPrompt()),
|
|
1619
|
-
listToolSpecs: () => Promise.resolve([
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
2209
|
+
systemPrompt: () => Promise.resolve(`${catalog.toPrompt()}\n\n${NO_DUPLICATE_RENDER}`),
|
|
2210
|
+
listToolSpecs: () => Promise.resolve(presets.length > 0 ? [renderTool(), presetTool()] : [renderTool()]),
|
|
2211
|
+
canHandle: (name) => name === "render_ui" || presets.length > 0 && name === "describe_ui_preset",
|
|
2212
|
+
call: (name, args) => {
|
|
2213
|
+
if (name === "describe_ui_preset") {
|
|
2214
|
+
const requested = args["preset"];
|
|
2215
|
+
const preset = presets.find((candidate) => candidate.name === requested);
|
|
2216
|
+
if (!preset) return Promise.resolve({
|
|
2217
|
+
ok: false,
|
|
2218
|
+
content: [],
|
|
2219
|
+
error: {
|
|
2220
|
+
code: "VALIDATION_FAILED",
|
|
2221
|
+
message: `Unknown UI preset "${String(requested)}"; available presets: ${presets.map((candidate) => candidate.name).join(", ")}`
|
|
2222
|
+
}
|
|
2223
|
+
});
|
|
2224
|
+
return Promise.resolve({
|
|
2225
|
+
ok: true,
|
|
2226
|
+
content: [{
|
|
2227
|
+
type: "text",
|
|
2228
|
+
text: `${preset.guidance}\n\nExample spec:\n${JSON.stringify(preset.example)}`
|
|
2229
|
+
}]
|
|
2230
|
+
});
|
|
1627
2231
|
}
|
|
1628
|
-
}]),
|
|
1629
|
-
canHandle: (name) => name === RENDER_UI_TOOL,
|
|
1630
|
-
call: (_name, args) => {
|
|
1631
2232
|
const spec = args["spec"];
|
|
1632
2233
|
const result = catalog.validate(spec);
|
|
1633
2234
|
if (!result.ok) return Promise.resolve({
|
|
@@ -1728,13 +2329,14 @@ function toOpenUiSpecLang(spec, catalog = uiCatalog) {
|
|
|
1728
2329
|
return [...lines.filter((line) => !line.startsWith(`${rootId} =`)), `root = ${lines.find((line) => line.startsWith(`${rootId} =`)).slice(rootId.length + 3)}`].join("\n");
|
|
1729
2330
|
}
|
|
1730
2331
|
const WEBSKILL_SURFACE_ACTION = "webskill:surface-action";
|
|
1731
|
-
function toUiSurfaceActionDispatch(snapshot, action, value) {
|
|
2332
|
+
function toUiSurfaceActionDispatch(snapshot, action, value, scopeId) {
|
|
1732
2333
|
return {
|
|
1733
2334
|
...snapshot.runId ? { runId: snapshot.runId } : {},
|
|
1734
2335
|
surfaceId: snapshot.id,
|
|
1735
2336
|
actionId: action.id,
|
|
1736
2337
|
intent: action.intent,
|
|
1737
2338
|
...action.nonce ? { nonce: action.nonce } : {},
|
|
2339
|
+
...scopeId ? { scopeId } : {},
|
|
1738
2340
|
...value ? { value } : {}
|
|
1739
2341
|
};
|
|
1740
2342
|
}
|
|
@@ -1749,6 +2351,7 @@ function fromUiSurfaceActionDispatch(value) {
|
|
|
1749
2351
|
actionId: event["actionId"],
|
|
1750
2352
|
intent: event["intent"],
|
|
1751
2353
|
...typeof event["nonce"] === "string" ? { nonce: event["nonce"] } : {},
|
|
2354
|
+
...typeof event["scopeId"] === "string" ? { scopeId: event["scopeId"] } : {},
|
|
1752
2355
|
...isRecord(event["value"]) ? { value: event["value"] } : {},
|
|
1753
2356
|
...event["cancelled"] === true ? { cancelled: true } : {}
|
|
1754
2357
|
};
|
|
@@ -1778,10 +2381,10 @@ function fromA2uiSurfaceAction(event) {
|
|
|
1778
2381
|
return fromUiSurfaceActionDispatch(action.context);
|
|
1779
2382
|
}
|
|
1780
2383
|
/** Builds the A2UI event payload an extension host sends after a user action. @experimental */
|
|
1781
|
-
function toA2uiSurfaceAction(snapshot, action, value) {
|
|
2384
|
+
function toA2uiSurfaceAction(snapshot, action, value, scopeId) {
|
|
1782
2385
|
return {
|
|
1783
2386
|
name: A2UI_SURFACE_ACTION,
|
|
1784
|
-
context: toUiSurfaceActionDispatch(snapshot, action, value)
|
|
2387
|
+
context: toUiSurfaceActionDispatch(snapshot, action, value, scopeId)
|
|
1785
2388
|
};
|
|
1786
2389
|
}
|
|
1787
2390
|
/** 声明树里按钮触发的事件名(宿主据 context.actionId 回传 surface action) */
|
|
@@ -1888,7 +2491,7 @@ function fromA2uiSpecAction(event) {
|
|
|
1888
2491
|
*/
|
|
1889
2492
|
async function loadWebSkillLitCatalog() {
|
|
1890
2493
|
try {
|
|
1891
|
-
const { webskillLitCatalog } = await import("./webskillLitCatalog-
|
|
2494
|
+
const { webskillLitCatalog } = await import("./webskillLitCatalog-_mugzRHx-B_54vxum.js");
|
|
1892
2495
|
return webskillLitCatalog();
|
|
1893
2496
|
} catch (cause) {
|
|
1894
2497
|
throw new WebSkillError("UI_UNAVAILABLE", "The WebSkill A2UI catalog is unavailable; install @a2ui/lit, @a2ui/web_core and lit to render catalog surfaces with A2UI", cause);
|
|
@@ -1913,4 +2516,4 @@ async function loadOpenUiPeers() {
|
|
|
1913
2516
|
}
|
|
1914
2517
|
|
|
1915
2518
|
//#endregion
|
|
1916
|
-
export {
|
|
2519
|
+
export { defineUiCatalog as $, loadOpenUiPeers as A, toOpenUiSpecLang as B, ensureStyles as C, fromVercelToolResult as D, fromUiSurfaceActionDispatch as E, renderRenderResult as F, A2UI_COMMON_TYPES as G, toVercelToolInvocation as H, shapeInteractionValue as I, WEBSKILL_A2UI_CATALOG_ID as J, UI_CATALOG_GROUPS as K, toA2uiSpecMessages as L, qualifyFieldName as M, renderBlocks as N, interactionToFormModel as O, renderMiniChart as P, chartSpecFromProps as Q, toA2uiSurfaceAction as R, createUiCatalogToolSource as S, fromA2uiSurfaceAction as T, uiPreset as U, toUiSurfaceActionDispatch as V, A2UI_CHILDREN_PROP as W, a2uiComponentShapes as X, a2uiComponentSchema as Y, buildA2uiCatalogDefinition as Z, chartToTable as _, A2UI_VERSION as a, collectSpecActions as b, RENDER_UI_TOOL as c, VERCEL_INTERACTION_TOOL_NAME as d, mountEchart as et, VercelUiBridge as f, applySuggestion as g, WebFormBridge as h, A2UI_SURFACE_ACTION as i, loadWebSkillLitCatalog as j, interactionToUiSpec as k, UI_PRESETS as l, WEBSKILL_SURFACE_ACTION as m, A2UI_SPEC_ACTION as n, uiCatalog as nt, CHART_PALETTE as o, WEBSKILL_STYLES_CSS as p, UI_CATALOG_PROMPT_BUDGET_BYTES as q, A2UI_SPEC_FORM_PATH as r, DESCRIBE_UI_PRESET_TOOL as s, A2UI_BASIC_CATALOG_ID as t, renderMiniMarkdown as tt, UI_PRESET_NAMES as u, collectFormScopes as v, fromA2uiSpecAction as w, collectValues as x, collectScopedValues as y, toJsonRenderSpec as z };
|