@icen.ai/ui 0.5.0 → 0.8.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.
Files changed (138) hide show
  1. package/AGENTS.md +256 -0
  2. package/README.md +47 -13
  3. package/dist/base.css +31 -4
  4. package/dist/behaviors/accordion.d.mts +4 -0
  5. package/dist/behaviors/accordion.mjs +4 -3
  6. package/dist/behaviors/ai-chat.d.mts +57 -0
  7. package/dist/behaviors/ai-chat.mjs +557 -0
  8. package/dist/behaviors/ai-composer.d.mts +112 -0
  9. package/dist/behaviors/ai-composer.mjs +2032 -0
  10. package/dist/behaviors/ai-core.d.mts +151 -0
  11. package/dist/behaviors/ai-core.mjs +491 -0
  12. package/dist/behaviors/ai-diff.d.mts +53 -0
  13. package/dist/behaviors/ai-diff.mjs +282 -0
  14. package/dist/behaviors/ai-panel.d.mts +98 -0
  15. package/dist/behaviors/ai-panel.mjs +1012 -0
  16. package/dist/behaviors/ai-provider.d.mts +198 -0
  17. package/dist/behaviors/ai-provider.mjs +1055 -0
  18. package/dist/behaviors/ai-tool.d.mts +18 -0
  19. package/dist/behaviors/ai-tool.mjs +2125 -0
  20. package/dist/behaviors/ai-tools.d.mts +81 -0
  21. package/dist/behaviors/ai-tools.mjs +1658 -0
  22. package/dist/behaviors/back-top.d.mts +20 -0
  23. package/dist/behaviors/back-top.mjs +49 -32
  24. package/dist/behaviors/carousel.d.mts +4 -0
  25. package/dist/behaviors/carousel.mjs +34 -16
  26. package/dist/behaviors/charts.d.mts +250 -0
  27. package/dist/behaviors/charts.mjs +1301 -448
  28. package/dist/behaviors/command-palette.d.mts +8 -0
  29. package/dist/behaviors/command-palette.mjs +24 -12
  30. package/dist/behaviors/context-menu.d.mts +36 -0
  31. package/dist/behaviors/context-menu.mjs +6 -2
  32. package/dist/behaviors/copy.d.mts +4 -0
  33. package/dist/behaviors/copy.mjs +7 -6
  34. package/dist/behaviors/datatable.d.mts +78 -0
  35. package/dist/behaviors/datatable.mjs +54 -11
  36. package/dist/behaviors/date-picker.d.mts +4 -0
  37. package/dist/behaviors/date-picker.mjs +104 -36
  38. package/dist/behaviors/dropdown.d.mts +4 -0
  39. package/dist/behaviors/dropdown.mjs +7 -3
  40. package/dist/behaviors/input.d.mts +4 -0
  41. package/dist/behaviors/input.mjs +29 -22
  42. package/dist/behaviors/modal.d.mts +11 -0
  43. package/dist/behaviors/modal.mjs +11 -6
  44. package/dist/behaviors/nav.d.mts +7 -0
  45. package/dist/behaviors/nav.mjs +25 -9
  46. package/dist/behaviors/notification.d.mts +172 -0
  47. package/dist/behaviors/notification.mjs +243 -89
  48. package/dist/behaviors/popover.d.mts +46 -0
  49. package/dist/behaviors/popover.mjs +2 -1
  50. package/dist/behaviors/select.d.mts +4 -0
  51. package/dist/behaviors/select.mjs +131 -26
  52. package/dist/behaviors/sidebar.d.mts +4 -0
  53. package/dist/behaviors/sidebar.mjs +4 -3
  54. package/dist/behaviors/slider.d.mts +4 -0
  55. package/dist/behaviors/slider.mjs +4 -3
  56. package/dist/behaviors/split-pane.d.mts +4 -0
  57. package/dist/behaviors/split-pane.mjs +13 -13
  58. package/dist/behaviors/tabs.d.mts +8 -0
  59. package/dist/behaviors/tabs.mjs +44 -19
  60. package/dist/behaviors/tag-input.d.mts +4 -0
  61. package/dist/behaviors/tag-input.mjs +12 -3
  62. package/dist/behaviors/theme.d.mts +25 -0
  63. package/dist/behaviors/theme.mjs +3 -1
  64. package/dist/behaviors/toast.d.mts +26 -0
  65. package/dist/behaviors/toast.mjs +24 -18
  66. package/dist/behaviors/tree.d.mts +4 -0
  67. package/dist/behaviors/tree.mjs +4 -3
  68. package/dist/behaviors/upload.d.mts +4 -0
  69. package/dist/behaviors/upload.mjs +63 -26
  70. package/dist/components/ai-chat.css +1037 -0
  71. package/dist/components/ai-chat.mjs +3 -0
  72. package/dist/components/ai-composer.mjs +3 -0
  73. package/dist/components/ai-context.mjs +2 -0
  74. package/dist/components/ai-diff.css +311 -0
  75. package/dist/components/ai-diff.mjs +3 -0
  76. package/dist/components/ai-files.mjs +3 -0
  77. package/dist/components/ai-message.mjs +3 -0
  78. package/dist/components/ai-panel.css +682 -0
  79. package/dist/components/ai-reasoning.mjs +3 -0
  80. package/dist/components/ai-subagent.mjs +2 -0
  81. package/dist/components/ai-todo.mjs +2 -0
  82. package/dist/components/ai-tool-call.mjs +2 -0
  83. package/dist/components/ai-tool.css +532 -0
  84. package/dist/components/ai-usage.mjs +3 -0
  85. package/dist/components/badge.css +4 -8
  86. package/dist/components/breadcrumb.css +1 -5
  87. package/dist/components/btn.css +4 -7
  88. package/dist/components/card.css +3 -6
  89. package/dist/components/chart-bar.css +10 -0
  90. package/dist/components/chart-calendar.mjs +5 -0
  91. package/dist/components/chart-gauge.css +1 -1
  92. package/dist/components/chart-heatmap.css +40 -0
  93. package/dist/components/chart-scatter.css +24 -0
  94. package/dist/components/chart-scatter.mjs +4 -0
  95. package/dist/components/charts.css +86 -1
  96. package/dist/components/charts.mjs +1 -0
  97. package/dist/components/command-palette.css +2 -2
  98. package/dist/components/content.css +8 -9
  99. package/dist/components/copy.css +8 -8
  100. package/dist/components/datatable.css +4 -8
  101. package/dist/components/date-picker.css +3 -7
  102. package/dist/components/empty.css +0 -4
  103. package/dist/components/feedback.css +10 -17
  104. package/dist/components/form.css +13 -14
  105. package/dist/components/input.css +2 -2
  106. package/dist/components/layout.css +36 -36
  107. package/dist/components/media.css +12 -8
  108. package/dist/components/menu.css +2 -2
  109. package/dist/components/modal.css +1 -1
  110. package/dist/components/nav.css +11 -6
  111. package/dist/components/notification.css +28 -28
  112. package/dist/components/pagination.css +1 -1
  113. package/dist/components/panel.css +16 -16
  114. package/dist/components/pill.css +15 -15
  115. package/dist/components/segmented.css +5 -5
  116. package/dist/components/segmented.mjs +1 -0
  117. package/dist/components/select.css +12 -20
  118. package/dist/components/sidebar.css +1 -1
  119. package/dist/components/split-pane.css +0 -1
  120. package/dist/components/stat.css +14 -18
  121. package/dist/components/steps.css +2 -2
  122. package/dist/components/switch.css +8 -12
  123. package/dist/components/table.css +46 -47
  124. package/dist/components/tabs.css +35 -46
  125. package/dist/components/tag-input.css +4 -4
  126. package/dist/components/toast.css +16 -13
  127. package/dist/components/tooltip.css +2 -2
  128. package/dist/index.d.mts +37 -0
  129. package/dist/index.mjs +7890 -1199
  130. package/dist/registry.json +113 -4
  131. package/dist/retro-effects.css +21 -17
  132. package/dist/tokens/colors.css +4 -1
  133. package/dist/tokens/retro-effects.css +21 -17
  134. package/dist/tokens/style-profiles.css +21 -0
  135. package/dist/tokens.css +25 -1
  136. package/dist/ui.css +3168 -433
  137. package/docs/spec/ai-native.md +659 -0
  138. package/package.json +15 -5
