@icen.ai/ui 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +49 -6
- package/README.md +12 -9
- package/dist/behaviors/ai-branch.d.mts +19 -0
- package/dist/behaviors/ai-branch.mjs +217 -0
- package/dist/behaviors/ai-chat.d.mts +8 -6
- package/dist/behaviors/ai-chat.mjs +68 -8
- package/dist/behaviors/ai-composer.mjs +108 -20
- package/dist/behaviors/ai-feedback.d.mts +26 -0
- package/dist/behaviors/ai-feedback.mjs +303 -0
- package/dist/behaviors/ai-panel.d.mts +15 -1
- package/dist/behaviors/ai-panel.mjs +26 -0
- package/dist/behaviors/ai-threads.d.mts +38 -0
- package/dist/behaviors/ai-threads.mjs +319 -0
- package/dist/behaviors/ai-tool.mjs +5 -0
- package/dist/behaviors/ai-tools.mjs +5 -0
- package/dist/behaviors/charts.d.mts +103 -1
- package/dist/behaviors/charts.mjs +795 -0
- package/dist/behaviors/events.d.mts +193 -0
- package/dist/behaviors/kb-access.d.mts +34 -0
- package/dist/behaviors/kb-access.mjs +569 -0
- package/dist/behaviors/kb-acl.d.mts +50 -0
- package/dist/behaviors/kb-acl.mjs +405 -0
- package/dist/behaviors/kb-answer.d.mts +38 -0
- package/dist/behaviors/kb-answer.mjs +2055 -0
- package/dist/behaviors/kb-audit.d.mts +23 -0
- package/dist/behaviors/kb-audit.mjs +375 -0
- package/dist/behaviors/kb-canvas.d.mts +44 -0
- package/dist/behaviors/kb-canvas.mjs +589 -0
- package/dist/behaviors/kb-chain.d.mts +15 -0
- package/dist/behaviors/kb-chain.mjs +270 -0
- package/dist/behaviors/kb-checkpoint.d.mts +16 -0
- package/dist/behaviors/kb-checkpoint.mjs +342 -0
- package/dist/behaviors/kb-chunks.d.mts +31 -0
- package/dist/behaviors/kb-chunks.mjs +475 -0
- package/dist/behaviors/kb-citation.d.mts +38 -0
- package/dist/behaviors/kb-citation.mjs +677 -0
- package/dist/behaviors/kb-clarify.d.mts +20 -0
- package/dist/behaviors/kb-clarify.mjs +248 -0
- package/dist/behaviors/kb-conflict.d.mts +22 -0
- package/dist/behaviors/kb-conflict.mjs +284 -0
- package/dist/behaviors/kb-connector.d.mts +17 -0
- package/dist/behaviors/kb-connector.mjs +375 -0
- package/dist/behaviors/kb-core.d.mts +529 -0
- package/dist/behaviors/kb-core.mjs +890 -0
- package/dist/behaviors/kb-eval.d.mts +22 -0
- package/dist/behaviors/kb-eval.mjs +408 -0
- package/dist/behaviors/kb-explain.d.mts +11 -0
- package/dist/behaviors/kb-explain.mjs +243 -0
- package/dist/behaviors/kb-filter.d.mts +31 -0
- package/dist/behaviors/kb-filter.mjs +587 -0
- package/dist/behaviors/kb-gap.d.mts +27 -0
- package/dist/behaviors/kb-gap.mjs +273 -0
- package/dist/behaviors/kb-hittest.d.mts +34 -0
- package/dist/behaviors/kb-hittest.mjs +409 -0
- package/dist/behaviors/kb-metadata.d.mts +29 -0
- package/dist/behaviors/kb-metadata.mjs +326 -0
- package/dist/behaviors/kb-passage.d.mts +41 -0
- package/dist/behaviors/kb-passage.mjs +529 -0
- package/dist/behaviors/kb-pipeline.d.mts +18 -0
- package/dist/behaviors/kb-pipeline.mjs +381 -0
- package/dist/behaviors/kb-qa.d.mts +26 -0
- package/dist/behaviors/kb-qa.mjs +332 -0
- package/dist/behaviors/kb-rerank.d.mts +20 -0
- package/dist/behaviors/kb-rerank.mjs +315 -0
- package/dist/behaviors/kb-retrieval.d.mts +49 -0
- package/dist/behaviors/kb-retrieval.mjs +746 -0
- package/dist/behaviors/kb-review.d.mts +44 -0
- package/dist/behaviors/kb-review.mjs +451 -0
- package/dist/behaviors/kb-sandbox.d.mts +33 -0
- package/dist/behaviors/kb-sandbox.mjs +325 -0
- package/dist/behaviors/kb-segment.d.mts +32 -0
- package/dist/behaviors/kb-segment.mjs +499 -0
- package/dist/behaviors/kb-sources.d.mts +27 -0
- package/dist/behaviors/kb-sources.mjs +373 -0
- package/dist/behaviors/kb-sql.d.mts +43 -0
- package/dist/behaviors/kb-sql.mjs +948 -0
- package/dist/behaviors/kb-trace.d.mts +16 -0
- package/dist/behaviors/kb-trace.mjs +414 -0
- package/dist/behaviors/kb-visibility.d.mts +44 -0
- package/dist/behaviors/kb-visibility.mjs +403 -0
- package/dist/behaviors/kb-who-can.d.mts +32 -0
- package/dist/behaviors/kb-who-can.mjs +379 -0
- package/dist/components/ai-branch.mjs +3 -0
- package/dist/components/ai-chat.css +295 -0
- package/dist/components/ai-feedback.mjs +3 -0
- package/dist/components/ai-panel.css +217 -0
- package/dist/components/ai-threads.mjs +3 -0
- package/dist/components/chart-graph.css +57 -0
- package/dist/components/chart-graph.mjs +4 -0
- package/dist/components/chart-map.mjs +3 -0
- package/dist/components/chart-scatter.mjs +1 -1
- package/dist/components/charts.css +32 -0
- package/dist/components/charts.mjs +1 -0
- package/dist/components/kb-access.mjs +4 -0
- package/dist/components/kb-acl.mjs +4 -0
- package/dist/components/kb-agent.css +481 -0
- package/dist/components/kb-answer.mjs +4 -0
- package/dist/components/kb-audit.mjs +4 -0
- package/dist/components/kb-canvas.mjs +4 -0
- package/dist/components/kb-chain.mjs +4 -0
- package/dist/components/kb-checkpoint.mjs +4 -0
- package/dist/components/kb-chunks.mjs +4 -0
- package/dist/components/kb-citation.mjs +4 -0
- package/dist/components/kb-clarify.mjs +4 -0
- package/dist/components/kb-conflict.mjs +4 -0
- package/dist/components/kb-connector.mjs +4 -0
- package/dist/components/kb-data.css +649 -0
- package/dist/components/kb-eval.mjs +4 -0
- package/dist/components/kb-explain.mjs +4 -0
- package/dist/components/kb-filter.mjs +4 -0
- package/dist/components/kb-gap.mjs +4 -0
- package/dist/components/kb-ground.css +566 -0
- package/dist/components/kb-hittest.mjs +4 -0
- package/dist/components/kb-ingest.css +898 -0
- package/dist/components/kb-metadata.mjs +4 -0
- package/dist/components/kb-ops.css +548 -0
- package/dist/components/kb-passage.mjs +4 -0
- package/dist/components/kb-perm.css +444 -0
- package/dist/components/kb-pipeline.mjs +4 -0
- package/dist/components/kb-qa.mjs +4 -0
- package/dist/components/kb-rerank.mjs +4 -0
- package/dist/components/kb-retrieval.mjs +4 -0
- package/dist/components/kb-review.mjs +4 -0
- package/dist/components/kb-sandbox.mjs +4 -0
- package/dist/components/kb-search.css +940 -0
- package/dist/components/kb-segment.mjs +4 -0
- package/dist/components/kb-sources.mjs +4 -0
- package/dist/components/kb-sql.mjs +4 -0
- package/dist/components/kb-trace.mjs +4 -0
- package/dist/components/kb-visibility.mjs +4 -0
- package/dist/components/kb-who-can.mjs +4 -0
- package/dist/components/kb.css +252 -0
- package/dist/components/kb.mjs +9 -0
- package/dist/index.d.mts +37 -2
- package/dist/index.mjs +8555 -205
- package/dist/registry.json +325 -3
- package/dist/ui.css +5451 -45
- package/docs/research/2026-10-03-ai-kb-components.md +392 -0
- package/docs/research/2026-10-03-kb-permissions.md +992 -0
- package/docs/spec/kb-family.md +443 -0
- package/package.json +2 -2
|
@@ -468,10 +468,16 @@ function buildTodoHead(done, total) {
|
|
|
468
468
|
head.appendChild(bar);
|
|
469
469
|
return head;
|
|
470
470
|
}
|
|
471
|
+
var ICON_TODO_CHECK = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m3.5 12.5 5 5L20.5 6" pathLength="1"/></svg>';
|
|
471
472
|
function buildTodoItem(item, index) {
|
|
472
473
|
const row = h("div", `ai-todo-item is-${item.status}`);
|
|
473
474
|
row.dataset.index = String(index);
|
|
474
475
|
row.appendChild(h("span", "ai-item-status"));
|
|
476
|
+
const check = h("span", "ai-todo-check is-anim");
|
|
477
|
+
check.setAttribute("aria-hidden", "true");
|
|
478
|
+
const checkSvg = svgIcon(ICON_TODO_CHECK);
|
|
479
|
+
if (checkSvg) check.appendChild(checkSvg);
|
|
480
|
+
row.appendChild(check);
|
|
475
481
|
row.appendChild(h("span", "ai-todo-text", item.content));
|
|
476
482
|
row.appendChild(h("span", "ai-todo-active", item.activeForm ?? item.content));
|
|
477
483
|
return row;
|
|
@@ -488,6 +494,18 @@ function renderAiTodo(el, items) {
|
|
|
488
494
|
el.appendChild(root);
|
|
489
495
|
return el;
|
|
490
496
|
}
|
|
497
|
+
function metricField(src, key) {
|
|
498
|
+
return typeof src === "object" && src !== null ? src[key] : void 0;
|
|
499
|
+
}
|
|
500
|
+
function metricNum(v) {
|
|
501
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
502
|
+
}
|
|
503
|
+
function metricStr(v) {
|
|
504
|
+
return typeof v === "string" && v ? v : void 0;
|
|
505
|
+
}
|
|
506
|
+
function trimNum(v) {
|
|
507
|
+
return String(Math.round(v * 10) / 10);
|
|
508
|
+
}
|
|
491
509
|
function renderAiUsage(el, usage, opts = {}) {
|
|
492
510
|
if (typeof document === "undefined") return el;
|
|
493
511
|
const u = normalizeUsage(usage);
|
|
@@ -528,6 +546,14 @@ function renderAiUsage(el, usage, opts = {}) {
|
|
|
528
546
|
}
|
|
529
547
|
root.appendChild(legend);
|
|
530
548
|
}
|
|
549
|
+
const ttft = metricNum(opts.metrics?.ttftMs) ?? metricNum(metricField(usage, "ttftMs"));
|
|
550
|
+
const tps = metricNum(opts.metrics?.tokensPerSec) ?? metricNum(metricField(usage, "tokensPerSec"));
|
|
551
|
+
const finish = metricStr(opts.metrics?.finishReason) ?? metricStr(metricField(usage, "finishReason"));
|
|
552
|
+
const metricBits = [];
|
|
553
|
+
if (ttft != null) metricBits.push(`TTFT ${formatDuration(ttft)}`);
|
|
554
|
+
if (tps != null && tps > 0) metricBits.push(`${trimNum(tps)} tok/s`);
|
|
555
|
+
if (finish) metricBits.push(`finish: ${finish}`);
|
|
556
|
+
if (metricBits.length > 0) root.appendChild(h("div", "ai-usage-metrics", metricBits.join(" \xB7 ")));
|
|
531
557
|
const totalParts = [formatTokens(total)];
|
|
532
558
|
if (typeof opts.total === "number" && opts.total > 0) {
|
|
533
559
|
const pct = Math.min(100, Math.round(total / opts.total * 100));
|
|
@@ -626,13 +652,73 @@ var ICON_SPARKLES = svgWrap(
|
|
|
626
652
|
// src/behaviors/ai-chat.ts
|
|
627
653
|
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>';
|
|
628
654
|
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>';
|
|
655
|
+
function emit(target, name, detail) {
|
|
656
|
+
emitIcen(target, name, detail);
|
|
657
|
+
}
|
|
658
|
+
var REASONING_AUTO_CLOSE_MS = 1e3;
|
|
659
|
+
function setReasoningOpen(reasoning, open) {
|
|
660
|
+
const head = reasoning.querySelector(".ai-reasoning-head");
|
|
661
|
+
const body = reasoning.querySelector(".ai-reasoning-body");
|
|
662
|
+
head?.setAttribute("aria-expanded", String(open));
|
|
663
|
+
reasoning.classList.toggle("is-open", open);
|
|
664
|
+
if (body) body.hidden = !open;
|
|
665
|
+
}
|
|
666
|
+
function clearContinueSlot(scope) {
|
|
667
|
+
scope?.querySelectorAll(".ai-msg-continue").forEach((n) => n.remove());
|
|
668
|
+
}
|
|
629
669
|
function createAiStream(el) {
|
|
630
670
|
const reasoning = el.closest(".ai-reasoning");
|
|
671
|
+
const msgBody = el.closest(".ai-msg-body");
|
|
672
|
+
const msg = el.closest(".ai-msg");
|
|
631
673
|
const startedAt = Date.now();
|
|
632
674
|
let state = "streaming";
|
|
633
675
|
el.classList.add("is-streaming");
|
|
634
676
|
el.setAttribute("aria-busy", "true");
|
|
635
|
-
reasoning
|
|
677
|
+
if (reasoning) {
|
|
678
|
+
reasoning.classList.add("is-streaming");
|
|
679
|
+
setReasoningOpen(reasoning, true);
|
|
680
|
+
const head = reasoning.querySelector(".ai-reasoning-head");
|
|
681
|
+
if (head && head.dataset.reasoningStart == null) head.dataset.reasoningStart = String(startedAt);
|
|
682
|
+
}
|
|
683
|
+
clearContinueSlot(msg ?? msgBody ?? el);
|
|
684
|
+
const fillReasoningTime = () => {
|
|
685
|
+
if (!reasoning) return;
|
|
686
|
+
const time = reasoning.querySelector(".ai-reasoning-time");
|
|
687
|
+
if (!time) return;
|
|
688
|
+
const head = reasoning.querySelector(".ai-reasoning-head");
|
|
689
|
+
const raw = Number(head?.dataset.reasoningStart ?? "");
|
|
690
|
+
const t0 = Number.isFinite(raw) && raw > 0 ? raw : startedAt;
|
|
691
|
+
time.textContent = `\u601D\u8003 ${formatDuration(Math.max(0, Date.now() - t0))}`;
|
|
692
|
+
};
|
|
693
|
+
const scheduleReasoningAutoClose = () => {
|
|
694
|
+
if (!reasoning) return;
|
|
695
|
+
const r = reasoning;
|
|
696
|
+
if (r.__icenReasonUserTouched || r.__icenReasonAutoClosed) return;
|
|
697
|
+
const close = () => {
|
|
698
|
+
if (r.__icenReasonUserTouched || r.__icenReasonAutoClosed) return;
|
|
699
|
+
r.__icenReasonAutoClosed = true;
|
|
700
|
+
setReasoningOpen(r, false);
|
|
701
|
+
};
|
|
702
|
+
if (typeof window === "undefined") {
|
|
703
|
+
close();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
window.setTimeout(close, REASONING_AUTO_CLOSE_MS);
|
|
707
|
+
};
|
|
708
|
+
const appendContinueSlot = () => {
|
|
709
|
+
if (typeof document === "undefined") return;
|
|
710
|
+
const mount = msgBody ?? msg ?? (el.parentElement instanceof HTMLElement ? el.parentElement : null);
|
|
711
|
+
if (!mount || mount.querySelector(":scope > .ai-msg-continue")) return;
|
|
712
|
+
const btn = document.createElement("button");
|
|
713
|
+
btn.type = "button";
|
|
714
|
+
btn.className = "ai-msg-continue";
|
|
715
|
+
btn.textContent = "\u7EE7\u7EED\u751F\u6210";
|
|
716
|
+
btn.addEventListener("click", () => {
|
|
717
|
+
const target = msg ?? btn;
|
|
718
|
+
emit(target, "icen:ai-retry", { el: target });
|
|
719
|
+
});
|
|
720
|
+
mount.appendChild(btn);
|
|
721
|
+
};
|
|
636
722
|
const settle = (finished) => {
|
|
637
723
|
if (state !== "streaming") return;
|
|
638
724
|
state = finished ? "done" : "cancelled";
|
|
@@ -641,12 +727,8 @@ function createAiStream(el) {
|
|
|
641
727
|
if (!reasoning) return;
|
|
642
728
|
reasoning.classList.remove("is-streaming");
|
|
643
729
|
if (!finished) return;
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
head?.setAttribute("aria-expanded", "false");
|
|
647
|
-
if (body) body.hidden = true;
|
|
648
|
-
const time = reasoning.querySelector(".ai-reasoning-time");
|
|
649
|
-
if (time) time.textContent = formatDuration(Date.now() - startedAt);
|
|
730
|
+
fillReasoningTime();
|
|
731
|
+
scheduleReasoningAutoClose();
|
|
650
732
|
};
|
|
651
733
|
return {
|
|
652
734
|
append(text) {
|
|
@@ -658,6 +740,7 @@ function createAiStream(el) {
|
|
|
658
740
|
},
|
|
659
741
|
cancel() {
|
|
660
742
|
settle(false);
|
|
743
|
+
appendContinueSlot();
|
|
661
744
|
},
|
|
662
745
|
fail() {
|
|
663
746
|
if (state !== "streaming") return;
|
|
@@ -665,7 +748,12 @@ function createAiStream(el) {
|
|
|
665
748
|
el.classList.remove("is-streaming");
|
|
666
749
|
el.removeAttribute("aria-busy");
|
|
667
750
|
el.classList.add("is-error");
|
|
668
|
-
reasoning
|
|
751
|
+
if (reasoning) {
|
|
752
|
+
reasoning.classList.remove("is-streaming");
|
|
753
|
+
fillReasoningTime();
|
|
754
|
+
scheduleReasoningAutoClose();
|
|
755
|
+
}
|
|
756
|
+
appendContinueSlot();
|
|
669
757
|
}
|
|
670
758
|
};
|
|
671
759
|
}
|
|
@@ -872,7 +960,7 @@ var POPUP_SIZING = /* @__PURE__ */ new WeakMap();
|
|
|
872
960
|
function isBrowser() {
|
|
873
961
|
return typeof document !== "undefined" && typeof window !== "undefined";
|
|
874
962
|
}
|
|
875
|
-
function
|
|
963
|
+
function emit2(target, name, detail) {
|
|
876
964
|
emitIcen(target, name, detail);
|
|
877
965
|
}
|
|
878
966
|
function resolveComposer(el) {
|
|
@@ -1242,7 +1330,7 @@ function openModelPopup(composer) {
|
|
|
1242
1330
|
MODEL_CTX.delete(composer);
|
|
1243
1331
|
}
|
|
1244
1332
|
updateModelButton(composer);
|
|
1245
|
-
|
|
1333
|
+
emit2(composer, "icen:ai-model-change", { provider, model, label, context });
|
|
1246
1334
|
refreshUsageRing(composer);
|
|
1247
1335
|
closePopup(composer);
|
|
1248
1336
|
},
|
|
@@ -1350,7 +1438,7 @@ function openCommandPopup(composer, token) {
|
|
|
1350
1438
|
ta.value = "";
|
|
1351
1439
|
autosizeTa(ta);
|
|
1352
1440
|
}
|
|
1353
|
-
|
|
1441
|
+
emit2(composer, "icen:ai-command", { name, args });
|
|
1354
1442
|
closePopup(composer);
|
|
1355
1443
|
},
|
|
1356
1444
|
build: (s) => {
|
|
@@ -1553,7 +1641,7 @@ function addActiveRef(composer, source) {
|
|
|
1553
1641
|
actives.push(source);
|
|
1554
1642
|
ACTIVE_REFS.set(composer, actives);
|
|
1555
1643
|
renderRefs(composer);
|
|
1556
|
-
|
|
1644
|
+
emit2(composer, "icen:ai-ref", { action: "add", ref: source });
|
|
1557
1645
|
}
|
|
1558
1646
|
function removeActiveRef(composer, source) {
|
|
1559
1647
|
const actives = ACTIVE_REFS.get(composer) ?? [];
|
|
@@ -1573,7 +1661,7 @@ function removeActiveRef(composer, source) {
|
|
|
1573
1661
|
autosizeTa(ta);
|
|
1574
1662
|
}
|
|
1575
1663
|
}
|
|
1576
|
-
|
|
1664
|
+
emit2(composer, "icen:ai-ref", { action: "remove", ref: source });
|
|
1577
1665
|
}
|
|
1578
1666
|
function pushHistory(composer, text) {
|
|
1579
1667
|
const h2 = HISTORIES.get(composer) ?? { items: [], index: -1 };
|
|
@@ -1614,10 +1702,10 @@ function setupComposer(composer) {
|
|
|
1614
1702
|
autosizeTa(ta);
|
|
1615
1703
|
if (isRunning()) {
|
|
1616
1704
|
enqueue(text);
|
|
1617
|
-
|
|
1705
|
+
emit2(composer, "icen:ai-queue", { text });
|
|
1618
1706
|
} else {
|
|
1619
1707
|
pushHistory(composer, text);
|
|
1620
|
-
|
|
1708
|
+
emit2(composer, "icen:ai-send", { text });
|
|
1621
1709
|
}
|
|
1622
1710
|
};
|
|
1623
1711
|
SUBMITS.set(composer, submit);
|
|
@@ -1630,7 +1718,7 @@ function setupComposer(composer) {
|
|
|
1630
1718
|
renderQueue();
|
|
1631
1719
|
ta.value = text;
|
|
1632
1720
|
autosizeTa(ta);
|
|
1633
|
-
|
|
1721
|
+
emit2(composer, "icen:ai-dequeue", { index });
|
|
1634
1722
|
};
|
|
1635
1723
|
const browseHistory = (delta) => {
|
|
1636
1724
|
const h2 = HISTORIES.get(composer);
|
|
@@ -1709,7 +1797,7 @@ function setupComposer(composer) {
|
|
|
1709
1797
|
};
|
|
1710
1798
|
const onSendClick = () => {
|
|
1711
1799
|
if (isRunning()) {
|
|
1712
|
-
|
|
1800
|
+
emit2(composer, "icen:ai-stop", {});
|
|
1713
1801
|
} else {
|
|
1714
1802
|
submit();
|
|
1715
1803
|
}
|
|
@@ -1723,7 +1811,7 @@ function setupComposer(composer) {
|
|
|
1723
1811
|
queue.splice(index, 1);
|
|
1724
1812
|
QUEUES.set(composer, queue);
|
|
1725
1813
|
renderQueue();
|
|
1726
|
-
|
|
1814
|
+
emit2(composer, "icen:ai-dequeue", { index });
|
|
1727
1815
|
}
|
|
1728
1816
|
};
|
|
1729
1817
|
const onRefsClick = (e) => {
|
|
@@ -1752,7 +1840,7 @@ function setupComposer(composer) {
|
|
|
1752
1840
|
}
|
|
1753
1841
|
attachEl.hidden = false;
|
|
1754
1842
|
}
|
|
1755
|
-
|
|
1843
|
+
emit2(composer, "icen:ai-attach", { files });
|
|
1756
1844
|
};
|
|
1757
1845
|
const onFileChange = () => {
|
|
1758
1846
|
const files = Array.from(fileInput.files ?? []);
|
|
@@ -2059,7 +2147,7 @@ function bindComposer(el, opts = {}) {
|
|
|
2059
2147
|
const next = queue.shift() ?? "";
|
|
2060
2148
|
QUEUES.set(composer, queue);
|
|
2061
2149
|
renderQueueChips(composer);
|
|
2062
|
-
|
|
2150
|
+
emit2(composer, "icen:ai-dequeue", { index: 0 });
|
|
2063
2151
|
send(next);
|
|
2064
2152
|
}
|
|
2065
2153
|
})();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** 反馈事件 detail(与 IcenEventMap['icen:ai-feedback'] 同构;el 为 .ai-feedback 根) */
|
|
2
|
+
interface AiFeedbackDetail {
|
|
3
|
+
value: 1 | -1 | 0;
|
|
4
|
+
reason?: string;
|
|
5
|
+
el?: HTMLElement;
|
|
6
|
+
}
|
|
7
|
+
interface AiFeedbackOpts {
|
|
8
|
+
/** 踩因枚举(默认五项) */
|
|
9
|
+
reasons?: string[];
|
|
10
|
+
/** 反馈回调(与 icen:ai-feedback 事件双通道) */
|
|
11
|
+
onFeedback?: (detail: AiFeedbackDetail) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 渲染 👍/👎 双钮 + 踩因 chips + 提交/已提交态(快照渲染,全 textContent)。
|
|
15
|
+
* 交互由 initAiFeedback 委托驱动;opts.onFeedback 与 icen:ai-feedback 事件双通道。
|
|
16
|
+
* 返回挂载容器 el(render* 返回挂载元素约定,SSR 下原样返回)。
|
|
17
|
+
*/
|
|
18
|
+
declare function renderAiFeedback(el: HTMLElement, opts?: AiFeedbackOpts): HTMLElement;
|
|
19
|
+
/**
|
|
20
|
+
* 反馈委托初始化:vote 切换/取消、chip 单选、提交进入 .is-done 终态。
|
|
21
|
+
* 幂等(scope 挂 __icenAiFeedbackInit);返回销毁函数(解绑监听 + 复位标记,可重新 init)。
|
|
22
|
+
* SSR 下为 no-op。按钮为原生 button,键盘 Enter/Space 走原生 click,无需键盘委托。
|
|
23
|
+
*/
|
|
24
|
+
declare function initAiFeedback(root?: ParentNode): () => void;
|
|
25
|
+
|
|
26
|
+
export { type AiFeedbackDetail, type AiFeedbackOpts, initAiFeedback, renderAiFeedback };
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// src/behaviors/ai-core.ts
|
|
2
|
+
var svg = (body) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
|
|
3
|
+
var ICON_TERMINAL = svg('<path d="m4 17 6-6-6-6"/><path d="M12 19h8"/>');
|
|
4
|
+
var ICON_FILE = svg(
|
|
5
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>'
|
|
6
|
+
);
|
|
7
|
+
var ICON_FILE_PEN = svg(
|
|
8
|
+
'<path d="M12.5 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8l5 5v3.5"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M19 6.5 16.5 4"/><path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/>'
|
|
9
|
+
);
|
|
10
|
+
var ICON_FILE_PLUS = svg(
|
|
11
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M9 15h6"/><path d="M12 12v6"/>'
|
|
12
|
+
);
|
|
13
|
+
var ICON_TRASH = svg(
|
|
14
|
+
'<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6"/><path d="M14 11v6"/>'
|
|
15
|
+
);
|
|
16
|
+
var ICON_SEARCH = svg('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>');
|
|
17
|
+
var ICON_FOLDER_SEARCH = svg(
|
|
18
|
+
'<path d="M11 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v4"/><circle cx="17" cy="17" r="3"/><path d="m21 21-1.9-1.9"/>'
|
|
19
|
+
);
|
|
20
|
+
var ICON_SCAN_SEARCH = svg(
|
|
21
|
+
'<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/>'
|
|
22
|
+
);
|
|
23
|
+
var ICON_GLOBE = svg(
|
|
24
|
+
'<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>'
|
|
25
|
+
);
|
|
26
|
+
var ICON_DOWNLOAD = svg(
|
|
27
|
+
'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>'
|
|
28
|
+
);
|
|
29
|
+
var ICON_BLOCKS = svg(
|
|
30
|
+
'<rect width="7" height="7" x="4" y="4" rx="1"/><rect width="7" height="7" x="13" y="4" rx="1"/><rect width="7" height="7" x="4" y="13" rx="1"/><rect width="7" height="7" x="13" y="13" rx="1"/>'
|
|
31
|
+
);
|
|
32
|
+
var ICON_ZAP = svg(
|
|
33
|
+
'<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>'
|
|
34
|
+
);
|
|
35
|
+
var ICON_LIST = svg(
|
|
36
|
+
'<path d="M3 12h.01"/><path d="M3 18h.01"/><path d="M3 6h.01"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M8 6h13"/>'
|
|
37
|
+
);
|
|
38
|
+
var ICON_LIST_CHECKS = svg(
|
|
39
|
+
'<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/>'
|
|
40
|
+
);
|
|
41
|
+
var ICON_BOT = svg(
|
|
42
|
+
'<path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/>'
|
|
43
|
+
);
|
|
44
|
+
var ICON_CHART = svg(
|
|
45
|
+
'<path d="M3 3v18h18"/><path d="M7 15l4-6 3 4 5-8"/>'
|
|
46
|
+
);
|
|
47
|
+
var ICON_FILE_TEXT = svg(
|
|
48
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>'
|
|
49
|
+
);
|
|
50
|
+
function isObj(v) {
|
|
51
|
+
return typeof v === "object" && v !== null;
|
|
52
|
+
}
|
|
53
|
+
function pickStr(input, keys) {
|
|
54
|
+
if (!isObj(input)) return void 0;
|
|
55
|
+
for (const k of keys) {
|
|
56
|
+
const v = input[k];
|
|
57
|
+
if (typeof v === "string" && v) return v;
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
function pickNum(src, keys) {
|
|
62
|
+
if (!isObj(src)) return void 0;
|
|
63
|
+
for (const k of keys) {
|
|
64
|
+
const v = src[k];
|
|
65
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
66
|
+
}
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
function firstLine(s, max = 80) {
|
|
70
|
+
const line = s.split("\n", 1)[0]?.trim() ?? "";
|
|
71
|
+
return line.length > max ? `${line.slice(0, max)}\u2026` : line;
|
|
72
|
+
}
|
|
73
|
+
function filePathOf(input) {
|
|
74
|
+
return pickStr(input, ["file_path", "path", "file", "filename"]);
|
|
75
|
+
}
|
|
76
|
+
var summarizePath = (input) => filePathOf(input);
|
|
77
|
+
function summarizeRead(input) {
|
|
78
|
+
const p = filePathOf(input);
|
|
79
|
+
if (!p) return void 0;
|
|
80
|
+
const offset = pickNum(input, ["offset", "start_line"]);
|
|
81
|
+
if (offset == null) return p;
|
|
82
|
+
const limit = pickNum(input, ["limit", "end_line"]);
|
|
83
|
+
return limit != null ? `${p}:${offset}-${offset + limit}` : `${p}:${offset}`;
|
|
84
|
+
}
|
|
85
|
+
function summarizeShell(input) {
|
|
86
|
+
const cmd = typeof input === "string" ? input : pickStr(input, ["command", "cmd", "script"]);
|
|
87
|
+
return cmd ? firstLine(cmd) : void 0;
|
|
88
|
+
}
|
|
89
|
+
function summarizeGrep(input) {
|
|
90
|
+
const pattern = pickStr(input, ["pattern", "regex", "query"]);
|
|
91
|
+
const p = filePathOf(input);
|
|
92
|
+
if (pattern) return p ? `${firstLine(pattern, 40)} \xB7 ${p}` : firstLine(pattern);
|
|
93
|
+
return p;
|
|
94
|
+
}
|
|
95
|
+
var sum = (fn) => {
|
|
96
|
+
return (input) => fn(input) ?? "";
|
|
97
|
+
};
|
|
98
|
+
var BUILTIN_KINDS = {
|
|
99
|
+
shell: { label: "\u547D\u4EE4", icon: ICON_TERMINAL, tint: "accent", summarize: sum(summarizeShell) },
|
|
100
|
+
read: { label: "\u8BFB\u53D6", icon: ICON_FILE, tint: "info", summarize: sum(summarizeRead) },
|
|
101
|
+
edit: { label: "\u7F16\u8F91", icon: ICON_FILE_PEN, tint: "warning", summarize: sum(summarizePath) },
|
|
102
|
+
write: { label: "\u5199\u5165", icon: ICON_FILE_PLUS, tint: "warning", summarize: sum(summarizePath) },
|
|
103
|
+
rm: { label: "\u5220\u9664", icon: ICON_TRASH, tint: "error", summarize: sum(summarizePath) },
|
|
104
|
+
grep: { label: "\u641C\u7D22", icon: ICON_SEARCH, tint: "info", summarize: sum(summarizeGrep) },
|
|
105
|
+
glob: { label: "\u5339\u914D", icon: ICON_FOLDER_SEARCH, tint: "info", summarize: sum((i) => pickStr(i, ["pattern", "glob", "path"])) },
|
|
106
|
+
browser: { label: "\u6D4F\u89C8", icon: ICON_GLOBE, tint: "accent", summarize: sum((i) => pickStr(i, ["url", "uri", "target"])) },
|
|
107
|
+
search: { label: "\u68C0\u7D22", icon: ICON_SCAN_SEARCH, tint: "accent", summarize: sum((i) => pickStr(i, ["query", "q", "pattern"])) },
|
|
108
|
+
fetch: { label: "\u8BF7\u6C42", icon: ICON_DOWNLOAD, tint: "info", summarize: sum((i) => pickStr(i, ["url", "uri", "endpoint"])) },
|
|
109
|
+
mcp: { label: "MCP", icon: ICON_BLOCKS, tint: "muted", summarize: sum((i) => pickStr(i, ["tool", "method", "name"])) },
|
|
110
|
+
skill: { label: "\u6280\u80FD", icon: ICON_ZAP, tint: "accent", summarize: sum((i) => pickStr(i, ["skill", "name", "command"])) },
|
|
111
|
+
todo: {
|
|
112
|
+
label: "\u5F85\u529E",
|
|
113
|
+
icon: ICON_LIST,
|
|
114
|
+
tint: "info",
|
|
115
|
+
summarize: sum((input) => {
|
|
116
|
+
const items = input && typeof input === "object" && Array.isArray(input.items) ? input.items : Array.isArray(input) ? input : [];
|
|
117
|
+
const done = items.filter((it) => it?.status === "done").length;
|
|
118
|
+
return items.length > 0 ? `${done}/${items.length} \u5B8C\u6210` : "";
|
|
119
|
+
})
|
|
120
|
+
},
|
|
121
|
+
plan: { label: "\u8BA1\u5212", icon: ICON_LIST_CHECKS, tint: "info" },
|
|
122
|
+
subagent: { label: "\u5B50\u4EE3\u7406", icon: ICON_BOT, tint: "accent", summarize: sum((i) => pickStr(i, ["task", "description", "prompt", "message", "subagent_type"])) },
|
|
123
|
+
note: { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" },
|
|
124
|
+
chart: { label: "\u56FE\u8868", icon: ICON_CHART, tint: "info", summarize: sum((i) => pickStr(i, ["title", "name"])) }
|
|
125
|
+
};
|
|
126
|
+
var kindRegistry = new Map(Object.entries(BUILTIN_KINDS));
|
|
127
|
+
function h(tag, className, text) {
|
|
128
|
+
const node = document.createElement(tag);
|
|
129
|
+
if (className) node.className = className;
|
|
130
|
+
if (text != null) node.textContent = text;
|
|
131
|
+
return node;
|
|
132
|
+
}
|
|
133
|
+
function svgIcon(svg2) {
|
|
134
|
+
if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
|
|
135
|
+
let parsed;
|
|
136
|
+
try {
|
|
137
|
+
parsed = new DOMParser().parseFromString(svg2, "image/svg+xml");
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const root = parsed.documentElement;
|
|
142
|
+
if (!root || root.tagName.toLowerCase() !== "svg" || parsed.querySelector("parsererror")) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const sanitize = (el) => {
|
|
146
|
+
for (const attr of Array.from(el.attributes)) {
|
|
147
|
+
const name = attr.name.toLowerCase();
|
|
148
|
+
if (name.startsWith("on")) el.removeAttribute(attr.name);
|
|
149
|
+
if ((name === "href" || name === "xlink:href") && attr.value.trim().toLowerCase().startsWith("javascript:")) {
|
|
150
|
+
el.removeAttribute(attr.name);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const child of Array.from(el.children)) {
|
|
154
|
+
const tag = child.tagName.toLowerCase();
|
|
155
|
+
if (tag === "script" || tag === "foreignobject") {
|
|
156
|
+
child.remove();
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
sanitize(child);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
sanitize(root);
|
|
163
|
+
return document.importNode(root, true);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/behaviors/events.ts
|
|
167
|
+
function emitIcen(source, type, detail) {
|
|
168
|
+
return source.dispatchEvent(
|
|
169
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/behaviors/ai-feedback.ts
|
|
174
|
+
var DEFAULT_REASONS = ["\u7B54\u6848\u6709\u8BEF", "\u5F15\u7528\u4E0D\u7B26", "\u5185\u5BB9\u8FC7\u65F6", "\u65E0\u5F15\u7528\u4F9D\u636E", "\u8D8A\u6743\u4FE1\u606F"];
|
|
175
|
+
var ICON_THUMB_UP = '<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="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/></svg>';
|
|
176
|
+
var ICON_THUMB_DOWN = '<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="M17 14V2"/><path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z"/></svg>';
|
|
177
|
+
var feedbackCallbacks = /* @__PURE__ */ new WeakMap();
|
|
178
|
+
function renderAiFeedback(el, opts = {}) {
|
|
179
|
+
if (typeof document === "undefined") return el;
|
|
180
|
+
el.textContent = "";
|
|
181
|
+
const root = h("div", "ai-feedback");
|
|
182
|
+
const votes = h("div", "ai-feedback-votes");
|
|
183
|
+
for (const dir of ["is-up", "is-down"]) {
|
|
184
|
+
const btn = h("button", `ai-feedback-vote ${dir}`);
|
|
185
|
+
btn.type = "button";
|
|
186
|
+
btn.setAttribute("aria-pressed", "false");
|
|
187
|
+
btn.setAttribute("aria-label", dir === "is-up" ? "\u6709\u5E2E\u52A9" : "\u65E0\u5E2E\u52A9");
|
|
188
|
+
const icon = svgIcon(dir === "is-up" ? ICON_THUMB_UP : ICON_THUMB_DOWN);
|
|
189
|
+
if (icon) btn.appendChild(icon);
|
|
190
|
+
votes.appendChild(btn);
|
|
191
|
+
}
|
|
192
|
+
root.appendChild(votes);
|
|
193
|
+
const reasons = h("div", "ai-feedback-reasons");
|
|
194
|
+
reasons.hidden = true;
|
|
195
|
+
const chips = (opts.reasons && opts.reasons.length > 0 ? opts.reasons : Array.from(DEFAULT_REASONS)).filter((r) => typeof r === "string" && r.trim() !== "");
|
|
196
|
+
for (const r of chips) {
|
|
197
|
+
const chip = h("button", "ai-feedback-chip", r);
|
|
198
|
+
chip.type = "button";
|
|
199
|
+
chip.setAttribute("aria-pressed", "false");
|
|
200
|
+
reasons.appendChild(chip);
|
|
201
|
+
}
|
|
202
|
+
const submit = h("button", "ai-feedback-submit", "\u63D0\u4EA4");
|
|
203
|
+
submit.type = "button";
|
|
204
|
+
reasons.appendChild(submit);
|
|
205
|
+
root.appendChild(reasons);
|
|
206
|
+
const done = h("div", "ai-feedback-done", "\u611F\u8C22\u53CD\u9988\uFF0C\u5DF2\u5173\u8054\u5230\u8BE5\u6761\u56DE\u7B54\u7684\u8FFD\u8E2A\u8BB0\u5F55");
|
|
207
|
+
done.hidden = true;
|
|
208
|
+
root.appendChild(done);
|
|
209
|
+
if (opts.onFeedback) feedbackCallbacks.set(root, opts.onFeedback);
|
|
210
|
+
el.appendChild(root);
|
|
211
|
+
return el;
|
|
212
|
+
}
|
|
213
|
+
function notify(root, detail) {
|
|
214
|
+
emitIcen(root, "icen:ai-feedback", { value: detail.value, reason: detail.reason, el: root });
|
|
215
|
+
const cb = feedbackCallbacks.get(root);
|
|
216
|
+
if (cb) cb({ value: detail.value, reason: detail.reason, el: root });
|
|
217
|
+
}
|
|
218
|
+
function voteOf(btn) {
|
|
219
|
+
if (btn.classList.contains("is-up")) return 1;
|
|
220
|
+
if (btn.classList.contains("is-down")) return -1;
|
|
221
|
+
return void 0;
|
|
222
|
+
}
|
|
223
|
+
function resetVotes(root) {
|
|
224
|
+
for (const btn of Array.from(root.querySelectorAll(".ai-feedback-vote"))) {
|
|
225
|
+
btn.classList.remove("is-picked");
|
|
226
|
+
btn.disabled = false;
|
|
227
|
+
btn.setAttribute("aria-pressed", "false");
|
|
228
|
+
}
|
|
229
|
+
const reasons = root.querySelector(".ai-feedback-reasons");
|
|
230
|
+
if (reasons) reasons.hidden = true;
|
|
231
|
+
}
|
|
232
|
+
function pickVote(root, btn, value) {
|
|
233
|
+
for (const other of Array.from(root.querySelectorAll(".ai-feedback-vote"))) {
|
|
234
|
+
if (other === btn) {
|
|
235
|
+
other.classList.add("is-picked");
|
|
236
|
+
other.disabled = false;
|
|
237
|
+
other.setAttribute("aria-pressed", "true");
|
|
238
|
+
} else {
|
|
239
|
+
other.classList.remove("is-picked");
|
|
240
|
+
other.disabled = true;
|
|
241
|
+
other.setAttribute("aria-pressed", "false");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const reasons = root.querySelector(".ai-feedback-reasons");
|
|
245
|
+
if (reasons) reasons.hidden = value !== -1;
|
|
246
|
+
}
|
|
247
|
+
function initAiFeedback(root) {
|
|
248
|
+
if (typeof document === "undefined") return () => void 0;
|
|
249
|
+
const scope = root ?? document;
|
|
250
|
+
const marked = scope;
|
|
251
|
+
if (marked.__icenAiFeedbackInit) return () => void 0;
|
|
252
|
+
marked.__icenAiFeedbackInit = true;
|
|
253
|
+
const target = scope;
|
|
254
|
+
const onClick = (e) => {
|
|
255
|
+
const t = e.target instanceof Element ? e.target : null;
|
|
256
|
+
if (!t) return;
|
|
257
|
+
const rootEl = t.closest(".ai-feedback");
|
|
258
|
+
if (!rootEl || rootEl.classList.contains("is-done")) return;
|
|
259
|
+
const chip = t.closest(".ai-feedback-chip");
|
|
260
|
+
if (chip) {
|
|
261
|
+
const picked = chip.classList.contains("is-picked");
|
|
262
|
+
for (const c of Array.from(rootEl.querySelectorAll(".ai-feedback-chip"))) {
|
|
263
|
+
c.classList.remove("is-picked");
|
|
264
|
+
c.setAttribute("aria-pressed", "false");
|
|
265
|
+
}
|
|
266
|
+
if (!picked) {
|
|
267
|
+
chip.classList.add("is-picked");
|
|
268
|
+
chip.setAttribute("aria-pressed", "true");
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (t.closest(".ai-feedback-submit")) {
|
|
273
|
+
const reason = rootEl.querySelector(".ai-feedback-chip.is-picked")?.textContent ?? void 0;
|
|
274
|
+
rootEl.classList.add("is-done");
|
|
275
|
+
const done = rootEl.querySelector(".ai-feedback-done");
|
|
276
|
+
if (done) done.hidden = false;
|
|
277
|
+
const reasons = rootEl.querySelector(".ai-feedback-reasons");
|
|
278
|
+
if (reasons) reasons.hidden = true;
|
|
279
|
+
notify(rootEl, { value: -1, reason: reason || void 0 });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const voteBtn = t.closest(".ai-feedback-vote");
|
|
283
|
+
if (!voteBtn || voteBtn.disabled) return;
|
|
284
|
+
const value = voteOf(voteBtn);
|
|
285
|
+
if (value === void 0) return;
|
|
286
|
+
if (voteBtn.classList.contains("is-picked")) {
|
|
287
|
+
resetVotes(rootEl);
|
|
288
|
+
notify(rootEl, { value: 0 });
|
|
289
|
+
} else {
|
|
290
|
+
pickVote(rootEl, voteBtn, value);
|
|
291
|
+
notify(rootEl, { value });
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
target.addEventListener("click", onClick);
|
|
295
|
+
return () => {
|
|
296
|
+
target.removeEventListener("click", onClick);
|
|
297
|
+
marked.__icenAiFeedbackInit = false;
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
export {
|
|
301
|
+
initAiFeedback,
|
|
302
|
+
renderAiFeedback
|
|
303
|
+
};
|
|
@@ -9,6 +9,7 @@ interface AiTodoItem {
|
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* 渲染 todo 列表(DOM API,全 textContent)。全部完成时容器加 .is-complete。
|
|
12
|
+
* 每项附 .ai-todo-check 描边勾(§5.27:done 时 CSS 显隐 + 480ms 描边动画,一次不循环)。
|
|
12
13
|
* 返回挂载容器 el(render* 返回挂载元素约定,SSR 下原样返回)。
|
|
13
14
|
*/
|
|
14
15
|
declare function renderAiTodo(el: HTMLElement, items: AiTodoItem[]): HTMLElement;
|
|
@@ -17,15 +18,28 @@ declare function renderAiTodo(el: HTMLElement, items: AiTodoItem[]): HTMLElement
|
|
|
17
18
|
* 只读列表不挂交互。重复调用安全;返回销毁函数。
|
|
18
19
|
*/
|
|
19
20
|
declare function initAiTodo(root?: ParentNode): () => void;
|
|
21
|
+
/** 用量派生指标(§5.27):opts.metrics 给出,或直接挂在 usage 对象同级字段上 */
|
|
22
|
+
interface AiUsageMetrics {
|
|
23
|
+
/** 首 token 延迟(毫秒) */
|
|
24
|
+
ttftMs?: number;
|
|
25
|
+
/** 生成吞吐(tokens/秒) */
|
|
26
|
+
tokensPerSec?: number;
|
|
27
|
+
/** finish reason(stop / length / tool_calls…) */
|
|
28
|
+
finishReason?: string;
|
|
29
|
+
}
|
|
20
30
|
interface AiUsageRenderOpts {
|
|
21
31
|
/** 上下文上限(如 200k):给出时 total 行显示 82k / 200k · 41% */
|
|
22
32
|
total?: number;
|
|
23
33
|
/** 成本(USD):给出时追加到 total 行 */
|
|
24
34
|
cost?: number;
|
|
35
|
+
/** 派生指标(§5.27):任一字段给出时图例下补一行 mono 明细(TTFT 0.8s · 42 tok/s · finish: stop) */
|
|
36
|
+
metrics?: AiUsageMetrics;
|
|
25
37
|
}
|
|
26
38
|
/**
|
|
27
39
|
* 渲染用量分段条 + 图例 + 占比。返回挂载容器 el(render* 返回挂载元素约定)。
|
|
28
40
|
* 配色契约:input=accent / output=success / cacheRead=info / cacheWrite=warning / reasoning=faint。
|
|
41
|
+
* opts.metrics(或 usage 同级 ttftMs/tokensPerSec/finishReason)存在时图例下补一行
|
|
42
|
+
* .ai-usage-metrics mono 明细(§5.27)。
|
|
29
43
|
*/
|
|
30
44
|
declare function renderAiUsage(el: HTMLElement, usage: AiUsage, opts?: AiUsageRenderOpts): HTMLElement;
|
|
31
45
|
interface AiUsageRingOpts extends AiUsageRenderOpts {
|
|
@@ -98,4 +112,4 @@ interface AiAuditRenderOpts {
|
|
|
98
112
|
*/
|
|
99
113
|
declare function renderAiAudit(el: HTMLElement, source: AiAuditor | AiAuditEntry[], opts?: AiAuditRenderOpts): HTMLElement;
|
|
100
114
|
|
|
101
|
-
export { type AiAuditRenderOpts, type AiContextFile, type AiContextMcpServer, type AiContextModel, type AiContextSkill, type AiTodoItem, type AiUsageRenderOpts, type AiUsageRingOpts, initAiContext, initAiTodo, renderAiAudit, renderAiContext, renderAiTodo, renderAiUsage, renderAiUsageRing };
|
|
115
|
+
export { type AiAuditRenderOpts, type AiContextFile, type AiContextMcpServer, type AiContextModel, type AiContextSkill, type AiTodoItem, type AiUsageMetrics, type AiUsageRenderOpts, type AiUsageRingOpts, initAiContext, initAiTodo, renderAiAudit, renderAiContext, renderAiTodo, renderAiUsage, renderAiUsageRing };
|