@icen.ai/ui 0.4.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 (155) 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 +101 -0
  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 +189 -0
  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 +296 -0
  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 +184 -8
  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 +908 -0
  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 +268 -34
  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 +102 -0
  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 +205 -17
  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/back-top.css +88 -0
  86. package/dist/components/back-top.mjs +3 -0
  87. package/dist/components/badge.css +127 -0
  88. package/dist/components/badge.mjs +2 -0
  89. package/dist/components/breadcrumb.css +53 -0
  90. package/dist/components/btn.css +4 -7
  91. package/dist/components/card.css +66 -1
  92. package/dist/components/chart-bar.css +10 -0
  93. package/dist/components/chart-calendar.mjs +5 -0
  94. package/dist/components/chart-gauge.css +1 -1
  95. package/dist/components/chart-heatmap.css +40 -0
  96. package/dist/components/chart-scatter.css +24 -0
  97. package/dist/components/chart-scatter.mjs +4 -0
  98. package/dist/components/charts.css +86 -1
  99. package/dist/components/charts.mjs +1 -0
  100. package/dist/components/command-palette.css +246 -0
  101. package/dist/components/command-palette.mjs +3 -0
  102. package/dist/components/content.css +221 -8
  103. package/dist/components/copy.css +93 -0
  104. package/dist/components/copy.mjs +3 -0
  105. package/dist/components/datatable.css +4 -8
  106. package/dist/components/date-picker.css +240 -0
  107. package/dist/components/date-picker.mjs +3 -0
  108. package/dist/components/empty.css +69 -3
  109. package/dist/components/feedback.css +241 -10
  110. package/dist/components/form.css +129 -11
  111. package/dist/components/form.mjs +2 -0
  112. package/dist/components/input.css +25 -2
  113. package/dist/components/layout.css +36 -36
  114. package/dist/components/media.css +153 -6
  115. package/dist/components/menu.css +2 -2
  116. package/dist/components/modal.css +1 -1
  117. package/dist/components/nav.css +106 -2
  118. package/dist/components/notification.css +387 -0
  119. package/dist/components/notification.mjs +3 -0
  120. package/dist/components/pagination.css +1 -1
  121. package/dist/components/panel.css +104 -9
  122. package/dist/components/pill.css +82 -12
  123. package/dist/components/scroll-area.css +112 -0
  124. package/dist/components/scroll-area.mjs +2 -0
  125. package/dist/components/segmented.css +80 -5
  126. package/dist/components/segmented.mjs +1 -0
  127. package/dist/components/select.css +104 -17
  128. package/dist/components/sidebar.css +119 -1
  129. package/dist/components/slider.css +113 -0
  130. package/dist/components/split-pane.css +111 -0
  131. package/dist/components/split-pane.mjs +3 -0
  132. package/dist/components/stat.css +131 -12
  133. package/dist/components/steps.css +98 -1
  134. package/dist/components/switch.css +145 -7
  135. package/dist/components/table.css +111 -29
  136. package/dist/components/tabs.css +132 -22
  137. package/dist/components/tag-input.css +4 -4
  138. package/dist/components/tag.css +164 -0
  139. package/dist/components/tag.mjs +2 -0
  140. package/dist/components/toast.css +122 -5
  141. package/dist/components/toolbar.css +101 -0
  142. package/dist/components/toolbar.mjs +2 -0
  143. package/dist/components/tooltip.css +2 -2
  144. package/dist/components/upload.css +61 -4
  145. package/dist/index.d.mts +37 -0
  146. package/dist/index.mjs +9409 -929
  147. package/dist/registry.json +176 -4
  148. package/dist/retro-effects.css +21 -17
  149. package/dist/tokens/colors.css +4 -1
  150. package/dist/tokens/retro-effects.css +21 -17
  151. package/dist/tokens/style-profiles.css +21 -0
  152. package/dist/tokens.css +25 -1
  153. package/dist/ui.css +9616 -2839
  154. package/docs/spec/ai-native.md +659 -0
  155. package/package.json +15 -5
