@icen.ai/ui 0.8.0 → 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 +63 -14
- package/README.md +17 -9
- package/dist/base.css +4 -3
- package/dist/behaviors/accordion.d.mts +5 -2
- package/dist/behaviors/accordion.mjs +36 -11
- package/dist/behaviors/ai-branch.d.mts +19 -0
- package/dist/behaviors/ai-branch.mjs +217 -0
- package/dist/behaviors/ai-chat.d.mts +11 -7
- package/dist/behaviors/ai-chat.mjs +134 -51
- package/dist/behaviors/ai-composer.d.mts +28 -3
- package/dist/behaviors/ai-composer.mjs +399 -229
- package/dist/behaviors/ai-core.d.mts +9 -4
- package/dist/behaviors/ai-core.mjs +21 -8
- package/dist/behaviors/ai-diff.d.mts +2 -2
- package/dist/behaviors/ai-diff.mjs +172 -41
- package/dist/behaviors/ai-feedback.d.mts +26 -0
- package/dist/behaviors/ai-feedback.mjs +303 -0
- package/dist/behaviors/ai-panel.d.mts +26 -9
- package/dist/behaviors/ai-panel.mjs +211 -110
- package/dist/behaviors/ai-provider.mjs +54 -48
- package/dist/behaviors/ai-threads.d.mts +38 -0
- package/dist/behaviors/ai-threads.mjs +319 -0
- package/dist/behaviors/ai-tool.d.mts +12 -4
- package/dist/behaviors/ai-tool.mjs +316 -263
- package/dist/behaviors/ai-tools.d.mts +9 -6
- package/dist/behaviors/ai-tools.mjs +264 -155
- package/dist/behaviors/back-top.mjs +16 -1
- package/dist/behaviors/carousel.d.mts +5 -2
- package/dist/behaviors/carousel.mjs +53 -26
- package/dist/behaviors/charts.d.mts +170 -22
- package/dist/behaviors/charts.mjs +1016 -150
- package/dist/behaviors/command-palette.d.mts +40 -6
- package/dist/behaviors/command-palette.mjs +143 -15
- package/dist/behaviors/context-menu.d.mts +21 -4
- package/dist/behaviors/context-menu.mjs +122 -38
- package/dist/behaviors/controls.d.mts +59 -0
- package/dist/behaviors/controls.mjs +601 -0
- package/dist/behaviors/copy.d.mts +5 -2
- package/dist/behaviors/copy.mjs +37 -12
- package/dist/behaviors/datatable.d.mts +12 -0
- package/dist/behaviors/datatable.mjs +226 -43
- package/dist/behaviors/date-picker.d.mts +2 -2
- package/dist/behaviors/date-picker.mjs +171 -32
- package/dist/behaviors/dropdown.d.mts +6 -2
- package/dist/behaviors/dropdown.mjs +164 -25
- package/dist/behaviors/events.d.mts +523 -0
- package/dist/behaviors/events.mjs +208 -0
- package/dist/behaviors/input.mjs +25 -12
- 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/behaviors/modal.d.mts +24 -5
- package/dist/behaviors/modal.mjs +76 -10
- package/dist/behaviors/nav.d.mts +1 -1
- package/dist/behaviors/nav.mjs +33 -7
- package/dist/behaviors/notification.d.mts +20 -4
- package/dist/behaviors/notification.mjs +53 -73
- package/dist/behaviors/popover.d.mts +39 -3
- package/dist/behaviors/popover.mjs +90 -4
- package/dist/behaviors/select.d.mts +4 -2
- package/dist/behaviors/select.mjs +161 -24
- package/dist/behaviors/sidebar.d.mts +5 -2
- package/dist/behaviors/sidebar.mjs +43 -13
- package/dist/behaviors/slider.d.mts +5 -2
- package/dist/behaviors/slider.mjs +54 -43
- package/dist/behaviors/split-pane.d.mts +6 -2
- package/dist/behaviors/split-pane.mjs +62 -21
- package/dist/behaviors/tabs.d.mts +3 -2
- package/dist/behaviors/tabs.mjs +65 -14
- package/dist/behaviors/tag-input.d.mts +5 -2
- package/dist/behaviors/tag-input.mjs +61 -21
- package/dist/behaviors/theme.d.mts +13 -2
- package/dist/behaviors/theme.mjs +48 -4
- package/dist/behaviors/toast.d.mts +30 -8
- package/dist/behaviors/toast.mjs +74 -7
- package/dist/behaviors/tree.d.mts +5 -2
- package/dist/behaviors/tree.mjs +38 -15
- package/dist/behaviors/upload.mjs +21 -23
- package/dist/cli.mjs +1 -1
- 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 +218 -1
- package/dist/components/ai-threads.mjs +3 -0
- package/dist/components/ai-tool-call.mjs +1 -1
- package/dist/components/badge.css +1 -1
- package/dist/components/breadcrumb.css +0 -32
- package/dist/components/chart-calendar.mjs +1 -2
- 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-pie.css +0 -6
- package/dist/components/chart-scatter.mjs +1 -1
- package/dist/components/charts.css +33 -1
- package/dist/components/charts.mjs +1 -0
- package/dist/components/content.css +3 -16
- package/dist/components/datatable.css +1 -1
- package/dist/components/date-picker.css +1 -1
- 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/components/notification.css +2 -3
- package/dist/components/pagination.css +3 -1
- package/dist/components/pagination.mjs +1 -0
- package/dist/components/rating.mjs +1 -0
- package/dist/components/segmented.css +9 -1
- package/dist/components/segmented.mjs +1 -1
- package/dist/components/select.css +67 -4
- package/dist/components/steps.css +13 -3
- package/dist/components/steps.mjs +1 -0
- package/dist/components/switch.css +3 -1
- package/dist/components/switch.mjs +1 -0
- package/dist/components/table.mjs +1 -0
- package/dist/components/tabs.css +23 -0
- package/dist/components/tag-input.css +1 -6
- package/dist/components/upload.css +0 -15
- package/dist/index.d.mts +48 -11
- package/dist/index.mjs +13205 -3615
- package/dist/registry.json +366 -15
- package/dist/retro-effects.css +3 -16
- package/dist/tokens/retro-effects.css +3 -16
- package/dist/tokens/style-profiles.css +3 -2
- package/dist/tokens/typography.css +2 -0
- package/dist/tokens.css +5 -2
- package/dist/ui.css +5713 -267
- 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/ai-native.md +7 -4
- package/docs/spec/kb-family.md +443 -0
- package/package.json +2 -2
|
@@ -0,0 +1,217 @@
|
|
|
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
|
+
|
|
134
|
+
// src/behaviors/events.ts
|
|
135
|
+
function emitIcen(source, type, detail) {
|
|
136
|
+
return source.dispatchEvent(
|
|
137
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/behaviors/ai-branch.ts
|
|
142
|
+
function clampIndex(index, count) {
|
|
143
|
+
if (!Number.isFinite(index) || index < 0) return 0;
|
|
144
|
+
if (index > count - 1) return count - 1;
|
|
145
|
+
return Math.floor(index);
|
|
146
|
+
}
|
|
147
|
+
function renderAiBranch(el, model) {
|
|
148
|
+
if (typeof document === "undefined") return el;
|
|
149
|
+
el.textContent = "";
|
|
150
|
+
const count = Number.isFinite(model.count) ? Math.floor(model.count) : 0;
|
|
151
|
+
if (count <= 1) {
|
|
152
|
+
el.hidden = true;
|
|
153
|
+
el.setAttribute("aria-hidden", "true");
|
|
154
|
+
return el;
|
|
155
|
+
}
|
|
156
|
+
el.hidden = false;
|
|
157
|
+
el.removeAttribute("aria-hidden");
|
|
158
|
+
const index = clampIndex(model.index, count);
|
|
159
|
+
const root = h("div", "ai-branch");
|
|
160
|
+
root.dataset.index = String(index);
|
|
161
|
+
root.dataset.count = String(count);
|
|
162
|
+
root.setAttribute("role", "group");
|
|
163
|
+
root.setAttribute("aria-label", "\u5206\u652F\u7248\u672C");
|
|
164
|
+
const prev = h("button", "ai-branch-prev", "\u2039");
|
|
165
|
+
prev.type = "button";
|
|
166
|
+
prev.setAttribute("aria-label", "\u4E0A\u4E00\u7248\u672C");
|
|
167
|
+
prev.disabled = index <= 0;
|
|
168
|
+
root.appendChild(prev);
|
|
169
|
+
root.appendChild(h("span", "ai-branch-page", `${index + 1}/${count}`));
|
|
170
|
+
const next = h("button", "ai-branch-next", "\u203A");
|
|
171
|
+
next.type = "button";
|
|
172
|
+
next.setAttribute("aria-label", "\u4E0B\u4E00\u7248\u672C");
|
|
173
|
+
next.disabled = index >= count - 1;
|
|
174
|
+
root.appendChild(next);
|
|
175
|
+
el.appendChild(root);
|
|
176
|
+
return el;
|
|
177
|
+
}
|
|
178
|
+
function initAiBranch(root) {
|
|
179
|
+
if (typeof document === "undefined") return () => void 0;
|
|
180
|
+
const scope = root ?? document;
|
|
181
|
+
const marked = scope;
|
|
182
|
+
if (marked.__icenAiBranchInit) return () => void 0;
|
|
183
|
+
marked.__icenAiBranchInit = true;
|
|
184
|
+
const target = scope;
|
|
185
|
+
const step = (branch, delta) => {
|
|
186
|
+
const count = Number(branch.dataset.count ?? "");
|
|
187
|
+
if (!Number.isFinite(count) || count <= 1) return;
|
|
188
|
+
const current = clampIndex(Number(branch.dataset.index ?? "0"), count);
|
|
189
|
+
const index = clampIndex(current + delta, count);
|
|
190
|
+
if (index === current) return;
|
|
191
|
+
branch.dataset.index = String(index);
|
|
192
|
+
const page = branch.querySelector(".ai-branch-page");
|
|
193
|
+
if (page) page.textContent = `${index + 1}/${count}`;
|
|
194
|
+
const prev = branch.querySelector(".ai-branch-prev");
|
|
195
|
+
if (prev) prev.disabled = index <= 0;
|
|
196
|
+
const next = branch.querySelector(".ai-branch-next");
|
|
197
|
+
if (next) next.disabled = index >= count - 1;
|
|
198
|
+
emitIcen(branch, "icen:ai-branch-change", { index, count });
|
|
199
|
+
};
|
|
200
|
+
const onClick = (e) => {
|
|
201
|
+
const t = e.target instanceof Element ? e.target : null;
|
|
202
|
+
if (!t) return;
|
|
203
|
+
const branch = t.closest(".ai-branch");
|
|
204
|
+
if (!branch) return;
|
|
205
|
+
if (t.closest(".ai-branch-prev")) step(branch, -1);
|
|
206
|
+
else if (t.closest(".ai-branch-next")) step(branch, 1);
|
|
207
|
+
};
|
|
208
|
+
target.addEventListener("click", onClick);
|
|
209
|
+
return () => {
|
|
210
|
+
target.removeEventListener("click", onClick);
|
|
211
|
+
marked.__icenAiBranchInit = false;
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export {
|
|
215
|
+
initAiBranch,
|
|
216
|
+
renderAiBranch
|
|
217
|
+
};
|
|
@@ -8,17 +8,19 @@ declare function initAiChat(root?: ParentNode): () => void;
|
|
|
8
8
|
interface AiStreamHandle {
|
|
9
9
|
/** textContent 级追加一段文本(不解析 HTML) */
|
|
10
10
|
append(text: string): void;
|
|
11
|
-
/** 流正常结束:移除 .is-streaming / aria-busy;reasoning
|
|
11
|
+
/** 流正常结束:移除 .is-streaming / aria-busy;reasoning 回填「思考 Ns」并延迟 1s 自动收起一次(§5.27) */
|
|
12
12
|
done(): void;
|
|
13
|
-
/**
|
|
13
|
+
/** 流被取消:保留已生成内容,补「继续生成」槽;不折叠 reasoning、不计耗时(aborted ≠ error) */
|
|
14
14
|
cancel(): void;
|
|
15
|
-
/**
|
|
15
|
+
/** 流失败:终止追加并挂 .is-error(错误文本展示由消费方/renderAiMessage.setError 负责);reasoning 同 done 收起 + 补「继续生成」槽 */
|
|
16
16
|
fail(): void;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* 创建流式追加句柄:追加期间宿主挂 .is-streaming(aria-busy=true
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* 创建流式追加句柄:追加期间宿主挂 .is-streaming(aria-busy=true),完成/取消移除。
|
|
20
|
+
* 宿主在 .ai-reasoning 内时同步驱动 reasoning 折叠契约(§5.27):流式态默认展开;
|
|
21
|
+
* done/fail 移除 .is-streaming、回填「思考 Ns」(自折叠头 data-reasoning-start 起算)并
|
|
22
|
+
* 延迟 1s 自动收起一次(用户手动 toggle 过则永不自动收);fail/cancel 保留已生成内容
|
|
23
|
+
* 并在消息尾补 .ai-msg-continue「继续生成」槽(cancel 不显示错误——aborted ≠ error)。
|
|
22
24
|
*/
|
|
23
25
|
declare function createAiStream(el: HTMLElement): AiStreamHandle;
|
|
24
26
|
interface AiMessageModel {
|
|
@@ -45,7 +47,9 @@ interface AiMessageHandle {
|
|
|
45
47
|
stream(): AiStreamHandle;
|
|
46
48
|
}
|
|
47
49
|
/** 单个内容部件 → DOM 节点(文本一律 textContent;媒体 src 走 aiContentUrl 归一)。
|
|
48
|
-
* 导出供 ai-tool 的 IO 区复用(MCP 多模态回执;类名 .ai-msg-* 语义即「消息部件」,全局生效)。
|
|
50
|
+
* 导出供 ai-tool 的 IO 区复用(MCP 多模态回执;类名 .ai-msg-* 语义即「消息部件」,全局生效)。
|
|
51
|
+
* 前置条件:浏览器环境——本函数无 SSR 守卫,SSR 由调用方守卫覆盖(renderAiMessage /
|
|
52
|
+
* renderAiToolCall 头部守卫后才会触达此处)。 */
|
|
49
53
|
declare function renderAiContentPart(part: AiContentPart): HTMLElement | undefined;
|
|
50
54
|
/**
|
|
51
55
|
* DOM API 渲染一条消息(append 到容器末尾):多角色变体 + 多模态部件 + copy/retry
|
|
@@ -135,9 +135,9 @@ function formatDuration(ms) {
|
|
|
135
135
|
const m = Math.floor(ms / 6e4);
|
|
136
136
|
const s = Math.round(ms % 6e4 / 1e3);
|
|
137
137
|
if (ms < 36e5) return s > 0 ? `${m}m${s}s` : `${m}m`;
|
|
138
|
-
const
|
|
138
|
+
const h2 = Math.floor(ms / 36e5);
|
|
139
139
|
const rm = Math.round(ms % 36e5 / 6e4);
|
|
140
|
-
return rm > 0 ? `${
|
|
140
|
+
return rm > 0 ? `${h2}h${rm}m` : `${h2}h`;
|
|
141
141
|
}
|
|
142
142
|
function aiContentUrl(part) {
|
|
143
143
|
if (part.type === "text" || part.type === "resource-link") return void 0;
|
|
@@ -147,6 +147,12 @@ function aiContentUrl(part) {
|
|
|
147
147
|
}
|
|
148
148
|
return void 0;
|
|
149
149
|
}
|
|
150
|
+
function h(tag, className, text) {
|
|
151
|
+
const node = document.createElement(tag);
|
|
152
|
+
if (className) node.className = className;
|
|
153
|
+
if (text != null) node.textContent = text;
|
|
154
|
+
return node;
|
|
155
|
+
}
|
|
150
156
|
function svgIcon(svg2) {
|
|
151
157
|
if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
|
|
152
158
|
let parsed;
|
|
@@ -159,15 +165,15 @@ function svgIcon(svg2) {
|
|
|
159
165
|
if (!root || root.tagName.toLowerCase() !== "svg" || parsed.querySelector("parsererror")) {
|
|
160
166
|
return null;
|
|
161
167
|
}
|
|
162
|
-
const sanitize = (
|
|
163
|
-
for (const attr of Array.from(
|
|
168
|
+
const sanitize = (el) => {
|
|
169
|
+
for (const attr of Array.from(el.attributes)) {
|
|
164
170
|
const name = attr.name.toLowerCase();
|
|
165
|
-
if (name.startsWith("on"))
|
|
171
|
+
if (name.startsWith("on")) el.removeAttribute(attr.name);
|
|
166
172
|
if ((name === "href" || name === "xlink:href") && attr.value.trim().toLowerCase().startsWith("javascript:")) {
|
|
167
|
-
|
|
173
|
+
el.removeAttribute(attr.name);
|
|
168
174
|
}
|
|
169
175
|
}
|
|
170
|
-
for (const child of Array.from(
|
|
176
|
+
for (const child of Array.from(el.children)) {
|
|
171
177
|
const tag = child.tagName.toLowerCase();
|
|
172
178
|
if (tag === "script" || tag === "foreignobject") {
|
|
173
179
|
child.remove();
|
|
@@ -180,6 +186,13 @@ function svgIcon(svg2) {
|
|
|
180
186
|
return document.importNode(root, true);
|
|
181
187
|
}
|
|
182
188
|
|
|
189
|
+
// src/behaviors/events.ts
|
|
190
|
+
function emitIcen(source, type, detail) {
|
|
191
|
+
return source.dispatchEvent(
|
|
192
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
183
196
|
// src/behaviors/ai-chat.ts
|
|
184
197
|
var ICON_COPY = '<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"><rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>';
|
|
185
198
|
var ICON_RETRY = '<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"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>';
|
|
@@ -191,7 +204,7 @@ function prefersReducedMotion() {
|
|
|
191
204
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
192
205
|
}
|
|
193
206
|
function emit(target, name, detail) {
|
|
194
|
-
target
|
|
207
|
+
emitIcen(target, name, detail);
|
|
195
208
|
}
|
|
196
209
|
function fallbackCopy(text) {
|
|
197
210
|
const ta = document.createElement("textarea");
|
|
@@ -214,9 +227,9 @@ function copyText(text) {
|
|
|
214
227
|
}
|
|
215
228
|
}
|
|
216
229
|
function setupChat(chat) {
|
|
217
|
-
const
|
|
218
|
-
if (
|
|
219
|
-
|
|
230
|
+
const el = chat;
|
|
231
|
+
if (el.__icenAiChatInit) return void 0;
|
|
232
|
+
el.__icenAiChatInit = true;
|
|
220
233
|
const scrollEl = chat.querySelector(".ai-chat-scroll");
|
|
221
234
|
let jump = chat.querySelector(".ai-chat-jump") ?? null;
|
|
222
235
|
let pinned = true;
|
|
@@ -253,7 +266,7 @@ function setupChat(chat) {
|
|
|
253
266
|
} else {
|
|
254
267
|
unread = 0;
|
|
255
268
|
}
|
|
256
|
-
lastMsgCount =
|
|
269
|
+
lastMsgCount = count;
|
|
257
270
|
updateJump();
|
|
258
271
|
}
|
|
259
272
|
};
|
|
@@ -289,7 +302,14 @@ function setupChat(chat) {
|
|
|
289
302
|
if (!msg) return;
|
|
290
303
|
const action = actionBtn.dataset.aiMsgAction;
|
|
291
304
|
if (action === "copy") {
|
|
292
|
-
|
|
305
|
+
const bodyEl = msg.querySelector(".ai-msg-body");
|
|
306
|
+
const clone = bodyEl?.cloneNode(true);
|
|
307
|
+
if (clone instanceof HTMLElement) {
|
|
308
|
+
clone.querySelectorAll(".ai-msg-model").forEach((chip) => chip.remove());
|
|
309
|
+
copyText(clone.textContent ?? "");
|
|
310
|
+
} else {
|
|
311
|
+
copyText("");
|
|
312
|
+
}
|
|
293
313
|
emit(msg, "icen:ai-copy", { el: msg });
|
|
294
314
|
} else if (action === "retry") {
|
|
295
315
|
emit(msg, "icen:ai-retry", { el: msg });
|
|
@@ -305,6 +325,7 @@ function setupChat(chat) {
|
|
|
305
325
|
head.setAttribute("aria-expanded", String(open));
|
|
306
326
|
reasoning.classList.toggle("is-open", open);
|
|
307
327
|
if (body) body.hidden = !open;
|
|
328
|
+
reasoning.__icenReasonUserTouched = true;
|
|
308
329
|
emit(reasoning, "icen:ai-toggle", { el: reasoning, open });
|
|
309
330
|
}
|
|
310
331
|
};
|
|
@@ -353,7 +374,7 @@ function setupChat(chat) {
|
|
|
353
374
|
disposers.push(() => chat.removeEventListener("click", onClick));
|
|
354
375
|
return () => {
|
|
355
376
|
for (const dispose of disposers) dispose();
|
|
356
|
-
|
|
377
|
+
el.__icenAiChatInit = false;
|
|
357
378
|
};
|
|
358
379
|
}
|
|
359
380
|
function initAiChat(root) {
|
|
@@ -375,46 +396,105 @@ function initAiChat(root) {
|
|
|
375
396
|
cleanups.length = 0;
|
|
376
397
|
};
|
|
377
398
|
}
|
|
378
|
-
|
|
379
|
-
|
|
399
|
+
var REASONING_AUTO_CLOSE_MS = 1e3;
|
|
400
|
+
function setReasoningOpen(reasoning, open) {
|
|
401
|
+
const head = reasoning.querySelector(".ai-reasoning-head");
|
|
402
|
+
const body = reasoning.querySelector(".ai-reasoning-body");
|
|
403
|
+
head?.setAttribute("aria-expanded", String(open));
|
|
404
|
+
reasoning.classList.toggle("is-open", open);
|
|
405
|
+
if (body) body.hidden = !open;
|
|
406
|
+
}
|
|
407
|
+
function clearContinueSlot(scope) {
|
|
408
|
+
scope?.querySelectorAll(".ai-msg-continue").forEach((n) => n.remove());
|
|
409
|
+
}
|
|
410
|
+
function createAiStream(el) {
|
|
411
|
+
const reasoning = el.closest(".ai-reasoning");
|
|
412
|
+
const msgBody = el.closest(".ai-msg-body");
|
|
413
|
+
const msg = el.closest(".ai-msg");
|
|
380
414
|
const startedAt = Date.now();
|
|
381
415
|
let state = "streaming";
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
reasoning
|
|
416
|
+
el.classList.add("is-streaming");
|
|
417
|
+
el.setAttribute("aria-busy", "true");
|
|
418
|
+
if (reasoning) {
|
|
419
|
+
reasoning.classList.add("is-streaming");
|
|
420
|
+
setReasoningOpen(reasoning, true);
|
|
421
|
+
const head = reasoning.querySelector(".ai-reasoning-head");
|
|
422
|
+
if (head && head.dataset.reasoningStart == null) head.dataset.reasoningStart = String(startedAt);
|
|
423
|
+
}
|
|
424
|
+
clearContinueSlot(msg ?? msgBody ?? el);
|
|
425
|
+
const fillReasoningTime = () => {
|
|
426
|
+
if (!reasoning) return;
|
|
427
|
+
const time = reasoning.querySelector(".ai-reasoning-time");
|
|
428
|
+
if (!time) return;
|
|
429
|
+
const head = reasoning.querySelector(".ai-reasoning-head");
|
|
430
|
+
const raw = Number(head?.dataset.reasoningStart ?? "");
|
|
431
|
+
const t0 = Number.isFinite(raw) && raw > 0 ? raw : startedAt;
|
|
432
|
+
time.textContent = `\u601D\u8003 ${formatDuration(Math.max(0, Date.now() - t0))}`;
|
|
433
|
+
};
|
|
434
|
+
const scheduleReasoningAutoClose = () => {
|
|
435
|
+
if (!reasoning) return;
|
|
436
|
+
const r = reasoning;
|
|
437
|
+
if (r.__icenReasonUserTouched || r.__icenReasonAutoClosed) return;
|
|
438
|
+
const close = () => {
|
|
439
|
+
if (r.__icenReasonUserTouched || r.__icenReasonAutoClosed) return;
|
|
440
|
+
r.__icenReasonAutoClosed = true;
|
|
441
|
+
setReasoningOpen(r, false);
|
|
442
|
+
};
|
|
443
|
+
if (typeof window === "undefined") {
|
|
444
|
+
close();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
window.setTimeout(close, REASONING_AUTO_CLOSE_MS);
|
|
448
|
+
};
|
|
449
|
+
const appendContinueSlot = () => {
|
|
450
|
+
if (typeof document === "undefined") return;
|
|
451
|
+
const mount = msgBody ?? msg ?? (el.parentElement instanceof HTMLElement ? el.parentElement : null);
|
|
452
|
+
if (!mount || mount.querySelector(":scope > .ai-msg-continue")) return;
|
|
453
|
+
const btn = document.createElement("button");
|
|
454
|
+
btn.type = "button";
|
|
455
|
+
btn.className = "ai-msg-continue";
|
|
456
|
+
btn.textContent = "\u7EE7\u7EED\u751F\u6210";
|
|
457
|
+
btn.addEventListener("click", () => {
|
|
458
|
+
const target = msg ?? btn;
|
|
459
|
+
emit(target, "icen:ai-retry", { el: target });
|
|
460
|
+
});
|
|
461
|
+
mount.appendChild(btn);
|
|
462
|
+
};
|
|
385
463
|
const settle = (finished) => {
|
|
386
464
|
if (state !== "streaming") return;
|
|
387
465
|
state = finished ? "done" : "cancelled";
|
|
388
|
-
|
|
389
|
-
|
|
466
|
+
el.classList.remove("is-streaming");
|
|
467
|
+
el.removeAttribute("aria-busy");
|
|
390
468
|
if (!reasoning) return;
|
|
391
469
|
reasoning.classList.remove("is-streaming");
|
|
392
470
|
if (!finished) return;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
head?.setAttribute("aria-expanded", "false");
|
|
396
|
-
if (body) body.hidden = true;
|
|
397
|
-
const time = reasoning.querySelector(".ai-reasoning-time");
|
|
398
|
-
if (time) time.textContent = formatDuration(Date.now() - startedAt);
|
|
471
|
+
fillReasoningTime();
|
|
472
|
+
scheduleReasoningAutoClose();
|
|
399
473
|
};
|
|
400
474
|
return {
|
|
401
475
|
append(text) {
|
|
402
476
|
if (state !== "streaming" || !text) return;
|
|
403
|
-
|
|
477
|
+
el.textContent += text;
|
|
404
478
|
},
|
|
405
479
|
done() {
|
|
406
480
|
settle(true);
|
|
407
481
|
},
|
|
408
482
|
cancel() {
|
|
409
483
|
settle(false);
|
|
484
|
+
appendContinueSlot();
|
|
410
485
|
},
|
|
411
486
|
fail() {
|
|
412
487
|
if (state !== "streaming") return;
|
|
413
488
|
state = "error";
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
reasoning
|
|
489
|
+
el.classList.remove("is-streaming");
|
|
490
|
+
el.removeAttribute("aria-busy");
|
|
491
|
+
el.classList.add("is-error");
|
|
492
|
+
if (reasoning) {
|
|
493
|
+
reasoning.classList.remove("is-streaming");
|
|
494
|
+
fillReasoningTime();
|
|
495
|
+
scheduleReasoningAutoClose();
|
|
496
|
+
}
|
|
497
|
+
appendContinueSlot();
|
|
418
498
|
}
|
|
419
499
|
};
|
|
420
500
|
}
|
|
@@ -429,15 +509,9 @@ function safeHref(uri) {
|
|
|
429
509
|
if (s.startsWith("javascript:") || s.startsWith("data:text/html")) return void 0;
|
|
430
510
|
return uri;
|
|
431
511
|
}
|
|
432
|
-
function el(tag, className, text) {
|
|
433
|
-
const node = document.createElement(tag);
|
|
434
|
-
if (className) node.className = className;
|
|
435
|
-
if (text != null) node.textContent = text;
|
|
436
|
-
return node;
|
|
437
|
-
}
|
|
438
512
|
function renderAiContentPart(part) {
|
|
439
513
|
if (part.type === "text") {
|
|
440
|
-
const span =
|
|
514
|
+
const span = h("span", "ai-msg-text");
|
|
441
515
|
span.textContent = part.text;
|
|
442
516
|
if (part.state === "streaming") span.classList.add("is-streaming");
|
|
443
517
|
return span;
|
|
@@ -472,8 +546,8 @@ function renderAiContentPart(part) {
|
|
|
472
546
|
return video;
|
|
473
547
|
}
|
|
474
548
|
if (part.type === "file") {
|
|
475
|
-
const chip =
|
|
476
|
-
chip.appendChild(
|
|
549
|
+
const chip = h("span", "ai-msg-file");
|
|
550
|
+
chip.appendChild(h("span", "ai-msg-file-name", part.filename ?? "\u6587\u4EF6"));
|
|
477
551
|
const href2 = part.url ? safeHref(part.url) : void 0;
|
|
478
552
|
if (href2 && /^(https?:|data:)/i.test(href2)) {
|
|
479
553
|
const a2 = document.createElement("a");
|
|
@@ -494,9 +568,18 @@ function renderAiContentPart(part) {
|
|
|
494
568
|
return a;
|
|
495
569
|
}
|
|
496
570
|
function renderAiMessage(scrollEl, model) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
571
|
+
if (typeof document === "undefined") {
|
|
572
|
+
return {
|
|
573
|
+
el: scrollEl,
|
|
574
|
+
body: scrollEl,
|
|
575
|
+
setMeta: () => void 0,
|
|
576
|
+
setError: () => void 0,
|
|
577
|
+
stream: () => createAiStream(scrollEl)
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
const row = h("div", `ai-msg ai-msg--${model.role}`);
|
|
581
|
+
const avatar = h("span", "ai-msg-avatar", ROLE_AVATAR[model.role] ?? "");
|
|
582
|
+
const body = h("div", "ai-msg-body");
|
|
500
583
|
row.appendChild(avatar);
|
|
501
584
|
row.appendChild(body);
|
|
502
585
|
const parts = typeof model.content === "string" ? [{ type: "text", text: model.content }] : model.content;
|
|
@@ -504,18 +587,18 @@ function renderAiMessage(scrollEl, model) {
|
|
|
504
587
|
const node = renderAiContentPart(part);
|
|
505
588
|
if (node) body.appendChild(node);
|
|
506
589
|
}
|
|
507
|
-
if (model.model) body.appendChild(
|
|
508
|
-
const metaEl =
|
|
590
|
+
if (model.model) body.appendChild(h("span", "ai-msg-model", model.model));
|
|
591
|
+
const metaEl = h("div", "ai-msg-meta");
|
|
509
592
|
if (model.meta != null) metaEl.textContent = model.meta;
|
|
510
593
|
row.appendChild(metaEl);
|
|
511
|
-
const actions =
|
|
512
|
-
const copyBtn =
|
|
594
|
+
const actions = h("div", "ai-msg-actions");
|
|
595
|
+
const copyBtn = h("button", "ai-msg-action");
|
|
513
596
|
copyBtn.type = "button";
|
|
514
597
|
copyBtn.setAttribute("data-ai-msg-action", "copy");
|
|
515
598
|
copyBtn.setAttribute("aria-label", "\u590D\u5236");
|
|
516
599
|
const copyIcon = svgIcon(ICON_COPY);
|
|
517
600
|
if (copyIcon) copyBtn.appendChild(copyIcon);
|
|
518
|
-
const retryBtn =
|
|
601
|
+
const retryBtn = h("button", "ai-msg-action");
|
|
519
602
|
retryBtn.type = "button";
|
|
520
603
|
retryBtn.setAttribute("data-ai-msg-action", "retry");
|
|
521
604
|
retryBtn.setAttribute("aria-label", "\u91CD\u8BD5");
|
|
@@ -533,13 +616,13 @@ function renderAiMessage(scrollEl, model) {
|
|
|
533
616
|
row.classList.add("ai-msg--error");
|
|
534
617
|
let errEl = row.querySelector(".ai-msg-error-text");
|
|
535
618
|
if (!errEl) {
|
|
536
|
-
errEl =
|
|
619
|
+
errEl = h("div", "ai-msg-error-text");
|
|
537
620
|
body.appendChild(errEl);
|
|
538
621
|
}
|
|
539
622
|
errEl.textContent = message;
|
|
540
623
|
},
|
|
541
624
|
stream() {
|
|
542
|
-
const span =
|
|
625
|
+
const span = h("span", "ai-msg-stream-text");
|
|
543
626
|
body.appendChild(span);
|
|
544
627
|
return createAiStream(span);
|
|
545
628
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AiUsage } from './ai-core.js';
|
|
2
|
+
import { PanelSizing } from './popover.js';
|
|
2
3
|
import { AiTodoItem, AiUsageRingOpts } from './ai-panel.js';
|
|
3
4
|
import { AiClient, AiAuditor } from './ai-provider.js';
|
|
4
5
|
|
|
@@ -25,13 +26,29 @@ interface AiComposerCommand {
|
|
|
25
26
|
/** 参数提示(如 "<path>"),弹层命令行右侧灰色展示 */
|
|
26
27
|
argsHint?: string;
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
/** 引用 kind:内置 4 种 + 开放注册;`string & {}` 保自动补全同时放行 registerRefKind 注册的任意 kind(照 charts.ts ChartTone 手法) */
|
|
30
|
+
type AiComposerRefKind = 'file' | 'folder' | 'doc' | 'agent' | (string & {});
|
|
29
31
|
interface AiComposerRefSource {
|
|
30
32
|
kind: AiComposerRefKind;
|
|
31
33
|
id: string;
|
|
32
34
|
label: string;
|
|
33
35
|
sub?: string;
|
|
34
36
|
}
|
|
37
|
+
/** 引用 kind 定义(registerRefKind 的注册面)。 */
|
|
38
|
+
interface AiComposerRefDef {
|
|
39
|
+
/** 分组标题(弹层 group label;未知 kind 兜底为 kind 原文) */
|
|
40
|
+
label: string;
|
|
41
|
+
/** 单色 SVG 字符串(经 svgIcon 消毒);缺省用通用文档图标 */
|
|
42
|
+
icon?: string;
|
|
43
|
+
/** 分组排序权重(小者在前);缺省排在已知 kind 之后(最后) */
|
|
44
|
+
order?: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 注册(或覆盖)一个引用 kind(第三方扩展入口,照 registerAiKind 的 Map + 兜底模式)。
|
|
48
|
+
* 注册后弹层分组标题/图标/排序取注册表值;未注册的未知 kind 不再静默丢弃——
|
|
49
|
+
* 标题用 kind 原文、图标用通用文档图标、排序排最后。
|
|
50
|
+
*/
|
|
51
|
+
declare function registerRefKind(kind: string, def: AiComposerRefDef): void;
|
|
35
52
|
/**
|
|
36
53
|
* 运行态切换(spec §4.3):
|
|
37
54
|
* el 传 composer 容器或其内部任意元素(含发送钮自身),就近解析 [data-ai-composer]。
|
|
@@ -57,7 +74,9 @@ declare function setComposerModels(el: HTMLElement, providers: AiProviderOption[
|
|
|
57
74
|
*/
|
|
58
75
|
declare function setComposerCommands(el: HTMLElement, commands: AiComposerCommand[]): void;
|
|
59
76
|
/**
|
|
60
|
-
* 配置 @ 引用源(§8):任意位置 token 起点 `@` 触发引用弹层(按 kind
|
|
77
|
+
* 配置 @ 引用源(§8):任意位置 token 起点 `@` 触发引用弹层(按 kind 分组:内置
|
|
78
|
+
* file/folder/doc/agent,可经 registerRefKind 扩展/覆盖;未知 kind 不丢弃——通用文档
|
|
79
|
+
* 图标 + kind 原文兜底,排序缺省排最后)。
|
|
61
80
|
* 选中后文本插入 `@label ` + refs chip +1,派 icen:ai-ref {action:'add', ref};
|
|
62
81
|
* chip × 移除 chip 并删文本里首个 `@label`,派 {action:'remove', ref}。
|
|
63
82
|
*/
|
|
@@ -96,6 +115,12 @@ interface AiComposerBindOpts {
|
|
|
96
115
|
};
|
|
97
116
|
/** 运行态自动管理(默认 true):icen:ai-send → running;icen:ai-done → 解除 */
|
|
98
117
|
running?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* 三弹层(模型/命令/引用)尺寸的整体程序覆盖(PanelSizing 契约):
|
|
120
|
+
* 优先于 composer 根上的 data-panel-*(三弹层共用的用户覆盖面)与各弹层内置默认,
|
|
121
|
+
* 一次设置对三个弹层同时生效。
|
|
122
|
+
*/
|
|
123
|
+
popoverSizing?: PanelSizing;
|
|
99
124
|
}
|
|
100
125
|
interface AiComposerBinding {
|
|
101
126
|
/** 解绑全部监听并中止进行中的托管会话(composer 本身的 init 不受影响) */
|
|
@@ -109,4 +134,4 @@ interface AiComposerBinding {
|
|
|
109
134
|
*/
|
|
110
135
|
declare function bindComposer(el: HTMLElement, opts?: AiComposerBindOpts): AiComposerBinding;
|
|
111
136
|
|
|
112
|
-
export { type AiComposerBindOpts, type AiComposerBinding, type AiComposerCommand, type AiComposerCurrentModel, type AiComposerRefKind, type AiComposerRefSource, type AiProviderModelOption, type AiProviderOption, bindComposer, initAiComposer, setComposerCommands, setComposerModels, setComposerRefs, setComposerRunning, setComposerTodo, setComposerUsage };
|
|
137
|
+
export { type AiComposerBindOpts, type AiComposerBinding, type AiComposerCommand, type AiComposerCurrentModel, type AiComposerRefDef, type AiComposerRefKind, type AiComposerRefSource, type AiProviderModelOption, type AiProviderOption, bindComposer, initAiComposer, registerRefKind, setComposerCommands, setComposerModels, setComposerRefs, setComposerRunning, setComposerTodo, setComposerUsage };
|