@webskill/sdk 0.2.8 → 0.4.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/README.md +42 -0
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +4 -9
- package/dist/{catalogComponents-C_V39rbF-BOHveMWa.js → catalogComponents-KsujmL4b-Clx1kCnU.js} +278 -122
- package/dist/client-BCM6Z3yq-qUQNkKrK.js +7787 -0
- package/dist/{dist-BQzncxXg.js → dist-8oQRa8Xz.js} +212 -13
- package/dist/{dist-B9VLwOME.js → dist-C-Sh0MDU.js} +1019 -317
- package/dist/{dist-CtBLBbEz.js → dist-D9Lcn5Pp.js} +528 -838
- package/dist/governance.d.ts +46 -11
- package/dist/governance.js +152 -25
- package/dist/{index-7DVaZJU7.d.ts → index-CHXxDccV.d.ts} +62 -144
- package/dist/{index-QrHtAudz.d.ts → index-DLfR2Y6I.d.ts} +412 -21
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -3
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +2 -2
- package/dist/memoryArtifactStore-BtOeB_hm-tj3fC5ip.js +78 -0
- package/dist/node.d.ts +297 -4
- package/dist/node.js +281 -7
- package/dist/{openUiLibrary-B8-Cvou9-D3RsU2EB.js → openUiLibrary-YLS-cxyT-C96jWDQq.js} +6 -5
- package/dist/skillVersionStore-uyefLPR1-DXOzbksv.d.ts +158 -0
- package/dist/stdio-CFMoANJJ-BxrTeXh7.js +31 -0
- package/dist/{testing-BUoXvm1u.js → testing-DDCJWvgA.js} +8 -6
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +2 -2
- package/dist/{types-AmKCKJn_-BogJPQHU.d.ts → types-7Wcg--Vh-1YlQ4jF9.d.ts} +219 -70
- package/dist/types-WovEf4ED-CZSDiiBU.js +6215 -0
- package/dist/ui-react.d.ts +329 -18
- package/dist/ui-react.js +3715 -3464
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +1 -1
- package/dist/ui.d.ts +4 -3
- package/dist/ui.js +3 -3
- package/dist/{webskillLitCatalog-CNaUpasU-CfSRvqCZ.js → webskillLitCatalog-CSTbhBe_-CYIs5BX8.js} +312 -122
- package/package.json +4 -7
- package/dist/jsonRenderRegistry-9GrWP_hE-CQY8bT9w.js +0 -2468
- package/dist/memoryArtifactStore-C9lFVqPF-yFz6yJj0.js +0 -48
- package/dist/skillVersionStore-B7rGjtMi-BgnQho9v.d.ts +0 -365
package/README.md
CHANGED
|
@@ -49,6 +49,12 @@ npm install @webskill/sdk
|
|
|
49
49
|
React/Vue integrations require their respective peer dependencies
|
|
50
50
|
(`react` + `react-dom`, or `vue`), which are optional.
|
|
51
51
|
|
|
52
|
+
`zod` ships as a direct dependency: the `@webskill/sdk/ui` catalog references it
|
|
53
|
+
in its static import graph and in its public types, so declaring it optional
|
|
54
|
+
would misrepresent the artifact. Hosts do not need to install it themselves.
|
|
55
|
+
`tar`, `oxc-parser` and `@modelcontextprotocol/sdk` remain optional peers —
|
|
56
|
+
they are loaded lazily and only when the corresponding API is called.
|
|
57
|
+
|
|
52
58
|
## Requirements
|
|
53
59
|
|
|
54
60
|
- Node.js >= 22.18 (native type stripping for `.ts` skill scripts)
|
|
@@ -214,6 +220,42 @@ import { MockLlmClient, MockUiBridge, InMemoryStore, MemoryArtifactStore } from
|
|
|
214
220
|
const llm = new MockLlmClient([{ content: 'deterministic answer' }]);
|
|
215
221
|
```
|
|
216
222
|
|
|
223
|
+
## `allowed-tools`
|
|
224
|
+
|
|
225
|
+
A skill may narrow the tool surface it needs by declaring `allowed-tools` in its
|
|
226
|
+
`SKILL.md` frontmatter. Three kinds of tools can be listed:
|
|
227
|
+
|
|
228
|
+
```yaml
|
|
229
|
+
allowed-tools:
|
|
230
|
+
- render # a script of this skill (scripts/render.ts)
|
|
231
|
+
- endpoint:github/create_issue # one tool of an MCP endpoint
|
|
232
|
+
- endpoint:github/* # every tool of an MCP endpoint
|
|
233
|
+
- mcp#read_file # one page-provided (WebMCP) tool
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Matching rules:
|
|
237
|
+
|
|
238
|
+
- Only whole-entry matches and a trailing `/*` are supported. Wildcards in any
|
|
239
|
+
other position are not, and a bare `endpoint:github` is **not** a prefix
|
|
240
|
+
wildcard — it simply matches nothing.
|
|
241
|
+
- A bare identifier means a script of the declaring skill only. It never grants
|
|
242
|
+
access to another skill's script of the same name.
|
|
243
|
+
|
|
244
|
+
When several skills are active at the same time:
|
|
245
|
+
|
|
246
|
+
| Situation | Result |
|
|
247
|
+
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
248
|
+
| No active skill declares `allowed-tools` | Unrestricted |
|
|
249
|
+
| At least one active skill declares it | A tool is allowed if it matches any declaring skill's list, **or** if some active skill declares nothing |
|
|
250
|
+
|
|
251
|
+
Enforcement level differs per tool kind in 0.3.0:
|
|
252
|
+
|
|
253
|
+
- **Scripts** — enforced since 0.0.5: a script outside the list is not
|
|
254
|
+
registered, and calling it by name returns `TOOL_NOT_FOUND`.
|
|
255
|
+
- **MCP / WebMCP tools** — 0.3.0 only records a `run.warning` naming the tool
|
|
256
|
+
and the entry to add, and still allows the call. This becomes a rejection in
|
|
257
|
+
0.4.0, so treat the warning as a build error in your own pipeline.
|
|
258
|
+
|
|
217
259
|
## Stability
|
|
218
260
|
|
|
219
261
|
This package follows semver starting at 0.1.0. Public symbols carry JSDoc
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { D as ArchiveLimits, M as FileStat, N as FileSystemProvider, a as InteractionPolicy, c as LlmClient, et as SkillInstallSource, f as LlmResponse, gt as VerifyResult, it as SkillManifest, o as InteractionRequest, p as LlmStreamEvent, s as InteractionResponse, ut as SkillsLockfile, v as RenderResultRequest, y as UiBridge } from "./types-7Wcg--Vh-1YlQ4jF9.js";
|
|
2
|
+
import { Dt as ToolDefinition, It as WebSkillApi, V as NetworkPolicy, b as ExternalSkillProvider, d as ApprovalScope, f as BridgeCapabilities, gt as ScriptExecutor, h as BridgeResponse, ht as ScriptExecutionContext, kt as ToolResult, m as BridgeRequest, tn as parseBridgeRequest, x as ExternalToolSource, zt as bridgeError } from "./index-DLfR2Y6I.js";
|
|
3
3
|
//#region ../browser/dist/index.d.ts
|
|
4
4
|
//#region src/fs/featureDetection.d.ts
|
|
5
5
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as MockLlmClient } from "./testing-
|
|
1
|
+
import { A as parseSkillMarkdown, B as unzipWithLimits, H as verifyManifest, L as resolveInsideRoot, M as readResponseWithLimit, O as messageOf, T as isValidSkillName, V as validateSkills, _ as atomicWriteText, f as SkillDiscovery, h as assertRemoteUrlAllowed, j as parseSkillPackManifest, m as WebSkillError, o as SKILLS_LOCKFILE, r as MANIFEST_EXCLUDED_FILES, s as SKILL_MANIFEST_FILE, u as SKILL_PACK_FILE, v as buildCatalog, w as exportSkills, y as buildManifest } from "./dist-8oQRa8Xz.js";
|
|
2
|
+
import { H as normalizeToolContent, O as bridgeError, R as mergeCatalogEntries, U as normalizeToolError, W as parseBridgeRequest, _ as OpenAiCompatibleClient, d as FsRunSnapshotStore, i as AgentLoop, j as createWebSkillApi, l as FsArtifactStore, o as CapabilityApproval, u as FsMemoryStore, v as ProgressiveRouter, z as networkPolicyLibSource } from "./dist-C-Sh0MDU.js";
|
|
3
|
+
import { n as MockLlmClient } from "./testing-DDCJWvgA.js";
|
|
4
4
|
|
|
5
5
|
//#region ../browser/dist/index.js
|
|
6
6
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
|
@@ -702,10 +702,9 @@ var BrowserSkillManager = class {
|
|
|
702
702
|
}
|
|
703
703
|
}
|
|
704
704
|
async #createManifest(skillRoot, input) {
|
|
705
|
-
const excluded = /* @__PURE__ */ new Set([SKILL_MANIFEST_FILE, SKILLS_LOCKFILE]);
|
|
706
705
|
const files = [];
|
|
707
706
|
for (const rel of (await listFiles(this.#fs, skillRoot)).sort()) {
|
|
708
|
-
if (
|
|
707
|
+
if (MANIFEST_EXCLUDED_FILES.has(rel.split("/").pop() ?? "")) continue;
|
|
709
708
|
const content = await this.#fs.readBinary(`${skillRoot}/${rel}`);
|
|
710
709
|
files.push({
|
|
711
710
|
path: rel,
|
|
@@ -1411,10 +1410,6 @@ function startWorkerRuntimeHost(scope, overrides = {}) {
|
|
|
1411
1410
|
(async () => {
|
|
1412
1411
|
const snapshot = await state.snapshotStore.load(message.runId);
|
|
1413
1412
|
if (!snapshot) throw new WebSkillError("RUN_SNAPSHOT_NOT_FOUND", `No snapshot found for run "${message.runId}"`);
|
|
1414
|
-
if (snapshot.schemaVersion !== 1) {
|
|
1415
|
-
await state.snapshotStore.delete(message.runId);
|
|
1416
|
-
throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${message.runId}" has incompatible schemaVersion ${String(snapshot.schemaVersion)}`);
|
|
1417
|
-
}
|
|
1418
1413
|
if (Date.now() > Date.parse(snapshot.interactionExpiresAt)) {
|
|
1419
1414
|
await state.snapshotStore.delete(message.runId);
|
|
1420
1415
|
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
package/dist/{catalogComponents-C_V39rbF-BOHveMWa.js → catalogComponents-KsujmL4b-Clx1kCnU.js}
RENAMED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { a as __require, i as __reExport, n as __esmMin, o as __toCommonJS, r as __exportAll$2, s as __toESM$1, t as __commonJSMin$1 } from "./rolldown-runtime-BOF7iYI8.js";
|
|
2
|
+
import { m as WebSkillError } from "./dist-8oQRa8Xz.js";
|
|
3
|
+
import { G as mountEchart, U as chartSpecFromProps } from "./dist-D9Lcn5Pp.js";
|
|
2
4
|
import minpath$1 from "node:path";
|
|
3
5
|
import { fileURLToPath as urlToPath$1 } from "node:url";
|
|
4
6
|
import * as React$10 from "react";
|
|
@@ -99792,32 +99794,6 @@ const sidebarMenuButtonVariants = cva("peer/menu-button flex w-full cursor-point
|
|
|
99792
99794
|
size: "default"
|
|
99793
99795
|
}
|
|
99794
99796
|
});
|
|
99795
|
-
function Select({ options, value, onChange, placeholder, disabled = false, id, label, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, className = "" }) {
|
|
99796
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
99797
|
-
className: `relative inline-flex ${className}`,
|
|
99798
|
-
children: [/* @__PURE__ */ jsxs("select", {
|
|
99799
|
-
id,
|
|
99800
|
-
value,
|
|
99801
|
-
disabled,
|
|
99802
|
-
"aria-label": label,
|
|
99803
|
-
"aria-labelledby": ariaLabelledBy,
|
|
99804
|
-
"aria-describedby": ariaDescribedBy,
|
|
99805
|
-
onChange: (event) => onChange(event.target.value),
|
|
99806
|
-
className: "h-9 w-full cursor-pointer appearance-none rounded-lg border border-border bg-surface pr-8 pl-3 text-sm text-ink transition-colors hover:border-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
99807
|
-
children: [placeholder !== void 0 && /* @__PURE__ */ jsx("option", {
|
|
99808
|
-
value: "",
|
|
99809
|
-
disabled: true,
|
|
99810
|
-
children: placeholder
|
|
99811
|
-
}), options.map((option) => /* @__PURE__ */ jsx("option", {
|
|
99812
|
-
value: option.value,
|
|
99813
|
-
children: option.label
|
|
99814
|
-
}, option.value))]
|
|
99815
|
-
}), /* @__PURE__ */ jsx(ChevronDown, {
|
|
99816
|
-
className: "pointer-events-none absolute top-1/2 right-2.5 size-4 -translate-y-1/2 text-muted",
|
|
99817
|
-
"aria-hidden": true
|
|
99818
|
-
})]
|
|
99819
|
-
});
|
|
99820
|
-
}
|
|
99821
99797
|
/**
|
|
99822
99798
|
* @typedef Options
|
|
99823
99799
|
* Configuration for `stringify`.
|
|
@@ -120111,75 +120087,279 @@ const MarkdownText = memo(function MarkdownText({ className }) {
|
|
|
120111
120087
|
});
|
|
120112
120088
|
|
|
120113
120089
|
//#endregion
|
|
120114
|
-
//#region ../ui-react/dist/catalogComponents-
|
|
120115
|
-
|
|
120116
|
-
return {
|
|
120117
|
-
animation: false,
|
|
120118
|
-
title: chart.title ? {
|
|
120119
|
-
text: chart.title,
|
|
120120
|
-
left: "center",
|
|
120121
|
-
textStyle: { fontSize: 14 }
|
|
120122
|
-
} : void 0,
|
|
120123
|
-
tooltip: { trigger: chart.kind === "pie" ? "item" : "axis" },
|
|
120124
|
-
legend: { bottom: 0 },
|
|
120125
|
-
xAxis: chart.kind === "pie" ? void 0 : {
|
|
120126
|
-
type: "category",
|
|
120127
|
-
data: chart.labels
|
|
120128
|
-
},
|
|
120129
|
-
yAxis: chart.kind === "pie" ? void 0 : { type: "value" },
|
|
120130
|
-
series: chart.kind === "pie" ? chart.series.map((series) => ({
|
|
120131
|
-
type: "pie",
|
|
120132
|
-
name: series.name,
|
|
120133
|
-
data: chart.labels.map((label, index) => ({
|
|
120134
|
-
name: label,
|
|
120135
|
-
value: series.data[index] ?? 0
|
|
120136
|
-
}))
|
|
120137
|
-
})) : chart.series.map((series) => ({
|
|
120138
|
-
type: chart.kind,
|
|
120139
|
-
name: series.name,
|
|
120140
|
-
data: series.data
|
|
120141
|
-
}))
|
|
120142
|
-
};
|
|
120143
|
-
}
|
|
120144
|
-
/** echarts 懒加载挂载点:surface 图表与 catalog 声明树的 Chart 节点共用 */
|
|
120090
|
+
//#region ../ui-react/dist/catalogComponents-KsujmL4b.js
|
|
120091
|
+
/** echarts 挂载点:surface 图表与 catalog 声明树的 Chart 节点共用,实现在 `@webskill/ui`,与 A2UI 档同一份 */
|
|
120145
120092
|
function EChart({ chart }) {
|
|
120146
120093
|
const containerRef = useRef(null);
|
|
120147
|
-
const
|
|
120148
|
-
const [ready, setReady] = useState(false);
|
|
120094
|
+
const handleRef = useRef(void 0);
|
|
120149
120095
|
useEffect(() => {
|
|
120150
|
-
|
|
120151
|
-
|
|
120152
|
-
|
|
120153
|
-
|
|
120154
|
-
if (disposed || !container) return;
|
|
120155
|
-
const instance = echarts.init(container);
|
|
120156
|
-
chartRef.current = instance;
|
|
120157
|
-
setReady(true);
|
|
120158
|
-
if (typeof ResizeObserver !== "undefined") {
|
|
120159
|
-
observer = new ResizeObserver(() => instance.resize());
|
|
120160
|
-
observer.observe(container);
|
|
120161
|
-
}
|
|
120162
|
-
});
|
|
120096
|
+
const container = containerRef.current;
|
|
120097
|
+
if (!container) return;
|
|
120098
|
+
const handle = mountEchart(container);
|
|
120099
|
+
handleRef.current = handle;
|
|
120163
120100
|
return () => {
|
|
120164
|
-
|
|
120165
|
-
|
|
120166
|
-
chartRef.current?.dispose();
|
|
120167
|
-
chartRef.current = void 0;
|
|
120101
|
+
handle.dispose();
|
|
120102
|
+
handleRef.current = void 0;
|
|
120168
120103
|
};
|
|
120169
120104
|
}, []);
|
|
120170
120105
|
useEffect(() => {
|
|
120171
|
-
|
|
120172
|
-
|
|
120173
|
-
lazyUpdate: true
|
|
120174
|
-
});
|
|
120175
|
-
}, [ready, chart]);
|
|
120106
|
+
handleRef.current?.setChart(chart);
|
|
120107
|
+
}, [chart]);
|
|
120176
120108
|
return /* @__PURE__ */ jsx("div", {
|
|
120177
120109
|
className: "webskill-surface__chart",
|
|
120178
|
-
ref: containerRef
|
|
120179
|
-
"aria-label": chart.title
|
|
120110
|
+
ref: containerRef
|
|
120180
120111
|
});
|
|
120181
120112
|
}
|
|
120113
|
+
const REQUIRED_MESSAGE = "This field is required";
|
|
120182
120114
|
const str = (props, key, fallback = "") => typeof props[key] === "string" ? props[key] : fallback;
|
|
120115
|
+
function collectFields(node, out) {
|
|
120116
|
+
if (node.component === "Field" && typeof node.props?.["name"] === "string") out.push(node.props);
|
|
120117
|
+
for (const child of node.children ?? []) collectFields(child, out);
|
|
120118
|
+
}
|
|
120119
|
+
const isBlank = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
120120
|
+
function useSurfaceForm({ surfaceId, spec, actions, runId, draft, onAction, onDraftChange }) {
|
|
120121
|
+
const [touched, setTouched] = useState(() => ({ ...draft }));
|
|
120122
|
+
const [errors, setErrors] = useState({});
|
|
120123
|
+
const fields = useMemo(() => {
|
|
120124
|
+
const out = [];
|
|
120125
|
+
collectFields(spec, out);
|
|
120126
|
+
return out;
|
|
120127
|
+
}, [spec]);
|
|
120128
|
+
const values = {
|
|
120129
|
+
...useMemo(() => Object.fromEntries(fields.filter((field) => field["defaultValue"] !== void 0).map((field) => [String(field["name"]), field["defaultValue"]])), [fields]),
|
|
120130
|
+
...touched
|
|
120131
|
+
};
|
|
120132
|
+
const resolveActionId = (intent, nodeId) => nodeId ?? actions.find((action) => action.intent === intent)?.id ?? intent;
|
|
120133
|
+
const dispatch = (actionId, withValues) => {
|
|
120134
|
+
const declared = actions.find((action) => action.id === actionId);
|
|
120135
|
+
onAction?.({
|
|
120136
|
+
surfaceId,
|
|
120137
|
+
actionId,
|
|
120138
|
+
intent: declared?.intent ?? "select",
|
|
120139
|
+
...declared?.nonce ? { nonce: declared.nonce } : {},
|
|
120140
|
+
...withValues ? { value: { ...values } } : {}
|
|
120141
|
+
});
|
|
120142
|
+
};
|
|
120143
|
+
return {
|
|
120144
|
+
surfaceId,
|
|
120145
|
+
actions,
|
|
120146
|
+
values,
|
|
120147
|
+
errors,
|
|
120148
|
+
resolveActionId,
|
|
120149
|
+
setValue: (name, value) => {
|
|
120150
|
+
const next = {
|
|
120151
|
+
...touched,
|
|
120152
|
+
[name]: value
|
|
120153
|
+
};
|
|
120154
|
+
setTouched(next);
|
|
120155
|
+
setErrors(({ [name]: _cleared, ...rest }) => rest);
|
|
120156
|
+
if (runId) onDraftChange?.({
|
|
120157
|
+
runId,
|
|
120158
|
+
surfaceId,
|
|
120159
|
+
value: next
|
|
120160
|
+
});
|
|
120161
|
+
},
|
|
120162
|
+
emit: (actionId) => dispatch(actionId, false),
|
|
120163
|
+
submit: (intent, nodeId) => {
|
|
120164
|
+
const missing = fields.filter((field) => field["required"] === true && isBlank(values[String(field["name"])]));
|
|
120165
|
+
if (missing.length > 0) {
|
|
120166
|
+
setErrors(Object.fromEntries(missing.map((field) => [String(field["name"]), REQUIRED_MESSAGE])));
|
|
120167
|
+
return;
|
|
120168
|
+
}
|
|
120169
|
+
setErrors({});
|
|
120170
|
+
dispatch(resolveActionId(intent, nodeId), true);
|
|
120171
|
+
}
|
|
120172
|
+
};
|
|
120173
|
+
}
|
|
120174
|
+
/** catalog `Field` 的唯一控件实现:8 种 type 的取值语义在这里定死 */
|
|
120175
|
+
function SurfaceField({ form, props }) {
|
|
120176
|
+
const name = str(props, "name");
|
|
120177
|
+
const label = str(props, "label", name);
|
|
120178
|
+
const type = str(props, "type", "text");
|
|
120179
|
+
const required = props["required"] === true;
|
|
120180
|
+
const value = form.values[name];
|
|
120181
|
+
const error = form.errors[name];
|
|
120182
|
+
const errorId = error ? `webskill-surface-${form.surfaceId}-${name}-error` : void 0;
|
|
120183
|
+
const options = Array.isArray(props["options"]) ? props["options"] : [];
|
|
120184
|
+
/** 控件的机器可读锚点:宿主与测试据此定位字段,不依赖标签文案 */
|
|
120185
|
+
const anchor = {
|
|
120186
|
+
"data-webskill-surface-control": name,
|
|
120187
|
+
...error ? {
|
|
120188
|
+
"aria-invalid": true,
|
|
120189
|
+
"aria-describedby": errorId
|
|
120190
|
+
} : {}
|
|
120191
|
+
};
|
|
120192
|
+
const control = () => {
|
|
120193
|
+
if (type === "textarea") return /* @__PURE__ */ jsx(Textarea, {
|
|
120194
|
+
id: name,
|
|
120195
|
+
...anchor,
|
|
120196
|
+
invalid: Boolean(error),
|
|
120197
|
+
value: typeof value === "string" ? value : "",
|
|
120198
|
+
onChange: (event) => form.setValue(name, event.target.value)
|
|
120199
|
+
});
|
|
120200
|
+
if (type === "toggle") return /* @__PURE__ */ jsx(Switch, {
|
|
120201
|
+
id: name,
|
|
120202
|
+
...anchor,
|
|
120203
|
+
checked: value === true,
|
|
120204
|
+
onCheckedChange: (next) => form.setValue(name, next)
|
|
120205
|
+
});
|
|
120206
|
+
if (type === "select" || type === "multi-select") {
|
|
120207
|
+
const multiple = type === "multi-select";
|
|
120208
|
+
const indexOf = (candidate) => {
|
|
120209
|
+
const index = options.findIndex((option) => option.value === candidate);
|
|
120210
|
+
return index < 0 ? "" : String(index);
|
|
120211
|
+
};
|
|
120212
|
+
return /* @__PURE__ */ jsx("select", {
|
|
120213
|
+
id: name,
|
|
120214
|
+
...anchor,
|
|
120215
|
+
multiple,
|
|
120216
|
+
value: multiple ? Array.isArray(value) ? value.map(indexOf) : [] : indexOf(value),
|
|
120217
|
+
onChange: (event) => {
|
|
120218
|
+
const pick = (index) => options[Number(index)].value;
|
|
120219
|
+
form.setValue(name, multiple ? [...event.target.selectedOptions].map((option) => pick(option.value)) : pick(event.target.value));
|
|
120220
|
+
},
|
|
120221
|
+
children: options.map((option, index) => /* @__PURE__ */ jsx("option", {
|
|
120222
|
+
value: String(index),
|
|
120223
|
+
children: option.label
|
|
120224
|
+
}, index))
|
|
120225
|
+
});
|
|
120226
|
+
}
|
|
120227
|
+
if (type === "file") return /* @__PURE__ */ jsx("input", {
|
|
120228
|
+
id: name,
|
|
120229
|
+
...anchor,
|
|
120230
|
+
type: "file",
|
|
120231
|
+
onChange: (event) => form.setValue(name, event.target.files?.[0]?.name ?? "")
|
|
120232
|
+
});
|
|
120233
|
+
return /* @__PURE__ */ jsx(Input, {
|
|
120234
|
+
id: name,
|
|
120235
|
+
...anchor,
|
|
120236
|
+
invalid: Boolean(error),
|
|
120237
|
+
type: type === "number" ? "number" : type === "date" ? "date" : "text",
|
|
120238
|
+
value: typeof value === "string" || typeof value === "number" ? String(value) : "",
|
|
120239
|
+
onChange: (event) => form.setValue(name, type === "number" ? Number(event.target.value) : event.target.value)
|
|
120240
|
+
});
|
|
120241
|
+
};
|
|
120242
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
120243
|
+
className: "flex flex-col gap-1 text-sm",
|
|
120244
|
+
htmlFor: name,
|
|
120245
|
+
children: [
|
|
120246
|
+
/* @__PURE__ */ jsxs("span", {
|
|
120247
|
+
className: "text-ink",
|
|
120248
|
+
children: [label, required ? " *" : ""]
|
|
120249
|
+
}),
|
|
120250
|
+
control(),
|
|
120251
|
+
props["description"] ? /* @__PURE__ */ jsx("span", {
|
|
120252
|
+
className: "text-xs text-muted",
|
|
120253
|
+
children: str(props, "description")
|
|
120254
|
+
}) : null,
|
|
120255
|
+
error ? /* @__PURE__ */ jsx("small", {
|
|
120256
|
+
id: errorId,
|
|
120257
|
+
role: "alert",
|
|
120258
|
+
className: "text-xs text-destructive",
|
|
120259
|
+
children: error
|
|
120260
|
+
}) : null
|
|
120261
|
+
]
|
|
120262
|
+
});
|
|
120263
|
+
}
|
|
120264
|
+
/** catalog `Form` 的提交 / 取消按钮:提交带表单值,取消不带 */
|
|
120265
|
+
function SurfaceFormButtons({ form, props }) {
|
|
120266
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
120267
|
+
className: "flex gap-2",
|
|
120268
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
120269
|
+
type: "submit",
|
|
120270
|
+
size: "sm",
|
|
120271
|
+
"data-webskill-surface-action": form.resolveActionId("submit"),
|
|
120272
|
+
children: str(props, "submitLabel", "Submit")
|
|
120273
|
+
}), props["cancelLabel"] ? /* @__PURE__ */ jsx(Button, {
|
|
120274
|
+
type: "button",
|
|
120275
|
+
size: "sm",
|
|
120276
|
+
variant: "secondary",
|
|
120277
|
+
"data-webskill-surface-action": form.resolveActionId("cancel"),
|
|
120278
|
+
onClick: () => form.emit(form.resolveActionId("cancel")),
|
|
120279
|
+
children: str(props, "cancelLabel")
|
|
120280
|
+
}) : null]
|
|
120281
|
+
});
|
|
120282
|
+
}
|
|
120283
|
+
/** catalog `Button` 的唯一实现:submit 走带值提交,其余走裸动作 */
|
|
120284
|
+
function SurfaceButton({ form, props, nodeId }) {
|
|
120285
|
+
const intent = str(props, "action");
|
|
120286
|
+
const actionId = form.resolveActionId(intent, nodeId);
|
|
120287
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
120288
|
+
size: "sm",
|
|
120289
|
+
type: "button",
|
|
120290
|
+
disabled: props["disabled"] === true,
|
|
120291
|
+
"data-webskill-surface-action": actionId,
|
|
120292
|
+
onClick: () => intent === "submit" ? form.submit(intent, nodeId) : form.emit(actionId),
|
|
120293
|
+
children: str(props, "label")
|
|
120294
|
+
});
|
|
120295
|
+
}
|
|
120296
|
+
/**
|
|
120297
|
+
* json-render 与 OpenUI 两档的 Renderer 都只把 props 交给组件实现,拿不到节点 id,
|
|
120298
|
+
* 也拿不到表单状态,因此表单值与动作回传统一走这个 Context。
|
|
120299
|
+
* 缺省值故意不给空实现:没包 Provider 就是宿主接线错了,必须失败而不是“点了没反应”。
|
|
120300
|
+
*/
|
|
120301
|
+
const CatalogSurfaceContext = createContext(void 0);
|
|
120302
|
+
function useCatalogSurfaceForm() {
|
|
120303
|
+
const form = useContext(CatalogSurfaceContext);
|
|
120304
|
+
if (!form) throw new WebSkillError("UI_UNAVAILABLE", "Catalog surface components must be rendered inside a CatalogSurfaceProvider");
|
|
120305
|
+
return form;
|
|
120306
|
+
}
|
|
120307
|
+
/** 非 native 档的表单状态宿主:状态必须在 Renderer 之上,否则每次重渲染都会丢值 */
|
|
120308
|
+
function CatalogSurfaceProvider({ surfaceId, spec, actions, onAction, children }) {
|
|
120309
|
+
const form = useSurfaceForm({
|
|
120310
|
+
surfaceId,
|
|
120311
|
+
spec,
|
|
120312
|
+
actions,
|
|
120313
|
+
onAction
|
|
120314
|
+
});
|
|
120315
|
+
return /* @__PURE__ */ jsx(CatalogSurfaceContext.Provider, {
|
|
120316
|
+
value: form,
|
|
120317
|
+
children
|
|
120318
|
+
});
|
|
120319
|
+
}
|
|
120320
|
+
/** 把实现表的条目包成真正的 React 组件,使其内部的 hook 调用处于自己的渲染周期 */
|
|
120321
|
+
function CatalogNode({ name, props, children }) {
|
|
120322
|
+
const Impl = catalogComponentImpls[name];
|
|
120323
|
+
if (!Impl) return null;
|
|
120324
|
+
return /* @__PURE__ */ jsx(Impl, {
|
|
120325
|
+
props,
|
|
120326
|
+
children
|
|
120327
|
+
});
|
|
120328
|
+
}
|
|
120329
|
+
function CatalogForm({ props, children }) {
|
|
120330
|
+
const form = useCatalogSurfaceForm();
|
|
120331
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
120332
|
+
className: "flex flex-col gap-3",
|
|
120333
|
+
noValidate: true,
|
|
120334
|
+
onSubmit: (event) => {
|
|
120335
|
+
event.preventDefault();
|
|
120336
|
+
form.submit("submit");
|
|
120337
|
+
},
|
|
120338
|
+
children: [
|
|
120339
|
+
props["title"] ? /* @__PURE__ */ jsx("h3", {
|
|
120340
|
+
className: "text-sm font-semibold text-ink",
|
|
120341
|
+
children: str(props, "title")
|
|
120342
|
+
}) : null,
|
|
120343
|
+
children,
|
|
120344
|
+
/* @__PURE__ */ jsx(SurfaceFormButtons, {
|
|
120345
|
+
form,
|
|
120346
|
+
props
|
|
120347
|
+
})
|
|
120348
|
+
]
|
|
120349
|
+
});
|
|
120350
|
+
}
|
|
120351
|
+
function CatalogField({ props }) {
|
|
120352
|
+
return /* @__PURE__ */ jsx(SurfaceField, {
|
|
120353
|
+
form: useCatalogSurfaceForm(),
|
|
120354
|
+
props
|
|
120355
|
+
});
|
|
120356
|
+
}
|
|
120357
|
+
function CatalogButton({ props }) {
|
|
120358
|
+
return /* @__PURE__ */ jsx(SurfaceButton, {
|
|
120359
|
+
form: useCatalogSurfaceForm(),
|
|
120360
|
+
props
|
|
120361
|
+
});
|
|
120362
|
+
}
|
|
120183
120363
|
/**
|
|
120184
120364
|
* catalog 16 个组件的展示实现:json-render 与 OpenUI 两档共用同一份,
|
|
120185
120365
|
* 避免"一个 catalog、多套 registry"退化成多套各自演进的视觉。
|
|
@@ -120240,50 +120420,26 @@ const catalogComponentImpls = {
|
|
|
120240
120420
|
const rows = Array.isArray(props["rows"]) ? props["rows"] : [];
|
|
120241
120421
|
return /* @__PURE__ */ jsxs(DataTable, {
|
|
120242
120422
|
"aria-label": str(props, "title", "Data"),
|
|
120243
|
-
children: [/* @__PURE__ */ jsx("thead", {
|
|
120244
|
-
|
|
120245
|
-
children: column
|
|
120246
|
-
|
|
120423
|
+
children: [/* @__PURE__ */ jsx("thead", {
|
|
120424
|
+
className: "webskill-surface__table-header",
|
|
120425
|
+
children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx("th", {
|
|
120426
|
+
scope: "col",
|
|
120427
|
+
children: column
|
|
120428
|
+
}, column)) })
|
|
120429
|
+
}), /* @__PURE__ */ jsx("tbody", {
|
|
120430
|
+
className: "webskill-surface__table-body",
|
|
120431
|
+
children: rows.map((row, rowIndex) => /* @__PURE__ */ jsx("tr", { children: row.map((cell, cellIndex) => /* @__PURE__ */ jsx("td", { children: String(cell ?? "") }, cellIndex)) }, rowIndex))
|
|
120432
|
+
})]
|
|
120247
120433
|
});
|
|
120248
120434
|
},
|
|
120249
|
-
Chart: ({ props }) => {
|
|
120250
|
-
const series = Array.isArray(props["series"]) ? props["series"] : [];
|
|
120251
|
-
return /* @__PURE__ */ jsx(EChart, { chart: {
|
|
120252
|
-
kind: str(props, "type", "bar"),
|
|
120253
|
-
labels: Array.isArray(props["labels"]) ? props["labels"] : [],
|
|
120254
|
-
series: series.map((item) => ({
|
|
120255
|
-
name: item.name,
|
|
120256
|
-
data: item.values
|
|
120257
|
-
})),
|
|
120258
|
-
...props["title"] ? { title: str(props, "title") } : {}
|
|
120259
|
-
} });
|
|
120260
|
-
},
|
|
120435
|
+
Chart: ({ props }) => /* @__PURE__ */ jsx(EChart, { chart: chartSpecFromProps(props) }),
|
|
120261
120436
|
FileLink: ({ props }) => /* @__PURE__ */ jsx("span", {
|
|
120262
120437
|
className: "text-sm text-ink",
|
|
120263
120438
|
children: str(props, "label", str(props, "path"))
|
|
120264
120439
|
}),
|
|
120265
|
-
Form:
|
|
120266
|
-
|
|
120267
|
-
|
|
120268
|
-
className: "text-sm font-semibold text-ink",
|
|
120269
|
-
children: str(props, "title")
|
|
120270
|
-
}) : null, children]
|
|
120271
|
-
}),
|
|
120272
|
-
Field: ({ props }) => {
|
|
120273
|
-
const name = str(props, "name");
|
|
120274
|
-
return /* @__PURE__ */ jsxs("label", {
|
|
120275
|
-
className: "flex flex-col gap-1 text-sm",
|
|
120276
|
-
htmlFor: name,
|
|
120277
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
120278
|
-
className: "text-ink",
|
|
120279
|
-
children: str(props, "label", name)
|
|
120280
|
-
}), str(props, "type") === "textarea" ? /* @__PURE__ */ jsx(Textarea, { id: name }) : /* @__PURE__ */ jsx(Input, { id: name })]
|
|
120281
|
-
});
|
|
120282
|
-
},
|
|
120283
|
-
Button: ({ props }) => /* @__PURE__ */ jsx(Button, {
|
|
120284
|
-
size: "sm",
|
|
120285
|
-
children: str(props, "label")
|
|
120286
|
-
}),
|
|
120440
|
+
Form: CatalogForm,
|
|
120441
|
+
Field: CatalogField,
|
|
120442
|
+
Button: CatalogButton,
|
|
120287
120443
|
Alert: ({ props }) => /* @__PURE__ */ jsxs("div", {
|
|
120288
120444
|
role: "alert",
|
|
120289
120445
|
className: "rounded-md border border-border p-2 text-sm",
|
|
@@ -120300,4 +120456,4 @@ const catalogComponentImpls = {
|
|
|
120300
120456
|
};
|
|
120301
120457
|
|
|
120302
120458
|
//#endregion
|
|
120303
|
-
export {
|
|
120459
|
+
export { DropdownMenuContent as _, SurfaceField as a, Markdown as b, str as c, Badge as d, Button as f, DropdownMenuCheckboxItem as g, DropdownMenu as h, SurfaceButton as i, useCatalogSurfaceForm as l, DataTable as m, CatalogSurfaceProvider as n, SurfaceFormButtons as o, Checkbox as p, EChart as r, catalogComponentImpls as s, CatalogNode as t, useSurfaceForm as u, DropdownMenuTrigger as v, Separator as x, Input as y };
|