@webskill/sdk 0.10.0 → 0.11.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 +3 -2
- package/dist/agent.js +3 -1102
- package/dist/browser.d.ts +258 -11
- package/dist/browser.js +758 -46
- package/dist/{catalogComponents-DTcYfpLQ-CcoOaz-Z.js → catalogComponents-BFoqpT1v-CjUBZ3bc.js} +253 -14
- package/dist/{dist-BViUeszk.js → dist-B-cOu08W.js} +526 -76
- package/dist/{dist-1OFC-zax.js → dist-DTHZS2k1.js} +520 -28
- package/dist/dist-qnlI2Iup.js +1280 -0
- package/dist/{eventTypes-s2uwAcLG-Go3l_dUe.js → eventTypes-FllCrX-Z-DNDeHWoG.js} +6 -2
- package/dist/governance.d.ts +7 -3
- package/dist/governance.js +1 -1
- package/dist/{index-DtFdMKBX.d.ts → index-D3mONFHD.d.ts} +220 -7
- package/dist/{index-B0QPLWPZ.d.ts → index-DACk2_XZ.d.ts} +110 -7
- package/dist/{index-BF4E1a9j.d.ts → index-DWbs58LF.d.ts} +227 -14
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +35 -7
- package/dist/mcp.js +88 -21
- package/dist/node.d.ts +3 -3
- package/dist/node.js +52 -2
- package/dist/{openUiLibrary-CIrV--Ad-B8zG_91e.js → openUiLibrary-D5u8oIvx-BLOAQCho.js} +3 -3
- package/dist/processSandboxEntry.js +6 -0
- package/dist/sandboxWorkerEntry.js +6 -0
- package/dist/{skillVersionStore-D-qHk9ZE-DBsYYCWn.d.ts → skillVersionStore-D-qHk9ZE-BcmFLykd.d.ts} +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{types-CrRcT-LM-DZAp8sWv.d.ts → types-C26b05fW-CdrRCRDb.d.ts} +20 -4
- package/dist/ui-react.d.ts +2 -2
- package/dist/ui-react.js +28 -10
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +2 -2
- package/dist/ui.d.ts +4 -4
- package/dist/ui.js +3 -3
- package/dist/{webskillLitCatalog-BJrphK0y-SGmRXaaO.js → webskillLitCatalog-DME6PBkV-CmYNLlIT.js} +135 -16
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { h as WebSkillError } from "./dist-Bev6i6Ip.js";
|
|
2
2
|
import { toJSONSchema, z } from "zod";
|
|
3
3
|
|
|
4
|
-
//#region ../ui/dist/webskillCatalog-
|
|
4
|
+
//#region ../ui/dist/webskillCatalog-DrmnCbsR.js
|
|
5
5
|
const DEFAULT_INTERACTION_TEXTS = {
|
|
6
6
|
submit: "Submit",
|
|
7
7
|
cancel: "Cancel",
|
|
@@ -324,6 +324,74 @@ function mountEchart(container) {
|
|
|
324
324
|
}
|
|
325
325
|
};
|
|
326
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* catalog 请求体积上限(0.11.0 分册 14 / AC-G20)的**数字单一来源**。
|
|
329
|
+
*
|
|
330
|
+
* 度量与报错格式在 `scripts/catalogBudget.mjs`(`check:contracts` 与 vitest 共用);
|
|
331
|
+
* 那边不重复这些数字,一律从本模块读——两处各写一份阈值就等于没有阈值。
|
|
332
|
+
*
|
|
333
|
+
* catalog 进入每一次 LLM 请求的有两处:系统提示词 `toPrompt()` 与
|
|
334
|
+
* `render_ui` 入参 `toJsonSchema()`。只量前者会漏掉约 2/3 的成本。
|
|
335
|
+
*
|
|
336
|
+
* **超阈值的处置是砍组件或收窄枚举取值,不是抬高这里的数字。**
|
|
337
|
+
*/
|
|
338
|
+
/**
|
|
339
|
+
* 预算阶段。分册 12 是本版最后一处 catalog 增量(分册 11 只把 `date` 加进
|
|
340
|
+
* 已有的 `Field.type`,分册 23 不碰 catalog),故在此切到 `'final'`:
|
|
341
|
+
* 阈值收紧到实测终态 + 余量,且余量小于一个平均组件的成本
|
|
342
|
+
* ——余量大于一个组件,等于默许下一版无声塞进一个。
|
|
343
|
+
*/
|
|
344
|
+
const CATALOG_BUDGET_STAGE = "final";
|
|
345
|
+
/**
|
|
346
|
+
* 阈值 = **已落地实测** + 余量,余量须小于一个平均组件的成本。
|
|
347
|
+
*
|
|
348
|
+
* 2026-08-14 实测终态(分册 12 落地后):prompt 12 744 / schema 21 470,30 个组件。
|
|
349
|
+
* 平均组件成本 prompt 425 / schema 716。
|
|
350
|
+
* 当前余量 prompt 256 / schema 330,均小于一个平均组件——再加一个组件必红。
|
|
351
|
+
*
|
|
352
|
+
* ⚠️ **本版这两个数字被上调过两次**,两次都由需求方裁决并改写了需求文档
|
|
353
|
+
* (分册 29 §2 范围订正 第 7 条与 §2.2),不是实现阶段自行放宽:
|
|
354
|
+
*
|
|
355
|
+
* 1. 11 200 → 12 200 / 20 500 → 21 200:分册 13 组件描述由短改长。
|
|
356
|
+
* `example` 是既有护栏的硬要求(`catalog.test.ts` 与 `catalogMatrix.test.tsx`
|
|
357
|
+
* 逐个组件校验并四档渲染),靠省掉示例腾出的空间收不回来。
|
|
358
|
+
* 2. 12 200 → 13 000 / 21 200 → 21 800:分册 12 的 `Split` 实测 490 + 694,
|
|
359
|
+
* 超出计划增量(403 + 1 047)的 prompt 侧。需求方裁定
|
|
360
|
+
* 「描述要长,可用性大于预算阈值」——收紧措辞可省回 158 字节,
|
|
361
|
+
* 但砍的正是各组件「何时改用 X」的辨析句,即选型准确率本身。
|
|
362
|
+
*
|
|
363
|
+
* 两次上调合计把单请求成本从 21.7 KB 抬到约 34.2 KB。
|
|
364
|
+
* **AC-G20 原文「超了砍组件、不抬阈值」已被推翻,但仅限本版;
|
|
365
|
+
* 后续再撞顶仍须回到需求方,不得沿用本版作为先例。**
|
|
366
|
+
*/
|
|
367
|
+
const CATALOG_PROMPT_MAX = 13e3;
|
|
368
|
+
const CATALOG_SCHEMA_MAX = 21800;
|
|
369
|
+
/**
|
|
370
|
+
* **尚未落地**的计划增量。本版已无剩余 catalog 增量,故归零;
|
|
371
|
+
* `CATALOG_BUDGET_STAGE` 同时切到 `'final'`,余量改由「小于一个平均组件」判定。
|
|
372
|
+
*/
|
|
373
|
+
const PLANNED_INCREMENT = {
|
|
374
|
+
prompt: 0,
|
|
375
|
+
schema: 0
|
|
376
|
+
};
|
|
377
|
+
/** 码与人话之间的分隔符。放在消息最前面,`codeOfIssue` 才能从 zod 的 issue 里认出它。 */
|
|
378
|
+
const SEPARATOR = ": ";
|
|
379
|
+
/**
|
|
380
|
+
* 给 zod 的 `error` 文案打上稳定码。
|
|
381
|
+
*
|
|
382
|
+
* 组件自己在 schema 里声明,sanitize 只负责把码搬到 `UiSpecDegradation.code` 上——
|
|
383
|
+
* 这样新增一条策略性约束不需要去 sanitize 里加组件名分支(写死清单是本仓反复出问题的地方)。
|
|
384
|
+
*/
|
|
385
|
+
function codedError(code, message) {
|
|
386
|
+
return `${code}${SEPARATOR}${message}`;
|
|
387
|
+
}
|
|
388
|
+
/** 从 zod issue 的消息里取回码;没有码就返回 undefined(普通类型错误就是这种)。 */
|
|
389
|
+
function codeOfIssue(message) {
|
|
390
|
+
const index = message.indexOf(SEPARATOR);
|
|
391
|
+
if (index <= 0) return void 0;
|
|
392
|
+
const head = message.slice(0, index);
|
|
393
|
+
return /^[A-Z][A-Z0-9_]*$/.test(head) ? head : void 0;
|
|
394
|
+
}
|
|
327
395
|
function isRecord$1$1(value) {
|
|
328
396
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
329
397
|
}
|
|
@@ -371,24 +439,36 @@ function sanitizeNode(catalog, byName, node, path, degradations) {
|
|
|
371
439
|
const parsed = def.props.safeParse(rawProps);
|
|
372
440
|
if (!parsed.success) {
|
|
373
441
|
const offending = new Set(parsed.error.issues.map((issue) => String(issue.path[0] ?? "")).filter((key) => key !== ""));
|
|
442
|
+
const codes = /* @__PURE__ */ new Map();
|
|
443
|
+
for (const issue of parsed.error.issues) {
|
|
444
|
+
const key = String(issue.path[0] ?? "");
|
|
445
|
+
const code = codeOfIssue(issue.message);
|
|
446
|
+
if (key !== "" && code !== void 0) codes.set(key, code);
|
|
447
|
+
}
|
|
374
448
|
const kept = {};
|
|
375
449
|
for (const [key, value] of Object.entries(rawProps)) if (!offending.has(key)) kept[key] = value;
|
|
376
450
|
const retry = def.props.safeParse(kept);
|
|
377
451
|
if (!retry.success) {
|
|
452
|
+
const carried = [...codes.values()].find((code) => code !== void 0);
|
|
378
453
|
degradations.push({
|
|
379
454
|
path,
|
|
380
455
|
kind: "node-dropped",
|
|
381
456
|
component: name,
|
|
382
|
-
detail: `Props cannot be repaired by dropping invalid keys: ${retry.error.issues.map((i) => i.message).join("; ")}
|
|
457
|
+
detail: `Props cannot be repaired by dropping invalid keys: ${retry.error.issues.map((i) => i.message).join("; ")}`,
|
|
458
|
+
...carried === void 0 ? {} : { code: carried }
|
|
383
459
|
});
|
|
384
460
|
return;
|
|
385
461
|
}
|
|
386
|
-
for (const key of offending)
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
462
|
+
for (const key of offending) {
|
|
463
|
+
const code = codes.get(key);
|
|
464
|
+
degradations.push({
|
|
465
|
+
path: `${path}.props.${key}`,
|
|
466
|
+
kind: "prop-dropped",
|
|
467
|
+
component: name,
|
|
468
|
+
detail: `Dropped invalid prop "${key}".`,
|
|
469
|
+
...code === void 0 ? {} : { code }
|
|
470
|
+
});
|
|
471
|
+
}
|
|
392
472
|
props = kept;
|
|
393
473
|
}
|
|
394
474
|
const sanitized = {
|
|
@@ -638,6 +718,56 @@ const tone = z.enum([
|
|
|
638
718
|
"info",
|
|
639
719
|
"destructive"
|
|
640
720
|
]);
|
|
721
|
+
/**
|
|
722
|
+
* 装饰色。与 `tone` 语义**不重叠**:tone 表状态(success 是「成功」),
|
|
723
|
+
* palette 表美观(「行程第三天」用绿色不代表它成功了)。
|
|
724
|
+
* 取值是色板名不是颜色值——必须映射到 design token,深浅色主题才能自动跟随。
|
|
725
|
+
*/
|
|
726
|
+
const palette = z.enum([
|
|
727
|
+
"sky",
|
|
728
|
+
"violet",
|
|
729
|
+
"amber",
|
|
730
|
+
"rose",
|
|
731
|
+
"emerald"
|
|
732
|
+
]);
|
|
733
|
+
/**
|
|
734
|
+
* surface 级视觉档(FR-13.4)。只在**根节点**上有意义,切的是一组 token
|
|
735
|
+
* (圆角/阴影/底色),不是逐组件改样式;作用域限于该 surface 子树。
|
|
736
|
+
* `office` 是缺省,与本版之前逐字节一致——它是回归基线。
|
|
737
|
+
*/
|
|
738
|
+
const surfaceTheme = z.enum([
|
|
739
|
+
"office",
|
|
740
|
+
"vivid",
|
|
741
|
+
"dashboard-dark"
|
|
742
|
+
]);
|
|
743
|
+
/** 图标白名单:任意名字会让模型产出渲染不出来的节点,且枚举长度直接计入每次请求 */
|
|
744
|
+
const iconName = z.enum([
|
|
745
|
+
"info",
|
|
746
|
+
"check",
|
|
747
|
+
"alert",
|
|
748
|
+
"x",
|
|
749
|
+
"clock",
|
|
750
|
+
"calendar",
|
|
751
|
+
"user",
|
|
752
|
+
"star",
|
|
753
|
+
"search",
|
|
754
|
+
"settings",
|
|
755
|
+
"download",
|
|
756
|
+
"link",
|
|
757
|
+
"chart",
|
|
758
|
+
"file",
|
|
759
|
+
"folder",
|
|
760
|
+
"mail"
|
|
761
|
+
]);
|
|
762
|
+
/**
|
|
763
|
+
* `Image.src` 的同源约束(AC-13.4)。**判定放在 schema 上,`validate` 与 `sanitize` 两条路径就都覆盖**;
|
|
764
|
+
* 只写进组件描述等于把安全交给模型自觉——提示注入可以用 `src` 把数据带到任意主机上去。
|
|
765
|
+
*
|
|
766
|
+
* 同源按**语法**判:`data:image/` 与相对路径放行,任何带 scheme 的绝对 URL
|
|
767
|
+
* 以及协议相对的 `//host/...` 一律拒。这样判定不需要知道当前 origin,
|
|
768
|
+
* `@webskill/ui` 得以保持环境无关。
|
|
769
|
+
*/
|
|
770
|
+
const imageSrc = z.string().refine((src) => /^data:image\//i.test(src) || !/^([a-z][a-z0-9+.-]*:|\/\/)/i.test(src), { error: codedError("IMAGE_SRC_NOT_ALLOWED", "Image.src must be a same-origin path or a data:image URL; remote URLs are rejected by policy.") });
|
|
641
771
|
const actionRef = z.object({
|
|
642
772
|
/** catalog actions 里声明过的名字 */
|
|
643
773
|
action: z.string().describe("Action name declared by the catalog"),
|
|
@@ -711,7 +841,7 @@ const uiCatalog = defineUiCatalog({
|
|
|
711
841
|
"- The root node of a surface must be `Stack` or `Card`.",
|
|
712
842
|
"- Ask for user input with `Form` + `Field`; never describe an input in `Text`.",
|
|
713
843
|
"- A `Button`'s `action` must be one of the catalog actions.",
|
|
714
|
-
"- Never emit HTML
|
|
844
|
+
"- Never emit HTML or scripts. `Image.src` must be same-origin or a data URL.",
|
|
715
845
|
"- Prefer `Table` for many rows, `Chart` for trends, `Metric` for a single number,",
|
|
716
846
|
" `Timeline` for ordered steps and `Progress` for how far a long task has come.",
|
|
717
847
|
"- Match `Field.type` to the question instead of using `text` for everything:",
|
|
@@ -737,7 +867,8 @@ const uiCatalog = defineUiCatalog({
|
|
|
737
867
|
"center",
|
|
738
868
|
"end",
|
|
739
869
|
"stretch"
|
|
740
|
-
]).optional()
|
|
870
|
+
]).optional(),
|
|
871
|
+
theme: surfaceTheme.optional()
|
|
741
872
|
}),
|
|
742
873
|
children: "any",
|
|
743
874
|
example: {
|
|
@@ -758,7 +889,15 @@ const uiCatalog = defineUiCatalog({
|
|
|
758
889
|
description: "A titled section that groups related content.",
|
|
759
890
|
props: z.object({
|
|
760
891
|
title: z.string(),
|
|
761
|
-
description: z.string().optional()
|
|
892
|
+
description: z.string().optional(),
|
|
893
|
+
palette: palette.optional(),
|
|
894
|
+
theme: surfaceTheme.optional(),
|
|
895
|
+
span: z.union([z.literal(1), z.literal(2)]).default(1),
|
|
896
|
+
density: z.enum([
|
|
897
|
+
"compact",
|
|
898
|
+
"default",
|
|
899
|
+
"comfortable"
|
|
900
|
+
]).default("default")
|
|
762
901
|
}),
|
|
763
902
|
children: "any",
|
|
764
903
|
example: {
|
|
@@ -805,15 +944,46 @@ const uiCatalog = defineUiCatalog({
|
|
|
805
944
|
}]
|
|
806
945
|
}
|
|
807
946
|
},
|
|
947
|
+
{
|
|
948
|
+
name: "Split",
|
|
949
|
+
group: "layout",
|
|
950
|
+
description: "Two panes side by side at a fixed ratio — a chart next to its takeaways, a form next to its help text, a summary next to the detail it summarises. Takes exactly two children and stacks them vertically when the container is narrow, so the main pane always comes first. Use `Grid` when the cells are peers rather than a main/side pair.",
|
|
951
|
+
props: z.object({ ratio: z.enum([
|
|
952
|
+
"1:1",
|
|
953
|
+
"2:1",
|
|
954
|
+
"3:1",
|
|
955
|
+
"1:2"
|
|
956
|
+
]).default("1:1") }),
|
|
957
|
+
children: PANEL,
|
|
958
|
+
example: {
|
|
959
|
+
component: "Split",
|
|
960
|
+
props: { ratio: "2:1" },
|
|
961
|
+
children: [{
|
|
962
|
+
component: "Card",
|
|
963
|
+
props: { title: "Trend" }
|
|
964
|
+
}, {
|
|
965
|
+
component: "Card",
|
|
966
|
+
props: { title: "Notes" }
|
|
967
|
+
}]
|
|
968
|
+
}
|
|
969
|
+
},
|
|
808
970
|
{
|
|
809
971
|
name: "Grid",
|
|
810
972
|
group: "layout",
|
|
811
|
-
description: "Dashboard grid. Each child is one cell and its own form container.",
|
|
812
|
-
props: z.object({
|
|
813
|
-
z.
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
973
|
+
description: "Dashboard grid: several tiles, metrics or charts the reader scans in parallel. Each child is one cell and its own form container. Prefer `columns: 'auto'` with a `minColumnWidth` — it fits as many cells as the container allows and reflows when narrow, which a fixed count cannot do. Set a fixed count only when the layout really needs exactly N.",
|
|
974
|
+
props: z.object({
|
|
975
|
+
columns: z.union([
|
|
976
|
+
z.literal(2),
|
|
977
|
+
z.literal(3),
|
|
978
|
+
z.literal(4),
|
|
979
|
+
z.literal("auto")
|
|
980
|
+
]).default(2),
|
|
981
|
+
minColumnWidth: z.enum([
|
|
982
|
+
"sm",
|
|
983
|
+
"md",
|
|
984
|
+
"lg"
|
|
985
|
+
]).default("md")
|
|
986
|
+
}),
|
|
817
987
|
children: PANEL,
|
|
818
988
|
example: {
|
|
819
989
|
component: "Grid",
|
|
@@ -851,7 +1021,8 @@ const uiCatalog = defineUiCatalog({
|
|
|
851
1021
|
z.literal(3),
|
|
852
1022
|
z.literal(4)
|
|
853
1023
|
]).default(3),
|
|
854
|
-
text: z.string()
|
|
1024
|
+
text: z.string(),
|
|
1025
|
+
palette: palette.optional()
|
|
855
1026
|
}),
|
|
856
1027
|
example: {
|
|
857
1028
|
component: "Heading",
|
|
@@ -891,7 +1062,8 @@ const uiCatalog = defineUiCatalog({
|
|
|
891
1062
|
description: "A short status label.",
|
|
892
1063
|
props: z.object({
|
|
893
1064
|
text: z.string(),
|
|
894
|
-
tone: tone.default("neutral")
|
|
1065
|
+
tone: tone.default("neutral"),
|
|
1066
|
+
palette: palette.optional()
|
|
895
1067
|
}),
|
|
896
1068
|
example: {
|
|
897
1069
|
component: "Badge",
|
|
@@ -952,13 +1124,16 @@ const uiCatalog = defineUiCatalog({
|
|
|
952
1124
|
{
|
|
953
1125
|
name: "Chart",
|
|
954
1126
|
group: "data",
|
|
955
|
-
description: "A bar, line, area or
|
|
1127
|
+
description: "A bar, line, area, pie, scatter, stacked or dual-axis chart over labelled series.",
|
|
956
1128
|
props: z.object({
|
|
957
1129
|
type: z.enum([
|
|
958
1130
|
"bar",
|
|
959
1131
|
"line",
|
|
960
1132
|
"area",
|
|
961
|
-
"pie"
|
|
1133
|
+
"pie",
|
|
1134
|
+
"scatter",
|
|
1135
|
+
"stacked-bar",
|
|
1136
|
+
"dual-axis"
|
|
962
1137
|
]),
|
|
963
1138
|
title: z.string().optional(),
|
|
964
1139
|
labels: z.array(z.string()).min(1),
|
|
@@ -1160,6 +1335,160 @@ const uiCatalog = defineUiCatalog({
|
|
|
1160
1335
|
}
|
|
1161
1336
|
}
|
|
1162
1337
|
},
|
|
1338
|
+
{
|
|
1339
|
+
name: "Icon",
|
|
1340
|
+
group: "content",
|
|
1341
|
+
description: "A small semantic icon, picked from the catalog whitelist. Use it to mark an item inside a list or next to a heading, never on its own as decoration. Names outside the whitelist are dropped by the renderer, so choose the closest listed name instead of inventing one.",
|
|
1342
|
+
props: z.object({
|
|
1343
|
+
name: iconName,
|
|
1344
|
+
palette: palette.optional()
|
|
1345
|
+
}),
|
|
1346
|
+
example: {
|
|
1347
|
+
component: "Icon",
|
|
1348
|
+
props: {
|
|
1349
|
+
name: "check",
|
|
1350
|
+
palette: "emerald"
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
name: "Image",
|
|
1356
|
+
group: "content",
|
|
1357
|
+
description: "An image the host already serves. `src` must be same-origin or a data URL — remote URLs are rejected by policy, so never link to an image you found on the web. `alt` is required: it is what a screen reader and a later model read instead of the pixels.",
|
|
1358
|
+
props: z.object({
|
|
1359
|
+
src: imageSrc,
|
|
1360
|
+
alt: z.string(),
|
|
1361
|
+
ratio: z.enum([
|
|
1362
|
+
"auto",
|
|
1363
|
+
"1:1",
|
|
1364
|
+
"4:3",
|
|
1365
|
+
"16:9"
|
|
1366
|
+
]).default("auto")
|
|
1367
|
+
}),
|
|
1368
|
+
example: {
|
|
1369
|
+
component: "Image",
|
|
1370
|
+
props: {
|
|
1371
|
+
src: "/chart.png",
|
|
1372
|
+
alt: "Revenue by region",
|
|
1373
|
+
ratio: "16:9"
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
name: "Quote",
|
|
1379
|
+
group: "content",
|
|
1380
|
+
description: "Someone else's words, set apart from your own. Use it when you are repeating a customer comment, a policy sentence or a source document, so the reader can tell it apart from your summary. Do not use it for emphasis.",
|
|
1381
|
+
props: z.object({
|
|
1382
|
+
text: z.string(),
|
|
1383
|
+
by: z.string().optional()
|
|
1384
|
+
}),
|
|
1385
|
+
example: {
|
|
1386
|
+
component: "Quote",
|
|
1387
|
+
props: {
|
|
1388
|
+
text: "Delivery was two days late.",
|
|
1389
|
+
by: "Order A-1002"
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
},
|
|
1393
|
+
{
|
|
1394
|
+
name: "Callout",
|
|
1395
|
+
group: "content",
|
|
1396
|
+
description: "A side note the reader may want but does not have to act on — background, a caveat, a definition. Use `Alert` instead when it is a state they must react to, and plain `Text` when it belongs in the flow of the answer.",
|
|
1397
|
+
props: z.object({
|
|
1398
|
+
title: z.string().optional(),
|
|
1399
|
+
text: z.string(),
|
|
1400
|
+
palette: palette.optional()
|
|
1401
|
+
}),
|
|
1402
|
+
example: {
|
|
1403
|
+
component: "Callout",
|
|
1404
|
+
props: {
|
|
1405
|
+
title: "How this is counted",
|
|
1406
|
+
text: "Refunds are excluded."
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
},
|
|
1410
|
+
{
|
|
1411
|
+
name: "KeyValue",
|
|
1412
|
+
group: "data",
|
|
1413
|
+
description: "Facts about one thing, as label/value pairs. Use it when a `Table` would have a single row, for example the details of one order. For several comparable rows use `Table`, and for one headline number use `Metric`.",
|
|
1414
|
+
props: z.object({ items: z.array(z.object({
|
|
1415
|
+
label: z.string(),
|
|
1416
|
+
value: z.string()
|
|
1417
|
+
})).min(1) }),
|
|
1418
|
+
example: {
|
|
1419
|
+
component: "KeyValue",
|
|
1420
|
+
props: { items: [{
|
|
1421
|
+
label: "Status",
|
|
1422
|
+
value: "Shipped"
|
|
1423
|
+
}, {
|
|
1424
|
+
label: "Carrier",
|
|
1425
|
+
value: "DHL"
|
|
1426
|
+
}] }
|
|
1427
|
+
}
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
name: "Gauge",
|
|
1431
|
+
group: "data",
|
|
1432
|
+
description: "One value read against a range — capacity used, budget spent, a score out of a maximum. Give `min` and `max` when the range is not 0-100. Use `Progress` for how far a task has come, and `Metric` when there is no range to compare against.",
|
|
1433
|
+
props: z.object({
|
|
1434
|
+
label: z.string().optional(),
|
|
1435
|
+
value: z.number(),
|
|
1436
|
+
min: z.number().default(0),
|
|
1437
|
+
max: z.number().default(100),
|
|
1438
|
+
tone: z.enum([
|
|
1439
|
+
"neutral",
|
|
1440
|
+
"success",
|
|
1441
|
+
"warning"
|
|
1442
|
+
]).default("neutral")
|
|
1443
|
+
}),
|
|
1444
|
+
example: {
|
|
1445
|
+
component: "Gauge",
|
|
1446
|
+
props: {
|
|
1447
|
+
label: "Disk used",
|
|
1448
|
+
value: 82,
|
|
1449
|
+
tone: "warning"
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
name: "Accordion",
|
|
1455
|
+
group: "layout",
|
|
1456
|
+
description: "Sections the reader opens one at a time. Use it for a long report where most readers only need one part, so the answer stays scannable instead of becoming a wall of text. Use `Tabs` when the sections are alternatives rather than a sequence, and plain `Card`s when there are only two or three short ones.",
|
|
1457
|
+
props: z.object({ items: z.array(z.object({
|
|
1458
|
+
title: z.string(),
|
|
1459
|
+
text: z.string()
|
|
1460
|
+
})).min(1) }),
|
|
1461
|
+
example: {
|
|
1462
|
+
component: "Accordion",
|
|
1463
|
+
props: { items: [{
|
|
1464
|
+
title: "Method",
|
|
1465
|
+
text: "Sampled hourly."
|
|
1466
|
+
}, {
|
|
1467
|
+
title: "Caveats",
|
|
1468
|
+
text: "Week 12 is partial."
|
|
1469
|
+
}] }
|
|
1470
|
+
}
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
name: "Carousel",
|
|
1474
|
+
group: "layout",
|
|
1475
|
+
description: "Slides the reader scrolls through sideways — an itinerary day by day, a set of options, a small gallery. Each slide carries its own title and optional image, and only one is in view at a time. Use `Grid` when the reader should compare the items side by side instead of one at a time.",
|
|
1476
|
+
props: z.object({ items: z.array(z.object({
|
|
1477
|
+
title: z.string(),
|
|
1478
|
+
text: z.string().optional(),
|
|
1479
|
+
src: z.string().optional()
|
|
1480
|
+
})).min(1) }),
|
|
1481
|
+
example: {
|
|
1482
|
+
component: "Carousel",
|
|
1483
|
+
props: { items: [{
|
|
1484
|
+
title: "Day 1",
|
|
1485
|
+
text: "Arrival"
|
|
1486
|
+
}, {
|
|
1487
|
+
title: "Day 2",
|
|
1488
|
+
text: "Old town"
|
|
1489
|
+
}] }
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1163
1492
|
{
|
|
1164
1493
|
name: "Alert",
|
|
1165
1494
|
group: "feedback",
|
|
@@ -1230,10 +1559,12 @@ const UI_CATALOG_GROUPS = {
|
|
|
1230
1559
|
};
|
|
1231
1560
|
/**
|
|
1232
1561
|
* catalog 系统提示的体积上限(UTF-8 字节,FR-6.6)。
|
|
1233
|
-
*
|
|
1234
|
-
*
|
|
1562
|
+
*
|
|
1563
|
+
* **0.11.0 起只是 `CATALOG_PROMPT_MAX` 的别名**:旧值 20 480 相对实际 7 788 宽了 2.6 倍,
|
|
1564
|
+
* 拦不住任何东西;且它只量提示词,漏掉了 `render_ui` 入参 Schema 那约两分之一的成本。
|
|
1565
|
+
* @deprecated 用 `CATALOG_PROMPT_MAX` / `CATALOG_SCHEMA_MAX`(`./budget`)。
|
|
1235
1566
|
*/
|
|
1236
|
-
const UI_CATALOG_PROMPT_BUDGET_BYTES =
|
|
1567
|
+
const UI_CATALOG_PROMPT_BUDGET_BYTES = CATALOG_PROMPT_MAX;
|
|
1237
1568
|
/** A2UI 协议公共类型(与 @a2ui/web_core 的 common-types 同一套定义) */
|
|
1238
1569
|
const A2UI_COMMON_TYPES = "https://a2ui.org/specification/v0_9/common_types.json";
|
|
1239
1570
|
const CHILD_LIST_REF = `${A2UI_COMMON_TYPES}#/$defs/ChildList`;
|
|
@@ -1606,6 +1937,18 @@ function chartToTable(chart) {
|
|
|
1606
1937
|
rows: chart.labels.map((label, li) => [label, ...chart.series.map((s) => s.data[li] ?? null)])
|
|
1607
1938
|
};
|
|
1608
1939
|
}
|
|
1940
|
+
/**
|
|
1941
|
+
* `Gauge` 的百分比换算(0.11.0 分册 19 FR-19.2)。
|
|
1942
|
+
*
|
|
1943
|
+
* catalog 的 React `SpecGauge` 与文档面的预置组件都读这一份:
|
|
1944
|
+
* 换算里有两个容易各写各的判断——`max === min` 时不能除零、越界值要夹紧——
|
|
1945
|
+
* 两处各写一遍就会在边界值上给出不同的条。
|
|
1946
|
+
*/
|
|
1947
|
+
function gaugePercent(value, min, max) {
|
|
1948
|
+
const span = max - min;
|
|
1949
|
+
if (span <= 0) return 0;
|
|
1950
|
+
return Math.max(0, Math.min(100, (value - min) / span * 100));
|
|
1951
|
+
}
|
|
1609
1952
|
/** RenderBlock → DOM(markdown/json/table/image/file/chart 六种) */
|
|
1610
1953
|
function renderBlocks(container, blocks, doc) {
|
|
1611
1954
|
for (const block of blocks) switch (block.type) {
|
|
@@ -1683,6 +2026,154 @@ function renderRenderResult(request, doc) {
|
|
|
1683
2026
|
renderBlocks(root, request.blocks, doc);
|
|
1684
2027
|
return root;
|
|
1685
2028
|
}
|
|
2029
|
+
/**
|
|
2030
|
+
* viewer 的宿主预置组件(0.11.0 分册 18 FR-18.5 / 分册 19)。
|
|
2031
|
+
*
|
|
2032
|
+
* 技能**不写 JS**:文档 HTML 里只留占位,由这里挂载真实组件。
|
|
2033
|
+
* 交互只落在图表上——表格排序的例子已作废(分册 29 §2 登记 #13)。
|
|
2034
|
+
*
|
|
2035
|
+
* 三条硬约束:
|
|
2036
|
+
* - props 用 **catalog 自己的 zod schema** 解析,同形因此是机制上成立的,
|
|
2037
|
+
* 不是靠一句「记得同步」的注释(FR-19.2);
|
|
2038
|
+
* - 值一律经 `textContent` 落地,**永不** `innerHTML`(FR-19.4 / AC-19.5);
|
|
2039
|
+
* - 白名单外的名字只渲染降级提示,**不触发任何按名 import**(FR-19.3)。
|
|
2040
|
+
*/
|
|
2041
|
+
/** 占位元素的属性名,模板与挂载方共用(FR-19.1 的契约) */
|
|
2042
|
+
const VIEWER_COMPONENT_ATTR = "data-webskill-component";
|
|
2043
|
+
const VIEWER_PROPS_ATTR = "data-webskill-props";
|
|
2044
|
+
/** 降级提示的标记;模板 CSS 与判据都认这个属性,不认某个 class 名 */
|
|
2045
|
+
const VIEWER_FALLBACK_ATTR = "data-webskill-fallback";
|
|
2046
|
+
/** 文档面可用的组件白名单(FR-19.3)。字面量数组——不接受任何按名动态加载 */
|
|
2047
|
+
const DOCUMENT_COMPONENTS = [
|
|
2048
|
+
"Chart",
|
|
2049
|
+
"Table",
|
|
2050
|
+
"Metric",
|
|
2051
|
+
"Gauge",
|
|
2052
|
+
"KeyValue"
|
|
2053
|
+
];
|
|
2054
|
+
const WHITELIST = new Set(DOCUMENT_COMPONENTS);
|
|
2055
|
+
/**
|
|
2056
|
+
* 扫描 `root` 下的占位并挂载预置组件。
|
|
2057
|
+
*
|
|
2058
|
+
* 逐个占位独立处理:一个坏占位只降级它自己,不会让整篇文档变空白。
|
|
2059
|
+
*/
|
|
2060
|
+
function mountViewerComponents(root) {
|
|
2061
|
+
const handles = [];
|
|
2062
|
+
for (const node of Array.from(root.querySelectorAll(`[${VIEWER_COMPONENT_ATTR}]`))) {
|
|
2063
|
+
const element = node;
|
|
2064
|
+
const name = element.getAttribute("data-webskill-component") ?? "";
|
|
2065
|
+
if (!WHITELIST.has(name)) {
|
|
2066
|
+
fallback(element, `Unsupported component "${name}"`);
|
|
2067
|
+
continue;
|
|
2068
|
+
}
|
|
2069
|
+
const props = readProps(element, name);
|
|
2070
|
+
if (!props) continue;
|
|
2071
|
+
const handle = render(element, name, props);
|
|
2072
|
+
if (handle) handles.push(handle);
|
|
2073
|
+
}
|
|
2074
|
+
return { dispose: () => {
|
|
2075
|
+
for (const handle of handles) handle.dispose();
|
|
2076
|
+
handles.length = 0;
|
|
2077
|
+
} };
|
|
2078
|
+
}
|
|
2079
|
+
/** 用 catalog 的 schema 解析:多余字段被丢弃,缺省值与 catalog 完全一致 */
|
|
2080
|
+
function readProps(element, name) {
|
|
2081
|
+
const raw = element.getAttribute(VIEWER_PROPS_ATTR);
|
|
2082
|
+
if (raw === null || raw === "") {
|
|
2083
|
+
fallback(element, `Component "${name}" is missing ${VIEWER_PROPS_ATTR}`);
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
let parsed;
|
|
2087
|
+
try {
|
|
2088
|
+
parsed = JSON.parse(raw);
|
|
2089
|
+
} catch {
|
|
2090
|
+
fallback(element, `Component "${name}" has invalid props JSON`);
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
const schema = uiCatalog.component(name)?.props;
|
|
2094
|
+
if (!schema) {
|
|
2095
|
+
fallback(element, `Component "${name}" is not declared in the catalog`);
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
const result = schema.safeParse(parsed);
|
|
2099
|
+
if (!result.success) {
|
|
2100
|
+
fallback(element, `Component "${name}" has props the catalog rejects`);
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
return result.data;
|
|
2104
|
+
}
|
|
2105
|
+
function render(element, name, props) {
|
|
2106
|
+
const doc = element.ownerDocument;
|
|
2107
|
+
element.replaceChildren();
|
|
2108
|
+
switch (name) {
|
|
2109
|
+
case "Chart": {
|
|
2110
|
+
const handle = mountEchart(element);
|
|
2111
|
+
handle.setChart(chartSpecFromProps(props));
|
|
2112
|
+
return handle;
|
|
2113
|
+
}
|
|
2114
|
+
case "Table":
|
|
2115
|
+
renderBlocks(element, [{
|
|
2116
|
+
type: "table",
|
|
2117
|
+
columns: props["columns"],
|
|
2118
|
+
rows: props["rows"]
|
|
2119
|
+
}], doc);
|
|
2120
|
+
return;
|
|
2121
|
+
case "Metric": {
|
|
2122
|
+
append(doc, element, "span", String(props["label"]), { "data-webskill-metric": "label" });
|
|
2123
|
+
append(doc, element, "strong", String(props["value"]), { "data-webskill-metric": "value" });
|
|
2124
|
+
const change = props["change"];
|
|
2125
|
+
if (typeof change === "string") append(doc, element, "span", change, {
|
|
2126
|
+
"data-webskill-metric": "change",
|
|
2127
|
+
"data-trend": String(props["trend"] ?? "neutral")
|
|
2128
|
+
});
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
case "Gauge": {
|
|
2132
|
+
const value = props["value"];
|
|
2133
|
+
const min = props["min"];
|
|
2134
|
+
const max = props["max"];
|
|
2135
|
+
const label = typeof props["label"] === "string" ? props["label"] : "";
|
|
2136
|
+
element.setAttribute("data-tone", String(props["tone"]));
|
|
2137
|
+
if (label !== "") append(doc, element, "span", label, { "data-webskill-gauge": "label" });
|
|
2138
|
+
const fill = append(doc, append(doc, element, "div", "", {
|
|
2139
|
+
role: "meter",
|
|
2140
|
+
"aria-label": label === "" ? "Gauge" : label,
|
|
2141
|
+
"aria-valuenow": String(value),
|
|
2142
|
+
"aria-valuemin": String(min),
|
|
2143
|
+
"aria-valuemax": String(max),
|
|
2144
|
+
"data-webskill-gauge": "track"
|
|
2145
|
+
}), "div", "", { "data-webskill-gauge": "fill" });
|
|
2146
|
+
fill.style.width = `${gaugePercent(value, min, max)}%`;
|
|
2147
|
+
append(doc, element, "span", String(value), { "data-webskill-gauge": "value" });
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
case "KeyValue": {
|
|
2151
|
+
const dl = append(doc, element, "dl", "", {});
|
|
2152
|
+
for (const item of props["items"]) {
|
|
2153
|
+
append(doc, dl, "dt", item.label, {});
|
|
2154
|
+
append(doc, dl, "dd", item.value, {});
|
|
2155
|
+
}
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
function append(doc, parent, tag, text, attrs) {
|
|
2161
|
+
const el = doc.createElement(tag);
|
|
2162
|
+
for (const [key, value] of Object.entries(attrs)) el.setAttribute(key, value);
|
|
2163
|
+
if (text !== "") el.textContent = text;
|
|
2164
|
+
parent.appendChild(el);
|
|
2165
|
+
return el;
|
|
2166
|
+
}
|
|
2167
|
+
/**
|
|
2168
|
+
* 可见降级(AC-19.4)。
|
|
2169
|
+
*
|
|
2170
|
+
* 静默留空是最坏的结果:文档看着「渲染成功」,作者却不知道自己写错了组件名。
|
|
2171
|
+
* 原有内容整体替换,避免半截渲染与提示并存。
|
|
2172
|
+
*/
|
|
2173
|
+
function fallback(element, reason) {
|
|
2174
|
+
element.setAttribute(VIEWER_FALLBACK_ATTR, "true");
|
|
2175
|
+
element.textContent = reason;
|
|
2176
|
+
}
|
|
1686
2177
|
/** 最小样式(BEM class,应用可整体覆写) */
|
|
1687
2178
|
const WEBSKILL_STYLES_CSS = `
|
|
1688
2179
|
.webskill-form { display: block; padding: 12px; border: 1px solid #d0d0d0; border-radius: 8px; font-family: system-ui, sans-serif; max-width: 420px; }
|
|
@@ -1886,7 +2377,7 @@ var WebFormBridge = class {
|
|
|
1886
2377
|
input = textarea;
|
|
1887
2378
|
} else {
|
|
1888
2379
|
const textInput = doc.createElement("input");
|
|
1889
|
-
textInput.type = control.control === "number" ? "number" : "text";
|
|
2380
|
+
textInput.type = control.control === "number" ? "number" : control.control === "date" ? "date" : "text";
|
|
1890
2381
|
textInput.className = "webskill-form__input";
|
|
1891
2382
|
if (control.defaultValue !== void 0) textInput.value = String(control.defaultValue);
|
|
1892
2383
|
input = textInput;
|
|
@@ -2433,7 +2924,8 @@ const FIELD_TYPE = {
|
|
|
2433
2924
|
select: "select",
|
|
2434
2925
|
textarea: "textarea",
|
|
2435
2926
|
file: "file",
|
|
2436
|
-
password: "password"
|
|
2927
|
+
password: "password",
|
|
2928
|
+
date: "date"
|
|
2437
2929
|
};
|
|
2438
2930
|
function toOptions(options) {
|
|
2439
2931
|
return (options ?? []).map((option) => ({
|
|
@@ -2873,7 +3365,7 @@ function fromA2uiSpecAction(event) {
|
|
|
2873
3365
|
*/
|
|
2874
3366
|
async function loadWebSkillLitCatalog() {
|
|
2875
3367
|
try {
|
|
2876
|
-
const { webskillLitCatalog } = await import("./webskillLitCatalog-
|
|
3368
|
+
const { webskillLitCatalog } = await import("./webskillLitCatalog-DME6PBkV-CmYNLlIT.js");
|
|
2877
3369
|
return webskillLitCatalog();
|
|
2878
3370
|
} catch (cause) {
|
|
2879
3371
|
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);
|
|
@@ -2898,4 +3390,4 @@ async function loadOpenUiPeers() {
|
|
|
2898
3390
|
}
|
|
2899
3391
|
|
|
2900
3392
|
//#endregion
|
|
2901
|
-
export {
|
|
3393
|
+
export { uiPreset as $, ensureStyles as A, mountViewerComponents as B, applySuggestion as C, collectSpecActions as D, collectScopedValues as E, fromVercelToolResult as F, renderRenderResult as G, qualifyFieldName as H, gaugePercent as I, toA2uiSurfaceAction as J, resolveColumnWidths as K, interactionToUiSpec as L, fromA2uiSpecAction as M, fromA2uiSurfaceAction as N, collectValues as O, fromUiSurfaceActionDispatch as P, toVercelToolInvocation as Q, loadOpenUiPeers as R, WebFormBridge as S, uiCatalog as St, collectFormScopes as T, renderBlocks as U, normalizeColumnWidths as V, renderMiniChart as W, toOpenUiSpecLang as X, toJsonRenderSpec as Y, toUiSurfaceActionDispatch as Z, VIEWER_FALLBACK_ATTR as _, mountEchart as _t, A2UI_VERSION as a, DEFAULT_INTERACTION_TEXTS as at, WEBSKILL_STYLES_CSS as b, resolveSurfaceFormTexts as bt, DOCUMENT_COMPONENTS as c, UI_CATALOG_GROUPS as ct, SPEC_TABLE_MIN_COLUMN_VAR as d, a2uiComponentSchema as dt, A2UI_CHILDREN_PROP as et, SPEC_TABLE_MIN_COLUMN_WIDTH as f, a2uiComponentShapes as ft, VIEWER_COMPONENT_ATTR as g, interactionToFormModel as gt, VERCEL_INTERACTION_TOOL_NAME as h, defineUiCatalog as ht, A2UI_SURFACE_ACTION as i, CATALOG_SCHEMA_MAX as it, evaluateFieldCondition as j, createUiCatalogToolSource as k, MAX_CONDITION_DEPTH as l, UI_CATALOG_PROMPT_BUDGET_BYTES as lt, UI_PRESET_NAMES as m, chartSpecFromProps as mt, A2UI_SPEC_ACTION as n, CATALOG_BUDGET_STAGE as nt, CHART_PALETTE as o, DEFAULT_SURFACE_FORM_TEXTS as ot, UI_PRESETS as p, buildA2uiCatalogDefinition as pt, toA2uiSpecMessages as q, A2UI_SPEC_FORM_PATH as r, CATALOG_PROMPT_MAX as rt, DESCRIBE_UI_PRESET_TOOL as s, PLANNED_INCREMENT as st, A2UI_BASIC_CATALOG_ID as t, A2UI_COMMON_TYPES as tt, RENDER_UI_TOOL as u, WEBSKILL_A2UI_CATALOG_ID as ut, VIEWER_PROPS_ATTR as v, renderMiniMarkdown as vt, chartToTable as w, WEBSKILL_SURFACE_ACTION as x, shapeInteractionValue as xt, VercelUiBridge as y, resolveInteractionTexts as yt, loadWebSkillLitCatalog as z };
|