@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,499 @@
|
|
|
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(svg3) {
|
|
134
|
+
if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
|
|
135
|
+
let parsed;
|
|
136
|
+
try {
|
|
137
|
+
parsed = new DOMParser().parseFromString(svg3, "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
|
+
var KB_CLEAN_OPTIONS = ["collapse-blank", "strip-url", "strip-email"];
|
|
182
|
+
function normalizeSegmentConfig(raw) {
|
|
183
|
+
const r = raw ?? {};
|
|
184
|
+
return {
|
|
185
|
+
mode: r.mode === "custom" || r.mode === "parent-child" ? r.mode : "auto",
|
|
186
|
+
delimiter: typeof r.delimiter === "string" && r.delimiter ? r.delimiter : r.mode === "custom" || r.mode === "parent-child" ? void 0 : "\n\n",
|
|
187
|
+
maxLength: typeof r.maxLength === "number" && r.maxLength > 0 ? Math.floor(r.maxLength) : 500,
|
|
188
|
+
overlap: typeof r.overlap === "number" && r.overlap >= 0 ? Math.floor(r.overlap) : 50,
|
|
189
|
+
clean: Array.isArray(r.clean) ? r.clean.filter((c) => KB_CLEAN_OPTIONS.includes(c)) : [],
|
|
190
|
+
locked: r.locked === true
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
var URL_RE = /https?:\/\/\S+/g;
|
|
194
|
+
var EMAIL_RE = /[\w.+-]+@[\w-]+\.[\w.]+/g;
|
|
195
|
+
function cleanText(text, clean) {
|
|
196
|
+
let out = text;
|
|
197
|
+
if (clean.includes("collapse-blank")) out = out.replace(/\n{3,}/g, "\n\n");
|
|
198
|
+
if (clean.includes("strip-url")) out = out.replace(URL_RE, "");
|
|
199
|
+
if (clean.includes("strip-email")) out = out.replace(EMAIL_RE, "");
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
function delimiterToRe(delim) {
|
|
203
|
+
const literal = delim.replace(/\\n/g, "\n").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
204
|
+
return new RegExp(literal, "g");
|
|
205
|
+
}
|
|
206
|
+
function segmentText(text, config) {
|
|
207
|
+
const cfg = normalizeSegmentConfig(config);
|
|
208
|
+
const cleaned = cleanText(text ?? "", cfg.clean);
|
|
209
|
+
if (!cleaned.trim()) return [];
|
|
210
|
+
const parts = [];
|
|
211
|
+
if (cfg.mode === "custom" || cfg.mode === "parent-child" || cfg.mode === "auto" && cfg.delimiter) {
|
|
212
|
+
const re = delimiterToRe(cfg.delimiter ?? "\n\n");
|
|
213
|
+
for (const seg of cleaned.split(re)) {
|
|
214
|
+
const s = seg.trim();
|
|
215
|
+
if (s) parts.push(s);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
parts.push(cleaned.trim());
|
|
219
|
+
}
|
|
220
|
+
if (cfg.mode === "parent-child") return parts;
|
|
221
|
+
const out = [];
|
|
222
|
+
for (const p of parts) {
|
|
223
|
+
if (p.length <= cfg.maxLength) {
|
|
224
|
+
out.push(p);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const overlap = Math.min(cfg.overlap, Math.max(0, cfg.maxLength - 1));
|
|
228
|
+
for (let i = 0; i < p.length; i += cfg.maxLength - overlap) {
|
|
229
|
+
out.push(p.slice(i, i + cfg.maxLength));
|
|
230
|
+
if (i + cfg.maxLength >= p.length) break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
function segmentStats(chunks) {
|
|
236
|
+
const count = chunks.length;
|
|
237
|
+
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
238
|
+
return { count, avgLength: count ? Math.round(total / count) : 0, tokensEstimate: Math.round(total / 2) };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/behaviors/events.ts
|
|
242
|
+
function emitIcen(source, type, detail) {
|
|
243
|
+
return source.dispatchEvent(
|
|
244
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/behaviors/kb-segment.ts
|
|
249
|
+
var svg2 = (d) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${d}</svg>`;
|
|
250
|
+
var ICON_LOCK = svg2('<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>');
|
|
251
|
+
function icon(parent, svgStr) {
|
|
252
|
+
const node = svgIcon(svgStr);
|
|
253
|
+
if (node) parent.appendChild(node);
|
|
254
|
+
}
|
|
255
|
+
var MODES = ["auto", "custom", "parent-child"];
|
|
256
|
+
var MODE_LABELS = {
|
|
257
|
+
auto: "\u81EA\u52A8",
|
|
258
|
+
custom: "\u81EA\u5B9A\u4E49",
|
|
259
|
+
"parent-child": "\u7236\u5B50"
|
|
260
|
+
};
|
|
261
|
+
var CLEAN_LABELS = {
|
|
262
|
+
"collapse-blank": "\u5408\u5E76\u8FDE\u7EED\u7A7A\u884C",
|
|
263
|
+
"strip-url": "\u53BB\u9664\u94FE\u63A5",
|
|
264
|
+
"strip-email": "\u53BB\u9664\u90AE\u7BB1"
|
|
265
|
+
};
|
|
266
|
+
var PREVIEW_LIMIT = 20;
|
|
267
|
+
function buildModeSeg(cfg) {
|
|
268
|
+
const seg = h("div", "kb-seg kb-segment-mode");
|
|
269
|
+
seg.setAttribute("role", "radiogroup");
|
|
270
|
+
seg.setAttribute("aria-label", "\u5206\u6BB5\u6A21\u5F0F");
|
|
271
|
+
MODES.forEach((m) => {
|
|
272
|
+
const item = h("button", `kb-seg-item kb-segment-mode-item${cfg.mode === m ? " is-active" : ""}`);
|
|
273
|
+
item.type = "button";
|
|
274
|
+
item.setAttribute("role", "radio");
|
|
275
|
+
item.setAttribute("aria-checked", String(cfg.mode === m));
|
|
276
|
+
if (cfg.locked) item.disabled = true;
|
|
277
|
+
item.dataset.value = m;
|
|
278
|
+
item.textContent = MODE_LABELS[m];
|
|
279
|
+
seg.appendChild(item);
|
|
280
|
+
});
|
|
281
|
+
return seg;
|
|
282
|
+
}
|
|
283
|
+
function buildCleanGroup(cfg) {
|
|
284
|
+
const group = h("div", "kb-segment-clean");
|
|
285
|
+
KB_CLEAN_OPTIONS.forEach((opt) => {
|
|
286
|
+
const label = h("label", "kb-segment-clean-item");
|
|
287
|
+
const cb = document.createElement("input");
|
|
288
|
+
cb.type = "checkbox";
|
|
289
|
+
cb.value = opt;
|
|
290
|
+
cb.checked = cfg.clean.includes(opt);
|
|
291
|
+
cb.dataset.kbSegmentClean = opt;
|
|
292
|
+
label.appendChild(cb);
|
|
293
|
+
label.appendChild(document.createTextNode(CLEAN_LABELS[opt]));
|
|
294
|
+
group.appendChild(label);
|
|
295
|
+
});
|
|
296
|
+
return group;
|
|
297
|
+
}
|
|
298
|
+
function createKbSegment(el, opts) {
|
|
299
|
+
const o = opts ?? {};
|
|
300
|
+
let cfg = normalizeSegmentConfig(o.config);
|
|
301
|
+
let sample = typeof o.sample === "string" ? o.sample : "";
|
|
302
|
+
let alive = true;
|
|
303
|
+
if (typeof document === "undefined") {
|
|
304
|
+
return {
|
|
305
|
+
getConfig: () => normalizeSegmentConfig(cfg),
|
|
306
|
+
setConfig: (next) => {
|
|
307
|
+
cfg = normalizeSegmentConfig({ ...cfg, ...next });
|
|
308
|
+
},
|
|
309
|
+
refresh: () => void 0,
|
|
310
|
+
destroy: () => void 0
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
const ac = new AbortController();
|
|
314
|
+
const listen = (node, type, fn) => {
|
|
315
|
+
node.addEventListener(type, fn, { signal: ac.signal });
|
|
316
|
+
};
|
|
317
|
+
el.textContent = "";
|
|
318
|
+
el.classList.add("kb-segment-host");
|
|
319
|
+
const root = h("div", "kb-segment");
|
|
320
|
+
const form = h("div", "kb-segment-form");
|
|
321
|
+
const lockedNotice = h("div", "kb-segment-locked kb-notice");
|
|
322
|
+
icon(lockedNotice, ICON_LOCK);
|
|
323
|
+
lockedNotice.appendChild(document.createTextNode("\u5206\u6BB5\u6A21\u5F0F\u521B\u5EFA\u540E\u4E0D\u53EF\u66F4\u6539\uFF0C\u53C2\u6570\u53EF\u968F\u65F6\u8C03\u6574"));
|
|
324
|
+
if (!cfg.locked) lockedNotice.hidden = true;
|
|
325
|
+
form.appendChild(lockedNotice);
|
|
326
|
+
const modeField = h("div", "kb-field");
|
|
327
|
+
modeField.appendChild(h("span", "kb-field-label", "\u5206\u6BB5\u6A21\u5F0F"));
|
|
328
|
+
const modeSeg = buildModeSeg(cfg);
|
|
329
|
+
modeField.appendChild(modeSeg);
|
|
330
|
+
form.appendChild(modeField);
|
|
331
|
+
const delimField = h("label", "kb-field");
|
|
332
|
+
delimField.appendChild(h("span", "kb-field-label", "\u5206\u9694\u7B26"));
|
|
333
|
+
const delimInput = h("input", "kb-input kb-input--mono kb-segment-delimiter");
|
|
334
|
+
delimInput.type = "text";
|
|
335
|
+
delimInput.placeholder = "\\n\\n";
|
|
336
|
+
delimInput.value = cfg.delimiter ?? "";
|
|
337
|
+
delimInput.setAttribute("aria-label", "\u5206\u6BB5\u5206\u9694\u7B26");
|
|
338
|
+
delimField.appendChild(delimInput);
|
|
339
|
+
form.appendChild(delimField);
|
|
340
|
+
const maxField = h("label", "kb-field");
|
|
341
|
+
maxField.appendChild(h("span", "kb-field-label", "\u6700\u5927\u957F\u5EA6"));
|
|
342
|
+
const maxInput = h("input", "kb-input kb-segment-max");
|
|
343
|
+
maxInput.type = "number";
|
|
344
|
+
maxInput.min = "1";
|
|
345
|
+
maxInput.step = "1";
|
|
346
|
+
maxInput.value = String(cfg.maxLength);
|
|
347
|
+
maxInput.setAttribute("aria-label", "\u5206\u6BB5\u6700\u5927\u957F\u5EA6");
|
|
348
|
+
maxField.appendChild(maxInput);
|
|
349
|
+
form.appendChild(maxField);
|
|
350
|
+
const overlapField = h("label", "kb-field");
|
|
351
|
+
overlapField.appendChild(h("span", "kb-field-label", "\u91CD\u53E0"));
|
|
352
|
+
const overlapInput = h("input", "kb-input kb-segment-overlap");
|
|
353
|
+
overlapInput.type = "number";
|
|
354
|
+
overlapInput.min = "0";
|
|
355
|
+
overlapInput.step = "1";
|
|
356
|
+
overlapInput.value = String(cfg.overlap);
|
|
357
|
+
overlapInput.setAttribute("aria-label", "\u5206\u6BB5\u91CD\u53E0");
|
|
358
|
+
overlapField.appendChild(overlapInput);
|
|
359
|
+
form.appendChild(overlapField);
|
|
360
|
+
const overlapError = h("div", "kb-segment-overlap-error kb-notice", "\u91CD\u53E0\u9700\u5C0F\u4E8E\u6700\u5927\u957F\u5EA6");
|
|
361
|
+
overlapError.hidden = true;
|
|
362
|
+
form.appendChild(overlapError);
|
|
363
|
+
form.appendChild(buildCleanGroup(cfg));
|
|
364
|
+
const pcNotice = h("div", "kb-segment-pc-notice kb-notice", "\u5B50\u5206\u6BB5\u6309\u957F\u5EA6\u81EA\u52A8\u751F\u6210");
|
|
365
|
+
pcNotice.hidden = cfg.mode !== "parent-child";
|
|
366
|
+
form.appendChild(pcNotice);
|
|
367
|
+
root.appendChild(form);
|
|
368
|
+
const preview = h("div", "kb-segment-preview");
|
|
369
|
+
const stats = h("div", "kb-segment-stats");
|
|
370
|
+
preview.appendChild(stats);
|
|
371
|
+
const blocks = h("div", "kb-segment-blocks");
|
|
372
|
+
preview.appendChild(blocks);
|
|
373
|
+
const more = h("div", "kb-segment-more kb-notice");
|
|
374
|
+
more.hidden = true;
|
|
375
|
+
preview.appendChild(more);
|
|
376
|
+
root.appendChild(preview);
|
|
377
|
+
el.appendChild(root);
|
|
378
|
+
const syncModeSeg = () => {
|
|
379
|
+
modeSeg.querySelectorAll(".kb-segment-mode-item").forEach((it) => {
|
|
380
|
+
const on = it.dataset.value === cfg.mode;
|
|
381
|
+
it.classList.toggle("is-active", on);
|
|
382
|
+
it.setAttribute("aria-checked", String(on));
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
const syncForm = () => {
|
|
386
|
+
syncModeSeg();
|
|
387
|
+
delimInput.value = cfg.delimiter ?? "";
|
|
388
|
+
maxInput.value = String(cfg.maxLength);
|
|
389
|
+
overlapInput.value = String(cfg.overlap);
|
|
390
|
+
modeSeg.querySelectorAll("[data-kb-segment-clean]").forEach((cb) => {
|
|
391
|
+
cb.checked = cfg.clean.includes(cb.value);
|
|
392
|
+
});
|
|
393
|
+
pcNotice.hidden = cfg.mode !== "parent-child";
|
|
394
|
+
};
|
|
395
|
+
const validate = () => {
|
|
396
|
+
const invalid = cfg.overlap >= cfg.maxLength;
|
|
397
|
+
overlapError.hidden = !invalid;
|
|
398
|
+
overlapInput.setAttribute("aria-invalid", String(invalid));
|
|
399
|
+
};
|
|
400
|
+
const apply = () => {
|
|
401
|
+
validate();
|
|
402
|
+
refresh();
|
|
403
|
+
if (!alive) return;
|
|
404
|
+
emitIcen(el, "icen:kb-segment-change", { config: normalizeSegmentConfig(cfg) });
|
|
405
|
+
o.onChange?.(normalizeSegmentConfig(cfg));
|
|
406
|
+
};
|
|
407
|
+
const readForm = () => {
|
|
408
|
+
const mode = modeSeg.querySelector(".kb-segment-mode-item.is-active")?.dataset.value;
|
|
409
|
+
const delim = delimInput.value;
|
|
410
|
+
const max = Math.floor(Number(maxInput.value));
|
|
411
|
+
const overlap = Math.floor(Number(overlapInput.value));
|
|
412
|
+
const clean = Array.from(
|
|
413
|
+
form.querySelectorAll("[data-kb-segment-clean]:checked")
|
|
414
|
+
).map((cb) => cb.value);
|
|
415
|
+
cfg = normalizeSegmentConfig({
|
|
416
|
+
mode: mode === "custom" || mode === "parent-child" ? mode : "auto",
|
|
417
|
+
delimiter: delim || void 0,
|
|
418
|
+
maxLength: Number.isFinite(max) && max > 0 ? max : cfg.maxLength,
|
|
419
|
+
overlap: Number.isFinite(overlap) && overlap >= 0 ? overlap : cfg.overlap,
|
|
420
|
+
clean,
|
|
421
|
+
locked: cfg.locked
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
listen(modeSeg, "click", (e) => {
|
|
425
|
+
const t = e.target instanceof Element ? e.target : null;
|
|
426
|
+
const item = t?.closest(".kb-segment-mode-item");
|
|
427
|
+
if (!item || item.classList.contains("is-active")) return;
|
|
428
|
+
readForm();
|
|
429
|
+
cfg = normalizeSegmentConfig({ ...cfg, mode: item.dataset.value });
|
|
430
|
+
syncForm();
|
|
431
|
+
apply();
|
|
432
|
+
});
|
|
433
|
+
listen(delimInput, "input", () => {
|
|
434
|
+
readForm();
|
|
435
|
+
apply();
|
|
436
|
+
});
|
|
437
|
+
listen(maxInput, "input", () => {
|
|
438
|
+
readForm();
|
|
439
|
+
apply();
|
|
440
|
+
});
|
|
441
|
+
listen(overlapInput, "input", () => {
|
|
442
|
+
readForm();
|
|
443
|
+
apply();
|
|
444
|
+
});
|
|
445
|
+
form.querySelectorAll("[data-kb-segment-clean]").forEach((cb) => {
|
|
446
|
+
listen(cb, "change", () => {
|
|
447
|
+
readForm();
|
|
448
|
+
apply();
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
listen(blocks, "click", (e) => {
|
|
452
|
+
const t = e.target instanceof Element ? e.target : null;
|
|
453
|
+
const text = t?.closest(".kb-segment-block-text");
|
|
454
|
+
if (text) text.classList.toggle("is-open");
|
|
455
|
+
});
|
|
456
|
+
function refresh(nextSample) {
|
|
457
|
+
if (typeof nextSample === "string") sample = nextSample;
|
|
458
|
+
stats.textContent = "";
|
|
459
|
+
blocks.textContent = "";
|
|
460
|
+
const segs = segmentText(sample, cfg);
|
|
461
|
+
const st = segmentStats(segs);
|
|
462
|
+
stats.appendChild(h("span", "kb-num kb-segment-stat", `${st.count} \u6BB5`));
|
|
463
|
+
stats.appendChild(h("span", "kb-num kb-segment-stat", `\u5747\u957F ${st.avgLength}`));
|
|
464
|
+
stats.appendChild(h("span", "kb-num kb-segment-stat", `\u2248${st.tokensEstimate} tokens`));
|
|
465
|
+
segs.slice(0, PREVIEW_LIMIT).forEach((text, i) => {
|
|
466
|
+
const block = h("div", "kb-segment-block");
|
|
467
|
+
block.appendChild(h("span", "kb-num kb-segment-block-no", String(i + 1).padStart(2, "0")));
|
|
468
|
+
const body = h("div", "kb-segment-block-text", text);
|
|
469
|
+
body.title = "\u70B9\u51FB\u5C55\u5F00/\u6536\u8D77";
|
|
470
|
+
block.appendChild(body);
|
|
471
|
+
blocks.appendChild(block);
|
|
472
|
+
});
|
|
473
|
+
if (!segs.length) blocks.appendChild(h("div", "kb-empty", "\u6837\u672C\u4E3A\u7A7A\uFF0C\u6682\u65E0\u53EF\u9884\u89C8\u5206\u6BB5"));
|
|
474
|
+
more.textContent = segs.length > PREVIEW_LIMIT ? `\u5171 ${segs.length} \u6BB5\uFF0C\u4EC5\u9884\u89C8\u524D ${PREVIEW_LIMIT} \u6BB5` : "";
|
|
475
|
+
more.hidden = segs.length <= PREVIEW_LIMIT;
|
|
476
|
+
}
|
|
477
|
+
validate();
|
|
478
|
+
refresh();
|
|
479
|
+
return {
|
|
480
|
+
getConfig() {
|
|
481
|
+
return normalizeSegmentConfig(cfg);
|
|
482
|
+
},
|
|
483
|
+
setConfig(next) {
|
|
484
|
+
cfg = normalizeSegmentConfig({ ...cfg, ...next });
|
|
485
|
+
if (typeof document === "undefined") return;
|
|
486
|
+
syncForm();
|
|
487
|
+
validate();
|
|
488
|
+
refresh();
|
|
489
|
+
},
|
|
490
|
+
refresh,
|
|
491
|
+
destroy() {
|
|
492
|
+
alive = false;
|
|
493
|
+
ac.abort();
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
export {
|
|
498
|
+
createKbSegment
|
|
499
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { KbCitation } from './kb-core.js';
|
|
2
|
+
import './ai-core.js';
|
|
3
|
+
|
|
4
|
+
/** renderKbSources 的配置项:布局变体 / onlyCited 过滤 / 基准时钟 / 点击回调 */
|
|
5
|
+
interface KbSourcesOptions {
|
|
6
|
+
/** 布局变体:row = 答案上方横排(默认);rail = 侧栏纵列 */
|
|
7
|
+
variant?: 'row' | 'rail';
|
|
8
|
+
/** 只保留被引过的来源(useCount > 0;流式期间「随流填充」模式) */
|
|
9
|
+
onlyCited?: boolean;
|
|
10
|
+
/** 相对时间 / stale 判定的基准时钟(测试注入用;缺省 new Date()) */
|
|
11
|
+
now?: Date;
|
|
12
|
+
/** 点击来源行回调(与 icen:kb-source-open 事件双通道) */
|
|
13
|
+
onOpen?: (source: KbCitation) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 渲染来源列表(useCount 降序;--row 横排 / --rail 纵列;onlyCited 过滤;空态 .kb-empty)。
|
|
17
|
+
* 返回挂载容器 el。
|
|
18
|
+
*/
|
|
19
|
+
declare function renderKbSources(el: HTMLElement, sources: KbCitation[], opts?: KbSourcesOptions): HTMLElement;
|
|
20
|
+
/**
|
|
21
|
+
* 初始化来源列表交互(root 级委托;幂等 WeakSet 标记,返回销毁函数,销毁后可重新 init)。
|
|
22
|
+
* 点击来源行 / 申请访问按钮 → icen:kb-source-open { source } + opts.onOpen 回调;
|
|
23
|
+
* role=button 受限行补 Enter/Space 键控等价。
|
|
24
|
+
*/
|
|
25
|
+
declare function initKbSources(root?: ParentNode): () => void;
|
|
26
|
+
|
|
27
|
+
export { type KbSourcesOptions, initKbSources, renderKbSources };
|