@icen.ai/ui 0.8.1 → 0.9.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/AGENTS.md +49 -6
- package/README.md +12 -9
- package/dist/behaviors/ai-branch.d.mts +19 -0
- package/dist/behaviors/ai-branch.mjs +217 -0
- package/dist/behaviors/ai-chat.d.mts +8 -6
- package/dist/behaviors/ai-chat.mjs +68 -8
- package/dist/behaviors/ai-composer.mjs +108 -20
- package/dist/behaviors/ai-feedback.d.mts +26 -0
- package/dist/behaviors/ai-feedback.mjs +303 -0
- package/dist/behaviors/ai-panel.d.mts +15 -1
- package/dist/behaviors/ai-panel.mjs +26 -0
- package/dist/behaviors/ai-threads.d.mts +38 -0
- package/dist/behaviors/ai-threads.mjs +319 -0
- package/dist/behaviors/ai-tool.mjs +5 -0
- package/dist/behaviors/ai-tools.mjs +5 -0
- package/dist/behaviors/charts.d.mts +103 -1
- package/dist/behaviors/charts.mjs +795 -0
- package/dist/behaviors/events.d.mts +193 -0
- package/dist/behaviors/kb-access.d.mts +34 -0
- package/dist/behaviors/kb-access.mjs +569 -0
- package/dist/behaviors/kb-acl.d.mts +50 -0
- package/dist/behaviors/kb-acl.mjs +405 -0
- package/dist/behaviors/kb-answer.d.mts +38 -0
- package/dist/behaviors/kb-answer.mjs +2055 -0
- package/dist/behaviors/kb-audit.d.mts +23 -0
- package/dist/behaviors/kb-audit.mjs +375 -0
- package/dist/behaviors/kb-canvas.d.mts +44 -0
- package/dist/behaviors/kb-canvas.mjs +589 -0
- package/dist/behaviors/kb-chain.d.mts +15 -0
- package/dist/behaviors/kb-chain.mjs +270 -0
- package/dist/behaviors/kb-checkpoint.d.mts +16 -0
- package/dist/behaviors/kb-checkpoint.mjs +342 -0
- package/dist/behaviors/kb-chunks.d.mts +31 -0
- package/dist/behaviors/kb-chunks.mjs +475 -0
- package/dist/behaviors/kb-citation.d.mts +38 -0
- package/dist/behaviors/kb-citation.mjs +677 -0
- package/dist/behaviors/kb-clarify.d.mts +20 -0
- package/dist/behaviors/kb-clarify.mjs +248 -0
- package/dist/behaviors/kb-conflict.d.mts +22 -0
- package/dist/behaviors/kb-conflict.mjs +284 -0
- package/dist/behaviors/kb-connector.d.mts +17 -0
- package/dist/behaviors/kb-connector.mjs +375 -0
- package/dist/behaviors/kb-core.d.mts +529 -0
- package/dist/behaviors/kb-core.mjs +890 -0
- package/dist/behaviors/kb-eval.d.mts +22 -0
- package/dist/behaviors/kb-eval.mjs +408 -0
- package/dist/behaviors/kb-explain.d.mts +11 -0
- package/dist/behaviors/kb-explain.mjs +243 -0
- package/dist/behaviors/kb-filter.d.mts +31 -0
- package/dist/behaviors/kb-filter.mjs +587 -0
- package/dist/behaviors/kb-gap.d.mts +27 -0
- package/dist/behaviors/kb-gap.mjs +273 -0
- package/dist/behaviors/kb-hittest.d.mts +34 -0
- package/dist/behaviors/kb-hittest.mjs +409 -0
- package/dist/behaviors/kb-metadata.d.mts +29 -0
- package/dist/behaviors/kb-metadata.mjs +326 -0
- package/dist/behaviors/kb-passage.d.mts +41 -0
- package/dist/behaviors/kb-passage.mjs +529 -0
- package/dist/behaviors/kb-pipeline.d.mts +18 -0
- package/dist/behaviors/kb-pipeline.mjs +381 -0
- package/dist/behaviors/kb-qa.d.mts +26 -0
- package/dist/behaviors/kb-qa.mjs +332 -0
- package/dist/behaviors/kb-rerank.d.mts +20 -0
- package/dist/behaviors/kb-rerank.mjs +315 -0
- package/dist/behaviors/kb-retrieval.d.mts +49 -0
- package/dist/behaviors/kb-retrieval.mjs +746 -0
- package/dist/behaviors/kb-review.d.mts +44 -0
- package/dist/behaviors/kb-review.mjs +451 -0
- package/dist/behaviors/kb-sandbox.d.mts +33 -0
- package/dist/behaviors/kb-sandbox.mjs +325 -0
- package/dist/behaviors/kb-segment.d.mts +32 -0
- package/dist/behaviors/kb-segment.mjs +499 -0
- package/dist/behaviors/kb-sources.d.mts +27 -0
- package/dist/behaviors/kb-sources.mjs +373 -0
- package/dist/behaviors/kb-sql.d.mts +43 -0
- package/dist/behaviors/kb-sql.mjs +948 -0
- package/dist/behaviors/kb-trace.d.mts +16 -0
- package/dist/behaviors/kb-trace.mjs +414 -0
- package/dist/behaviors/kb-visibility.d.mts +44 -0
- package/dist/behaviors/kb-visibility.mjs +403 -0
- package/dist/behaviors/kb-who-can.d.mts +32 -0
- package/dist/behaviors/kb-who-can.mjs +379 -0
- package/dist/components/ai-branch.mjs +3 -0
- package/dist/components/ai-chat.css +295 -0
- package/dist/components/ai-feedback.mjs +3 -0
- package/dist/components/ai-panel.css +217 -0
- package/dist/components/ai-threads.mjs +3 -0
- package/dist/components/chart-graph.css +57 -0
- package/dist/components/chart-graph.mjs +4 -0
- package/dist/components/chart-map.mjs +3 -0
- package/dist/components/chart-scatter.mjs +1 -1
- package/dist/components/charts.css +32 -0
- package/dist/components/charts.mjs +1 -0
- package/dist/components/kb-access.mjs +4 -0
- package/dist/components/kb-acl.mjs +4 -0
- package/dist/components/kb-agent.css +481 -0
- package/dist/components/kb-answer.mjs +4 -0
- package/dist/components/kb-audit.mjs +4 -0
- package/dist/components/kb-canvas.mjs +4 -0
- package/dist/components/kb-chain.mjs +4 -0
- package/dist/components/kb-checkpoint.mjs +4 -0
- package/dist/components/kb-chunks.mjs +4 -0
- package/dist/components/kb-citation.mjs +4 -0
- package/dist/components/kb-clarify.mjs +4 -0
- package/dist/components/kb-conflict.mjs +4 -0
- package/dist/components/kb-connector.mjs +4 -0
- package/dist/components/kb-data.css +649 -0
- package/dist/components/kb-eval.mjs +4 -0
- package/dist/components/kb-explain.mjs +4 -0
- package/dist/components/kb-filter.mjs +4 -0
- package/dist/components/kb-gap.mjs +4 -0
- package/dist/components/kb-ground.css +566 -0
- package/dist/components/kb-hittest.mjs +4 -0
- package/dist/components/kb-ingest.css +898 -0
- package/dist/components/kb-metadata.mjs +4 -0
- package/dist/components/kb-ops.css +548 -0
- package/dist/components/kb-passage.mjs +4 -0
- package/dist/components/kb-perm.css +444 -0
- package/dist/components/kb-pipeline.mjs +4 -0
- package/dist/components/kb-qa.mjs +4 -0
- package/dist/components/kb-rerank.mjs +4 -0
- package/dist/components/kb-retrieval.mjs +4 -0
- package/dist/components/kb-review.mjs +4 -0
- package/dist/components/kb-sandbox.mjs +4 -0
- package/dist/components/kb-search.css +940 -0
- package/dist/components/kb-segment.mjs +4 -0
- package/dist/components/kb-sources.mjs +4 -0
- package/dist/components/kb-sql.mjs +4 -0
- package/dist/components/kb-trace.mjs +4 -0
- package/dist/components/kb-visibility.mjs +4 -0
- package/dist/components/kb-who-can.mjs +4 -0
- package/dist/components/kb.css +252 -0
- package/dist/components/kb.mjs +9 -0
- package/dist/index.d.mts +37 -2
- package/dist/index.mjs +8555 -205
- package/dist/registry.json +325 -3
- package/dist/ui.css +5451 -45
- package/docs/research/2026-10-03-ai-kb-components.md +392 -0
- package/docs/research/2026-10-03-kb-permissions.md +992 -0
- package/docs/spec/kb-family.md +443 -0
- package/package.json +2 -2
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
// src/behaviors/ai-core.ts
|
|
2
|
+
var svg = (body) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
|
|
3
|
+
var ICON_TERMINAL = svg('<path d="m4 17 6-6-6-6"/><path d="M12 19h8"/>');
|
|
4
|
+
var ICON_FILE = svg(
|
|
5
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>'
|
|
6
|
+
);
|
|
7
|
+
var ICON_FILE_PEN = svg(
|
|
8
|
+
'<path d="M12.5 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8l5 5v3.5"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M19 6.5 16.5 4"/><path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/>'
|
|
9
|
+
);
|
|
10
|
+
var ICON_FILE_PLUS = svg(
|
|
11
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M9 15h6"/><path d="M12 12v6"/>'
|
|
12
|
+
);
|
|
13
|
+
var ICON_TRASH = svg(
|
|
14
|
+
'<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6"/><path d="M14 11v6"/>'
|
|
15
|
+
);
|
|
16
|
+
var ICON_SEARCH = svg('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>');
|
|
17
|
+
var ICON_FOLDER_SEARCH = svg(
|
|
18
|
+
'<path d="M11 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v4"/><circle cx="17" cy="17" r="3"/><path d="m21 21-1.9-1.9"/>'
|
|
19
|
+
);
|
|
20
|
+
var ICON_SCAN_SEARCH = svg(
|
|
21
|
+
'<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/>'
|
|
22
|
+
);
|
|
23
|
+
var ICON_GLOBE = svg(
|
|
24
|
+
'<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>'
|
|
25
|
+
);
|
|
26
|
+
var ICON_DOWNLOAD = svg(
|
|
27
|
+
'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>'
|
|
28
|
+
);
|
|
29
|
+
var ICON_BLOCKS = svg(
|
|
30
|
+
'<rect width="7" height="7" x="4" y="4" rx="1"/><rect width="7" height="7" x="13" y="4" rx="1"/><rect width="7" height="7" x="4" y="13" rx="1"/><rect width="7" height="7" x="13" y="13" rx="1"/>'
|
|
31
|
+
);
|
|
32
|
+
var ICON_ZAP = svg(
|
|
33
|
+
'<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>'
|
|
34
|
+
);
|
|
35
|
+
var ICON_LIST = svg(
|
|
36
|
+
'<path d="M3 12h.01"/><path d="M3 18h.01"/><path d="M3 6h.01"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M8 6h13"/>'
|
|
37
|
+
);
|
|
38
|
+
var ICON_LIST_CHECKS = svg(
|
|
39
|
+
'<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/>'
|
|
40
|
+
);
|
|
41
|
+
var ICON_BOT = svg(
|
|
42
|
+
'<path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/>'
|
|
43
|
+
);
|
|
44
|
+
var ICON_CHART = svg(
|
|
45
|
+
'<path d="M3 3v18h18"/><path d="M7 15l4-6 3 4 5-8"/>'
|
|
46
|
+
);
|
|
47
|
+
var ICON_FILE_TEXT = svg(
|
|
48
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>'
|
|
49
|
+
);
|
|
50
|
+
function isObj(v) {
|
|
51
|
+
return typeof v === "object" && v !== null;
|
|
52
|
+
}
|
|
53
|
+
function pickStr(input, keys) {
|
|
54
|
+
if (!isObj(input)) return void 0;
|
|
55
|
+
for (const k of keys) {
|
|
56
|
+
const v = input[k];
|
|
57
|
+
if (typeof v === "string" && v) return v;
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
function pickNum(src, keys) {
|
|
62
|
+
if (!isObj(src)) return void 0;
|
|
63
|
+
for (const k of keys) {
|
|
64
|
+
const v = src[k];
|
|
65
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
66
|
+
}
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
function firstLine(s, max = 80) {
|
|
70
|
+
const line = s.split("\n", 1)[0]?.trim() ?? "";
|
|
71
|
+
return line.length > max ? `${line.slice(0, max)}\u2026` : line;
|
|
72
|
+
}
|
|
73
|
+
function filePathOf(input) {
|
|
74
|
+
return pickStr(input, ["file_path", "path", "file", "filename"]);
|
|
75
|
+
}
|
|
76
|
+
var summarizePath = (input) => filePathOf(input);
|
|
77
|
+
function summarizeRead(input) {
|
|
78
|
+
const p = filePathOf(input);
|
|
79
|
+
if (!p) return void 0;
|
|
80
|
+
const offset = pickNum(input, ["offset", "start_line"]);
|
|
81
|
+
if (offset == null) return p;
|
|
82
|
+
const limit = pickNum(input, ["limit", "end_line"]);
|
|
83
|
+
return limit != null ? `${p}:${offset}-${offset + limit}` : `${p}:${offset}`;
|
|
84
|
+
}
|
|
85
|
+
function summarizeShell(input) {
|
|
86
|
+
const cmd = typeof input === "string" ? input : pickStr(input, ["command", "cmd", "script"]);
|
|
87
|
+
return cmd ? firstLine(cmd) : void 0;
|
|
88
|
+
}
|
|
89
|
+
function summarizeGrep(input) {
|
|
90
|
+
const pattern = pickStr(input, ["pattern", "regex", "query"]);
|
|
91
|
+
const p = filePathOf(input);
|
|
92
|
+
if (pattern) return p ? `${firstLine(pattern, 40)} \xB7 ${p}` : firstLine(pattern);
|
|
93
|
+
return p;
|
|
94
|
+
}
|
|
95
|
+
var sum = (fn) => {
|
|
96
|
+
return (input) => fn(input) ?? "";
|
|
97
|
+
};
|
|
98
|
+
var BUILTIN_KINDS = {
|
|
99
|
+
shell: { label: "\u547D\u4EE4", icon: ICON_TERMINAL, tint: "accent", summarize: sum(summarizeShell) },
|
|
100
|
+
read: { label: "\u8BFB\u53D6", icon: ICON_FILE, tint: "info", summarize: sum(summarizeRead) },
|
|
101
|
+
edit: { label: "\u7F16\u8F91", icon: ICON_FILE_PEN, tint: "warning", summarize: sum(summarizePath) },
|
|
102
|
+
write: { label: "\u5199\u5165", icon: ICON_FILE_PLUS, tint: "warning", summarize: sum(summarizePath) },
|
|
103
|
+
rm: { label: "\u5220\u9664", icon: ICON_TRASH, tint: "error", summarize: sum(summarizePath) },
|
|
104
|
+
grep: { label: "\u641C\u7D22", icon: ICON_SEARCH, tint: "info", summarize: sum(summarizeGrep) },
|
|
105
|
+
glob: { label: "\u5339\u914D", icon: ICON_FOLDER_SEARCH, tint: "info", summarize: sum((i) => pickStr(i, ["pattern", "glob", "path"])) },
|
|
106
|
+
browser: { label: "\u6D4F\u89C8", icon: ICON_GLOBE, tint: "accent", summarize: sum((i) => pickStr(i, ["url", "uri", "target"])) },
|
|
107
|
+
search: { label: "\u68C0\u7D22", icon: ICON_SCAN_SEARCH, tint: "accent", summarize: sum((i) => pickStr(i, ["query", "q", "pattern"])) },
|
|
108
|
+
fetch: { label: "\u8BF7\u6C42", icon: ICON_DOWNLOAD, tint: "info", summarize: sum((i) => pickStr(i, ["url", "uri", "endpoint"])) },
|
|
109
|
+
mcp: { label: "MCP", icon: ICON_BLOCKS, tint: "muted", summarize: sum((i) => pickStr(i, ["tool", "method", "name"])) },
|
|
110
|
+
skill: { label: "\u6280\u80FD", icon: ICON_ZAP, tint: "accent", summarize: sum((i) => pickStr(i, ["skill", "name", "command"])) },
|
|
111
|
+
todo: {
|
|
112
|
+
label: "\u5F85\u529E",
|
|
113
|
+
icon: ICON_LIST,
|
|
114
|
+
tint: "info",
|
|
115
|
+
summarize: sum((input) => {
|
|
116
|
+
const items = input && typeof input === "object" && Array.isArray(input.items) ? input.items : Array.isArray(input) ? input : [];
|
|
117
|
+
const done = items.filter((it) => it?.status === "done").length;
|
|
118
|
+
return items.length > 0 ? `${done}/${items.length} \u5B8C\u6210` : "";
|
|
119
|
+
})
|
|
120
|
+
},
|
|
121
|
+
plan: { label: "\u8BA1\u5212", icon: ICON_LIST_CHECKS, tint: "info" },
|
|
122
|
+
subagent: { label: "\u5B50\u4EE3\u7406", icon: ICON_BOT, tint: "accent", summarize: sum((i) => pickStr(i, ["task", "description", "prompt", "message", "subagent_type"])) },
|
|
123
|
+
note: { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" },
|
|
124
|
+
chart: { label: "\u56FE\u8868", icon: ICON_CHART, tint: "info", summarize: sum((i) => pickStr(i, ["title", "name"])) }
|
|
125
|
+
};
|
|
126
|
+
var kindRegistry = new Map(Object.entries(BUILTIN_KINDS));
|
|
127
|
+
function h(tag, className, text) {
|
|
128
|
+
const node = document.createElement(tag);
|
|
129
|
+
if (className) node.className = className;
|
|
130
|
+
if (text != null) node.textContent = text;
|
|
131
|
+
return node;
|
|
132
|
+
}
|
|
133
|
+
function svgIcon(svg2) {
|
|
134
|
+
if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
|
|
135
|
+
let parsed;
|
|
136
|
+
try {
|
|
137
|
+
parsed = new DOMParser().parseFromString(svg2, "image/svg+xml");
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const root = parsed.documentElement;
|
|
142
|
+
if (!root || root.tagName.toLowerCase() !== "svg" || parsed.querySelector("parsererror")) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const sanitize = (el) => {
|
|
146
|
+
for (const attr of Array.from(el.attributes)) {
|
|
147
|
+
const name = attr.name.toLowerCase();
|
|
148
|
+
if (name.startsWith("on")) el.removeAttribute(attr.name);
|
|
149
|
+
if ((name === "href" || name === "xlink:href") && attr.value.trim().toLowerCase().startsWith("javascript:")) {
|
|
150
|
+
el.removeAttribute(attr.name);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const child of Array.from(el.children)) {
|
|
154
|
+
const tag = child.tagName.toLowerCase();
|
|
155
|
+
if (tag === "script" || tag === "foreignobject") {
|
|
156
|
+
child.remove();
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
sanitize(child);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
sanitize(root);
|
|
163
|
+
return document.importNode(root, true);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/behaviors/kb-core.ts
|
|
167
|
+
var ic = (label, d) => ({ label, icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${d}</svg>` });
|
|
168
|
+
var BUILTIN_SOURCE_TYPES = {
|
|
169
|
+
doc: ic("\u6587\u6863", '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/>'),
|
|
170
|
+
web: ic("\u7F51\u9875", '<circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20z"/>'),
|
|
171
|
+
wiki: ic("\u77E5\u8BC6\u5E93", '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>'),
|
|
172
|
+
sheet: ic("\u8868\u683C", '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/>'),
|
|
173
|
+
db: ic("\u6570\u636E\u5E93", '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/>'),
|
|
174
|
+
ticket: ic("\u5DE5\u5355", '<path d="M3 9V7a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2a2 2 0 0 0 0 6v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2a2 2 0 0 0 0-6z"/><path d="M13 5v14"/>'),
|
|
175
|
+
code: ic("\u4EE3\u7801", '<path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/>'),
|
|
176
|
+
api: ic("\u63A5\u53E3", '<path d="M4 7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v3a2 2 0 0 0 0 4v3a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-3a2 2 0 0 0 0-4z"/>'),
|
|
177
|
+
image: ic("\u56FE\u7247", '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-4.6-4.6a2 2 0 0 0-2.8 0L3 21"/>'),
|
|
178
|
+
mail: ic("\u90AE\u4EF6", '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 7L2 7"/>')
|
|
179
|
+
};
|
|
180
|
+
var sourceTypeRegistry = new Map(Object.entries(BUILTIN_SOURCE_TYPES));
|
|
181
|
+
function formatScore(v, digits = 3) {
|
|
182
|
+
if (v == null || !Number.isFinite(v)) return "\u2014";
|
|
183
|
+
return v.toFixed(digits).replace(/\.?0+$/, (s) => s.includes(".") ? "" : s);
|
|
184
|
+
}
|
|
185
|
+
function hitTestStats(cases) {
|
|
186
|
+
const ran = cases.filter((c) => c.passed !== void 0);
|
|
187
|
+
const passed = ran.filter((c) => c.passed === true);
|
|
188
|
+
const scores = ran.flatMap((c) => (c.lastHits ?? []).map((h2) => h2.score)).filter((s) => typeof s === "number");
|
|
189
|
+
return {
|
|
190
|
+
total: cases.length,
|
|
191
|
+
passed: passed.length,
|
|
192
|
+
hitRate: ran.length ? passed.length / ran.length : null,
|
|
193
|
+
avgScore: scores.length ? scores.reduce((a, b) => a + b, 0) / scores.length : null
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function formatPercent(v, digits = 1) {
|
|
197
|
+
if (v == null || !Number.isFinite(v)) return "\u2014";
|
|
198
|
+
return `${(v * 100).toFixed(digits)}%`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/behaviors/events.ts
|
|
202
|
+
function emitIcen(source, type, detail) {
|
|
203
|
+
return source.dispatchEvent(
|
|
204
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/behaviors/kb-hittest.ts
|
|
209
|
+
var EXPECTED_CHIP_LIMIT = 3;
|
|
210
|
+
var ICON_PIN = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1z"/></svg>';
|
|
211
|
+
var ICON_RUN = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 4 14 8-14 8z"/></svg>';
|
|
212
|
+
var caseSeq = 0;
|
|
213
|
+
function derivePassed(c) {
|
|
214
|
+
if (!c.lastHits?.length) return void 0;
|
|
215
|
+
if (!c.expectedChunkIds?.length) return void 0;
|
|
216
|
+
const expected = new Set(c.expectedChunkIds);
|
|
217
|
+
return c.lastHits.some((hh) => expected.has(hh.chunkId));
|
|
218
|
+
}
|
|
219
|
+
function cloneCase(c) {
|
|
220
|
+
return {
|
|
221
|
+
...c,
|
|
222
|
+
expectedChunkIds: c.expectedChunkIds ? [...c.expectedChunkIds] : void 0,
|
|
223
|
+
lastHits: c.lastHits?.map((hh) => ({ ...hh }))
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function newCaseId() {
|
|
227
|
+
return `kbht-${++caseSeq}-${Date.now().toString(36)}`;
|
|
228
|
+
}
|
|
229
|
+
function renderKbHitStats(el, stats) {
|
|
230
|
+
if (typeof document === "undefined" || !el) return el;
|
|
231
|
+
el.textContent = "";
|
|
232
|
+
const box = h("div", "kb-hittest-stats");
|
|
233
|
+
const rate = h("div", "kb-hittest-stat kb-hittest-stat--rate");
|
|
234
|
+
rate.append(
|
|
235
|
+
h("span", "kb-hittest-stat-label", "\u547D\u4E2D\u7387"),
|
|
236
|
+
h("span", "kb-num kb-hittest-hitrate", formatPercent(stats.hitRate))
|
|
237
|
+
);
|
|
238
|
+
const avg = h("div", "kb-hittest-stat");
|
|
239
|
+
avg.append(
|
|
240
|
+
h("span", "kb-hittest-stat-label", "\u5747\u5206"),
|
|
241
|
+
h("span", "kb-num", formatScore(stats.avgScore))
|
|
242
|
+
);
|
|
243
|
+
const pass = h("div", "kb-hittest-stat");
|
|
244
|
+
pass.append(
|
|
245
|
+
h("span", "kb-hittest-stat-label", "\u901A\u8FC7"),
|
|
246
|
+
h("span", "kb-num", `${stats.passed}/${stats.total}`)
|
|
247
|
+
);
|
|
248
|
+
const badge = h("span", "kb-badge", "\u53C2\u6570\u4EC5\u672C\u6B21\u4F1A\u8BDD\u751F\u6548");
|
|
249
|
+
badge.title = "\u6D4B\u8BD5\u8FD0\u884C\u7684\u68C0\u7D22\u53C2\u6570\uFF08topK/\u9608\u503C/\u91CD\u6392\uFF09\u4E0D\u843D\u5E93\uFF0C\u4EC5\u672C\u6B21\u4F1A\u8BDD\u751F\u6548";
|
|
250
|
+
box.append(rate, avg, pass, badge);
|
|
251
|
+
el.appendChild(box);
|
|
252
|
+
return box;
|
|
253
|
+
}
|
|
254
|
+
function createKbHitTest(el, opts = {}) {
|
|
255
|
+
if (typeof document === "undefined" || !el) {
|
|
256
|
+
const stub = {
|
|
257
|
+
getCases: () => [],
|
|
258
|
+
setCases: () => {
|
|
259
|
+
},
|
|
260
|
+
run: () => {
|
|
261
|
+
},
|
|
262
|
+
destroy: () => {
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
return stub;
|
|
266
|
+
}
|
|
267
|
+
const marked = el;
|
|
268
|
+
if (marked.__icenKbHitTest) return marked.__icenKbHitTest;
|
|
269
|
+
const ctrl = new AbortController();
|
|
270
|
+
const listen = (target, type, fn) => {
|
|
271
|
+
target.addEventListener(type, fn, { signal: ctrl.signal });
|
|
272
|
+
};
|
|
273
|
+
let cases = (opts.cases ?? []).map(cloneCase);
|
|
274
|
+
let destroyed = false;
|
|
275
|
+
el.classList.add("kb-hittest");
|
|
276
|
+
el.dataset.kbHitTest = "";
|
|
277
|
+
el.textContent = "";
|
|
278
|
+
const statsBox = h("div", "kb-hittest-stats-slot");
|
|
279
|
+
const listBox = h("div", "kb-hittest-list");
|
|
280
|
+
const input = h("input", "kb-hittest-input");
|
|
281
|
+
input.type = "text";
|
|
282
|
+
input.placeholder = "\u65B0\u589E\u95EE\u9898\uFF0CEnter \u6DFB\u52A0";
|
|
283
|
+
input.setAttribute("aria-label", "\u65B0\u589E\u6D4B\u8BD5\u95EE\u9898");
|
|
284
|
+
const addBtn = h("button", "kb-hittest-add-btn", "\u6DFB\u52A0");
|
|
285
|
+
addBtn.type = "button";
|
|
286
|
+
const addRow = h("div", "kb-hittest-add");
|
|
287
|
+
addRow.append(input, addBtn);
|
|
288
|
+
const runBtn = h("button", "kb-run", "\u8FD0\u884C\u5168\u90E8");
|
|
289
|
+
runBtn.type = "button";
|
|
290
|
+
const runIcon = svgIcon(ICON_RUN);
|
|
291
|
+
if (runIcon) runBtn.prepend(runIcon);
|
|
292
|
+
el.append(statsBox, listBox, addRow, runBtn);
|
|
293
|
+
function orderedCases() {
|
|
294
|
+
return cases.map((c, i) => ({ c, i })).sort((a, b) => a.c.pinned === b.c.pinned ? a.i - b.i : a.c.pinned ? -1 : 1).map((x) => x.c);
|
|
295
|
+
}
|
|
296
|
+
function renderList() {
|
|
297
|
+
listBox.textContent = "";
|
|
298
|
+
if (!cases.length) {
|
|
299
|
+
listBox.appendChild(h("div", "kb-empty", "\u6682\u65E0\u6D4B\u8BD5\u95EE\u9898\u2014\u2014\u6DFB\u52A0\u7B2C\u4E00\u6761\uFF0C\u5F00\u59CB\u53EC\u56DE\u56DE\u5F52"));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
for (const c of orderedCases()) {
|
|
303
|
+
const passed = derivePassed(c);
|
|
304
|
+
const row = h("div", "kb-hittest-row");
|
|
305
|
+
if (passed === true) row.classList.add("is-pass");
|
|
306
|
+
if (passed === false) row.classList.add("is-fail");
|
|
307
|
+
const pin = h("button", "kb-hittest-pin");
|
|
308
|
+
pin.type = "button";
|
|
309
|
+
pin.title = c.pinned ? "\u53D6\u6D88\u7F6E\u9876" : "\u7F6E\u9876\u56FA\u5B9A";
|
|
310
|
+
pin.setAttribute("aria-label", pin.title);
|
|
311
|
+
pin.setAttribute("aria-pressed", c.pinned ? "true" : "false");
|
|
312
|
+
if (c.pinned) pin.classList.add("is-pinned");
|
|
313
|
+
const pinIcon = svgIcon(ICON_PIN);
|
|
314
|
+
if (pinIcon) pin.appendChild(pinIcon);
|
|
315
|
+
pin.dataset.id = c.id;
|
|
316
|
+
const q = h("span", "kb-hittest-q", c.question);
|
|
317
|
+
const expected = h("span", "kb-hittest-expected");
|
|
318
|
+
const ids = c.expectedChunkIds ?? [];
|
|
319
|
+
for (const id of ids.slice(0, EXPECTED_CHIP_LIMIT)) {
|
|
320
|
+
expected.appendChild(h("span", "kb-chip", id));
|
|
321
|
+
}
|
|
322
|
+
if (ids.length > EXPECTED_CHIP_LIMIT) {
|
|
323
|
+
expected.appendChild(h("span", "kb-chip", `+${ids.length - EXPECTED_CHIP_LIMIT}`));
|
|
324
|
+
}
|
|
325
|
+
if (!ids.length) expected.appendChild(h("span", "kb-chip", "\u672A\u8BBE\u671F\u671B"));
|
|
326
|
+
const result = h("span", "kb-hittest-result kb-num", passed === true ? "\u2713" : passed === false ? "\u2717" : "\u2014");
|
|
327
|
+
result.title = passed === void 0 ? "\u672A\u8FD0\u884C" : passed ? "\u671F\u671B\u547D\u4E2D\u5728\u524D\u6392\u547D\u4E2D\u4E2D" : "\u671F\u671B\u672A\u547D\u4E2D";
|
|
328
|
+
const remove = h("button", "kb-hittest-remove", "\xD7");
|
|
329
|
+
remove.type = "button";
|
|
330
|
+
remove.title = "\u5220\u9664\u95EE\u9898";
|
|
331
|
+
remove.setAttribute("aria-label", "\u5220\u9664\u95EE\u9898");
|
|
332
|
+
remove.dataset.id = c.id;
|
|
333
|
+
row.append(pin, q, expected, result, remove);
|
|
334
|
+
listBox.appendChild(row);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function renderStats() {
|
|
338
|
+
renderKbHitStats(statsBox, hitTestStats(cases.map((c) => ({ ...c, passed: derivePassed(c) }))));
|
|
339
|
+
}
|
|
340
|
+
function refresh() {
|
|
341
|
+
renderList();
|
|
342
|
+
renderStats();
|
|
343
|
+
}
|
|
344
|
+
function addQuestion() {
|
|
345
|
+
const question = input.value.trim();
|
|
346
|
+
if (!question || destroyed) return;
|
|
347
|
+
cases = [...cases, { id: newCaseId(), question }];
|
|
348
|
+
input.value = "";
|
|
349
|
+
refresh();
|
|
350
|
+
emitIcen(el, "icen:kb-hittest-add", { question });
|
|
351
|
+
}
|
|
352
|
+
listen(listBox, "click", (ev) => {
|
|
353
|
+
const target = ev.target;
|
|
354
|
+
if (!(target instanceof Element)) return;
|
|
355
|
+
const pin = target.closest(".kb-hittest-pin");
|
|
356
|
+
if (pin instanceof HTMLButtonElement) {
|
|
357
|
+
const c = cases.find((x) => x.id === pin.dataset.id);
|
|
358
|
+
if (c) {
|
|
359
|
+
c.pinned = !c.pinned;
|
|
360
|
+
refresh();
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const remove = target.closest(".kb-hittest-remove");
|
|
365
|
+
if (remove instanceof HTMLButtonElement) {
|
|
366
|
+
cases = cases.filter((x) => x.id !== remove.dataset.id);
|
|
367
|
+
refresh();
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
listen(input, "keydown", (ev) => {
|
|
371
|
+
const e = ev;
|
|
372
|
+
if (e.key === "Enter" && !e.isComposing) {
|
|
373
|
+
e.preventDefault();
|
|
374
|
+
addQuestion();
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
listen(addBtn, "click", () => addQuestion());
|
|
378
|
+
listen(runBtn, "click", () => handle.run());
|
|
379
|
+
refresh();
|
|
380
|
+
const handle = {
|
|
381
|
+
getCases() {
|
|
382
|
+
return cases.map((c) => cloneCase({ ...c, passed: derivePassed(c) }));
|
|
383
|
+
},
|
|
384
|
+
setCases(next) {
|
|
385
|
+
cases = (next ?? []).map(cloneCase);
|
|
386
|
+
refresh();
|
|
387
|
+
},
|
|
388
|
+
run() {
|
|
389
|
+
if (destroyed) return;
|
|
390
|
+
emitIcen(el, "icen:kb-hittest-run", {});
|
|
391
|
+
opts.onRun?.(cases.map((c) => cloneCase({ ...c, passed: derivePassed(c) })));
|
|
392
|
+
},
|
|
393
|
+
destroy() {
|
|
394
|
+
if (destroyed) return;
|
|
395
|
+
destroyed = true;
|
|
396
|
+
ctrl.abort();
|
|
397
|
+
el.classList.remove("kb-hittest");
|
|
398
|
+
delete el.dataset.kbHitTest;
|
|
399
|
+
el.textContent = "";
|
|
400
|
+
delete el.__icenKbHitTest;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
marked.__icenKbHitTest = handle;
|
|
404
|
+
return handle;
|
|
405
|
+
}
|
|
406
|
+
export {
|
|
407
|
+
createKbHitTest,
|
|
408
|
+
renderKbHitStats
|
|
409
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** 字段类型(徽标呈现;time = 日期时间口径) */
|
|
2
|
+
type KbMetadataType = 'string' | 'number' | 'time';
|
|
3
|
+
/** 字段定义(builtin = 系统内置字段:值只读、不可移除) */
|
|
4
|
+
interface KbMetadataField {
|
|
5
|
+
key: string;
|
|
6
|
+
type: KbMetadataType;
|
|
7
|
+
builtin?: boolean;
|
|
8
|
+
/** 展示名(缺省用 key) */
|
|
9
|
+
label?: string;
|
|
10
|
+
}
|
|
11
|
+
/** renderKbMetadata 数据模型 */
|
|
12
|
+
interface KbMetadataModel {
|
|
13
|
+
fields: KbMetadataField[];
|
|
14
|
+
values: Record<string, string | number>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 渲染元数据字段定义表 + 新增行(快照:整树重建后返回挂载容器 el)。
|
|
18
|
+
* builtin 字段值只读;自定义字段值为可编辑 input。SSR(无 document)下原样返回 el。
|
|
19
|
+
*/
|
|
20
|
+
declare function renderKbMetadata(el: HTMLElement, model: KbMetadataModel): HTMLElement;
|
|
21
|
+
/**
|
|
22
|
+
* 接线元数据编辑(scope 级事件委托,重复调用幂等)。
|
|
23
|
+
* 值绑定 / 新增字段 / 移除字段统一派 icen:kb-metadata-change { action, key };
|
|
24
|
+
* define/remove 同时做本地 DOM 更新(直接操纵纪律),消费方可凭事件重渲染纠偏。
|
|
25
|
+
* root 缺省 document。返回销毁函数(复位幂等标记,可重新 init)。SSR 下 no-op。
|
|
26
|
+
*/
|
|
27
|
+
declare function initKbMetadata(root?: ParentNode): () => void;
|
|
28
|
+
|
|
29
|
+
export { type KbMetadataField, type KbMetadataModel, type KbMetadataType, initKbMetadata, renderKbMetadata };
|