@icen.ai/ui 0.8.0 → 0.8.1
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 +15 -9
- package/README.md +8 -3
- 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-chat.d.mts +3 -1
- package/dist/behaviors/ai-chat.mjs +66 -43
- package/dist/behaviors/ai-composer.d.mts +28 -3
- package/dist/behaviors/ai-composer.mjs +291 -209
- 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-panel.d.mts +11 -8
- package/dist/behaviors/ai-panel.mjs +185 -110
- package/dist/behaviors/ai-provider.mjs +54 -48
- package/dist/behaviors/ai-tool.d.mts +12 -4
- package/dist/behaviors/ai-tool.mjs +311 -263
- package/dist/behaviors/ai-tools.d.mts +9 -6
- package/dist/behaviors/ai-tools.mjs +259 -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 +68 -22
- package/dist/behaviors/charts.mjs +221 -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 +330 -0
- package/dist/behaviors/events.mjs +208 -0
- package/dist/behaviors/input.mjs +25 -12
- 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-panel.css +1 -1
- 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-pie.css +0 -6
- package/dist/components/charts.css +1 -1
- 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/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 +12 -10
- package/dist/index.mjs +2700 -1460
- package/dist/registry.json +42 -13
- 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 +138 -98
- package/docs/spec/ai-native.md +7 -4
- package/package.json +1 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
type AiStatus = 'pending' | 'running' | 'streaming' | 'approval' | 'done' | 'error' | 'cancelled';
|
|
2
|
+
/** 状态机七态(规格 §1 的唯一语言)——集群共享(切 is-* 类 / 校验 wire status 用) */
|
|
3
|
+
declare const AI_STATUSES: readonly AiStatus[];
|
|
2
4
|
/** 中文状态标签:等待/执行中/流式中/待确认/完成/失败/已取消 */
|
|
3
5
|
declare function aiStatusLabel(s: AiStatus): string;
|
|
4
6
|
interface AiUsage {
|
|
@@ -9,6 +11,8 @@ interface AiUsage {
|
|
|
9
11
|
reasoning?: number;
|
|
10
12
|
total?: number;
|
|
11
13
|
}
|
|
14
|
+
/** 六键用量求和(非破坏性:不动入参,返回新对象);语义对齐 ai-provider 的同名私有实现,集群共用 */
|
|
15
|
+
declare function addUsage(sum: AiUsage, u: AiUsage): AiUsage;
|
|
12
16
|
type AiKindTint = 'accent' | 'success' | 'warning' | 'error' | 'info' | 'muted';
|
|
13
17
|
interface AiKindDef {
|
|
14
18
|
/** 一行中文名(tool-call 卡片的标题区展示) */
|
|
@@ -39,7 +43,7 @@ interface AiToolCallModel {
|
|
|
39
43
|
}
|
|
40
44
|
/** 注册(或覆盖)一个 kind;name 大小写不敏感。第三方扩展入口。 */
|
|
41
45
|
declare function registerAiKind(name: string, def: AiKindDef): void;
|
|
42
|
-
/** 取 kind
|
|
46
|
+
/** 取 kind 定义;未注册回退内置 'note'。 */
|
|
43
47
|
declare function getAiKind(name: string): AiKindDef;
|
|
44
48
|
/** 从 tool 名推断 kind:Bash/Shell→shell、Read→read、mcp__*→mcp 等。 */
|
|
45
49
|
declare function inferKind(toolName: string): string;
|
|
@@ -114,9 +118,8 @@ interface AiResourceLinkPart {
|
|
|
114
118
|
type AiContentPart = AiTextPart | AiMediaPart | AiResourceLinkPart;
|
|
115
119
|
/** 消息与工具回执的内容:字符串(纯文本捷径)或部件数组(多模态) */
|
|
116
120
|
type AiContent = string | AiContentPart[];
|
|
121
|
+
/** 单部件守卫:type 合法且槽位类型正确(text 看 text、resource-link 看 uri、媒体看 url 或 data+mimeType) */
|
|
117
122
|
declare function isAiContentPart(v: unknown): v is AiContentPart;
|
|
118
|
-
/** 数组且至少一项像内容部件 → 视为部件数组(tool output 多模态探测用) */
|
|
119
|
-
declare function isAiContentPartArray(v: unknown): v is AiContentPart[];
|
|
120
123
|
/** 渲染地址归一:url 直取;data + mimeType 拼 data URI(base64) */
|
|
121
124
|
declare function aiContentUrl(part: AiContentPart): string | undefined;
|
|
122
125
|
/**
|
|
@@ -140,6 +143,8 @@ declare function estimateTokens(content: AiContent | AiUsage): number;
|
|
|
140
143
|
* 本次输出会成为下轮输入的一部分。与 auditor.summary()(累计计费)语义不同。
|
|
141
144
|
*/
|
|
142
145
|
declare function contextEstimate(usage: AiUsage): number;
|
|
146
|
+
/** 极简 DOM 构件:createElement + 可选 className + 可选 textContent —— 集群共用,免四处 createElement 三连 */
|
|
147
|
+
declare function h<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string, text?: string): HTMLElementTagNameMap[K];
|
|
143
148
|
/**
|
|
144
149
|
* 把内联 SVG 字符串解析为消毒后的 SVGElement:
|
|
145
150
|
* - 剥全部 on* 事件属性;剔除 script / foreignObject 子树
|
|
@@ -148,4 +153,4 @@ declare function contextEstimate(usage: AiUsage): number;
|
|
|
148
153
|
*/
|
|
149
154
|
declare function svgIcon(svg: string): SVGElement | null;
|
|
150
155
|
|
|
151
|
-
export { type AiAudioPart, type AiContent, type AiContentPart, type AiFilePart, type AiImagePart, type AiKindDef, type AiKindTint, type AiMediaPart, type AiResourceLinkPart, type AiStatus, type AiTextPart, type AiToolCallModel, type AiUsage, type AiVideoPart, aiContentUrl, aiStatusLabel, contentToText, contextEstimate, estimateTokens, formatDuration, formatTokens, getAiKind, inferKind, isAiContentPart,
|
|
156
|
+
export { AI_STATUSES, type AiAudioPart, type AiContent, type AiContentPart, type AiFilePart, type AiImagePart, type AiKindDef, type AiKindTint, type AiMediaPart, type AiResourceLinkPart, type AiStatus, type AiTextPart, type AiToolCallModel, type AiUsage, type AiVideoPart, addUsage, aiContentUrl, aiStatusLabel, contentToText, contextEstimate, estimateTokens, formatDuration, formatTokens, getAiKind, h, inferKind, isAiContentPart, normalizeContentParts, normalizeMcpContent, normalizeToolCall, normalizeUsage, registerAiKind, svgIcon };
|
|
@@ -23,6 +23,15 @@ function aiStatusLabel(s) {
|
|
|
23
23
|
};
|
|
24
24
|
return labels[s] ?? s;
|
|
25
25
|
}
|
|
26
|
+
var USAGE_KEYS = ["input", "output", "cacheRead", "cacheWrite", "reasoning", "total"];
|
|
27
|
+
function addUsage(sum2, u) {
|
|
28
|
+
const out = { ...sum2 };
|
|
29
|
+
for (const k of USAGE_KEYS) {
|
|
30
|
+
const v = u[k];
|
|
31
|
+
if (typeof v === "number" && Number.isFinite(v)) out[k] = (out[k] ?? 0) + v;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
26
35
|
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>`;
|
|
27
36
|
var ICON_TERMINAL = svg('<path d="m4 17 6-6-6-6"/><path d="M12 19h8"/>');
|
|
28
37
|
var ICON_FILE = svg(
|
|
@@ -147,14 +156,13 @@ var BUILTIN_KINDS = {
|
|
|
147
156
|
note: { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" },
|
|
148
157
|
chart: { label: "\u56FE\u8868", icon: ICON_CHART, tint: "info", summarize: sum((i) => pickStr(i, ["title", "name"])) }
|
|
149
158
|
};
|
|
150
|
-
var FALLBACK_KIND = { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" };
|
|
151
159
|
var kindRegistry = new Map(Object.entries(BUILTIN_KINDS));
|
|
152
160
|
function registerAiKind(name, def) {
|
|
153
161
|
const key = name.trim().toLowerCase();
|
|
154
162
|
if (key) kindRegistry.set(key, def);
|
|
155
163
|
}
|
|
156
164
|
function getAiKind(name) {
|
|
157
|
-
return kindRegistry.get(name.trim().toLowerCase()) ??
|
|
165
|
+
return kindRegistry.get(name.trim().toLowerCase()) ?? BUILTIN_KINDS.note;
|
|
158
166
|
}
|
|
159
167
|
var KIND_RULES = [
|
|
160
168
|
[/^mcp__/, "mcp"],
|
|
@@ -280,9 +288,9 @@ function formatDuration(ms) {
|
|
|
280
288
|
const m = Math.floor(ms / 6e4);
|
|
281
289
|
const s = Math.round(ms % 6e4 / 1e3);
|
|
282
290
|
if (ms < 36e5) return s > 0 ? `${m}m${s}s` : `${m}m`;
|
|
283
|
-
const
|
|
291
|
+
const h2 = Math.floor(ms / 36e5);
|
|
284
292
|
const rm = Math.round(ms % 36e5 / 6e4);
|
|
285
|
-
return rm > 0 ? `${
|
|
293
|
+
return rm > 0 ? `${h2}h${rm}m` : `${h2}h`;
|
|
286
294
|
}
|
|
287
295
|
var PART_TYPES = /* @__PURE__ */ new Set(["text", "image", "audio", "video", "file", "resource-link"]);
|
|
288
296
|
function isAiContentPart(v) {
|
|
@@ -293,9 +301,6 @@ function isAiContentPart(v) {
|
|
|
293
301
|
if (t === "resource-link") return typeof v.uri === "string";
|
|
294
302
|
return typeof v.url === "string" || typeof v.data === "string" && typeof v.mimeType === "string";
|
|
295
303
|
}
|
|
296
|
-
function isAiContentPartArray(v) {
|
|
297
|
-
return Array.isArray(v) && v.length > 0 && v.every(isAiContentPart);
|
|
298
|
-
}
|
|
299
304
|
function aiContentUrl(part) {
|
|
300
305
|
if (part.type === "text" || part.type === "resource-link") return void 0;
|
|
301
306
|
if (typeof part.url === "string" && part.url) return part.url;
|
|
@@ -438,6 +443,12 @@ function contextEstimate(usage) {
|
|
|
438
443
|
const u = normalizeUsage(usage);
|
|
439
444
|
return (u.input ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0) + (u.output ?? 0) + (u.reasoning ?? 0);
|
|
440
445
|
}
|
|
446
|
+
function h(tag, className, text) {
|
|
447
|
+
const node = document.createElement(tag);
|
|
448
|
+
if (className) node.className = className;
|
|
449
|
+
if (text != null) node.textContent = text;
|
|
450
|
+
return node;
|
|
451
|
+
}
|
|
441
452
|
function svgIcon(svg2) {
|
|
442
453
|
if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
|
|
443
454
|
let parsed;
|
|
@@ -471,6 +482,8 @@ function svgIcon(svg2) {
|
|
|
471
482
|
return document.importNode(root, true);
|
|
472
483
|
}
|
|
473
484
|
export {
|
|
485
|
+
AI_STATUSES,
|
|
486
|
+
addUsage,
|
|
474
487
|
aiContentUrl,
|
|
475
488
|
aiStatusLabel,
|
|
476
489
|
contentToText,
|
|
@@ -479,9 +492,9 @@ export {
|
|
|
479
492
|
formatDuration,
|
|
480
493
|
formatTokens,
|
|
481
494
|
getAiKind,
|
|
495
|
+
h,
|
|
482
496
|
inferKind,
|
|
483
497
|
isAiContentPart,
|
|
484
|
-
isAiContentPartArray,
|
|
485
498
|
normalizeContentParts,
|
|
486
499
|
normalizeMcpContent,
|
|
487
500
|
normalizeToolCall,
|
|
@@ -40,10 +40,10 @@ interface AiDiffModel {
|
|
|
40
40
|
*/
|
|
41
41
|
declare function parseUnifiedDiff(text: string): AiDiffFile[];
|
|
42
42
|
/**
|
|
43
|
-
* 渲染 diff 审阅卡(DOM API,全 textContent
|
|
43
|
+
* 渲染 diff 审阅卡(DOM API,全 textContent),返回挂载元素 el(render* 约定)。
|
|
44
44
|
* 接受 parseUnifiedDiff 的产物;重复调用会替换 el 内容。
|
|
45
45
|
*/
|
|
46
|
-
declare function renderAiDiff(
|
|
46
|
+
declare function renderAiDiff(el: HTMLElement, model: AiDiffModel): HTMLElement;
|
|
47
47
|
/**
|
|
48
48
|
* 委托初始化:展开/折叠 + 接受/拒绝。重复调用安全;
|
|
49
49
|
* 返回销毁函数(移除监听并复位标记,可重新 init)。
|
|
@@ -1,3 +1,143 @@
|
|
|
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
|
+
|
|
1
141
|
// src/behaviors/ai-diff.ts
|
|
2
142
|
var RE_HUNK = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
3
143
|
var RE_OLD_FILE = /^--- (.*)$/;
|
|
@@ -114,60 +254,54 @@ function parseUnifiedDiff(text) {
|
|
|
114
254
|
}
|
|
115
255
|
return files;
|
|
116
256
|
}
|
|
117
|
-
function el(tag, className, text) {
|
|
118
|
-
const node = document.createElement(tag);
|
|
119
|
-
if (className) node.className = className;
|
|
120
|
-
if (text !== void 0) node.textContent = text;
|
|
121
|
-
return node;
|
|
122
|
-
}
|
|
123
257
|
function renderLineRow(line) {
|
|
124
258
|
const tr = document.createElement("tr");
|
|
125
259
|
tr.className = `ai-diff-line--${line.type}`;
|
|
126
260
|
if (line.type === "hunk") {
|
|
127
|
-
const td =
|
|
261
|
+
const td = h("td", "", line.text);
|
|
128
262
|
td.colSpan = 3;
|
|
129
263
|
tr.appendChild(td);
|
|
130
264
|
return tr;
|
|
131
265
|
}
|
|
132
|
-
const oldTd =
|
|
266
|
+
const oldTd = h("td", "ai-diff-ln", line.oldNo === null ? "" : String(line.oldNo));
|
|
133
267
|
oldTd.setAttribute("aria-hidden", "true");
|
|
134
|
-
const newTd =
|
|
268
|
+
const newTd = h("td", "ai-diff-ln", line.newNo === null ? "" : String(line.newNo));
|
|
135
269
|
newTd.setAttribute("aria-hidden", "true");
|
|
136
270
|
tr.appendChild(oldTd);
|
|
137
271
|
tr.appendChild(newTd);
|
|
138
|
-
tr.appendChild(
|
|
272
|
+
tr.appendChild(h("td", "ai-diff-code", line.text));
|
|
139
273
|
return tr;
|
|
140
274
|
}
|
|
141
275
|
function renderFile(file) {
|
|
142
|
-
const root =
|
|
276
|
+
const root = h("div", `ai-diff-file is-${file.status}`);
|
|
143
277
|
root.dataset.path = file.path;
|
|
144
|
-
const head =
|
|
278
|
+
const head = h("div", "ai-diff-head");
|
|
145
279
|
head.setAttribute("role", "button");
|
|
146
280
|
head.tabIndex = 0;
|
|
147
281
|
head.setAttribute("aria-expanded", "false");
|
|
148
282
|
const pathText = file.status === "renamed" && file.oldPath ? `${file.oldPath} \u2192 ${file.path}` : file.path;
|
|
149
|
-
head.appendChild(
|
|
150
|
-
const stat =
|
|
151
|
-
if (file.addCount > 0) stat.appendChild(
|
|
152
|
-
if (file.delCount > 0) stat.appendChild(
|
|
283
|
+
head.appendChild(h("span", "ai-diff-path", pathText));
|
|
284
|
+
const stat = h("span", "ai-diff-stat");
|
|
285
|
+
if (file.addCount > 0) stat.appendChild(h("b", "ai-diff-add", `+${file.addCount}`));
|
|
286
|
+
if (file.delCount > 0) stat.appendChild(h("b", "ai-diff-del", `\u2212${file.delCount}`));
|
|
153
287
|
if (stat.children.length > 0) head.appendChild(stat);
|
|
154
|
-
const actions =
|
|
155
|
-
actions.appendChild(
|
|
156
|
-
const rejectBtn =
|
|
288
|
+
const actions = h("span", "ai-diff-actions");
|
|
289
|
+
actions.appendChild(h("button", "", "\u63A5\u53D7")).setAttribute("data-ai-diff-accept", "");
|
|
290
|
+
const rejectBtn = h("button", "", "\u62D2\u7EDD");
|
|
157
291
|
rejectBtn.setAttribute("data-ai-diff-reject", "");
|
|
158
292
|
actions.appendChild(rejectBtn);
|
|
159
293
|
head.appendChild(actions);
|
|
160
|
-
const verdict =
|
|
294
|
+
const verdict = h("span", "ai-diff-verdict");
|
|
161
295
|
verdict.hidden = true;
|
|
162
296
|
head.appendChild(verdict);
|
|
163
297
|
root.appendChild(head);
|
|
164
|
-
const body =
|
|
298
|
+
const body = h("div", "ai-diff-body");
|
|
165
299
|
body.hidden = true;
|
|
166
|
-
const table =
|
|
300
|
+
const table = h("table", "ai-diff-table");
|
|
167
301
|
if (file.hunks.length === 0) {
|
|
168
302
|
const tr = document.createElement("tr");
|
|
169
303
|
tr.className = "ai-diff-line--ctx";
|
|
170
|
-
const td =
|
|
304
|
+
const td = h("td", "", file.binary ? "\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u663E\u793A\u6587\u672C\u5DEE\u5F02" : "\u65E0\u5DEE\u5F02\u5185\u5BB9");
|
|
171
305
|
td.colSpan = 3;
|
|
172
306
|
tr.appendChild(td);
|
|
173
307
|
table.appendChild(tr);
|
|
@@ -181,13 +315,14 @@ function renderFile(file) {
|
|
|
181
315
|
root.appendChild(body);
|
|
182
316
|
return root;
|
|
183
317
|
}
|
|
184
|
-
function renderAiDiff(
|
|
185
|
-
if (typeof document === "undefined") return;
|
|
186
|
-
|
|
187
|
-
const root =
|
|
318
|
+
function renderAiDiff(el, model) {
|
|
319
|
+
if (typeof document === "undefined") return el;
|
|
320
|
+
el.textContent = "";
|
|
321
|
+
const root = h("div", "ai-diff");
|
|
188
322
|
const files = Array.isArray(model?.files) ? model.files : [];
|
|
189
323
|
for (const file of files) root.appendChild(renderFile(file));
|
|
190
|
-
|
|
324
|
+
el.appendChild(root);
|
|
325
|
+
return el;
|
|
191
326
|
}
|
|
192
327
|
var noopDestroy = () => void 0;
|
|
193
328
|
function closestFile(node) {
|
|
@@ -206,10 +341,7 @@ function toggleFile(head) {
|
|
|
206
341
|
const open = body.hidden;
|
|
207
342
|
body.hidden = !open;
|
|
208
343
|
head.setAttribute("aria-expanded", String(open));
|
|
209
|
-
file
|
|
210
|
-
bubbles: true,
|
|
211
|
-
detail: { el: file, open }
|
|
212
|
-
}));
|
|
344
|
+
emitIcen(file, "icen:ai-toggle", { el: file, open });
|
|
213
345
|
}
|
|
214
346
|
function decide(btn, accepted) {
|
|
215
347
|
const file = closestFile(btn);
|
|
@@ -218,7 +350,7 @@ function decide(btn, accepted) {
|
|
|
218
350
|
file.classList.add(accepted ? "is-accepted" : "is-rejected");
|
|
219
351
|
let verdict = file.querySelector(".ai-diff-verdict");
|
|
220
352
|
if (!verdict) {
|
|
221
|
-
verdict =
|
|
353
|
+
verdict = h("span", "ai-diff-verdict");
|
|
222
354
|
file.querySelector(".ai-diff-head")?.appendChild(verdict);
|
|
223
355
|
}
|
|
224
356
|
verdict.textContent = accepted ? "\u5DF2\u63A5\u53D7" : "\u5DF2\u62D2\u7EDD";
|
|
@@ -229,10 +361,7 @@ function decide(btn, accepted) {
|
|
|
229
361
|
b.hidden = true;
|
|
230
362
|
b.disabled = true;
|
|
231
363
|
});
|
|
232
|
-
file
|
|
233
|
-
bubbles: true,
|
|
234
|
-
detail: { path: filePath(file) }
|
|
235
|
-
}));
|
|
364
|
+
emitIcen(file, accepted ? "icen:ai-diff-accept" : "icen:ai-diff-reject", { path: filePath(file) });
|
|
236
365
|
}
|
|
237
366
|
function initAiDiff(root) {
|
|
238
367
|
if (typeof document === "undefined") return noopDestroy;
|
|
@@ -244,14 +373,16 @@ function initAiDiff(root) {
|
|
|
244
373
|
const onClick = (e) => {
|
|
245
374
|
const t = e.target instanceof Element ? e.target : null;
|
|
246
375
|
if (!t) return;
|
|
247
|
-
|
|
376
|
+
const acceptBtn = t.closest("[data-ai-diff-accept]");
|
|
377
|
+
if (acceptBtn) {
|
|
248
378
|
e.stopPropagation();
|
|
249
|
-
decide(
|
|
379
|
+
decide(acceptBtn, true);
|
|
250
380
|
return;
|
|
251
381
|
}
|
|
252
|
-
|
|
382
|
+
const rejectBtn = t.closest("[data-ai-diff-reject]");
|
|
383
|
+
if (rejectBtn) {
|
|
253
384
|
e.stopPropagation();
|
|
254
|
-
decide(
|
|
385
|
+
decide(rejectBtn, false);
|
|
255
386
|
return;
|
|
256
387
|
}
|
|
257
388
|
const head = t.closest(".ai-diff-head");
|
|
@@ -9,8 +9,9 @@ interface AiTodoItem {
|
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* 渲染 todo 列表(DOM API,全 textContent)。全部完成时容器加 .is-complete。
|
|
12
|
+
* 返回挂载容器 el(render* 返回挂载元素约定,SSR 下原样返回)。
|
|
12
13
|
*/
|
|
13
|
-
declare function renderAiTodo(
|
|
14
|
+
declare function renderAiTodo(el: HTMLElement, items: AiTodoItem[]): HTMLElement;
|
|
14
15
|
/**
|
|
15
16
|
* todo 委托初始化:仅 data-ai-todo-interactive 容器可点击循环状态(pending→running→done),
|
|
16
17
|
* 只读列表不挂交互。重复调用安全;返回销毁函数。
|
|
@@ -23,10 +24,10 @@ interface AiUsageRenderOpts {
|
|
|
23
24
|
cost?: number;
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
|
-
* 渲染用量分段条 + 图例 +
|
|
27
|
+
* 渲染用量分段条 + 图例 + 占比。返回挂载容器 el(render* 返回挂载元素约定)。
|
|
27
28
|
* 配色契约:input=accent / output=success / cacheRead=info / cacheWrite=warning / reasoning=faint。
|
|
28
29
|
*/
|
|
29
|
-
declare function renderAiUsage(
|
|
30
|
+
declare function renderAiUsage(el: HTMLElement, usage: AiUsage, opts?: AiUsageRenderOpts): HTMLElement;
|
|
30
31
|
interface AiUsageRingOpts extends AiUsageRenderOpts {
|
|
31
32
|
/** 弹层标题(默认「上下文窗口」) */
|
|
32
33
|
title?: string;
|
|
@@ -37,9 +38,9 @@ interface AiUsageRingOpts extends AiUsageRenderOpts {
|
|
|
37
38
|
* opts.total 给上下文窗口上限(如 200000)——环填充比 = 已用 / total;
|
|
38
39
|
* 缺省 total 时环不填充、中心显示已用量。
|
|
39
40
|
* 状态档:<60% is-ok(accent)/ 60–85% is-warn / >85% is-hot(error)。
|
|
40
|
-
*
|
|
41
|
+
* 重复调用重渲染;打开中的弹层会被关闭重建。返回挂载容器 el。
|
|
41
42
|
*/
|
|
42
|
-
declare function renderAiUsageRing(
|
|
43
|
+
declare function renderAiUsageRing(el: HTMLElement, usage: AiUsage, opts?: AiUsageRingOpts): HTMLElement;
|
|
43
44
|
interface AiContextFile {
|
|
44
45
|
path: string;
|
|
45
46
|
status?: 'added' | 'modified' | 'deleted';
|
|
@@ -68,14 +69,16 @@ interface AiContextModel {
|
|
|
68
69
|
/**
|
|
69
70
|
* 组合渲染上下文抽屉:usage → renderAiUsage;files → ai-file-chip;
|
|
70
71
|
* mcpServers / skills → .ai-item 行。aside 默认 hidden,由 initAiContext 开合。
|
|
72
|
+
* 返回挂载容器 el(render* 返回挂载元素约定)。
|
|
71
73
|
*/
|
|
72
|
-
declare function renderAiContext(
|
|
74
|
+
declare function renderAiContext(el: HTMLElement, model: AiContextModel): HTMLElement;
|
|
73
75
|
/**
|
|
74
76
|
* 触发器 [data-ai-context-open] 委托开合:
|
|
75
77
|
* - 属性值为 #id / 选择器时定位对应抽屉,空值取 scope 内第一个非 inline 抽屉
|
|
76
78
|
* - 打开:去 hidden → reflow → .is-open 滑入(CSS transition),焦点移入 close 钮
|
|
77
79
|
* - 关闭:[data-ai-context-close] / Esc / 外点;.is-open 移除 → transition 结束(或兜底计时)
|
|
78
80
|
* 后恢复 hidden,焦点还原触发器;Tab/Shift+Tab 焦点圈禁
|
|
81
|
+
* - 开/合从抽屉根元素派 icen:ai-context-open {} / icen:ai-context-close {}(bubbles)
|
|
79
82
|
* - .ai-context--inline 为页面流内嵌变体:不参与开合、不做 fixed
|
|
80
83
|
*/
|
|
81
84
|
declare function initAiContext(root?: ParentNode): () => void;
|
|
@@ -91,8 +94,8 @@ interface AiAuditRenderOpts {
|
|
|
91
94
|
* 渲染审计面板:totals(请求数/失败/累计 tokens/累计成本/平均 TTFT)+ byModel 分组 +
|
|
92
95
|
* 最近请求条目(默认 10 条,最新在前)。source 为 auditor 实例或条目数组(快照渲染,
|
|
93
96
|
* 数据更新后重调)。清除钮派 icen:ai-audit-clear { }(消费方调 auditor.clear() 后重渲染),
|
|
94
|
-
* opts.onClear
|
|
97
|
+
* opts.onClear 给了则走回调。返回挂载容器 el(render* 返回挂载元素约定)。
|
|
95
98
|
*/
|
|
96
|
-
declare function renderAiAudit(
|
|
99
|
+
declare function renderAiAudit(el: HTMLElement, source: AiAuditor | AiAuditEntry[], opts?: AiAuditRenderOpts): HTMLElement;
|
|
97
100
|
|
|
98
101
|
export { type AiAuditRenderOpts, type AiContextFile, type AiContextMcpServer, type AiContextModel, type AiContextSkill, type AiTodoItem, type AiUsageRenderOpts, type AiUsageRingOpts, initAiContext, initAiTodo, renderAiAudit, renderAiContext, renderAiTodo, renderAiUsage, renderAiUsageRing };
|