@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,13 @@
|
|
|
1
1
|
// src/behaviors/ai-core.ts
|
|
2
|
+
var USAGE_KEYS = ["input", "output", "cacheRead", "cacheWrite", "reasoning", "total"];
|
|
3
|
+
function addUsage(sum2, u) {
|
|
4
|
+
const out = { ...sum2 };
|
|
5
|
+
for (const k of USAGE_KEYS) {
|
|
6
|
+
const v = u[k];
|
|
7
|
+
if (typeof v === "number" && Number.isFinite(v)) out[k] = (out[k] ?? 0) + v;
|
|
8
|
+
}
|
|
9
|
+
return out;
|
|
10
|
+
}
|
|
2
11
|
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
12
|
var ICON_TERMINAL = svg('<path d="m4 17 6-6-6-6"/><path d="M12 19h8"/>');
|
|
4
13
|
var ICON_FILE = svg(
|
|
@@ -159,9 +168,9 @@ function formatDuration(ms) {
|
|
|
159
168
|
const m = Math.floor(ms / 6e4);
|
|
160
169
|
const s = Math.round(ms % 6e4 / 1e3);
|
|
161
170
|
if (ms < 36e5) return s > 0 ? `${m}m${s}s` : `${m}m`;
|
|
162
|
-
const
|
|
171
|
+
const h2 = Math.floor(ms / 36e5);
|
|
163
172
|
const rm = Math.round(ms % 36e5 / 6e4);
|
|
164
|
-
return rm > 0 ? `${
|
|
173
|
+
return rm > 0 ? `${h2}h${rm}m` : `${h2}h`;
|
|
165
174
|
}
|
|
166
175
|
function aiContentUrl(part) {
|
|
167
176
|
if (part.type === "text" || part.type === "resource-link") return void 0;
|
|
@@ -175,6 +184,12 @@ function contextEstimate(usage) {
|
|
|
175
184
|
const u = normalizeUsage(usage);
|
|
176
185
|
return (u.input ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0) + (u.output ?? 0) + (u.reasoning ?? 0);
|
|
177
186
|
}
|
|
187
|
+
function h(tag, className, text) {
|
|
188
|
+
const node = document.createElement(tag);
|
|
189
|
+
if (className) node.className = className;
|
|
190
|
+
if (text != null) node.textContent = text;
|
|
191
|
+
return node;
|
|
192
|
+
}
|
|
178
193
|
function svgIcon(svg2) {
|
|
179
194
|
if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
|
|
180
195
|
let parsed;
|
|
@@ -187,15 +202,15 @@ function svgIcon(svg2) {
|
|
|
187
202
|
if (!root || root.tagName.toLowerCase() !== "svg" || parsed.querySelector("parsererror")) {
|
|
188
203
|
return null;
|
|
189
204
|
}
|
|
190
|
-
const sanitize = (
|
|
191
|
-
for (const attr of Array.from(
|
|
205
|
+
const sanitize = (el) => {
|
|
206
|
+
for (const attr of Array.from(el.attributes)) {
|
|
192
207
|
const name = attr.name.toLowerCase();
|
|
193
|
-
if (name.startsWith("on"))
|
|
208
|
+
if (name.startsWith("on")) el.removeAttribute(attr.name);
|
|
194
209
|
if ((name === "href" || name === "xlink:href") && attr.value.trim().toLowerCase().startsWith("javascript:")) {
|
|
195
|
-
|
|
210
|
+
el.removeAttribute(attr.name);
|
|
196
211
|
}
|
|
197
212
|
}
|
|
198
|
-
for (const child of Array.from(
|
|
213
|
+
for (const child of Array.from(el.children)) {
|
|
199
214
|
const tag = child.tagName.toLowerCase();
|
|
200
215
|
if (tag === "script" || tag === "foreignobject") {
|
|
201
216
|
child.remove();
|
|
@@ -209,6 +224,44 @@ function svgIcon(svg2) {
|
|
|
209
224
|
}
|
|
210
225
|
|
|
211
226
|
// src/behaviors/popover.ts
|
|
227
|
+
var PANEL_SIZING_ATTRS = {
|
|
228
|
+
width: "data-panel-width",
|
|
229
|
+
minWidth: "data-panel-min",
|
|
230
|
+
maxWidth: "data-panel-max",
|
|
231
|
+
minHeight: "data-panel-min-height",
|
|
232
|
+
maxHeight: "data-panel-max-height"
|
|
233
|
+
};
|
|
234
|
+
function readPanelSizing(el) {
|
|
235
|
+
const out = {};
|
|
236
|
+
if (!el) return out;
|
|
237
|
+
for (const key of Object.keys(PANEL_SIZING_ATTRS)) {
|
|
238
|
+
const raw = el.getAttribute(PANEL_SIZING_ATTRS[key]);
|
|
239
|
+
if (raw == null) continue;
|
|
240
|
+
const n = Number(raw);
|
|
241
|
+
if (Number.isFinite(n) && n > 0) out[key] = n;
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
function resolvePanelSizing(el, overrides) {
|
|
246
|
+
const base = readPanelSizing(el);
|
|
247
|
+
if (!overrides) return base;
|
|
248
|
+
for (const key of Object.keys(overrides)) {
|
|
249
|
+
const v = overrides[key];
|
|
250
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0) base[key] = v;
|
|
251
|
+
}
|
|
252
|
+
return base;
|
|
253
|
+
}
|
|
254
|
+
function sizingToLayout(sizing, opts) {
|
|
255
|
+
const out = { ...opts };
|
|
256
|
+
if (sizing.minWidth != null) out.minWidth = sizing.minWidth;
|
|
257
|
+
if (sizing.maxWidth != null) out.maxWidth = sizing.maxWidth;
|
|
258
|
+
if (sizing.width != null) {
|
|
259
|
+
out.minWidth = sizing.width;
|
|
260
|
+
out.maxWidth = sizing.width;
|
|
261
|
+
}
|
|
262
|
+
if (sizing.maxHeight != null) out.maxHeight = sizing.maxHeight;
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
212
265
|
var DEFAULT_MIN_WIDTH = 220;
|
|
213
266
|
var DEFAULT_MAX_WIDTH = 420;
|
|
214
267
|
var DEFAULT_MIN_HEIGHT = 120;
|
|
@@ -233,13 +286,35 @@ function horizontalAnchor(anchor, width, align) {
|
|
|
233
286
|
function computePopoverLayout(anchor, viewport, opts = {}) {
|
|
234
287
|
const margin = opts.margin ?? 12;
|
|
235
288
|
const offset = opts.offset ?? 8;
|
|
289
|
+
const preferredSide = opts.side ?? "top";
|
|
290
|
+
const align = opts.align ?? "start";
|
|
291
|
+
if (preferredSide === "left" || preferredSide === "right") {
|
|
292
|
+
const availableLeft = Math.max(0, anchor.left - margin - offset);
|
|
293
|
+
const availableRight = Math.max(0, viewport.width - anchor.right - margin - offset);
|
|
294
|
+
const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= DEFAULT_MIN_WIDTH ? preferredSide : preferredSide === "left" ? "right" : "left";
|
|
295
|
+
const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
296
|
+
const maxWidth2 = Math.min(
|
|
297
|
+
Math.max(opts.maxWidth ?? DEFAULT_MAX_WIDTH, minWidth2),
|
|
298
|
+
side2 === "left" ? availableLeft : availableRight
|
|
299
|
+
);
|
|
300
|
+
const width2 = minWidth2;
|
|
301
|
+
const left2 = clamp(
|
|
302
|
+
side2 === "left" ? anchor.left - offset - width2 : anchor.right + offset,
|
|
303
|
+
margin,
|
|
304
|
+
viewport.width - margin - width2
|
|
305
|
+
);
|
|
306
|
+
const availableHeight2 = Math.max(80, viewport.height - margin * 2);
|
|
307
|
+
const maxHeight2 = Math.min(opts.maxHeight ?? DEFAULT_MAX_HEIGHT, availableHeight2);
|
|
308
|
+
const desired = clamp(opts.contentHeightHint ?? Math.min(anchor.height, maxHeight2), 40, maxHeight2);
|
|
309
|
+
const anchorTop = align === "center" ? anchor.top + anchor.height / 2 - desired / 2 : align === "end" ? anchor.bottom - desired : anchor.top;
|
|
310
|
+
const top2 = clamp(anchorTop, margin, viewport.height - margin - desired);
|
|
311
|
+
return { side: side2, left: left2, top: top2, width: width2, maxHeight: maxHeight2 };
|
|
312
|
+
}
|
|
236
313
|
const availableWidth = Math.max(80, viewport.width - margin * 2);
|
|
237
314
|
const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
238
315
|
const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
|
|
239
316
|
const maxWidth = Math.min(requestedMaxWidth, availableWidth);
|
|
240
317
|
const minWidth = Math.min(requestedMinWidth, maxWidth);
|
|
241
|
-
const preferredSide = opts.side ?? "top";
|
|
242
|
-
const align = opts.align ?? "start";
|
|
243
318
|
const availableTop = Math.max(0, anchor.top - margin - offset);
|
|
244
319
|
const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
|
|
245
320
|
const availableHeight = Math.max(80, viewport.height - margin * 2);
|
|
@@ -263,8 +338,8 @@ function computePopoverLayout(anchor, viewport, opts = {}) {
|
|
|
263
338
|
return { side, left, top, width, maxHeight };
|
|
264
339
|
}
|
|
265
340
|
var openPanels = /* @__PURE__ */ new Map();
|
|
266
|
-
function rectFromElement(
|
|
267
|
-
const r =
|
|
341
|
+
function rectFromElement(el) {
|
|
342
|
+
const r = el.getBoundingClientRect();
|
|
268
343
|
return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
|
|
269
344
|
}
|
|
270
345
|
function resolveAnchor(state) {
|
|
@@ -317,7 +392,8 @@ function onKeyDown(e) {
|
|
|
317
392
|
function openPopover(panel, options) {
|
|
318
393
|
if (typeof document === "undefined") return;
|
|
319
394
|
if (openPanels.has(panel)) closePopover(panel);
|
|
320
|
-
const { anchor, onClose, ...
|
|
395
|
+
const { anchor, onClose, sizing, ...rest } = options;
|
|
396
|
+
const layoutOpts = sizingToLayout(resolvePanelSizing(panel, sizing), rest);
|
|
321
397
|
const placeholder = document.createComment("popover-anchor");
|
|
322
398
|
panel.parentNode?.insertBefore(placeholder, panel);
|
|
323
399
|
const wasHidden = panel.hidden;
|
|
@@ -369,17 +445,18 @@ function closePopover(panel) {
|
|
|
369
445
|
state.onClose?.();
|
|
370
446
|
}
|
|
371
447
|
|
|
372
|
-
// src/behaviors/
|
|
373
|
-
function
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return node;
|
|
448
|
+
// src/behaviors/events.ts
|
|
449
|
+
function emitIcen(source, type, detail) {
|
|
450
|
+
return source.dispatchEvent(
|
|
451
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
452
|
+
);
|
|
378
453
|
}
|
|
454
|
+
|
|
455
|
+
// src/behaviors/ai-panel.ts
|
|
379
456
|
function buildTodoHead(done, total) {
|
|
380
|
-
const head =
|
|
381
|
-
head.appendChild(
|
|
382
|
-
const bar =
|
|
457
|
+
const head = h("div", "ai-todo-head");
|
|
458
|
+
head.appendChild(h("span", "ai-todo-progress", `${done}/${total}`));
|
|
459
|
+
const bar = h("div", "ai-todo-bar");
|
|
383
460
|
bar.setAttribute("role", "progressbar");
|
|
384
461
|
bar.setAttribute("aria-valuemin", "0");
|
|
385
462
|
bar.setAttribute("aria-valuemax", String(total));
|
|
@@ -392,26 +469,27 @@ function buildTodoHead(done, total) {
|
|
|
392
469
|
return head;
|
|
393
470
|
}
|
|
394
471
|
function buildTodoItem(item, index) {
|
|
395
|
-
const row =
|
|
472
|
+
const row = h("div", `ai-todo-item is-${item.status}`);
|
|
396
473
|
row.dataset.index = String(index);
|
|
397
|
-
row.appendChild(
|
|
398
|
-
row.appendChild(
|
|
399
|
-
row.appendChild(
|
|
474
|
+
row.appendChild(h("span", "ai-item-status"));
|
|
475
|
+
row.appendChild(h("span", "ai-todo-text", item.content));
|
|
476
|
+
row.appendChild(h("span", "ai-todo-active", item.activeForm ?? item.content));
|
|
400
477
|
return row;
|
|
401
478
|
}
|
|
402
|
-
function renderAiTodo(
|
|
403
|
-
if (typeof document === "undefined") return;
|
|
404
|
-
|
|
405
|
-
const root =
|
|
479
|
+
function renderAiTodo(el, items) {
|
|
480
|
+
if (typeof document === "undefined") return el;
|
|
481
|
+
el.textContent = "";
|
|
482
|
+
const root = h("div", "ai-todo");
|
|
406
483
|
const list = Array.isArray(items) ? items : [];
|
|
407
484
|
const done = list.filter((i) => i.status === "done").length;
|
|
408
485
|
root.appendChild(buildTodoHead(done, list.length));
|
|
409
486
|
list.forEach((item, i) => root.appendChild(buildTodoItem(item, i)));
|
|
410
487
|
if (list.length > 0 && done === list.length) root.classList.add("is-complete");
|
|
411
|
-
|
|
488
|
+
el.appendChild(root);
|
|
489
|
+
return el;
|
|
412
490
|
}
|
|
413
|
-
function renderAiUsage(
|
|
414
|
-
if (typeof document === "undefined") return;
|
|
491
|
+
function renderAiUsage(el, usage, opts = {}) {
|
|
492
|
+
if (typeof document === "undefined") return el;
|
|
415
493
|
const u = normalizeUsage(usage);
|
|
416
494
|
const segs = [
|
|
417
495
|
{ key: "input", className: "ai-usage-seg--input", label: "\u8F93\u5165", value: u.input ?? 0 },
|
|
@@ -423,9 +501,9 @@ function renderAiUsage(elm, usage, opts = {}) {
|
|
|
423
501
|
const activeSegs = segs.filter((s) => s.value > 0);
|
|
424
502
|
const sum2 = activeSegs.reduce((acc, s) => acc + s.value, 0);
|
|
425
503
|
const total = u.total ?? sum2;
|
|
426
|
-
|
|
427
|
-
const root =
|
|
428
|
-
const bar =
|
|
504
|
+
el.textContent = "";
|
|
505
|
+
const root = h("div", "ai-usage");
|
|
506
|
+
const bar = h("div", "ai-usage-bar");
|
|
429
507
|
const summary = activeSegs.length > 0 ? activeSegs.map((s) => `${s.label} ${formatTokens(s.value)}`).join("\uFF0C") : "\u6682\u65E0\u7528\u91CF";
|
|
430
508
|
bar.setAttribute("role", "img");
|
|
431
509
|
bar.setAttribute("aria-label", `\u7528\u91CF\uFF1A${summary}`);
|
|
@@ -437,15 +515,15 @@ function renderAiUsage(elm, usage, opts = {}) {
|
|
|
437
515
|
}
|
|
438
516
|
root.appendChild(bar);
|
|
439
517
|
if (activeSegs.length > 0) {
|
|
440
|
-
const legend =
|
|
518
|
+
const legend = h("div", "ai-usage-legend");
|
|
441
519
|
for (const s of activeSegs) {
|
|
442
|
-
const item =
|
|
520
|
+
const item = h("span", "ai-usage-legend-item");
|
|
443
521
|
const dot = document.createElement("i");
|
|
444
522
|
dot.className = `ai-usage-dot ${s.className}`;
|
|
445
523
|
dot.setAttribute("aria-hidden", "true");
|
|
446
524
|
item.appendChild(dot);
|
|
447
|
-
item.appendChild(
|
|
448
|
-
item.appendChild(
|
|
525
|
+
item.appendChild(h("span", "ai-usage-legend-label", s.label));
|
|
526
|
+
item.appendChild(h("span", "ai-usage-legend-value", formatTokens(s.value)));
|
|
449
527
|
legend.appendChild(item);
|
|
450
528
|
}
|
|
451
529
|
root.appendChild(legend);
|
|
@@ -458,17 +536,18 @@ function renderAiUsage(elm, usage, opts = {}) {
|
|
|
458
536
|
if (typeof opts.cost === "number" && Number.isFinite(opts.cost)) {
|
|
459
537
|
totalParts.push(` \xB7 $${opts.cost.toFixed(4)}`);
|
|
460
538
|
}
|
|
461
|
-
root.appendChild(
|
|
462
|
-
|
|
539
|
+
root.appendChild(h("div", "ai-usage-total", totalParts.join("")));
|
|
540
|
+
el.appendChild(root);
|
|
541
|
+
return el;
|
|
463
542
|
}
|
|
464
|
-
function renderAiUsageRing(
|
|
465
|
-
if (typeof document === "undefined") return;
|
|
543
|
+
function renderAiUsageRing(el, usage, opts = {}) {
|
|
544
|
+
if (typeof document === "undefined") return el;
|
|
466
545
|
const u = normalizeUsage(usage);
|
|
467
546
|
const used = u.total ?? (u.input ?? 0) + (u.output ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0) + (u.reasoning ?? 0);
|
|
468
547
|
const hasLimit = typeof opts.total === "number" && opts.total > 0;
|
|
469
548
|
const pct = hasLimit ? Math.min(100, Math.max(0, Math.round(used / opts.total * 100))) : 0;
|
|
470
|
-
|
|
471
|
-
const btn =
|
|
549
|
+
el.textContent = "";
|
|
550
|
+
const btn = h("button", "ai-usage-ring");
|
|
472
551
|
btn.type = "button";
|
|
473
552
|
btn.classList.add(pct >= 85 ? "is-hot" : pct >= 60 ? "is-warn" : "is-ok");
|
|
474
553
|
btn.setAttribute("aria-haspopup", "true");
|
|
@@ -492,7 +571,7 @@ function renderAiUsageRing(elm, usage, opts = {}) {
|
|
|
492
571
|
svg2.appendChild(c);
|
|
493
572
|
}
|
|
494
573
|
btn.appendChild(svg2);
|
|
495
|
-
btn.appendChild(
|
|
574
|
+
btn.appendChild(h("span", "ai-usage-ring-pct", hasLimit ? `${pct}%` : formatTokens(used)));
|
|
496
575
|
let panel = null;
|
|
497
576
|
const closePanel = () => {
|
|
498
577
|
if (!panel) return;
|
|
@@ -506,10 +585,10 @@ function renderAiUsageRing(elm, usage, opts = {}) {
|
|
|
506
585
|
closePanel();
|
|
507
586
|
return;
|
|
508
587
|
}
|
|
509
|
-
const p =
|
|
588
|
+
const p = h("div", "popover ai-usage-popover");
|
|
510
589
|
p.hidden = true;
|
|
511
|
-
p.appendChild(
|
|
512
|
-
const body =
|
|
590
|
+
p.appendChild(h("div", "ai-usage-popover-title", opts.title ?? "\u4E0A\u4E0B\u6587\u7A97\u53E3"));
|
|
591
|
+
const body = h("div", "ai-usage-popover-body");
|
|
513
592
|
renderAiUsage(body, usage, opts);
|
|
514
593
|
p.appendChild(body);
|
|
515
594
|
openPopover(p, {
|
|
@@ -523,7 +602,8 @@ function renderAiUsageRing(elm, usage, opts = {}) {
|
|
|
523
602
|
});
|
|
524
603
|
panel = p;
|
|
525
604
|
});
|
|
526
|
-
|
|
605
|
+
el.appendChild(btn);
|
|
606
|
+
return el;
|
|
527
607
|
}
|
|
528
608
|
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>`;
|
|
529
609
|
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"/>';
|
|
@@ -546,18 +626,18 @@ var ICON_SPARKLES = svgWrap(
|
|
|
546
626
|
// src/behaviors/ai-chat.ts
|
|
547
627
|
var ICON_COPY = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>';
|
|
548
628
|
var ICON_RETRY = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>';
|
|
549
|
-
function createAiStream(
|
|
550
|
-
const reasoning =
|
|
629
|
+
function createAiStream(el) {
|
|
630
|
+
const reasoning = el.closest(".ai-reasoning");
|
|
551
631
|
const startedAt = Date.now();
|
|
552
632
|
let state = "streaming";
|
|
553
|
-
|
|
554
|
-
|
|
633
|
+
el.classList.add("is-streaming");
|
|
634
|
+
el.setAttribute("aria-busy", "true");
|
|
555
635
|
reasoning?.classList.add("is-streaming");
|
|
556
636
|
const settle = (finished) => {
|
|
557
637
|
if (state !== "streaming") return;
|
|
558
638
|
state = finished ? "done" : "cancelled";
|
|
559
|
-
|
|
560
|
-
|
|
639
|
+
el.classList.remove("is-streaming");
|
|
640
|
+
el.removeAttribute("aria-busy");
|
|
561
641
|
if (!reasoning) return;
|
|
562
642
|
reasoning.classList.remove("is-streaming");
|
|
563
643
|
if (!finished) return;
|
|
@@ -571,7 +651,7 @@ function createAiStream(el3) {
|
|
|
571
651
|
return {
|
|
572
652
|
append(text) {
|
|
573
653
|
if (state !== "streaming" || !text) return;
|
|
574
|
-
|
|
654
|
+
el.textContent += text;
|
|
575
655
|
},
|
|
576
656
|
done() {
|
|
577
657
|
settle(true);
|
|
@@ -582,9 +662,9 @@ function createAiStream(el3) {
|
|
|
582
662
|
fail() {
|
|
583
663
|
if (state !== "streaming") return;
|
|
584
664
|
state = "error";
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
665
|
+
el.classList.remove("is-streaming");
|
|
666
|
+
el.removeAttribute("aria-busy");
|
|
667
|
+
el.classList.add("is-error");
|
|
588
668
|
reasoning?.classList.remove("is-streaming");
|
|
589
669
|
}
|
|
590
670
|
};
|
|
@@ -600,15 +680,9 @@ function safeHref(uri) {
|
|
|
600
680
|
if (s.startsWith("javascript:") || s.startsWith("data:text/html")) return void 0;
|
|
601
681
|
return uri;
|
|
602
682
|
}
|
|
603
|
-
function el2(tag, className, text) {
|
|
604
|
-
const node = document.createElement(tag);
|
|
605
|
-
if (className) node.className = className;
|
|
606
|
-
if (text != null) node.textContent = text;
|
|
607
|
-
return node;
|
|
608
|
-
}
|
|
609
683
|
function renderAiContentPart(part) {
|
|
610
684
|
if (part.type === "text") {
|
|
611
|
-
const span =
|
|
685
|
+
const span = h("span", "ai-msg-text");
|
|
612
686
|
span.textContent = part.text;
|
|
613
687
|
if (part.state === "streaming") span.classList.add("is-streaming");
|
|
614
688
|
return span;
|
|
@@ -643,8 +717,8 @@ function renderAiContentPart(part) {
|
|
|
643
717
|
return video;
|
|
644
718
|
}
|
|
645
719
|
if (part.type === "file") {
|
|
646
|
-
const chip =
|
|
647
|
-
chip.appendChild(
|
|
720
|
+
const chip = h("span", "ai-msg-file");
|
|
721
|
+
chip.appendChild(h("span", "ai-msg-file-name", part.filename ?? "\u6587\u4EF6"));
|
|
648
722
|
const href2 = part.url ? safeHref(part.url) : void 0;
|
|
649
723
|
if (href2 && /^(https?:|data:)/i.test(href2)) {
|
|
650
724
|
const a2 = document.createElement("a");
|
|
@@ -665,9 +739,18 @@ function renderAiContentPart(part) {
|
|
|
665
739
|
return a;
|
|
666
740
|
}
|
|
667
741
|
function renderAiMessage(scrollEl, model) {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
742
|
+
if (typeof document === "undefined") {
|
|
743
|
+
return {
|
|
744
|
+
el: scrollEl,
|
|
745
|
+
body: scrollEl,
|
|
746
|
+
setMeta: () => void 0,
|
|
747
|
+
setError: () => void 0,
|
|
748
|
+
stream: () => createAiStream(scrollEl)
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
const row = h("div", `ai-msg ai-msg--${model.role}`);
|
|
752
|
+
const avatar = h("span", "ai-msg-avatar", ROLE_AVATAR[model.role] ?? "");
|
|
753
|
+
const body = h("div", "ai-msg-body");
|
|
671
754
|
row.appendChild(avatar);
|
|
672
755
|
row.appendChild(body);
|
|
673
756
|
const parts = typeof model.content === "string" ? [{ type: "text", text: model.content }] : model.content;
|
|
@@ -675,18 +758,18 @@ function renderAiMessage(scrollEl, model) {
|
|
|
675
758
|
const node = renderAiContentPart(part);
|
|
676
759
|
if (node) body.appendChild(node);
|
|
677
760
|
}
|
|
678
|
-
if (model.model) body.appendChild(
|
|
679
|
-
const metaEl =
|
|
761
|
+
if (model.model) body.appendChild(h("span", "ai-msg-model", model.model));
|
|
762
|
+
const metaEl = h("div", "ai-msg-meta");
|
|
680
763
|
if (model.meta != null) metaEl.textContent = model.meta;
|
|
681
764
|
row.appendChild(metaEl);
|
|
682
|
-
const actions =
|
|
683
|
-
const copyBtn =
|
|
765
|
+
const actions = h("div", "ai-msg-actions");
|
|
766
|
+
const copyBtn = h("button", "ai-msg-action");
|
|
684
767
|
copyBtn.type = "button";
|
|
685
768
|
copyBtn.setAttribute("data-ai-msg-action", "copy");
|
|
686
769
|
copyBtn.setAttribute("aria-label", "\u590D\u5236");
|
|
687
770
|
const copyIcon = svgIcon(ICON_COPY);
|
|
688
771
|
if (copyIcon) copyBtn.appendChild(copyIcon);
|
|
689
|
-
const retryBtn =
|
|
772
|
+
const retryBtn = h("button", "ai-msg-action");
|
|
690
773
|
retryBtn.type = "button";
|
|
691
774
|
retryBtn.setAttribute("data-ai-msg-action", "retry");
|
|
692
775
|
retryBtn.setAttribute("aria-label", "\u91CD\u8BD5");
|
|
@@ -704,13 +787,13 @@ function renderAiMessage(scrollEl, model) {
|
|
|
704
787
|
row.classList.add("ai-msg--error");
|
|
705
788
|
let errEl = row.querySelector(".ai-msg-error-text");
|
|
706
789
|
if (!errEl) {
|
|
707
|
-
errEl =
|
|
790
|
+
errEl = h("div", "ai-msg-error-text");
|
|
708
791
|
body.appendChild(errEl);
|
|
709
792
|
}
|
|
710
793
|
errEl.textContent = message;
|
|
711
794
|
},
|
|
712
795
|
stream() {
|
|
713
|
-
const span =
|
|
796
|
+
const span = h("span", "ai-msg-stream-text");
|
|
714
797
|
body.appendChild(span);
|
|
715
798
|
return createAiStream(span);
|
|
716
799
|
}
|
|
@@ -744,19 +827,35 @@ var ICON_DOC = svgWrap2(
|
|
|
744
827
|
var ICON_BOT2 = svgWrap2(
|
|
745
828
|
'<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"/>'
|
|
746
829
|
);
|
|
747
|
-
var
|
|
748
|
-
file: "\u6587\u4EF6",
|
|
749
|
-
folder: "\u6587\u4EF6\u5939",
|
|
750
|
-
doc: "\u6587\u6863",
|
|
751
|
-
agent: "\u4EE3\u7406"
|
|
752
|
-
};
|
|
753
|
-
var REF_KIND_ICON = {
|
|
754
|
-
file: ICON_FILE3,
|
|
755
|
-
folder: ICON_FOLDER,
|
|
756
|
-
doc: ICON_DOC,
|
|
757
|
-
agent: ICON_BOT2
|
|
830
|
+
var REF_KIND_BUILTIN = {
|
|
831
|
+
file: { label: "\u6587\u4EF6", icon: ICON_FILE3, order: 0 },
|
|
832
|
+
folder: { label: "\u6587\u4EF6\u5939", icon: ICON_FOLDER, order: 1 },
|
|
833
|
+
doc: { label: "\u6587\u6863", icon: ICON_DOC, order: 2 },
|
|
834
|
+
agent: { label: "\u4EE3\u7406", icon: ICON_BOT2, order: 3 }
|
|
758
835
|
};
|
|
759
|
-
var
|
|
836
|
+
var REF_KIND_ORDER_LAST = Number.MAX_SAFE_INTEGER;
|
|
837
|
+
var REF_KIND_REGISTRY = /* @__PURE__ */ new Map();
|
|
838
|
+
function registerRefKind(kind, def) {
|
|
839
|
+
const key = kind.trim();
|
|
840
|
+
if (key) REF_KIND_REGISTRY.set(key, def);
|
|
841
|
+
}
|
|
842
|
+
function getRefKindDef(kind) {
|
|
843
|
+
const reg = REF_KIND_REGISTRY.get(kind);
|
|
844
|
+
const builtin = REF_KIND_BUILTIN[kind];
|
|
845
|
+
return {
|
|
846
|
+
label: reg?.label ?? builtin?.label ?? kind,
|
|
847
|
+
icon: reg?.icon ?? builtin?.icon ?? ICON_DOC,
|
|
848
|
+
order: reg && typeof reg.order === "number" && Number.isFinite(reg.order) ? reg.order : builtin?.order ?? REF_KIND_ORDER_LAST
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
function knownRefKinds(sources) {
|
|
852
|
+
const kinds = new Set(Object.keys(REF_KIND_BUILTIN));
|
|
853
|
+
for (const key of REF_KIND_REGISTRY.keys()) kinds.add(key);
|
|
854
|
+
for (const s of sources) {
|
|
855
|
+
if (typeof s?.kind === "string" && s.kind) kinds.add(s.kind);
|
|
856
|
+
}
|
|
857
|
+
return Array.from(kinds).sort((a, b) => getRefKindDef(a).order - getRefKindDef(b).order);
|
|
858
|
+
}
|
|
760
859
|
var RUNNING = /* @__PURE__ */ new WeakMap();
|
|
761
860
|
var QUEUES = /* @__PURE__ */ new WeakMap();
|
|
762
861
|
var MODELS = /* @__PURE__ */ new WeakMap();
|
|
@@ -765,18 +864,19 @@ var COMMANDS = /* @__PURE__ */ new WeakMap();
|
|
|
765
864
|
var REF_SOURCES = /* @__PURE__ */ new WeakMap();
|
|
766
865
|
var ACTIVE_REFS = /* @__PURE__ */ new WeakMap();
|
|
767
866
|
var USAGE = /* @__PURE__ */ new WeakMap();
|
|
768
|
-
var
|
|
867
|
+
var TODO_STATES = /* @__PURE__ */ new WeakMap();
|
|
769
868
|
var HISTORIES = /* @__PURE__ */ new WeakMap();
|
|
770
869
|
var POPUP = /* @__PURE__ */ new WeakMap();
|
|
771
870
|
var SUBMITS = /* @__PURE__ */ new WeakMap();
|
|
871
|
+
var POPUP_SIZING = /* @__PURE__ */ new WeakMap();
|
|
772
872
|
function isBrowser() {
|
|
773
873
|
return typeof document !== "undefined" && typeof window !== "undefined";
|
|
774
874
|
}
|
|
775
875
|
function emit(target, name, detail) {
|
|
776
|
-
target
|
|
876
|
+
emitIcen(target, name, detail);
|
|
777
877
|
}
|
|
778
|
-
function resolveComposer(
|
|
779
|
-
return
|
|
878
|
+
function resolveComposer(el) {
|
|
879
|
+
return el.closest("[data-ai-composer]");
|
|
780
880
|
}
|
|
781
881
|
function getTextarea(composer) {
|
|
782
882
|
return composer.querySelector(".ai-composer-input");
|
|
@@ -841,8 +941,8 @@ function renderQueueChips(composer) {
|
|
|
841
941
|
queueEl.replaceChildren(...queue.map((text, i) => makeChip(text, true, `\u79FB\u9664\u6392\u961F\u6D88\u606F ${i + 1}`)));
|
|
842
942
|
queueEl.hidden = queue.length === 0;
|
|
843
943
|
}
|
|
844
|
-
function setComposerRunning(
|
|
845
|
-
const composer = resolveComposer(
|
|
944
|
+
function setComposerRunning(el, running) {
|
|
945
|
+
const composer = resolveComposer(el);
|
|
846
946
|
if (!composer) return;
|
|
847
947
|
RUNNING.set(composer, running);
|
|
848
948
|
composer.classList.toggle("is-running", running);
|
|
@@ -1006,6 +1106,13 @@ function settlePopup(s) {
|
|
|
1006
1106
|
s.emptyEl.hidden = visibleOptions(s).length > 0;
|
|
1007
1107
|
setPopupFocus(s, 0);
|
|
1008
1108
|
}
|
|
1109
|
+
function filterPopupOptions(s, q) {
|
|
1110
|
+
const needle = q.trim().toLowerCase();
|
|
1111
|
+
for (const opt of s.options) {
|
|
1112
|
+
opt.hidden = needle !== "" && !(opt.dataset.hay ?? "").includes(needle);
|
|
1113
|
+
}
|
|
1114
|
+
settlePopup(s);
|
|
1115
|
+
}
|
|
1009
1116
|
function popupKeydown(composer, s, e) {
|
|
1010
1117
|
if (e.isComposing) return;
|
|
1011
1118
|
switch (e.key) {
|
|
@@ -1045,13 +1152,16 @@ function closePopup(composer, restoreFocus = true) {
|
|
|
1045
1152
|
}
|
|
1046
1153
|
if (s.mode === "model") {
|
|
1047
1154
|
composer.querySelector("[data-ai-model-open]")?.setAttribute("aria-expanded", "false");
|
|
1048
|
-
}
|
|
1049
|
-
if (s.mode !== "model") {
|
|
1155
|
+
} else {
|
|
1050
1156
|
getTextarea(composer)?.setAttribute("aria-expanded", "false");
|
|
1051
1157
|
}
|
|
1052
1158
|
closePopover(s.panel);
|
|
1053
1159
|
if (restoreFocus && s.returnFocus && s.returnFocus.isConnected) s.returnFocus.focus();
|
|
1054
1160
|
}
|
|
1161
|
+
function resolvePopupSizing(composer, defaults) {
|
|
1162
|
+
const explicit = resolvePanelSizing(composer, POPUP_SIZING.get(composer));
|
|
1163
|
+
return { ...defaults, ...explicit };
|
|
1164
|
+
}
|
|
1055
1165
|
function mountPopup(composer, init) {
|
|
1056
1166
|
closePopup(composer, false);
|
|
1057
1167
|
const panel = document.createElement("div");
|
|
@@ -1076,9 +1186,7 @@ function mountPopup(composer, init) {
|
|
|
1076
1186
|
side: "top",
|
|
1077
1187
|
align: "start",
|
|
1078
1188
|
offset: 8,
|
|
1079
|
-
|
|
1080
|
-
maxWidth: init.maxWidth,
|
|
1081
|
-
maxHeight: init.maxHeight,
|
|
1189
|
+
sizing: resolvePopupSizing(composer, init.sizing),
|
|
1082
1190
|
onClose: () => {
|
|
1083
1191
|
if (POPUP.get(composer) === session) POPUP.delete(composer);
|
|
1084
1192
|
panel.remove();
|
|
@@ -1101,6 +1209,16 @@ function mountPopup(composer, init) {
|
|
|
1101
1209
|
});
|
|
1102
1210
|
return session;
|
|
1103
1211
|
}
|
|
1212
|
+
function appendPopupEmpty(s, text) {
|
|
1213
|
+
s.emptyEl.className = "ai-composer-popup-empty";
|
|
1214
|
+
s.emptyEl.textContent = text;
|
|
1215
|
+
s.emptyEl.hidden = true;
|
|
1216
|
+
s.panel.appendChild(s.emptyEl);
|
|
1217
|
+
}
|
|
1218
|
+
function popupSizingFor(box) {
|
|
1219
|
+
const w = box.getBoundingClientRect().width;
|
|
1220
|
+
return { minWidth: Math.min(Math.max(w, 200), 320), maxWidth: 360, maxHeight: 320 };
|
|
1221
|
+
}
|
|
1104
1222
|
function openModelPopup(composer) {
|
|
1105
1223
|
const cfg = MODELS.get(composer);
|
|
1106
1224
|
if (!cfg) return;
|
|
@@ -1110,9 +1228,7 @@ function openModelPopup(composer) {
|
|
|
1110
1228
|
const session = mountPopup(composer, {
|
|
1111
1229
|
mode: "model",
|
|
1112
1230
|
anchor,
|
|
1113
|
-
minWidth: 220,
|
|
1114
|
-
maxWidth: 320,
|
|
1115
|
-
maxHeight: 340,
|
|
1231
|
+
sizing: { minWidth: 220, maxWidth: 320, maxHeight: 340 },
|
|
1116
1232
|
returnFocus: btn ?? ta,
|
|
1117
1233
|
pick: (opt) => {
|
|
1118
1234
|
const provider = opt.dataset.provider ?? "";
|
|
@@ -1164,6 +1280,7 @@ function openModelPopup(composer) {
|
|
|
1164
1280
|
opt.dataset.providerLabel = p.label;
|
|
1165
1281
|
opt.dataset.model = m.id;
|
|
1166
1282
|
opt.dataset.label = m.label;
|
|
1283
|
+
opt.dataset.hay = `${m.label} ${p.label}`.toLowerCase();
|
|
1167
1284
|
if (typeof m.context === "number") opt.dataset.context = String(m.context);
|
|
1168
1285
|
const selected = cfg.current.provider === p.id && cfg.current.model === m.id;
|
|
1169
1286
|
opt.setAttribute("aria-selected", String(selected));
|
|
@@ -1188,10 +1305,7 @@ function openModelPopup(composer) {
|
|
|
1188
1305
|
s.groups.push(group);
|
|
1189
1306
|
}
|
|
1190
1307
|
s.panel.appendChild(list);
|
|
1191
|
-
s
|
|
1192
|
-
s.emptyEl.textContent = "\u65E0\u5339\u914D\u6A21\u578B";
|
|
1193
|
-
s.emptyEl.hidden = true;
|
|
1194
|
-
s.panel.appendChild(s.emptyEl);
|
|
1308
|
+
appendPopupEmpty(s, "\u65E0\u5339\u914D\u6A21\u578B");
|
|
1195
1309
|
const foot = document.createElement("div");
|
|
1196
1310
|
foot.className = "ai-composer-popup-foot";
|
|
1197
1311
|
const usageCfg = USAGE.get(composer);
|
|
@@ -1211,12 +1325,7 @@ function openModelPopup(composer) {
|
|
|
1211
1325
|
s.keydownHandler = (e) => popupKeydown(composer, s, e);
|
|
1212
1326
|
input.addEventListener("keydown", s.keydownHandler);
|
|
1213
1327
|
input.addEventListener("input", () => {
|
|
1214
|
-
|
|
1215
|
-
for (const opt of s.options) {
|
|
1216
|
-
const hay = `${opt.dataset.label ?? ""} ${opt.dataset.providerLabel ?? ""}`.toLowerCase();
|
|
1217
|
-
opt.hidden = q !== "" && !hay.includes(q);
|
|
1218
|
-
}
|
|
1219
|
-
settlePopup(s);
|
|
1328
|
+
filterPopupOptions(s, input.value);
|
|
1220
1329
|
});
|
|
1221
1330
|
}
|
|
1222
1331
|
});
|
|
@@ -1228,13 +1337,10 @@ function openCommandPopup(composer, token) {
|
|
|
1228
1337
|
if (!commands || commands.length === 0) return;
|
|
1229
1338
|
const box = composer.querySelector(".ai-composer-box") ?? composer;
|
|
1230
1339
|
const ta = getTextarea(composer);
|
|
1231
|
-
const boxRect = box.getBoundingClientRect();
|
|
1232
1340
|
const session = mountPopup(composer, {
|
|
1233
1341
|
mode: "command",
|
|
1234
1342
|
anchor: box,
|
|
1235
|
-
|
|
1236
|
-
maxWidth: 360,
|
|
1237
|
-
maxHeight: 320,
|
|
1343
|
+
sizing: popupSizingFor(box),
|
|
1238
1344
|
returnFocus: ta,
|
|
1239
1345
|
pick: (opt) => {
|
|
1240
1346
|
const name = opt.dataset.name ?? "";
|
|
@@ -1286,25 +1392,15 @@ function openCommandPopup(composer, token) {
|
|
|
1286
1392
|
list.appendChild(group);
|
|
1287
1393
|
s.groups.push(group);
|
|
1288
1394
|
s.panel.appendChild(list);
|
|
1289
|
-
s
|
|
1290
|
-
s.emptyEl.textContent = "\u65E0\u5339\u914D\u547D\u4EE4";
|
|
1291
|
-
s.emptyEl.hidden = true;
|
|
1292
|
-
s.panel.appendChild(s.emptyEl);
|
|
1395
|
+
appendPopupEmpty(s, "\u65E0\u5339\u914D\u547D\u4EE4");
|
|
1293
1396
|
}
|
|
1294
1397
|
});
|
|
1295
1398
|
ta?.setAttribute("aria-expanded", "true");
|
|
1296
|
-
|
|
1399
|
+
filterPopupOptions(session, token);
|
|
1297
1400
|
}
|
|
1298
1401
|
function commandToken(value) {
|
|
1299
1402
|
return value.slice(1).split(/\s/, 1)[0] ?? "";
|
|
1300
1403
|
}
|
|
1301
|
-
function filterCommandOptions(s, token) {
|
|
1302
|
-
const q = token.trim().toLowerCase();
|
|
1303
|
-
for (const opt of s.options) {
|
|
1304
|
-
opt.hidden = q !== "" && !(opt.dataset.hay ?? "").includes(q);
|
|
1305
|
-
}
|
|
1306
|
-
settlePopup(s);
|
|
1307
|
-
}
|
|
1308
1404
|
function updateCommandPopup(composer, s) {
|
|
1309
1405
|
const ta = getTextarea(composer);
|
|
1310
1406
|
const value = ta?.value ?? "";
|
|
@@ -1313,20 +1409,17 @@ function updateCommandPopup(composer, s) {
|
|
|
1313
1409
|
maybeOpenTriggerPopups(composer);
|
|
1314
1410
|
return;
|
|
1315
1411
|
}
|
|
1316
|
-
|
|
1412
|
+
filterPopupOptions(s, commandToken(value));
|
|
1317
1413
|
}
|
|
1318
1414
|
function openRefPopup(composer, range, filter) {
|
|
1319
1415
|
const sources = REF_SOURCES.get(composer);
|
|
1320
1416
|
if (!sources || sources.length === 0) return;
|
|
1321
1417
|
const box = composer.querySelector(".ai-composer-box") ?? composer;
|
|
1322
1418
|
const ta = getTextarea(composer);
|
|
1323
|
-
const boxRect = box.getBoundingClientRect();
|
|
1324
1419
|
const session = mountPopup(composer, {
|
|
1325
1420
|
mode: "ref",
|
|
1326
1421
|
anchor: box,
|
|
1327
|
-
|
|
1328
|
-
maxWidth: 360,
|
|
1329
|
-
maxHeight: 320,
|
|
1422
|
+
sizing: popupSizingFor(box),
|
|
1330
1423
|
returnFocus: ta,
|
|
1331
1424
|
refRange: range,
|
|
1332
1425
|
pick: (opt) => {
|
|
@@ -1349,7 +1442,8 @@ function openRefPopup(composer, range, filter) {
|
|
|
1349
1442
|
list.className = "ai-composer-popup-list";
|
|
1350
1443
|
list.setAttribute("role", "listbox");
|
|
1351
1444
|
list.setAttribute("aria-label", "\u5F15\u7528");
|
|
1352
|
-
for (const kind of
|
|
1445
|
+
for (const kind of knownRefKinds(sources)) {
|
|
1446
|
+
const def = getRefKindDef(kind);
|
|
1353
1447
|
const items = sources.filter((r) => r.kind === kind);
|
|
1354
1448
|
if (items.length === 0) continue;
|
|
1355
1449
|
const group = document.createElement("div");
|
|
@@ -1357,7 +1451,7 @@ function openRefPopup(composer, range, filter) {
|
|
|
1357
1451
|
group.dataset.kind = kind;
|
|
1358
1452
|
group.appendChild(Object.assign(document.createElement("div"), {
|
|
1359
1453
|
className: "ai-composer-popup-group-label",
|
|
1360
|
-
textContent:
|
|
1454
|
+
textContent: def.label
|
|
1361
1455
|
}));
|
|
1362
1456
|
for (const r of items) {
|
|
1363
1457
|
const opt = document.createElement("button");
|
|
@@ -1368,7 +1462,7 @@ function openRefPopup(composer, range, filter) {
|
|
|
1368
1462
|
opt.dataset.hay = `${r.label} ${r.sub ?? ""}`.toLowerCase();
|
|
1369
1463
|
const icon = document.createElement("span");
|
|
1370
1464
|
icon.className = "ai-composer-popup-option-icon";
|
|
1371
|
-
const svg2 = svgIcon(
|
|
1465
|
+
const svg2 = svgIcon(def.icon);
|
|
1372
1466
|
if (svg2) icon.appendChild(svg2);
|
|
1373
1467
|
const label = document.createElement("span");
|
|
1374
1468
|
label.className = "ai-composer-popup-option-label";
|
|
@@ -1387,21 +1481,11 @@ function openRefPopup(composer, range, filter) {
|
|
|
1387
1481
|
s.groups.push(group);
|
|
1388
1482
|
}
|
|
1389
1483
|
s.panel.appendChild(list);
|
|
1390
|
-
s
|
|
1391
|
-
s.emptyEl.textContent = "\u65E0\u5339\u914D\u5F15\u7528";
|
|
1392
|
-
s.emptyEl.hidden = true;
|
|
1393
|
-
s.panel.appendChild(s.emptyEl);
|
|
1484
|
+
appendPopupEmpty(s, "\u65E0\u5339\u914D\u5F15\u7528");
|
|
1394
1485
|
}
|
|
1395
1486
|
});
|
|
1396
1487
|
ta?.setAttribute("aria-expanded", "true");
|
|
1397
|
-
|
|
1398
|
-
}
|
|
1399
|
-
function filterRefOptions(s, filter) {
|
|
1400
|
-
const q = filter.trim().toLowerCase();
|
|
1401
|
-
for (const opt of s.options) {
|
|
1402
|
-
opt.hidden = q !== "" && !(opt.dataset.hay ?? "").includes(q);
|
|
1403
|
-
}
|
|
1404
|
-
settlePopup(s);
|
|
1488
|
+
filterPopupOptions(session, filter);
|
|
1405
1489
|
}
|
|
1406
1490
|
function updateRefPopup(composer, s) {
|
|
1407
1491
|
const ta = getTextarea(composer);
|
|
@@ -1412,7 +1496,7 @@ function updateRefPopup(composer, s) {
|
|
|
1412
1496
|
return;
|
|
1413
1497
|
}
|
|
1414
1498
|
s.refRange = { start: t.start, end: t.end };
|
|
1415
|
-
|
|
1499
|
+
filterPopupOptions(s, t.filter);
|
|
1416
1500
|
}
|
|
1417
1501
|
function findRefToken(ta) {
|
|
1418
1502
|
const value = ta.value;
|
|
@@ -1492,21 +1576,21 @@ function removeActiveRef(composer, source) {
|
|
|
1492
1576
|
emit(composer, "icen:ai-ref", { action: "remove", ref: source });
|
|
1493
1577
|
}
|
|
1494
1578
|
function pushHistory(composer, text) {
|
|
1495
|
-
const
|
|
1496
|
-
if (
|
|
1497
|
-
|
|
1498
|
-
if (
|
|
1579
|
+
const h2 = HISTORIES.get(composer) ?? { items: [], index: -1 };
|
|
1580
|
+
if (h2.items[h2.items.length - 1] !== text) {
|
|
1581
|
+
h2.items.push(text);
|
|
1582
|
+
if (h2.items.length > HISTORY_LIMIT) h2.items.shift();
|
|
1499
1583
|
}
|
|
1500
|
-
|
|
1501
|
-
HISTORIES.set(composer,
|
|
1584
|
+
h2.index = -1;
|
|
1585
|
+
HISTORIES.set(composer, h2);
|
|
1502
1586
|
}
|
|
1503
1587
|
function setupComposer(composer) {
|
|
1504
|
-
const
|
|
1505
|
-
if (
|
|
1506
|
-
|
|
1588
|
+
const el = composer;
|
|
1589
|
+
if (el.__icenAiComposerInit) return void 0;
|
|
1590
|
+
el.__icenAiComposerInit = true;
|
|
1507
1591
|
const ta = getTextarea(composer);
|
|
1508
1592
|
if (!ta) {
|
|
1509
|
-
|
|
1593
|
+
el.__icenAiComposerInit = false;
|
|
1510
1594
|
return void 0;
|
|
1511
1595
|
}
|
|
1512
1596
|
const queueEl = composer.querySelector(".ai-composer-queue");
|
|
@@ -1541,7 +1625,7 @@ function setupComposer(composer) {
|
|
|
1541
1625
|
const queue = QUEUES.get(composer) ?? [];
|
|
1542
1626
|
if (queue.length === 0) return;
|
|
1543
1627
|
const index = queue.length - 1;
|
|
1544
|
-
const text = queue.pop();
|
|
1628
|
+
const text = queue.pop() ?? "";
|
|
1545
1629
|
QUEUES.set(composer, queue);
|
|
1546
1630
|
renderQueue();
|
|
1547
1631
|
ta.value = text;
|
|
@@ -1549,20 +1633,20 @@ function setupComposer(composer) {
|
|
|
1549
1633
|
emit(composer, "icen:ai-dequeue", { index });
|
|
1550
1634
|
};
|
|
1551
1635
|
const browseHistory = (delta) => {
|
|
1552
|
-
const
|
|
1553
|
-
if (!
|
|
1636
|
+
const h2 = HISTORIES.get(composer);
|
|
1637
|
+
if (!h2 || h2.items.length === 0) return;
|
|
1554
1638
|
if (delta === -1) {
|
|
1555
|
-
|
|
1639
|
+
h2.index = h2.index === -1 ? h2.items.length - 1 : Math.max(0, h2.index - 1);
|
|
1556
1640
|
} else {
|
|
1557
|
-
|
|
1558
|
-
if (
|
|
1559
|
-
|
|
1641
|
+
h2.index += 1;
|
|
1642
|
+
if (h2.index >= h2.items.length) {
|
|
1643
|
+
h2.index = -1;
|
|
1560
1644
|
ta.value = "";
|
|
1561
1645
|
autosizeTa(ta);
|
|
1562
1646
|
return;
|
|
1563
1647
|
}
|
|
1564
1648
|
}
|
|
1565
|
-
ta.value =
|
|
1649
|
+
ta.value = h2.items[h2.index] ?? "";
|
|
1566
1650
|
autosizeTa(ta);
|
|
1567
1651
|
};
|
|
1568
1652
|
const onKeydown = (e) => {
|
|
@@ -1578,15 +1662,15 @@ function setupComposer(composer) {
|
|
|
1578
1662
|
return;
|
|
1579
1663
|
}
|
|
1580
1664
|
if (e.key === "ArrowUp") {
|
|
1581
|
-
const
|
|
1582
|
-
const browsing = (
|
|
1665
|
+
const h2 = HISTORIES.get(composer);
|
|
1666
|
+
const browsing = (h2?.index ?? -1) >= 0;
|
|
1583
1667
|
if (ta.value === "") {
|
|
1584
1668
|
if (isRunning() && (QUEUES.get(composer) ?? []).length > 0) {
|
|
1585
1669
|
e.preventDefault();
|
|
1586
1670
|
takeBackLastQueued();
|
|
1587
1671
|
return;
|
|
1588
1672
|
}
|
|
1589
|
-
if (
|
|
1673
|
+
if (h2 && h2.items.length > 0) {
|
|
1590
1674
|
e.preventDefault();
|
|
1591
1675
|
browseHistory(-1);
|
|
1592
1676
|
}
|
|
@@ -1597,8 +1681,8 @@ function setupComposer(composer) {
|
|
|
1597
1681
|
return;
|
|
1598
1682
|
}
|
|
1599
1683
|
if (e.key === "ArrowDown") {
|
|
1600
|
-
const
|
|
1601
|
-
if (
|
|
1684
|
+
const h2 = HISTORIES.get(composer);
|
|
1685
|
+
if (h2 && h2.index >= 0) {
|
|
1602
1686
|
e.preventDefault();
|
|
1603
1687
|
browseHistory(1);
|
|
1604
1688
|
}
|
|
@@ -1606,8 +1690,8 @@ function setupComposer(composer) {
|
|
|
1606
1690
|
};
|
|
1607
1691
|
const onInput = () => {
|
|
1608
1692
|
autosizeTa(ta);
|
|
1609
|
-
const
|
|
1610
|
-
if (
|
|
1693
|
+
const h2 = HISTORIES.get(composer);
|
|
1694
|
+
if (h2 && h2.index >= 0 && ta.value !== h2.items[h2.index]) h2.index = -1;
|
|
1611
1695
|
const session = POPUP.get(composer);
|
|
1612
1696
|
if (session) {
|
|
1613
1697
|
if (session.mode === "command") updateCommandPopup(composer, session);
|
|
@@ -1735,7 +1819,7 @@ function setupComposer(composer) {
|
|
|
1735
1819
|
composer.querySelector("[data-ai-attach]")?.removeEventListener("click", onAttachClick);
|
|
1736
1820
|
fileInput.removeEventListener("change", onFileChange);
|
|
1737
1821
|
fileInput.remove();
|
|
1738
|
-
|
|
1822
|
+
el.__icenAiComposerInit = false;
|
|
1739
1823
|
};
|
|
1740
1824
|
}
|
|
1741
1825
|
function initAiComposer(root) {
|
|
@@ -1757,9 +1841,9 @@ function initAiComposer(root) {
|
|
|
1757
1841
|
cleanups.length = 0;
|
|
1758
1842
|
};
|
|
1759
1843
|
}
|
|
1760
|
-
function setComposerModels(
|
|
1844
|
+
function setComposerModels(el, providers, current) {
|
|
1761
1845
|
if (!isBrowser()) return;
|
|
1762
|
-
const composer = resolveComposer(
|
|
1846
|
+
const composer = resolveComposer(el);
|
|
1763
1847
|
if (!composer) return;
|
|
1764
1848
|
const list = (Array.isArray(providers) ? providers : []).filter(
|
|
1765
1849
|
(p) => p && Array.isArray(p.models) && p.models.length > 0
|
|
@@ -1786,9 +1870,9 @@ function setComposerModels(el3, providers, current) {
|
|
|
1786
1870
|
updateModelButton(composer);
|
|
1787
1871
|
refreshUsageRing(composer);
|
|
1788
1872
|
}
|
|
1789
|
-
function setComposerCommands(
|
|
1873
|
+
function setComposerCommands(el, commands) {
|
|
1790
1874
|
if (!isBrowser()) return;
|
|
1791
|
-
const composer = resolveComposer(
|
|
1875
|
+
const composer = resolveComposer(el);
|
|
1792
1876
|
if (!composer) return;
|
|
1793
1877
|
const list = (Array.isArray(commands) ? commands : []).filter((c) => c && typeof c.name === "string" && c.name);
|
|
1794
1878
|
if (list.length === 0) {
|
|
@@ -1799,9 +1883,9 @@ function setComposerCommands(el3, commands) {
|
|
|
1799
1883
|
}
|
|
1800
1884
|
COMMANDS.set(composer, list);
|
|
1801
1885
|
}
|
|
1802
|
-
function setComposerRefs(
|
|
1886
|
+
function setComposerRefs(el, sources) {
|
|
1803
1887
|
if (!isBrowser()) return;
|
|
1804
|
-
const composer = resolveComposer(
|
|
1888
|
+
const composer = resolveComposer(el);
|
|
1805
1889
|
if (!composer) return;
|
|
1806
1890
|
const list = (Array.isArray(sources) ? sources : []).filter((s) => s && typeof s.label === "string" && s.label);
|
|
1807
1891
|
REF_SOURCES.set(composer, list);
|
|
@@ -1812,9 +1896,9 @@ function setComposerRefs(el3, sources) {
|
|
|
1812
1896
|
ensureRefsRow(composer);
|
|
1813
1897
|
renderRefs(composer);
|
|
1814
1898
|
}
|
|
1815
|
-
function setComposerUsage(
|
|
1899
|
+
function setComposerUsage(el, usage, opts = {}) {
|
|
1816
1900
|
if (!isBrowser()) return;
|
|
1817
|
-
const composer = resolveComposer(
|
|
1901
|
+
const composer = resolveComposer(el);
|
|
1818
1902
|
if (!composer) return;
|
|
1819
1903
|
if (!usage) {
|
|
1820
1904
|
USAGE.delete(composer);
|
|
@@ -1827,24 +1911,24 @@ function setComposerUsage(el3, usage, opts = {}) {
|
|
|
1827
1911
|
syncToolbar(composer);
|
|
1828
1912
|
refreshUsageRing(composer);
|
|
1829
1913
|
}
|
|
1830
|
-
function setComposerTodo(
|
|
1914
|
+
function setComposerTodo(el, items) {
|
|
1831
1915
|
if (!isBrowser()) return;
|
|
1832
|
-
const composer = resolveComposer(
|
|
1916
|
+
const composer = resolveComposer(el);
|
|
1833
1917
|
if (!composer) return;
|
|
1834
1918
|
let chip = composer.querySelector(".ai-composer-todo");
|
|
1835
1919
|
if (!items || items.length === 0) {
|
|
1836
|
-
|
|
1920
|
+
TODO_STATES.delete(composer);
|
|
1837
1921
|
chip?.remove();
|
|
1838
1922
|
return;
|
|
1839
1923
|
}
|
|
1840
|
-
|
|
1924
|
+
TODO_STATES.set(composer, items);
|
|
1841
1925
|
if (!chip) {
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
const cur =
|
|
1926
|
+
const btn = document.createElement("button");
|
|
1927
|
+
btn.type = "button";
|
|
1928
|
+
btn.className = "ai-composer-todo";
|
|
1929
|
+
btn.setAttribute("aria-haspopup", "true");
|
|
1930
|
+
btn.addEventListener("click", () => {
|
|
1931
|
+
const cur = TODO_STATES.get(composer);
|
|
1848
1932
|
if (!cur) return;
|
|
1849
1933
|
const p = document.createElement("div");
|
|
1850
1934
|
p.className = "popover ai-composer-todo-pop";
|
|
@@ -1857,12 +1941,13 @@ function setComposerTodo(el3, items) {
|
|
|
1857
1941
|
renderAiTodo(body, cur);
|
|
1858
1942
|
p.append(title, body);
|
|
1859
1943
|
openPopover(p, {
|
|
1860
|
-
anchor:
|
|
1944
|
+
anchor: btn,
|
|
1861
1945
|
side: "top",
|
|
1862
1946
|
align: "end",
|
|
1863
1947
|
onClose: () => p.remove()
|
|
1864
1948
|
});
|
|
1865
1949
|
});
|
|
1950
|
+
chip = btn;
|
|
1866
1951
|
const icon = svgIcon(
|
|
1867
1952
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m3 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"/></svg>'
|
|
1868
1953
|
);
|
|
@@ -1885,21 +1970,14 @@ function setComposerTodo(el3, items) {
|
|
|
1885
1970
|
if (label) label.textContent = `${done}/${items.length}`;
|
|
1886
1971
|
}
|
|
1887
1972
|
var BOUND = /* @__PURE__ */ new WeakSet();
|
|
1888
|
-
|
|
1889
|
-
function accumulateUsage(acc, add) {
|
|
1890
|
-
if (!add) return;
|
|
1891
|
-
for (const k of USAGE_KEYS) {
|
|
1892
|
-
const v = add[k];
|
|
1893
|
-
if (typeof v === "number" && Number.isFinite(v)) acc[k] = (acc[k] ?? 0) + v;
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
function bindComposer(el3, opts = {}) {
|
|
1973
|
+
function bindComposer(el, opts = {}) {
|
|
1897
1974
|
const noop = () => void 0;
|
|
1898
1975
|
if (!isBrowser()) return { unbind: noop };
|
|
1899
|
-
const composer = resolveComposer(
|
|
1976
|
+
const composer = resolveComposer(el);
|
|
1900
1977
|
if (!composer) return { unbind: noop };
|
|
1901
1978
|
if (BOUND.has(composer)) return { unbind: noop };
|
|
1902
1979
|
BOUND.add(composer);
|
|
1980
|
+
if (opts.popoverSizing) POPUP_SIZING.set(composer, opts.popoverSizing);
|
|
1903
1981
|
const disposers = [];
|
|
1904
1982
|
const usageCfg = opts.usage;
|
|
1905
1983
|
const manageRunning = opts.running ?? true;
|
|
@@ -1922,7 +2000,7 @@ function bindComposer(el3, opts = {}) {
|
|
|
1922
2000
|
total: contextEstimate(u)
|
|
1923
2001
|
};
|
|
1924
2002
|
} else if (usageCfg.from === "billing") {
|
|
1925
|
-
|
|
2003
|
+
billing = addUsage(billing, detail.usage);
|
|
1926
2004
|
usage = billing;
|
|
1927
2005
|
} else {
|
|
1928
2006
|
usage = usageCfg.from.summary();
|
|
@@ -1935,6 +2013,9 @@ function bindComposer(el3, opts = {}) {
|
|
|
1935
2013
|
document.addEventListener("icen:ai-done", onDone);
|
|
1936
2014
|
disposers.push(() => document.removeEventListener("icen:ai-done", onDone));
|
|
1937
2015
|
}
|
|
2016
|
+
if (opts.client && !opts.messages) {
|
|
2017
|
+
console.warn("[ai-composer] client \u6A21\u5F0F\u9700\u8981 opts.messages\uFF0C\u5DF2\u964D\u7EA7\u4E3A\u7EAF\u72B6\u6001\u7ED1\u5B9A");
|
|
2018
|
+
}
|
|
1938
2019
|
if (opts.client && opts.messages) {
|
|
1939
2020
|
const client = opts.client;
|
|
1940
2021
|
const messages = opts.messages;
|
|
@@ -1975,7 +2056,7 @@ function bindComposer(el3, opts = {}) {
|
|
|
1975
2056
|
setComposerRunning(composer, false);
|
|
1976
2057
|
const queue = QUEUES.get(composer) ?? [];
|
|
1977
2058
|
if (queue.length > 0) {
|
|
1978
|
-
const next = queue.shift();
|
|
2059
|
+
const next = queue.shift() ?? "";
|
|
1979
2060
|
QUEUES.set(composer, queue);
|
|
1980
2061
|
renderQueueChips(composer);
|
|
1981
2062
|
emit(composer, "icen:ai-dequeue", { index: 0 });
|
|
@@ -2023,6 +2104,7 @@ function bindComposer(el3, opts = {}) {
|
|
|
2023
2104
|
export {
|
|
2024
2105
|
bindComposer,
|
|
2025
2106
|
initAiComposer,
|
|
2107
|
+
registerRefKind,
|
|
2026
2108
|
setComposerCommands,
|
|
2027
2109
|
setComposerModels,
|
|
2028
2110
|
setComposerRefs,
|