@@ -0,0 +1,53 @@
1
+ type AiDiffFileStatus = 'added' | 'modified' | 'deleted' | 'renamed';
2
+ interface AiDiffLine {
3
+ /** ctx = 上下文 / add = 新增 / del = 删除 / hunk = hunk 头 */
4
+ type: 'ctx' | 'add' | 'del' | 'hunk';
5
+ /** 旧文件行号(add/hunk 行为 null) */
6
+ oldNo: number | null;
7
+ /** 新文件行号(del/hunk 行为 null) */
8
+ newNo: number | null;
9
+ /** 行内容(不含行首 + / - / 空格前缀与 hunk 头) */
10
+ text: string;
11
+ }
12
+ interface AiDiffHunk {
13
+ oldStart: number;
14
+ oldCount: number;
15
+ newStart: number;
16
+ newCount: number;
17
+ /** 原始 hunk 头文本(@@ -10,6 +10,8 @@ …) */
18
+ header: string;
19
+ lines: AiDiffLine[];
20
+ }
21
+ interface AiDiffFile {
22
+ /** 新路径(重命名时为 rename to 目标) */
23
+ path: string;
24
+ /** 重命名前的旧路径 */
25
+ oldPath?: string;
26
+ status: AiDiffFileStatus;
27
+ hunks: AiDiffHunk[];
28
+ addCount: number;
29
+ delCount: number;
30
+ /** 二进制文件(无文本 hunks) */
31
+ binary?: boolean;
32
+ }
33
+ interface AiDiffModel {
34
+ files: AiDiffFile[];
35
+ }
36
+ /**
37
+ * 解析标准 unified diff 文本(支持多文件 `diff --git`、纯 `---/+++` 单文件、
38
+ * hunk `@@` 行号追踪、rename/new/deleted mode、Binary files)。
39
+ * 无法解析的行被安全忽略;SSR 安全(不触碰 DOM)。
40
+ */
41
+ declare function parseUnifiedDiff(text: string): AiDiffFile[];
42
+ /**
43
+ * 渲染 diff 审阅卡(DOM API,全 textContent)。
44
+ * 接受 parseUnifiedDiff 的产物;重复调用会替换 el 内容。
45
+ */
46
+ declare function renderAiDiff(elm: HTMLElement, model: AiDiffModel): void;
47
+ /**
48
+ * 委托初始化:展开/折叠 + 接受/拒绝。重复调用安全;
49
+ * 返回销毁函数(移除监听并复位标记,可重新 init)。
50
+ */
51
+ declare function initAiDiff(root?: ParentNode): () => void;
52
+
53
+ export { type AiDiffFile, type AiDiffFileStatus, type AiDiffHunk, type AiDiffLine, type AiDiffModel, initAiDiff, parseUnifiedDiff, renderAiDiff };
@@ -0,0 +1,282 @@
1
+ // src/behaviors/ai-diff.ts
2
+ var RE_HUNK = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
3
+ var RE_OLD_FILE = /^--- (.*)$/;
4
+ var RE_NEW_FILE_LINE = /^\+\+\+ (.*)$/;
5
+ var RE_NEW_MODE = /^new file mode /;
6
+ var RE_DEL_MODE = /^deleted file mode /;
7
+ var RE_RENAME_FROM = /^rename from (.*)$/;
8
+ var RE_RENAME_TO = /^rename to (.*)$/;
9
+ var RE_BINARY = /^Binary files .* differ$/;
10
+ function parseDiffPath(token) {
11
+ let p = token.split(" ")[0].trim();
12
+ if (p.length >= 2 && p.startsWith('"') && p.endsWith('"')) p = p.slice(1, -1);
13
+ if (p !== "/dev/null" && (p.startsWith("a/") || p.startsWith("b/"))) p = p.slice(2);
14
+ return p;
15
+ }
16
+ function parseGitHeader(line) {
17
+ const m = /^diff --git\s+(\S+)\s+(.+)$/.exec(line);
18
+ if (!m) return null;
19
+ return { oldPath: parseDiffPath(m[1]), newPath: parseDiffPath(m[2]) };
20
+ }
21
+ function emptyFile(path) {
22
+ return { path, status: "modified", hunks: [], addCount: 0, delCount: 0 };
23
+ }
24
+ function parseUnifiedDiff(text) {
25
+ const files = [];
26
+ if (typeof text !== "string" || text.length === 0) return files;
27
+ let cur = null;
28
+ let hunk = null;
29
+ let oldNo = 0;
30
+ let newNo = 0;
31
+ const lines = text.split("\n");
32
+ for (const raw of lines) {
33
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
34
+ const git = parseGitHeader(line);
35
+ if (git) {
36
+ cur = emptyFile(git.newPath);
37
+ cur.oldPath = git.oldPath;
38
+ files.push(cur);
39
+ hunk = null;
40
+ continue;
41
+ }
42
+ if (!cur && RE_OLD_FILE.test(line)) {
43
+ cur = emptyFile("");
44
+ files.push(cur);
45
+ hunk = null;
46
+ }
47
+ if (!cur) continue;
48
+ if (RE_NEW_MODE.test(line)) {
49
+ cur.status = "added";
50
+ continue;
51
+ }
52
+ if (RE_DEL_MODE.test(line)) {
53
+ cur.status = "deleted";
54
+ continue;
55
+ }
56
+ const renameFrom = RE_RENAME_FROM.exec(line);
57
+ if (renameFrom) {
58
+ cur.oldPath = parseDiffPath(renameFrom[1]);
59
+ cur.status = "renamed";
60
+ continue;
61
+ }
62
+ const renameTo = RE_RENAME_TO.exec(line);
63
+ if (renameTo) {
64
+ cur.path = parseDiffPath(renameTo[1]);
65
+ cur.status = "renamed";
66
+ continue;
67
+ }
68
+ const oldLine = RE_OLD_FILE.exec(line);
69
+ if (oldLine) {
70
+ const p = parseDiffPath(oldLine[1]);
71
+ if (p === "/dev/null") cur.status = "added";
72
+ else if (!cur.oldPath) cur.oldPath = p;
73
+ continue;
74
+ }
75
+ const newLine = RE_NEW_FILE_LINE.exec(line);
76
+ if (newLine) {
77
+ const p = parseDiffPath(newLine[1]);
78
+ if (p === "/dev/null") cur.status = "deleted";
79
+ else if (p) cur.path = p;
80
+ continue;
81
+ }
82
+ if (RE_BINARY.test(line)) {
83
+ cur.binary = true;
84
+ hunk = null;
85
+ continue;
86
+ }
87
+ const hm = RE_HUNK.exec(line);
88
+ if (hm) {
89
+ hunk = {
90
+ oldStart: Number(hm[1]),
91
+ oldCount: hm[2] ? Number(hm[2]) : 1,
92
+ newStart: Number(hm[3]),
93
+ newCount: hm[4] ? Number(hm[4]) : 1,
94
+ header: line,
95
+ lines: []
96
+ };
97
+ cur.hunks.push(hunk);
98
+ oldNo = hunk.oldStart;
99
+ newNo = hunk.newStart;
100
+ continue;
101
+ }
102
+ if (!hunk) continue;
103
+ if (line.startsWith("\\")) continue;
104
+ if (line.startsWith("+")) {
105
+ hunk.lines.push({ type: "add", oldNo: null, newNo: newNo++, text: line.slice(1) });
106
+ cur.addCount++;
107
+ } else if (line.startsWith("-")) {
108
+ hunk.lines.push({ type: "del", oldNo: oldNo++, newNo: null, text: line.slice(1) });
109
+ cur.delCount++;
110
+ } else {
111
+ const body = line.startsWith(" ") ? line.slice(1) : line;
112
+ hunk.lines.push({ type: "ctx", oldNo: oldNo++, newNo: newNo++, text: body });
113
+ }
114
+ }
115
+ return files;
116
+ }
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
+ function renderLineRow(line) {
124
+ const tr = document.createElement("tr");
125
+ tr.className = `ai-diff-line--${line.type}`;
126
+ if (line.type === "hunk") {
127
+ const td = el("td", "", line.text);
128
+ td.colSpan = 3;
129
+ tr.appendChild(td);
130
+ return tr;
131
+ }
132
+ const oldTd = el("td", "ai-diff-ln", line.oldNo === null ? "" : String(line.oldNo));
133
+ oldTd.setAttribute("aria-hidden", "true");
134
+ const newTd = el("td", "ai-diff-ln", line.newNo === null ? "" : String(line.newNo));
135
+ newTd.setAttribute("aria-hidden", "true");
136
+ tr.appendChild(oldTd);
137
+ tr.appendChild(newTd);
138
+ tr.appendChild(el("td", "ai-diff-code", line.text));
139
+ return tr;
140
+ }
141
+ function renderFile(file) {
142
+ const root = el("div", `ai-diff-file is-${file.status}`);
143
+ root.dataset.path = file.path;
144
+ const head = el("div", "ai-diff-head");
145
+ head.setAttribute("role", "button");
146
+ head.tabIndex = 0;
147
+ head.setAttribute("aria-expanded", "false");
148
+ const pathText = file.status === "renamed" && file.oldPath ? `${file.oldPath} \u2192 ${file.path}` : file.path;
149
+ head.appendChild(el("span", "ai-diff-path", pathText));
150
+ const stat = el("span", "ai-diff-stat");
151
+ if (file.addCount > 0) stat.appendChild(el("b", "ai-diff-add", `+${file.addCount}`));
152
+ if (file.delCount > 0) stat.appendChild(el("b", "ai-diff-del", `\u2212${file.delCount}`));
153
+ if (stat.children.length > 0) head.appendChild(stat);
154
+ const actions = el("span", "ai-diff-actions");
155
+ actions.appendChild(el("button", "", "\u63A5\u53D7")).setAttribute("data-ai-diff-accept", "");
156
+ const rejectBtn = el("button", "", "\u62D2\u7EDD");
157
+ rejectBtn.setAttribute("data-ai-diff-reject", "");
158
+ actions.appendChild(rejectBtn);
159
+ head.appendChild(actions);
160
+ const verdict = el("span", "ai-diff-verdict");
161
+ verdict.hidden = true;
162
+ head.appendChild(verdict);
163
+ root.appendChild(head);
164
+ const body = el("div", "ai-diff-body");
165
+ body.hidden = true;
166
+ const table = el("table", "ai-diff-table");
167
+ if (file.hunks.length === 0) {
168
+ const tr = document.createElement("tr");
169
+ tr.className = "ai-diff-line--ctx";
170
+ const td = el("td", "", file.binary ? "\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u663E\u793A\u6587\u672C\u5DEE\u5F02" : "\u65E0\u5DEE\u5F02\u5185\u5BB9");
171
+ td.colSpan = 3;
172
+ tr.appendChild(td);
173
+ table.appendChild(tr);
174
+ } else {
175
+ for (const hunk of file.hunks) {
176
+ table.appendChild(renderLineRow({ type: "hunk", oldNo: null, newNo: null, text: hunk.header }));
177
+ for (const line of hunk.lines) table.appendChild(renderLineRow(line));
178
+ }
179
+ }
180
+ body.appendChild(table);
181
+ root.appendChild(body);
182
+ return root;
183
+ }
184
+ function renderAiDiff(elm, model) {
185
+ if (typeof document === "undefined") return;
186
+ elm.textContent = "";
187
+ const root = el("div", "ai-diff");
188
+ const files = Array.isArray(model?.files) ? model.files : [];
189
+ for (const file of files) root.appendChild(renderFile(file));
190
+ elm.appendChild(root);
191
+ }
192
+ var noopDestroy = () => void 0;
193
+ function closestFile(node) {
194
+ const file = node?.closest(".ai-diff-file");
195
+ return file instanceof HTMLElement ? file : null;
196
+ }
197
+ function filePath(file) {
198
+ if (file.dataset.path) return file.dataset.path;
199
+ return file.querySelector(".ai-diff-path")?.textContent ?? "";
200
+ }
201
+ function toggleFile(head) {
202
+ const file = closestFile(head);
203
+ if (!file) return;
204
+ const body = file.querySelector(".ai-diff-body");
205
+ if (!body) return;
206
+ const open = body.hidden;
207
+ body.hidden = !open;
208
+ head.setAttribute("aria-expanded", String(open));
209
+ file.dispatchEvent(new CustomEvent("icen:ai-toggle", {
210
+ bubbles: true,
211
+ detail: { el: file, open }
212
+ }));
213
+ }
214
+ function decide(btn, accepted) {
215
+ const file = closestFile(btn);
216
+ if (!file) return;
217
+ if (file.classList.contains("is-accepted") || file.classList.contains("is-rejected")) return;
218
+ file.classList.add(accepted ? "is-accepted" : "is-rejected");
219
+ let verdict = file.querySelector(".ai-diff-verdict");
220
+ if (!verdict) {
221
+ verdict = el("span", "ai-diff-verdict");
222
+ file.querySelector(".ai-diff-head")?.appendChild(verdict);
223
+ }
224
+ verdict.textContent = accepted ? "\u5DF2\u63A5\u53D7" : "\u5DF2\u62D2\u7EDD";
225
+ verdict.hidden = false;
226
+ file.querySelectorAll(
227
+ "[data-ai-diff-accept], [data-ai-diff-reject]"
228
+ ).forEach((b) => {
229
+ b.hidden = true;
230
+ b.disabled = true;
231
+ });
232
+ file.dispatchEvent(new CustomEvent(accepted ? "icen:ai-diff-accept" : "icen:ai-diff-reject", {
233
+ bubbles: true,
234
+ detail: { path: filePath(file) }
235
+ }));
236
+ }
237
+ function initAiDiff(root) {
238
+ if (typeof document === "undefined") return noopDestroy;
239
+ const scope = root ?? document;
240
+ const marked = scope;
241
+ if (marked.__icenAiDiffInit) return noopDestroy;
242
+ marked.__icenAiDiffInit = true;
243
+ const target = scope;
244
+ const onClick = (e) => {
245
+ const t = e.target instanceof Element ? e.target : null;
246
+ if (!t) return;
247
+ if (t.closest("[data-ai-diff-accept]")) {
248
+ e.stopPropagation();
249
+ decide(t.closest("[data-ai-diff-accept]"), true);
250
+ return;
251
+ }
252
+ if (t.closest("[data-ai-diff-reject]")) {
253
+ e.stopPropagation();
254
+ decide(t.closest("[data-ai-diff-reject]"), false);
255
+ return;
256
+ }
257
+ const head = t.closest(".ai-diff-head");
258
+ if (head) toggleFile(head);
259
+ };
260
+ const onKeydown = (e) => {
261
+ const ke = e;
262
+ if (ke.key !== "Enter" && ke.key !== " ") return;
263
+ const t = ke.target instanceof Element ? ke.target : null;
264
+ if (!t || t.closest("button, a, input, textarea, select")) return;
265
+ const head = t.closest(".ai-diff-head");
266
+ if (!head) return;
267
+ ke.preventDefault();
268
+ toggleFile(head);
269
+ };
270
+ target.addEventListener("click", onClick);
271
+ target.addEventListener("keydown", onKeydown);
272
+ return () => {
273
+ target.removeEventListener("click", onClick);
274
+ target.removeEventListener("keydown", onKeydown);
275
+ marked.__icenAiDiffInit = false;
276
+ };
277
+ }
278
+ export {
279
+ initAiDiff,
280
+ parseUnifiedDiff,
281
+ renderAiDiff
282
+ };
@@ -0,0 +1,98 @@
1
+ import { AiStatus, AiUsage } from './ai-core.js';
2
+ import { AiAuditor, AiAuditEntry } from './ai-provider.js';
3
+
4
+ interface AiTodoItem {
5
+ content: string;
6
+ status: AiStatus;
7
+ /** Claude Code 模式:running 时的替换文案 */
8
+ activeForm?: string;
9
+ }
10
+ /**
11
+ * 渲染 todo 列表(DOM API,全 textContent)。全部完成时容器加 .is-complete。
12
+ */
13
+ declare function renderAiTodo(elm: HTMLElement, items: AiTodoItem[]): void;
14
+ /**
15
+ * todo 委托初始化:仅 data-ai-todo-interactive 容器可点击循环状态(pending→running→done),
16
+ * 只读列表不挂交互。重复调用安全;返回销毁函数。
17
+ */
18
+ declare function initAiTodo(root?: ParentNode): () => void;
19
+ interface AiUsageRenderOpts {
20
+ /** 上下文上限(如 200k):给出时 total 行显示 82k / 200k · 41% */
21
+ total?: number;
22
+ /** 成本(USD):给出时追加到 total 行 */
23
+ cost?: number;
24
+ }
25
+ /**
26
+ * 渲染用量分段条 + 图例 + 占比。
27
+ * 配色契约:input=accent / output=success / cacheRead=info / cacheWrite=warning / reasoning=faint。
28
+ */
29
+ declare function renderAiUsage(elm: HTMLElement, usage: AiUsage, opts?: AiUsageRenderOpts): void;
30
+ interface AiUsageRingOpts extends AiUsageRenderOpts {
31
+ /** 弹层标题(默认「上下文窗口」) */
32
+ title?: string;
33
+ }
34
+ /**
35
+ * 上下文窗口环形指示器(Claude Desktop 模式):紧凑圆环显示占用比,
36
+ * 点击弹出完整分段分解(复用 popover 浮层 + renderAiUsage)。
37
+ * opts.total 给上下文窗口上限(如 200000)——环填充比 = 已用 / total;
38
+ * 缺省 total 时环不填充、中心显示已用量。
39
+ * 状态档:<60% is-ok(accent)/ 60–85% is-warn / >85% is-hot(error)。
40
+ * 重复调用重渲染;打开中的弹层会被关闭重建。
41
+ */
42
+ declare function renderAiUsageRing(elm: HTMLElement, usage: AiUsage, opts?: AiUsageRingOpts): void;
43
+ interface AiContextFile {
44
+ path: string;
45
+ status?: 'added' | 'modified' | 'deleted';
46
+ }
47
+ interface AiContextMcpServer {
48
+ name: string;
49
+ tools?: number;
50
+ /** connected(默认)→ .is-done;disconnected → .is-error */
51
+ status?: 'connected' | 'disconnected';
52
+ }
53
+ interface AiContextSkill {
54
+ name: string;
55
+ description?: string;
56
+ }
57
+ interface AiContextModel {
58
+ usage?: AiUsage;
59
+ /** 传 renderAiUsage 的 opts(上限/成本),可选扩展 */
60
+ usageTotal?: number;
61
+ usageCost?: number;
62
+ files?: AiContextFile[];
63
+ mcpServers?: AiContextMcpServer[];
64
+ skills?: AiContextSkill[];
65
+ /** 审计源(auditor 实例或条目数组):抽屉自动出现「审计」节(§12.2,紧凑形态无清除钮) */
66
+ audit?: AiAuditor | AiAuditEntry[];
67
+ }
68
+ /**
69
+ * 组合渲染上下文抽屉:usage → renderAiUsage;files → ai-file-chip;
70
+ * mcpServers / skills → .ai-item 行。aside 默认 hidden,由 initAiContext 开合。
71
+ */
72
+ declare function renderAiContext(elm: HTMLElement, model: AiContextModel): void;
73
+ /**
74
+ * 触发器 [data-ai-context-open] 委托开合:
75
+ * - 属性值为 #id / 选择器时定位对应抽屉,空值取 scope 内第一个非 inline 抽屉
76
+ * - 打开:去 hidden → reflow → .is-open 滑入(CSS transition),焦点移入 close 钮
77
+ * - 关闭:[data-ai-context-close] / Esc / 外点;.is-open 移除 → transition 结束(或兜底计时)
78
+ * 后恢复 hidden,焦点还原触发器;Tab/Shift+Tab 焦点圈禁
79
+ * - .ai-context--inline 为页面流内嵌变体:不参与开合、不做 fixed
80
+ */
81
+ declare function initAiContext(root?: ParentNode): () => void;
82
+ interface AiAuditRenderOpts {
83
+ /** 最近请求条数(默认 10;抽屉紧凑形态 5) */
84
+ limit?: number;
85
+ /** 显示清除钮(默认 true);点击派 icen:ai-audit-clear(bubbles) */
86
+ clearButton?: boolean;
87
+ /** 替代事件回调(与 clearButton 二选一,都给时回调优先) */
88
+ onClear?: () => void;
89
+ }
90
+ /**
91
+ * 渲染审计面板:totals(请求数/失败/累计 tokens/累计成本/平均 TTFT)+ byModel 分组 +
92
+ * 最近请求条目(默认 10 条,最新在前)。source 为 auditor 实例或条目数组(快照渲染,
93
+ * 数据更新后重调)。清除钮派 icen:ai-audit-clear { }(消费方调 auditor.clear() 后重渲染),
94
+ * opts.onClear 给了则走回调。
95
+ */
96
+ declare function renderAiAudit(elm: HTMLElement, source: AiAuditor | AiAuditEntry[], opts?: AiAuditRenderOpts): void;
97
+
98
+ export { type AiAuditRenderOpts, type AiContextFile, type AiContextMcpServer, type AiContextModel, type AiContextSkill, type AiTodoItem, type AiUsageRenderOpts, type AiUsageRingOpts, initAiContext, initAiTodo, renderAiAudit, renderAiContext, renderAiTodo, renderAiUsage, renderAiUsageRing };