@@ -0,0 +1,1012 @@
1
+ // src/behaviors/ai-core.ts
2
+ function aiStatusLabel(s) {
3
+ const labels = {
4
+ pending: "\u7B49\u5F85",
5
+ running: "\u6267\u884C\u4E2D",
6
+ streaming: "\u6D41\u5F0F\u4E2D",
7
+ approval: "\u5F85\u786E\u8BA4",
8
+ done: "\u5B8C\u6210",
9
+ error: "\u5931\u8D25",
10
+ cancelled: "\u5DF2\u53D6\u6D88"
11
+ };
12
+ return labels[s] ?? s;
13
+ }
14
+ 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>`;
15
+ var ICON_TERMINAL = svg('<path d="m4 17 6-6-6-6"/><path d="M12 19h8"/>');
16
+ var ICON_FILE = svg(
17
+ '<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"/>'
18
+ );
19
+ var ICON_FILE_PEN = svg(
20
+ '<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"/>'
21
+ );
22
+ var ICON_FILE_PLUS = svg(
23
+ '<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"/>'
24
+ );
25
+ var ICON_TRASH = svg(
26
+ '<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"/>'
27
+ );
28
+ var ICON_SEARCH = svg('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>');
29
+ var ICON_FOLDER_SEARCH = svg(
30
+ '<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"/>'
31
+ );
32
+ var ICON_SCAN_SEARCH = svg(
33
+ '<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"/>'
34
+ );
35
+ var ICON_GLOBE = svg(
36
+ '<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"/>'
37
+ );
38
+ var ICON_DOWNLOAD = svg(
39
+ '<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"/>'
40
+ );
41
+ var ICON_BLOCKS = svg(
42
+ '<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"/>'
43
+ );
44
+ var ICON_ZAP = svg(
45
+ '<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"/>'
46
+ );
47
+ var ICON_LIST = svg(
48
+ '<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"/>'
49
+ );
50
+ var ICON_LIST_CHECKS = svg(
51
+ '<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"/>'
52
+ );
53
+ var ICON_BOT = svg(
54
+ '<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"/>'
55
+ );
56
+ var ICON_CHART = svg(
57
+ '<path d="M3 3v18h18"/><path d="M7 15l4-6 3 4 5-8"/>'
58
+ );
59
+ var ICON_FILE_TEXT = svg(
60
+ '<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"/>'
61
+ );
62
+ function isObj(v) {
63
+ return typeof v === "object" && v !== null;
64
+ }
65
+ function pickStr(input, keys) {
66
+ if (!isObj(input)) return void 0;
67
+ for (const k of keys) {
68
+ const v = input[k];
69
+ if (typeof v === "string" && v) return v;
70
+ }
71
+ return void 0;
72
+ }
73
+ function pickNum(src, keys) {
74
+ if (!isObj(src)) return void 0;
75
+ for (const k of keys) {
76
+ const v = src[k];
77
+ if (typeof v === "number" && Number.isFinite(v)) return v;
78
+ }
79
+ return void 0;
80
+ }
81
+ function firstLine(s, max = 80) {
82
+ const line = s.split("\n", 1)[0]?.trim() ?? "";
83
+ return line.length > max ? `${line.slice(0, max)}\u2026` : line;
84
+ }
85
+ function filePathOf(input) {
86
+ return pickStr(input, ["file_path", "path", "file", "filename"]);
87
+ }
88
+ var summarizePath = (input) => filePathOf(input);
89
+ function summarizeRead(input) {
90
+ const p = filePathOf(input);
91
+ if (!p) return void 0;
92
+ const offset = pickNum(input, ["offset", "start_line"]);
93
+ if (offset == null) return p;
94
+ const limit = pickNum(input, ["limit", "end_line"]);
95
+ return limit != null ? `${p}:${offset}-${offset + limit}` : `${p}:${offset}`;
96
+ }
97
+ function summarizeShell(input) {
98
+ const cmd = typeof input === "string" ? input : pickStr(input, ["command", "cmd", "script"]);
99
+ return cmd ? firstLine(cmd) : void 0;
100
+ }
101
+ function summarizeGrep(input) {
102
+ const pattern = pickStr(input, ["pattern", "regex", "query"]);
103
+ const p = filePathOf(input);
104
+ if (pattern) return p ? `${firstLine(pattern, 40)} \xB7 ${p}` : firstLine(pattern);
105
+ return p;
106
+ }
107
+ var sum = (fn) => {
108
+ return (input) => fn(input) ?? "";
109
+ };
110
+ var BUILTIN_KINDS = {
111
+ shell: { label: "\u547D\u4EE4", icon: ICON_TERMINAL, tint: "accent", summarize: sum(summarizeShell) },
112
+ read: { label: "\u8BFB\u53D6", icon: ICON_FILE, tint: "info", summarize: sum(summarizeRead) },
113
+ edit: { label: "\u7F16\u8F91", icon: ICON_FILE_PEN, tint: "warning", summarize: sum(summarizePath) },
114
+ write: { label: "\u5199\u5165", icon: ICON_FILE_PLUS, tint: "warning", summarize: sum(summarizePath) },
115
+ rm: { label: "\u5220\u9664", icon: ICON_TRASH, tint: "error", summarize: sum(summarizePath) },
116
+ grep: { label: "\u641C\u7D22", icon: ICON_SEARCH, tint: "info", summarize: sum(summarizeGrep) },
117
+ glob: { label: "\u5339\u914D", icon: ICON_FOLDER_SEARCH, tint: "info", summarize: sum((i) => pickStr(i, ["pattern", "glob", "path"])) },
118
+ browser: { label: "\u6D4F\u89C8", icon: ICON_GLOBE, tint: "accent", summarize: sum((i) => pickStr(i, ["url", "uri", "target"])) },
119
+ search: { label: "\u68C0\u7D22", icon: ICON_SCAN_SEARCH, tint: "accent", summarize: sum((i) => pickStr(i, ["query", "q", "pattern"])) },
120
+ fetch: { label: "\u8BF7\u6C42", icon: ICON_DOWNLOAD, tint: "info", summarize: sum((i) => pickStr(i, ["url", "uri", "endpoint"])) },
121
+ mcp: { label: "MCP", icon: ICON_BLOCKS, tint: "muted", summarize: sum((i) => pickStr(i, ["tool", "method", "name"])) },
122
+ skill: { label: "\u6280\u80FD", icon: ICON_ZAP, tint: "accent", summarize: sum((i) => pickStr(i, ["skill", "name", "command"])) },
123
+ todo: {
124
+ label: "\u5F85\u529E",
125
+ icon: ICON_LIST,
126
+ tint: "info",
127
+ summarize: sum((input) => {
128
+ const items = input && typeof input === "object" && Array.isArray(input.items) ? input.items : Array.isArray(input) ? input : [];
129
+ const done = items.filter((it) => it?.status === "done").length;
130
+ return items.length > 0 ? `${done}/${items.length} \u5B8C\u6210` : "";
131
+ })
132
+ },
133
+ plan: { label: "\u8BA1\u5212", icon: ICON_LIST_CHECKS, tint: "info" },
134
+ subagent: { label: "\u5B50\u4EE3\u7406", icon: ICON_BOT, tint: "accent", summarize: sum((i) => pickStr(i, ["task", "description", "prompt", "message", "subagent_type"])) },
135
+ note: { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" },
136
+ chart: { label: "\u56FE\u8868", icon: ICON_CHART, tint: "info", summarize: sum((i) => pickStr(i, ["title", "name"])) }
137
+ };
138
+ var kindRegistry = new Map(Object.entries(BUILTIN_KINDS));
139
+ function normalizeUsage(raw) {
140
+ if (!isObj(raw)) return {};
141
+ const usage = {};
142
+ const input = pickNum(raw, ["input"]) ?? pickNum(raw, ["prompt_tokens"]) ?? pickNum(raw, ["promptTokenCount"]) ?? pickNum(raw, ["input_tokens"]);
143
+ const output = pickNum(raw, ["output"]) ?? pickNum(raw, ["completion_tokens"]) ?? pickNum(raw, ["candidatesTokenCount"]) ?? pickNum(raw, ["output_tokens"]);
144
+ const cacheRead = pickNum(raw, ["cacheRead", "cache_read"]) ?? pickNum(raw, ["cache_read_input_tokens"]) ?? pickNum(raw, ["prompt_cache_hit_tokens"]) ?? pickNum(raw, ["cached_tokens"]) ?? pickNum(raw, ["cachedContentTokenCount"]) ?? (isObj(raw.prompt_tokens_details) ? pickNum(raw.prompt_tokens_details, ["cached_tokens"]) : void 0);
145
+ const cacheWrite = pickNum(raw, ["cacheWrite", "cache_write"]) ?? pickNum(raw, ["cache_creation_input_tokens"]) ?? pickNum(raw, ["cacheCreationTokenCount"]) ?? (isObj(raw.prompt_tokens_details) ? pickNum(raw.prompt_tokens_details, ["cache_write_tokens"]) : void 0);
146
+ const reasoning = pickNum(raw, ["reasoning"]) ?? pickNum(raw, ["thoughtsTokenCount"]) ?? (isObj(raw.completion_tokens_details) ? pickNum(raw.completion_tokens_details, ["reasoning_tokens"]) : void 0);
147
+ const total = pickNum(raw, ["total"]) ?? pickNum(raw, ["total_tokens"]) ?? pickNum(raw, ["totalTokenCount"]) ?? (input != null || output != null || cacheRead != null || cacheWrite != null || reasoning != null ? (input ?? 0) + (output ?? 0) + (cacheRead ?? 0) + (cacheWrite ?? 0) + (reasoning ?? 0) : void 0);
148
+ if (input != null) usage.input = input;
149
+ if (output != null) usage.output = output;
150
+ if (cacheRead != null) usage.cacheRead = cacheRead;
151
+ if (cacheWrite != null) usage.cacheWrite = cacheWrite;
152
+ if (reasoning != null) usage.reasoning = reasoning;
153
+ if (total != null) usage.total = total;
154
+ return usage;
155
+ }
156
+ function trim1(v) {
157
+ const s = (Math.round(v * 10) / 10).toFixed(1);
158
+ return s.endsWith(".0") ? s.slice(0, -2) : s;
159
+ }
160
+ function formatTokens(n) {
161
+ if (!Number.isFinite(n)) return "0";
162
+ const abs = Math.abs(n);
163
+ if (abs < 1e3) return String(Math.round(n));
164
+ if (abs < 1e6) return `${trim1(n / 1e3)}k`;
165
+ return `${trim1(n / 1e6)}M`;
166
+ }
167
+ function formatDuration(ms) {
168
+ if (!Number.isFinite(ms) || ms <= 0) return "0ms";
169
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
170
+ if (ms < 6e4) return `${trim1(ms / 1e3)}s`;
171
+ const m = Math.floor(ms / 6e4);
172
+ const s = Math.round(ms % 6e4 / 1e3);
173
+ if (ms < 36e5) return s > 0 ? `${m}m${s}s` : `${m}m`;
174
+ const h = Math.floor(ms / 36e5);
175
+ const rm = Math.round(ms % 36e5 / 6e4);
176
+ return rm > 0 ? `${h}h${rm}m` : `${h}h`;
177
+ }
178
+ function svgIcon(svg2) {
179
+ if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
180
+ let parsed;
181
+ try {
182
+ parsed = new DOMParser().parseFromString(svg2, "image/svg+xml");
183
+ } catch {
184
+ return null;
185
+ }
186
+ const root = parsed.documentElement;
187
+ if (!root || root.tagName.toLowerCase() !== "svg" || parsed.querySelector("parsererror")) {
188
+ return null;
189
+ }
190
+ const sanitize = (el2) => {
191
+ for (const attr of Array.from(el2.attributes)) {
192
+ const name = attr.name.toLowerCase();
193
+ if (name.startsWith("on")) el2.removeAttribute(attr.name);
194
+ if ((name === "href" || name === "xlink:href") && attr.value.trim().toLowerCase().startsWith("javascript:")) {
195
+ el2.removeAttribute(attr.name);
196
+ }
197
+ }
198
+ for (const child of Array.from(el2.children)) {
199
+ const tag = child.tagName.toLowerCase();
200
+ if (tag === "script" || tag === "foreignobject") {
201
+ child.remove();
202
+ continue;
203
+ }
204
+ sanitize(child);
205
+ }
206
+ };
207
+ sanitize(root);
208
+ return document.importNode(root, true);
209
+ }
210
+
211
+ // src/behaviors/popover.ts
212
+ var DEFAULT_MIN_WIDTH = 220;
213
+ var DEFAULT_MAX_WIDTH = 420;
214
+ var DEFAULT_MIN_HEIGHT = 120;
215
+ var DEFAULT_MAX_HEIGHT = 420;
216
+ function clamp(value, min, max) {
217
+ if (max < min) return min;
218
+ return Math.min(max, Math.max(min, value));
219
+ }
220
+ function chooseSide(preferredSide, availableTop, availableBottom, desiredHeight) {
221
+ const preferredSpace = preferredSide === "top" ? availableTop : availableBottom;
222
+ const oppositeSpace = preferredSide === "top" ? availableBottom : availableTop;
223
+ if (preferredSpace >= desiredHeight) return preferredSide;
224
+ if (oppositeSpace >= desiredHeight) return preferredSide === "top" ? "bottom" : "top";
225
+ if (preferredSpace >= oppositeSpace) return preferredSide;
226
+ return preferredSide === "top" ? "bottom" : "top";
227
+ }
228
+ function horizontalAnchor(anchor, width, align) {
229
+ if (align === "center") return anchor.left + anchor.width / 2 - width / 2;
230
+ if (align === "end") return anchor.right - width;
231
+ return anchor.left;
232
+ }
233
+ function computePopoverLayout(anchor, viewport, opts = {}) {
234
+ const margin = opts.margin ?? 12;
235
+ const offset = opts.offset ?? 8;
236
+ const availableWidth = Math.max(80, viewport.width - margin * 2);
237
+ const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
238
+ const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
239
+ const maxWidth = Math.min(requestedMaxWidth, availableWidth);
240
+ const minWidth = Math.min(requestedMinWidth, maxWidth);
241
+ const preferredSide = opts.side ?? "top";
242
+ const align = opts.align ?? "start";
243
+ const availableTop = Math.max(0, anchor.top - margin - offset);
244
+ const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
245
+ const availableHeight = Math.max(80, viewport.height - margin * 2);
246
+ const minHeight = Math.min(Math.max(80, opts.minHeight ?? DEFAULT_MIN_HEIGHT), availableHeight);
247
+ const requestedMaxHeight = Math.max(minHeight, opts.maxHeight ?? DEFAULT_MAX_HEIGHT);
248
+ const fallbackHeight = Math.min(260, requestedMaxHeight);
249
+ const contentHeight = Math.max(opts.contentHeightHint ?? 0, fallbackHeight);
250
+ const desiredHeight = clamp(contentHeight, minHeight, Math.min(requestedMaxHeight, availableHeight));
251
+ const side = chooseSide(preferredSide, availableTop, availableBottom, desiredHeight);
252
+ const sideSpace = side === "top" ? availableTop : availableBottom;
253
+ const maxHeight = Math.min(
254
+ requestedMaxHeight,
255
+ Math.max(80, sideSpace || Math.max(availableTop, availableBottom)),
256
+ availableHeight
257
+ );
258
+ const positionedHeight = clamp(desiredHeight, 40, maxHeight);
259
+ const width = align === "stretch" ? clamp(anchor.width, minWidth, maxWidth) : maxWidth;
260
+ const left = clamp(horizontalAnchor(anchor, width, align), margin, viewport.width - margin - width);
261
+ const desiredTop = side === "top" ? anchor.top - offset - positionedHeight : anchor.bottom + offset;
262
+ const top = clamp(desiredTop, margin, viewport.height - margin - positionedHeight);
263
+ return { side, left, top, width, maxHeight };
264
+ }
265
+ var openPanels = /* @__PURE__ */ new Map();
266
+ function rectFromElement(el2) {
267
+ const r = el2.getBoundingClientRect();
268
+ return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
269
+ }
270
+ function resolveAnchor(state) {
271
+ if (state.anchor instanceof Element) {
272
+ if (!state.anchor.isConnected) return null;
273
+ return rectFromElement(state.anchor);
274
+ }
275
+ return state.anchor;
276
+ }
277
+ function relayout(state) {
278
+ const anchor = resolveAnchor(state);
279
+ if (!anchor) {
280
+ closePopover(state.panel);
281
+ return;
282
+ }
283
+ const layout = computePopoverLayout(
284
+ anchor,
285
+ { width: window.innerWidth, height: window.innerHeight },
286
+ state.opts
287
+ );
288
+ const { panel } = state;
289
+ panel.style.left = `${layout.left}px`;
290
+ panel.style.top = `${layout.top}px`;
291
+ panel.style.width = `${layout.width}px`;
292
+ panel.style.maxHeight = `${layout.maxHeight}px`;
293
+ panel.dataset.side = layout.side;
294
+ panel.style.visibility = state.prevVisibility;
295
+ }
296
+ function onWindowChange() {
297
+ openPanels.forEach(relayout);
298
+ }
299
+ function onOutsidePointerDown(e) {
300
+ const target = e.target;
301
+ if (!(target instanceof Node)) return;
302
+ openPanels.forEach((state) => {
303
+ if (state.panel.contains(target)) return;
304
+ if (state.anchor instanceof Element && state.anchor.contains(target)) return;
305
+ closePopover(state.panel);
306
+ });
307
+ }
308
+ function onKeyDown(e) {
309
+ if (e.key !== "Escape") return;
310
+ const states = Array.from(openPanels.values());
311
+ const top = states[states.length - 1];
312
+ if (top) {
313
+ e.preventDefault();
314
+ closePopover(top.panel);
315
+ }
316
+ }
317
+ function openPopover(panel, options) {
318
+ if (typeof document === "undefined") return;
319
+ if (openPanels.has(panel)) closePopover(panel);
320
+ const { anchor, onClose, ...layoutOpts } = options;
321
+ const placeholder = document.createComment("popover-anchor");
322
+ panel.parentNode?.insertBefore(placeholder, panel);
323
+ const wasHidden = panel.hidden;
324
+ const prevVisibility = panel.style.visibility;
325
+ const state = {
326
+ panel,
327
+ anchor,
328
+ opts: layoutOpts,
329
+ onClose,
330
+ placeholder,
331
+ wasHidden,
332
+ prevVisibility
333
+ };
334
+ openPanels.set(panel, state);
335
+ document.body.appendChild(panel);
336
+ panel.hidden = false;
337
+ panel.style.visibility = "hidden";
338
+ relayout(state);
339
+ if (openPanels.size === 1) {
340
+ window.addEventListener("resize", onWindowChange);
341
+ window.addEventListener("scroll", onWindowChange, true);
342
+ document.addEventListener("pointerdown", onOutsidePointerDown, true);
343
+ window.addEventListener("keydown", onKeyDown);
344
+ }
345
+ }
346
+ function closePopover(panel) {
347
+ if (typeof document === "undefined") return;
348
+ const state = openPanels.get(panel);
349
+ if (!state) return;
350
+ openPanels.delete(panel);
351
+ panel.style.visibility = state.prevVisibility;
352
+ if (typeof state.wasHidden === "string") panel.setAttribute("hidden", state.wasHidden);
353
+ else panel.hidden = state.wasHidden;
354
+ panel.style.left = "";
355
+ panel.style.top = "";
356
+ panel.style.width = "";
357
+ panel.style.maxHeight = "";
358
+ delete panel.dataset.side;
359
+ if (state.placeholder.parentNode) {
360
+ state.placeholder.parentNode.insertBefore(panel, state.placeholder);
361
+ state.placeholder.remove();
362
+ }
363
+ if (openPanels.size === 0) {
364
+ window.removeEventListener("resize", onWindowChange);
365
+ window.removeEventListener("scroll", onWindowChange, true);
366
+ document.removeEventListener("pointerdown", onOutsidePointerDown, true);
367
+ window.removeEventListener("keydown", onKeyDown);
368
+ }
369
+ state.onClose?.();
370
+ }
371
+
372
+ // src/behaviors/ai-panel.ts
373
+ function el(tag, className, text) {
374
+ const node = document.createElement(tag);
375
+ if (className) node.className = className;
376
+ if (text !== void 0) node.textContent = text;
377
+ return node;
378
+ }
379
+ function dispatch(node, name, detail) {
380
+ node.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }));
381
+ }
382
+ var STATUS_CLASSES = [
383
+ "pending",
384
+ "running",
385
+ "streaming",
386
+ "approval",
387
+ "done",
388
+ "error",
389
+ "cancelled"
390
+ ];
391
+ function stripStatusClasses(node) {
392
+ for (const s of STATUS_CLASSES) node.classList.remove(`is-${s}`);
393
+ }
394
+ function buildTodoHead(done, total) {
395
+ const head = el("div", "ai-todo-head");
396
+ head.appendChild(el("span", "ai-todo-progress", `${done}/${total}`));
397
+ const bar = el("div", "ai-todo-bar");
398
+ bar.setAttribute("role", "progressbar");
399
+ bar.setAttribute("aria-valuemin", "0");
400
+ bar.setAttribute("aria-valuemax", String(total));
401
+ bar.setAttribute("aria-valuenow", String(done));
402
+ bar.setAttribute("aria-label", "\u4EFB\u52A1\u8FDB\u5EA6");
403
+ const fill = document.createElement("i");
404
+ fill.style.width = total > 0 ? `${Math.round(done / total * 100)}%` : "0%";
405
+ bar.appendChild(fill);
406
+ head.appendChild(bar);
407
+ return head;
408
+ }
409
+ function buildTodoItem(item, index) {
410
+ const row = el("div", `ai-todo-item is-${item.status}`);
411
+ row.dataset.index = String(index);
412
+ row.appendChild(el("span", "ai-item-status"));
413
+ row.appendChild(el("span", "ai-todo-text", item.content));
414
+ row.appendChild(el("span", "ai-todo-active", item.activeForm ?? item.content));
415
+ return row;
416
+ }
417
+ function renderAiTodo(elm, items) {
418
+ if (typeof document === "undefined") return;
419
+ elm.textContent = "";
420
+ const root = el("div", "ai-todo");
421
+ const list = Array.isArray(items) ? items : [];
422
+ const done = list.filter((i) => i.status === "done").length;
423
+ root.appendChild(buildTodoHead(done, list.length));
424
+ list.forEach((item, i) => root.appendChild(buildTodoItem(item, i)));
425
+ if (list.length > 0 && done === list.length) root.classList.add("is-complete");
426
+ elm.appendChild(root);
427
+ }
428
+ var TODO_NEXT = {
429
+ pending: "running",
430
+ running: "done",
431
+ done: "pending"
432
+ };
433
+ function todoItemStatus(item) {
434
+ for (const s of STATUS_CLASSES) {
435
+ if (item.classList.contains(`is-${s}`)) return s;
436
+ }
437
+ return "pending";
438
+ }
439
+ function refreshTodoProgress(todo) {
440
+ const items = Array.from(todo.querySelectorAll(".ai-todo-item"));
441
+ const done = items.filter((i) => i.classList.contains("is-done")).length;
442
+ const total = items.length;
443
+ const progress = todo.querySelector(".ai-todo-progress");
444
+ if (progress) progress.textContent = `${done}/${total}`;
445
+ const bar = todo.querySelector(".ai-todo-bar");
446
+ if (bar) {
447
+ bar.setAttribute("aria-valuemax", String(total));
448
+ bar.setAttribute("aria-valuenow", String(done));
449
+ const fill = bar.querySelector("i");
450
+ if (fill) fill.style.width = total > 0 ? `${Math.round(done / total * 100)}%` : "0%";
451
+ }
452
+ todo.classList.toggle("is-complete", total > 0 && done === total);
453
+ }
454
+ function toggleTodoItem(item) {
455
+ const todo = item.closest("[data-ai-todo-interactive]");
456
+ if (!todo || !todo.hasAttribute("data-ai-todo-interactive")) return;
457
+ const index = Array.from(
458
+ todo.querySelectorAll(".ai-todo-item")
459
+ ).indexOf(item);
460
+ const next = TODO_NEXT[todoItemStatus(item)] ?? "pending";
461
+ stripStatusClasses(item);
462
+ item.classList.add(`is-${next}`);
463
+ if (next === "running") {
464
+ const active = item.querySelector(".ai-todo-active");
465
+ if (active && item.dataset.activeForm) {
466
+ active.textContent = item.dataset.activeForm;
467
+ }
468
+ }
469
+ const text = item.querySelector(".ai-todo-text")?.textContent ?? "";
470
+ if (!item.hasAttribute("aria-label")) {
471
+ item.setAttribute("aria-label", `${text}\uFF0C\u72B6\u6001\uFF1A${aiStatusLabel(next)}`);
472
+ }
473
+ refreshTodoProgress(todo);
474
+ dispatch(item, "icen:ai-todo-toggle", { index, status: next });
475
+ }
476
+ function initAiTodo(root) {
477
+ if (typeof document === "undefined") return () => void 0;
478
+ const scope = root ?? document;
479
+ const marked = scope;
480
+ if (marked.__icenAiTodoInit) return () => void 0;
481
+ marked.__icenAiTodoInit = true;
482
+ const target = scope;
483
+ scope.querySelectorAll(
484
+ "[data-ai-todo-interactive] > .ai-todo-item"
485
+ ).forEach((item) => {
486
+ if (item.getAttribute("role") === "button") return;
487
+ item.setAttribute("role", "button");
488
+ item.tabIndex = 0;
489
+ const text = item.querySelector(".ai-todo-text")?.textContent ?? "";
490
+ item.setAttribute("aria-label", `${text}\uFF0C\u72B6\u6001\uFF1A${aiStatusLabel(todoItemStatus(item))}`);
491
+ });
492
+ const onClick = (e) => {
493
+ const t = e.target instanceof Element ? e.target : null;
494
+ const item = t?.closest(".ai-todo-item");
495
+ if (!item || !(item.closest("[data-ai-todo-interactive]") instanceof HTMLElement)) return;
496
+ toggleTodoItem(item);
497
+ };
498
+ const onKeydown = (e) => {
499
+ const ke = e;
500
+ if (ke.key !== "Enter" && ke.key !== " ") return;
501
+ const t = ke.target instanceof Element ? ke.target : null;
502
+ const item = t?.closest(".ai-todo-item");
503
+ if (!item || !(item.closest("[data-ai-todo-interactive]") instanceof HTMLElement)) return;
504
+ ke.preventDefault();
505
+ toggleTodoItem(item);
506
+ };
507
+ target.addEventListener("click", onClick);
508
+ target.addEventListener("keydown", onKeydown);
509
+ return () => {
510
+ target.removeEventListener("click", onClick);
511
+ target.removeEventListener("keydown", onKeydown);
512
+ marked.__icenAiTodoInit = false;
513
+ };
514
+ }
515
+ function renderAiUsage(elm, usage, opts = {}) {
516
+ if (typeof document === "undefined") return;
517
+ const u = normalizeUsage(usage);
518
+ const segs = [
519
+ { key: "input", className: "ai-usage-seg--input", label: "\u8F93\u5165", value: u.input ?? 0 },
520
+ { key: "output", className: "ai-usage-seg--output", label: "\u8F93\u51FA", value: u.output ?? 0 },
521
+ { key: "cacheRead", className: "ai-usage-seg--cache-read", label: "\u7F13\u5B58\u8BFB", value: u.cacheRead ?? 0 },
522
+ { key: "cacheWrite", className: "ai-usage-seg--cache-write", label: "\u7F13\u5B58\u5199", value: u.cacheWrite ?? 0 },
523
+ { key: "reasoning", className: "ai-usage-seg--reasoning", label: "\u63A8\u7406", value: u.reasoning ?? 0 }
524
+ ];
525
+ const activeSegs = segs.filter((s) => s.value > 0);
526
+ const sum2 = activeSegs.reduce((acc, s) => acc + s.value, 0);
527
+ const total = u.total ?? sum2;
528
+ elm.textContent = "";
529
+ const root = el("div", "ai-usage");
530
+ const bar = el("div", "ai-usage-bar");
531
+ const summary = activeSegs.length > 0 ? activeSegs.map((s) => `${s.label} ${formatTokens(s.value)}`).join("\uFF0C") : "\u6682\u65E0\u7528\u91CF";
532
+ bar.setAttribute("role", "img");
533
+ bar.setAttribute("aria-label", `\u7528\u91CF\uFF1A${summary}`);
534
+ for (const s of activeSegs) {
535
+ const seg = document.createElement("i");
536
+ seg.className = `ai-usage-seg ${s.className}`;
537
+ seg.style.width = sum2 > 0 ? `${s.value / sum2 * 100}%` : "0%";
538
+ bar.appendChild(seg);
539
+ }
540
+ root.appendChild(bar);
541
+ if (activeSegs.length > 0) {
542
+ const legend = el("div", "ai-usage-legend");
543
+ for (const s of activeSegs) {
544
+ const item = el("span", "ai-usage-legend-item");
545
+ const dot = document.createElement("i");
546
+ dot.className = `ai-usage-dot ${s.className}`;
547
+ dot.setAttribute("aria-hidden", "true");
548
+ item.appendChild(dot);
549
+ item.appendChild(el("span", "ai-usage-legend-label", s.label));
550
+ item.appendChild(el("span", "ai-usage-legend-value", formatTokens(s.value)));
551
+ legend.appendChild(item);
552
+ }
553
+ root.appendChild(legend);
554
+ }
555
+ const totalParts = [formatTokens(total)];
556
+ if (typeof opts.total === "number" && opts.total > 0) {
557
+ const pct = Math.min(100, Math.round(total / opts.total * 100));
558
+ totalParts.push(` / ${formatTokens(opts.total)} \xB7 ${pct}%`);
559
+ }
560
+ if (typeof opts.cost === "number" && Number.isFinite(opts.cost)) {
561
+ totalParts.push(` \xB7 $${opts.cost.toFixed(4)}`);
562
+ }
563
+ root.appendChild(el("div", "ai-usage-total", totalParts.join("")));
564
+ elm.appendChild(root);
565
+ }
566
+ function renderAiUsageRing(elm, usage, opts = {}) {
567
+ if (typeof document === "undefined") return;
568
+ const u = normalizeUsage(usage);
569
+ const used = u.total ?? (u.input ?? 0) + (u.output ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0) + (u.reasoning ?? 0);
570
+ const hasLimit = typeof opts.total === "number" && opts.total > 0;
571
+ const pct = hasLimit ? Math.min(100, Math.max(0, Math.round(used / opts.total * 100))) : 0;
572
+ elm.textContent = "";
573
+ const btn = el("button", "ai-usage-ring");
574
+ btn.type = "button";
575
+ btn.classList.add(pct >= 85 ? "is-hot" : pct >= 60 ? "is-warn" : "is-ok");
576
+ btn.setAttribute("aria-haspopup", "true");
577
+ btn.setAttribute(
578
+ "aria-label",
579
+ hasLimit ? `\u4E0A\u4E0B\u6587\u7A97\u53E3\u5360\u7528 ${pct}%\uFF0C\u70B9\u51FB\u67E5\u770B\u8BE6\u60C5` : `\u5DF2\u7528 ${formatTokens(used)}\uFF0C\u70B9\u51FB\u67E5\u770B\u8BE6\u60C5`
580
+ );
581
+ const NS = "http://www.w3.org/2000/svg";
582
+ const svg2 = document.createElementNS(NS, "svg");
583
+ svg2.setAttribute("viewBox", "0 0 36 36");
584
+ svg2.setAttribute("aria-hidden", "true");
585
+ const track = document.createElementNS(NS, "circle");
586
+ track.setAttribute("class", "ai-usage-ring-track");
587
+ const fill = document.createElementNS(NS, "circle");
588
+ fill.setAttribute("class", "ai-usage-ring-fill");
589
+ fill.setAttribute("stroke-dasharray", `${pct} 100`);
590
+ for (const c of [track, fill]) {
591
+ c.setAttribute("cx", "18");
592
+ c.setAttribute("cy", "18");
593
+ c.setAttribute("r", "15.9155");
594
+ svg2.appendChild(c);
595
+ }
596
+ btn.appendChild(svg2);
597
+ btn.appendChild(el("span", "ai-usage-ring-pct", hasLimit ? `${pct}%` : formatTokens(used)));
598
+ let panel = null;
599
+ const closePanel = () => {
600
+ if (!panel) return;
601
+ const p = panel;
602
+ panel = null;
603
+ closePopover(p);
604
+ p.remove();
605
+ };
606
+ btn.addEventListener("click", () => {
607
+ if (panel) {
608
+ closePanel();
609
+ return;
610
+ }
611
+ const p = el("div", "popover ai-usage-popover");
612
+ p.hidden = true;
613
+ p.appendChild(el("div", "ai-usage-popover-title", opts.title ?? "\u4E0A\u4E0B\u6587\u7A97\u53E3"));
614
+ const body = el("div", "ai-usage-popover-body");
615
+ renderAiUsage(body, usage, opts);
616
+ p.appendChild(body);
617
+ openPopover(p, {
618
+ anchor: btn,
619
+ side: "bottom",
620
+ align: "end",
621
+ onClose: () => {
622
+ p.remove();
623
+ if (panel === p) panel = null;
624
+ }
625
+ });
626
+ panel = p;
627
+ });
628
+ elm.appendChild(btn);
629
+ }
630
+ var svgWrap = (inner) => `<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">${inner}</svg>`;
631
+ var FILE_OUTLINE = '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/>';
632
+ var ICON_FILE2 = svgWrap(FILE_OUTLINE);
633
+ var ICON_FILE_CODE = svgWrap(`${FILE_OUTLINE}<path d="m10 13-2 2 2 2"/><path d="m14 17 2-2-2-2"/>`);
634
+ var ICON_BRACES = svgWrap(
635
+ '<path d="M8 3H7a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2 2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1"/><path d="M16 3h1a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2 2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-1"/>'
636
+ );
637
+ var ICON_FILE_TEXT2 = svgWrap(`${FILE_OUTLINE}<path d="M16 13H8"/><path d="M16 17H8"/>`);
638
+ var ICON_IMAGE = svgWrap(
639
+ '<rect width="18" height="18" x="3" y="3" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21"/>'
640
+ );
641
+ var ICON_SERVER = svgWrap(
642
+ '<rect width="20" height="8" x="2" y="2" rx="2"/><rect width="20" height="8" x="2" y="14" rx="2"/><path d="M6 6h.01"/><path d="M6 18h.01"/>'
643
+ );
644
+ var ICON_SPARKLES = svgWrap(
645
+ '<path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z"/>'
646
+ );
647
+ function fileKind(path) {
648
+ const m = /\.([a-z0-9]+)$/i.exec(path);
649
+ const ext = m ? m[1].toLowerCase() : "";
650
+ if (["ts", "tsx", "mts", "cts"].includes(ext)) return "ts";
651
+ if (["js", "jsx", "mjs", "cjs"].includes(ext)) return "js";
652
+ if (["css", "scss", "less"].includes(ext)) return "css";
653
+ if (ext === "json") return "json";
654
+ if (["md", "markdown"].includes(ext)) return "md";
655
+ if (["png", "jpg", "jpeg", "gif", "webp", "svg", "ico", "avif"].includes(ext)) return "img";
656
+ return "other";
657
+ }
658
+ function fileKindIcon(kind) {
659
+ switch (kind) {
660
+ case "ts":
661
+ case "js":
662
+ return ICON_FILE_CODE;
663
+ case "css":
664
+ case "json":
665
+ return ICON_BRACES;
666
+ case "md":
667
+ return ICON_FILE_TEXT2;
668
+ case "img":
669
+ return ICON_IMAGE;
670
+ default:
671
+ return ICON_FILE2;
672
+ }
673
+ }
674
+ function renderFileChip(file) {
675
+ const kind = fileKind(file.path);
676
+ const chip = el("span", `ai-file-chip ai-file-chip--${kind} is-${file.status ?? "modified"}`);
677
+ const icon = el("span", "ai-file-icon");
678
+ const svg2 = svgIcon(fileKindIcon(kind));
679
+ if (svg2) icon.appendChild(svg2);
680
+ chip.appendChild(icon);
681
+ chip.appendChild(document.createTextNode(file.path));
682
+ return chip;
683
+ }
684
+ function renderSection(title) {
685
+ const section = el("section", "ai-context-section");
686
+ section.appendChild(el("h3", "", title));
687
+ const body = el("div", "ai-context-section-body");
688
+ section.appendChild(body);
689
+ return { section, body };
690
+ }
691
+ function renderAiContext(elm, model) {
692
+ if (typeof document === "undefined") return;
693
+ elm.textContent = "";
694
+ const m = model ?? {};
695
+ const aside = el("aside", "ai-context");
696
+ aside.hidden = true;
697
+ const head = el("div", "ai-context-head");
698
+ head.appendChild(el("span", "", "\u4E0A\u4E0B\u6587"));
699
+ const closeBtn = el("button", "", "\xD7");
700
+ closeBtn.type = "button";
701
+ closeBtn.setAttribute("data-ai-context-close", "");
702
+ closeBtn.setAttribute("aria-label", "\u5173\u95ED");
703
+ head.appendChild(closeBtn);
704
+ aside.appendChild(head);
705
+ if (m.usage) {
706
+ const { section, body } = renderSection("\u7528\u91CF");
707
+ renderAiUsage(body, m.usage, { total: m.usageTotal, cost: m.usageCost });
708
+ aside.appendChild(section);
709
+ }
710
+ const auditEntries = auditEntriesOf(m.audit);
711
+ if (auditEntries.length > 0) {
712
+ const { section, body } = renderSection("\u5BA1\u8BA1");
713
+ buildAuditBody(body, auditEntries, { limit: 5, compact: true });
714
+ aside.appendChild(section);
715
+ }
716
+ const files = Array.isArray(m.files) ? m.files : [];
717
+ if (files.length > 0) {
718
+ const { section, body } = renderSection(`\u6587\u4EF6 (${files.length})`);
719
+ const chips = el("div", "ai-files");
720
+ for (const f of files) chips.appendChild(renderFileChip(f));
721
+ body.appendChild(chips);
722
+ aside.appendChild(section);
723
+ }
724
+ const servers = Array.isArray(m.mcpServers) ? m.mcpServers : [];
725
+ if (servers.length > 0) {
726
+ const { section, body } = renderSection(`MCP (${servers.length})`);
727
+ for (const s of servers) {
728
+ const ok = s.status !== "disconnected";
729
+ const row = el("div", `ai-item is-${ok ? "done" : "error"}`);
730
+ const icon = el("span", "ai-item-icon");
731
+ const svg2 = svgIcon(ICON_SERVER);
732
+ if (svg2) icon.appendChild(svg2);
733
+ row.appendChild(icon);
734
+ const main = el("span", "ai-item-main");
735
+ main.appendChild(el("span", "ai-item-title", s.name));
736
+ row.appendChild(main);
737
+ const side = el("span", "ai-item-side");
738
+ side.appendChild(el("span", "ai-item-status"));
739
+ side.appendChild(el("span", "ai-item-meta", typeof s.tools === "number" ? `${s.tools} \u5DE5\u5177` : ""));
740
+ row.appendChild(side);
741
+ body.appendChild(row);
742
+ }
743
+ aside.appendChild(section);
744
+ }
745
+ const skills = Array.isArray(m.skills) ? m.skills : [];
746
+ if (skills.length > 0) {
747
+ const { section, body } = renderSection("Skills");
748
+ for (const s of skills) {
749
+ const row = el("div", "ai-item");
750
+ const icon = el("span", "ai-item-icon");
751
+ const svg2 = svgIcon(ICON_SPARKLES);
752
+ if (svg2) icon.appendChild(svg2);
753
+ row.appendChild(icon);
754
+ const main = el("span", "ai-item-main");
755
+ main.appendChild(el("span", "ai-item-title", s.name));
756
+ if (s.description) main.appendChild(el("span", "ai-item-sub", s.description));
757
+ row.appendChild(main);
758
+ body.appendChild(row);
759
+ }
760
+ aside.appendChild(section);
761
+ }
762
+ elm.appendChild(aside);
763
+ }
764
+ function isInline(drawer) {
765
+ return drawer.classList.contains("ai-context--inline");
766
+ }
767
+ function initAiContext(root) {
768
+ if (typeof document === "undefined") return () => void 0;
769
+ const scope = root ?? document;
770
+ const marked = scope;
771
+ if (marked.__icenAiContextInit) return () => void 0;
772
+ marked.__icenAiContextInit = true;
773
+ const target = scope;
774
+ scope.querySelectorAll("[data-ai-context-open]").forEach((t) => {
775
+ if (!t.hasAttribute("aria-expanded")) t.setAttribute("aria-expanded", "false");
776
+ });
777
+ let lastTrigger = null;
778
+ const drawers = () => Array.from(scope.querySelectorAll(".ai-context")).filter((d) => !isInline(d));
779
+ function resolveDrawer(trigger) {
780
+ const sel = trigger.getAttribute("data-ai-context-open");
781
+ if (sel) {
782
+ const found = scope.querySelector(sel);
783
+ if (found && found.classList.contains("ai-context") && !isInline(found)) return found;
784
+ }
785
+ return drawers()[0] ?? null;
786
+ }
787
+ function syncTriggers(drawer, open) {
788
+ scope.querySelectorAll("[data-ai-context-open]").forEach((t) => {
789
+ if (!drawer || resolveDrawer(t) === drawer) {
790
+ t.setAttribute("aria-expanded", String(open));
791
+ }
792
+ });
793
+ }
794
+ function openDrawer(drawer, trigger) {
795
+ drawers().forEach((d) => {
796
+ if (d !== drawer && !d.hidden) closeDrawer(d, false);
797
+ });
798
+ lastTrigger = trigger;
799
+ if (!drawer.hasAttribute("role")) {
800
+ drawer.setAttribute("role", "dialog");
801
+ drawer.setAttribute("aria-modal", "false");
802
+ if (!drawer.hasAttribute("aria-label")) drawer.setAttribute("aria-label", "\u4E0A\u4E0B\u6587");
803
+ }
804
+ drawer.hidden = false;
805
+ void drawer.offsetWidth;
806
+ drawer.classList.add("is-open");
807
+ syncTriggers(drawer, true);
808
+ const closeBtn = drawer.querySelector("[data-ai-context-close]");
809
+ (closeBtn ?? drawer).focus?.();
810
+ }
811
+ function closeDrawer(drawer, restoreFocus) {
812
+ if (drawer.hidden && !drawer.classList.contains("is-open")) return;
813
+ drawer.classList.remove("is-open");
814
+ syncTriggers(drawer, false);
815
+ const finish = () => {
816
+ drawer.hidden = true;
817
+ if (restoreFocus && lastTrigger && document.contains(lastTrigger)) {
818
+ lastTrigger.focus();
819
+ }
820
+ lastTrigger = null;
821
+ };
822
+ let done = false;
823
+ const onEnd = () => {
824
+ if (done) return;
825
+ done = true;
826
+ drawer.removeEventListener("transitionend", onEnd);
827
+ finish();
828
+ };
829
+ drawer.addEventListener("transitionend", onEnd);
830
+ setTimeout(onEnd, 300);
831
+ }
832
+ function trapFocus(drawer, ke) {
833
+ const focusables = Array.from(
834
+ drawer.querySelectorAll(
835
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
836
+ )
837
+ ).filter((n) => !n.hidden && n.getAttribute("aria-hidden") !== "true");
838
+ if (focusables.length === 0) {
839
+ ke.preventDefault();
840
+ drawer.focus?.();
841
+ return;
842
+ }
843
+ const first = focusables[0];
844
+ const last = focusables[focusables.length - 1];
845
+ const active = document.activeElement;
846
+ if (ke.shiftKey) {
847
+ if (active === first || !drawer.contains(active)) {
848
+ ke.preventDefault();
849
+ last.focus();
850
+ }
851
+ } else if (active === last || !drawer.contains(active)) {
852
+ ke.preventDefault();
853
+ first.focus();
854
+ }
855
+ }
856
+ const onClick = (e) => {
857
+ const t = e.target instanceof Element ? e.target : null;
858
+ if (!t) return;
859
+ const closeBtn = t.closest("[data-ai-context-close]");
860
+ if (closeBtn) {
861
+ const drawer = closeBtn.closest(".ai-context");
862
+ if (drawer instanceof HTMLElement && !isInline(drawer)) closeDrawer(drawer, true);
863
+ return;
864
+ }
865
+ const trigger = t.closest("[data-ai-context-open]");
866
+ if (trigger) {
867
+ const drawer = resolveDrawer(trigger);
868
+ if (!drawer) return;
869
+ if (drawer.hidden || !drawer.classList.contains("is-open")) {
870
+ openDrawer(drawer, trigger);
871
+ } else {
872
+ closeDrawer(drawer, true);
873
+ }
874
+ return;
875
+ }
876
+ if (!t.closest(".ai-context")) {
877
+ drawers().forEach((d) => {
878
+ if (d.classList.contains("is-open")) closeDrawer(d, true);
879
+ });
880
+ }
881
+ };
882
+ const onKeydown = (e) => {
883
+ const ke = e;
884
+ if (ke.key === "Escape") {
885
+ drawers().forEach((d) => {
886
+ if (d.classList.contains("is-open")) closeDrawer(d, true);
887
+ });
888
+ return;
889
+ }
890
+ if (ke.key === "Tab") {
891
+ const open = drawers().find((d) => d.classList.contains("is-open") && !d.hidden);
892
+ if (open) trapFocus(open, ke);
893
+ return;
894
+ }
895
+ if (ke.key === "Enter" || ke.key === " ") {
896
+ const t = ke.target instanceof Element ? ke.target : null;
897
+ const trigger = t?.closest("[data-ai-context-open]");
898
+ if (!trigger || trigger.tagName === "BUTTON") return;
899
+ ke.preventDefault();
900
+ trigger.click();
901
+ }
902
+ };
903
+ target.addEventListener("click", onClick);
904
+ target.addEventListener("keydown", onKeydown);
905
+ return () => {
906
+ target.removeEventListener("click", onClick);
907
+ target.removeEventListener("keydown", onKeydown);
908
+ marked.__icenAiContextInit = false;
909
+ };
910
+ }
911
+ function auditEntriesOf(source) {
912
+ if (!source) return [];
913
+ return Array.isArray(source) ? source.slice() : source.list();
914
+ }
915
+ function timeOf(ts) {
916
+ try {
917
+ return new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
918
+ } catch {
919
+ return "";
920
+ }
921
+ }
922
+ function buildAuditBody(body, entries, opts = {}) {
923
+ const limit = opts.limit ?? 10;
924
+ let tokens = 0;
925
+ let cost = 0;
926
+ let errors = 0;
927
+ let ttftSum = 0;
928
+ let ttftN = 0;
929
+ const groups = /* @__PURE__ */ new Map();
930
+ for (const e of entries) {
931
+ const t = e.usage?.total ?? 0;
932
+ tokens += t;
933
+ if (typeof e.cost === "number") cost += e.cost;
934
+ if (e.status === "error") errors++;
935
+ if (typeof e.ttftMs === "number") {
936
+ ttftSum += e.ttftMs;
937
+ ttftN++;
938
+ }
939
+ const key = `${e.provider}/${e.model}`;
940
+ const g = groups.get(key) ?? { key, tokens: 0, cost: 0, requests: 0 };
941
+ g.tokens += t;
942
+ if (typeof e.cost === "number") g.cost += e.cost;
943
+ g.requests++;
944
+ groups.set(key, g);
945
+ }
946
+ const totals = el("div", "ai-audit-totals");
947
+ const bits = [`${entries.length} \u6B21\u8BF7\u6C42`];
948
+ if (errors > 0) bits.push(`${errors} \u5931\u8D25`);
949
+ bits.push(`${formatTokens(tokens)} tok`);
950
+ if (cost > 0) bits.push(`$${(Math.round(cost * 1e6) / 1e6).toFixed(4)}`);
951
+ if (ttftN > 0) bits.push(`TTFT ${formatDuration(Math.round(ttftSum / ttftN))}`);
952
+ totals.appendChild(el("span", "ai-audit-totals-text", bits.join(" \xB7 ")));
953
+ body.appendChild(totals);
954
+ const sorted = Array.from(groups.values()).sort((a, b) => b.tokens - a.tokens);
955
+ if (sorted.length > 0) {
956
+ const list2 = el("div", "ai-audit-models");
957
+ for (const g of sorted) {
958
+ const row = el("div", "ai-audit-model-row");
959
+ row.appendChild(el("span", "ai-audit-model-name", g.key));
960
+ row.appendChild(el("span", "ai-audit-model-meta", `${g.requests} \u6B21 \xB7 ${formatTokens(g.tokens)} tok${g.cost > 0 ? ` \xB7 $${g.cost.toFixed(4)}` : ""}`));
961
+ list2.appendChild(row);
962
+ }
963
+ body.appendChild(list2);
964
+ }
965
+ const recent = entries.slice(-limit).reverse();
966
+ const list = el("div", "ai-audit-list");
967
+ for (const e of recent) {
968
+ const item = el("div", `ai-audit-item is-${e.status === "ok" ? "done" : "error"}`);
969
+ item.appendChild(el("span", "ai-audit-dot"));
970
+ const main = el("span", "ai-audit-item-main");
971
+ main.appendChild(el("span", "ai-audit-item-model", `${e.provider} \xB7 ${e.model}`));
972
+ const metaBits = [timeOf(e.ts), formatDuration(e.durationMs)];
973
+ if (typeof e.ttftMs === "number") metaBits.push(`TTFT ${formatDuration(e.ttftMs)}`);
974
+ if ((e.usage?.total ?? 0) > 0) metaBits.push(`${formatTokens(e.usage?.total ?? 0)} tok`);
975
+ main.appendChild(el("span", "ai-audit-item-meta", metaBits.filter(Boolean).join(" \xB7 ")));
976
+ item.appendChild(main);
977
+ if (e.error) item.appendChild(el("span", "ai-audit-item-error", e.error));
978
+ list.appendChild(item);
979
+ }
980
+ body.appendChild(list);
981
+ }
982
+ function renderAiAudit(elm, source, opts = {}) {
983
+ if (typeof document === "undefined") return;
984
+ elm.textContent = "";
985
+ const root = el("div", "ai-audit");
986
+ const entries = auditEntriesOf(source);
987
+ if (entries.length === 0) {
988
+ root.appendChild(el("div", "ai-audit-empty", "\u6682\u65E0\u5BA1\u8BA1\u8BB0\u5F55"));
989
+ elm.appendChild(root);
990
+ return;
991
+ }
992
+ buildAuditBody(root, entries, { limit: opts.limit });
993
+ if (opts.clearButton !== false) {
994
+ const clear = el("button", "btn btn-sm ai-audit-clear", "\u6E05\u9664\u5BA1\u8BA1");
995
+ clear.type = "button";
996
+ clear.addEventListener("click", () => {
997
+ if (opts.onClear) opts.onClear();
998
+ else dispatch(root, "icen:ai-audit-clear", {});
999
+ });
1000
+ root.appendChild(clear);
1001
+ }
1002
+ elm.appendChild(root);
1003
+ }
1004
+ export {
1005
+ initAiContext,
1006
+ initAiTodo,
1007
+ renderAiAudit,
1008
+ renderAiContext,
1009
+ renderAiTodo,
1010
+ renderAiUsage,
1011
+ renderAiUsageRing
1012
+ };