@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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { KbReviewTask, KbScoreConfig } from './kb-core.js';
|
|
2
|
+
import './ai-core.js';
|
|
3
|
+
|
|
4
|
+
/** onAction 回调的动作信封(与 icen:kb-review-* 事件双通道;select 无对应事件,仅回调) */
|
|
5
|
+
interface KbReviewActionDetail {
|
|
6
|
+
type: 'select' | 'score' | 'submit' | 'assign';
|
|
7
|
+
taskId: string;
|
|
8
|
+
/** score:配置名 */
|
|
9
|
+
name?: string;
|
|
10
|
+
/** score:选项/数值;assign:分配人 */
|
|
11
|
+
value?: string | number;
|
|
12
|
+
}
|
|
13
|
+
/** createKbReview 入参 */
|
|
14
|
+
interface KbReviewOpts {
|
|
15
|
+
/** 审查队列(内部持有副本,不打脏调用方数据) */
|
|
16
|
+
queue: KbReviewTask[];
|
|
17
|
+
/** rubric 配置(categorical / numeric) */
|
|
18
|
+
configs: KbScoreConfig[];
|
|
19
|
+
/** 动作回调(与事件双通道) */
|
|
20
|
+
onAction?: (action: KbReviewActionDetail) => void;
|
|
21
|
+
/** 分配人名单(提供时审查卡显示分配下拉) */
|
|
22
|
+
assignees?: string[];
|
|
23
|
+
}
|
|
24
|
+
/** createKbReview 句柄(函数名冻结于 spec §5.20) */
|
|
25
|
+
interface KbReviewHandle {
|
|
26
|
+
el: HTMLElement;
|
|
27
|
+
/** 当前(可见列表中的)任务;空返回 null */
|
|
28
|
+
current(): KbReviewTask | null;
|
|
29
|
+
/** 下一条(循环);返回新当前任务 */
|
|
30
|
+
next(): KbReviewTask | null;
|
|
31
|
+
/** 上一条(循环) */
|
|
32
|
+
prev(): KbReviewTask | null;
|
|
33
|
+
/** 完成当前任务(置 done + 派发 submit + 自动 next) */
|
|
34
|
+
submit(): void;
|
|
35
|
+
/** 解绑键盘监听并清空 DOM(幂等) */
|
|
36
|
+
destroy(): void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 创建标注/审查队列(左右结构:队列列表 + 审查卡),返回带导航/提交/销毁的句柄。
|
|
40
|
+
* 键盘处理挂在组件容器上、仅当焦点位于审查卡内才生效;destroy 解绑。
|
|
41
|
+
*/
|
|
42
|
+
declare function createKbReview(el: HTMLElement, opts: KbReviewOpts): KbReviewHandle;
|
|
43
|
+
|
|
44
|
+
export { type KbReviewActionDetail, type KbReviewHandle, type KbReviewOpts, createKbReview };
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
// src/behaviors/ai-core.ts
|
|
2
|
+
var svg = (body) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
|
|
3
|
+
var ICON_TERMINAL = svg('<path d="m4 17 6-6-6-6"/><path d="M12 19h8"/>');
|
|
4
|
+
var ICON_FILE = svg(
|
|
5
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>'
|
|
6
|
+
);
|
|
7
|
+
var ICON_FILE_PEN = svg(
|
|
8
|
+
'<path d="M12.5 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8l5 5v3.5"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M19 6.5 16.5 4"/><path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/>'
|
|
9
|
+
);
|
|
10
|
+
var ICON_FILE_PLUS = svg(
|
|
11
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M9 15h6"/><path d="M12 12v6"/>'
|
|
12
|
+
);
|
|
13
|
+
var ICON_TRASH = svg(
|
|
14
|
+
'<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6"/><path d="M14 11v6"/>'
|
|
15
|
+
);
|
|
16
|
+
var ICON_SEARCH = svg('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>');
|
|
17
|
+
var ICON_FOLDER_SEARCH = svg(
|
|
18
|
+
'<path d="M11 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v4"/><circle cx="17" cy="17" r="3"/><path d="m21 21-1.9-1.9"/>'
|
|
19
|
+
);
|
|
20
|
+
var ICON_SCAN_SEARCH = svg(
|
|
21
|
+
'<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/>'
|
|
22
|
+
);
|
|
23
|
+
var ICON_GLOBE = svg(
|
|
24
|
+
'<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>'
|
|
25
|
+
);
|
|
26
|
+
var ICON_DOWNLOAD = svg(
|
|
27
|
+
'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>'
|
|
28
|
+
);
|
|
29
|
+
var ICON_BLOCKS = svg(
|
|
30
|
+
'<rect width="7" height="7" x="4" y="4" rx="1"/><rect width="7" height="7" x="13" y="4" rx="1"/><rect width="7" height="7" x="4" y="13" rx="1"/><rect width="7" height="7" x="13" y="13" rx="1"/>'
|
|
31
|
+
);
|
|
32
|
+
var ICON_ZAP = svg(
|
|
33
|
+
'<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>'
|
|
34
|
+
);
|
|
35
|
+
var ICON_LIST = svg(
|
|
36
|
+
'<path d="M3 12h.01"/><path d="M3 18h.01"/><path d="M3 6h.01"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M8 6h13"/>'
|
|
37
|
+
);
|
|
38
|
+
var ICON_LIST_CHECKS = svg(
|
|
39
|
+
'<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/>'
|
|
40
|
+
);
|
|
41
|
+
var ICON_BOT = svg(
|
|
42
|
+
'<path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/>'
|
|
43
|
+
);
|
|
44
|
+
var ICON_CHART = svg(
|
|
45
|
+
'<path d="M3 3v18h18"/><path d="M7 15l4-6 3 4 5-8"/>'
|
|
46
|
+
);
|
|
47
|
+
var ICON_FILE_TEXT = svg(
|
|
48
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>'
|
|
49
|
+
);
|
|
50
|
+
function isObj(v) {
|
|
51
|
+
return typeof v === "object" && v !== null;
|
|
52
|
+
}
|
|
53
|
+
function pickStr(input, keys) {
|
|
54
|
+
if (!isObj(input)) return void 0;
|
|
55
|
+
for (const k of keys) {
|
|
56
|
+
const v = input[k];
|
|
57
|
+
if (typeof v === "string" && v) return v;
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
function pickNum(src, keys) {
|
|
62
|
+
if (!isObj(src)) return void 0;
|
|
63
|
+
for (const k of keys) {
|
|
64
|
+
const v = src[k];
|
|
65
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
66
|
+
}
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
function firstLine(s, max = 80) {
|
|
70
|
+
const line = s.split("\n", 1)[0]?.trim() ?? "";
|
|
71
|
+
return line.length > max ? `${line.slice(0, max)}\u2026` : line;
|
|
72
|
+
}
|
|
73
|
+
function filePathOf(input) {
|
|
74
|
+
return pickStr(input, ["file_path", "path", "file", "filename"]);
|
|
75
|
+
}
|
|
76
|
+
var summarizePath = (input) => filePathOf(input);
|
|
77
|
+
function summarizeRead(input) {
|
|
78
|
+
const p = filePathOf(input);
|
|
79
|
+
if (!p) return void 0;
|
|
80
|
+
const offset = pickNum(input, ["offset", "start_line"]);
|
|
81
|
+
if (offset == null) return p;
|
|
82
|
+
const limit = pickNum(input, ["limit", "end_line"]);
|
|
83
|
+
return limit != null ? `${p}:${offset}-${offset + limit}` : `${p}:${offset}`;
|
|
84
|
+
}
|
|
85
|
+
function summarizeShell(input) {
|
|
86
|
+
const cmd = typeof input === "string" ? input : pickStr(input, ["command", "cmd", "script"]);
|
|
87
|
+
return cmd ? firstLine(cmd) : void 0;
|
|
88
|
+
}
|
|
89
|
+
function summarizeGrep(input) {
|
|
90
|
+
const pattern = pickStr(input, ["pattern", "regex", "query"]);
|
|
91
|
+
const p = filePathOf(input);
|
|
92
|
+
if (pattern) return p ? `${firstLine(pattern, 40)} \xB7 ${p}` : firstLine(pattern);
|
|
93
|
+
return p;
|
|
94
|
+
}
|
|
95
|
+
var sum = (fn) => {
|
|
96
|
+
return (input) => fn(input) ?? "";
|
|
97
|
+
};
|
|
98
|
+
var BUILTIN_KINDS = {
|
|
99
|
+
shell: { label: "\u547D\u4EE4", icon: ICON_TERMINAL, tint: "accent", summarize: sum(summarizeShell) },
|
|
100
|
+
read: { label: "\u8BFB\u53D6", icon: ICON_FILE, tint: "info", summarize: sum(summarizeRead) },
|
|
101
|
+
edit: { label: "\u7F16\u8F91", icon: ICON_FILE_PEN, tint: "warning", summarize: sum(summarizePath) },
|
|
102
|
+
write: { label: "\u5199\u5165", icon: ICON_FILE_PLUS, tint: "warning", summarize: sum(summarizePath) },
|
|
103
|
+
rm: { label: "\u5220\u9664", icon: ICON_TRASH, tint: "error", summarize: sum(summarizePath) },
|
|
104
|
+
grep: { label: "\u641C\u7D22", icon: ICON_SEARCH, tint: "info", summarize: sum(summarizeGrep) },
|
|
105
|
+
glob: { label: "\u5339\u914D", icon: ICON_FOLDER_SEARCH, tint: "info", summarize: sum((i) => pickStr(i, ["pattern", "glob", "path"])) },
|
|
106
|
+
browser: { label: "\u6D4F\u89C8", icon: ICON_GLOBE, tint: "accent", summarize: sum((i) => pickStr(i, ["url", "uri", "target"])) },
|
|
107
|
+
search: { label: "\u68C0\u7D22", icon: ICON_SCAN_SEARCH, tint: "accent", summarize: sum((i) => pickStr(i, ["query", "q", "pattern"])) },
|
|
108
|
+
fetch: { label: "\u8BF7\u6C42", icon: ICON_DOWNLOAD, tint: "info", summarize: sum((i) => pickStr(i, ["url", "uri", "endpoint"])) },
|
|
109
|
+
mcp: { label: "MCP", icon: ICON_BLOCKS, tint: "muted", summarize: sum((i) => pickStr(i, ["tool", "method", "name"])) },
|
|
110
|
+
skill: { label: "\u6280\u80FD", icon: ICON_ZAP, tint: "accent", summarize: sum((i) => pickStr(i, ["skill", "name", "command"])) },
|
|
111
|
+
todo: {
|
|
112
|
+
label: "\u5F85\u529E",
|
|
113
|
+
icon: ICON_LIST,
|
|
114
|
+
tint: "info",
|
|
115
|
+
summarize: sum((input) => {
|
|
116
|
+
const items = input && typeof input === "object" && Array.isArray(input.items) ? input.items : Array.isArray(input) ? input : [];
|
|
117
|
+
const done = items.filter((it) => it?.status === "done").length;
|
|
118
|
+
return items.length > 0 ? `${done}/${items.length} \u5B8C\u6210` : "";
|
|
119
|
+
})
|
|
120
|
+
},
|
|
121
|
+
plan: { label: "\u8BA1\u5212", icon: ICON_LIST_CHECKS, tint: "info" },
|
|
122
|
+
subagent: { label: "\u5B50\u4EE3\u7406", icon: ICON_BOT, tint: "accent", summarize: sum((i) => pickStr(i, ["task", "description", "prompt", "message", "subagent_type"])) },
|
|
123
|
+
note: { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" },
|
|
124
|
+
chart: { label: "\u56FE\u8868", icon: ICON_CHART, tint: "info", summarize: sum((i) => pickStr(i, ["title", "name"])) }
|
|
125
|
+
};
|
|
126
|
+
var kindRegistry = new Map(Object.entries(BUILTIN_KINDS));
|
|
127
|
+
function h(tag, className, text) {
|
|
128
|
+
const node = document.createElement(tag);
|
|
129
|
+
if (className) node.className = className;
|
|
130
|
+
if (text != null) node.textContent = text;
|
|
131
|
+
return node;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/behaviors/kb-core.ts
|
|
135
|
+
var ic = (label, d) => ({ label, icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${d}</svg>` });
|
|
136
|
+
var BUILTIN_SOURCE_TYPES = {
|
|
137
|
+
doc: ic("\u6587\u6863", '<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"/>'),
|
|
138
|
+
web: ic("\u7F51\u9875", '<circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20z"/>'),
|
|
139
|
+
wiki: ic("\u77E5\u8BC6\u5E93", '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>'),
|
|
140
|
+
sheet: ic("\u8868\u683C", '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/>'),
|
|
141
|
+
db: ic("\u6570\u636E\u5E93", '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/>'),
|
|
142
|
+
ticket: ic("\u5DE5\u5355", '<path d="M3 9V7a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2a2 2 0 0 0 0 6v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2a2 2 0 0 0 0-6z"/><path d="M13 5v14"/>'),
|
|
143
|
+
code: ic("\u4EE3\u7801", '<path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/>'),
|
|
144
|
+
api: ic("\u63A5\u53E3", '<path d="M4 7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v3a2 2 0 0 0 0 4v3a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-3a2 2 0 0 0 0-4z"/>'),
|
|
145
|
+
image: ic("\u56FE\u7247", '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-4.6-4.6a2 2 0 0 0-2.8 0L3 21"/>'),
|
|
146
|
+
mail: ic("\u90AE\u4EF6", '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 7L2 7"/>')
|
|
147
|
+
};
|
|
148
|
+
var sourceTypeRegistry = new Map(Object.entries(BUILTIN_SOURCE_TYPES));
|
|
149
|
+
|
|
150
|
+
// src/behaviors/events.ts
|
|
151
|
+
function emitIcen(source, type, detail) {
|
|
152
|
+
return source.dispatchEvent(
|
|
153
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/behaviors/kb-review.ts
|
|
158
|
+
var KIND_LABELS = { trace: "\u8F68\u8FF9", message: "\u6D88\u606F", answer: "\u56DE\u7B54" };
|
|
159
|
+
var fmtSlider = (v) => v.toFixed(1).replace(/\.0$/, "");
|
|
160
|
+
function cloneTask(t) {
|
|
161
|
+
return {
|
|
162
|
+
...t,
|
|
163
|
+
target: { ...t.target },
|
|
164
|
+
scores: t.scores ? t.scores.map((s) => ({ ...s })) : void 0
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function distinctAssignees(tasks) {
|
|
168
|
+
const out = [];
|
|
169
|
+
for (const t of tasks) if (t.assignee && !out.includes(t.assignee)) out.push(t.assignee);
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
function createKbReview(el, opts) {
|
|
173
|
+
if (typeof document === "undefined") {
|
|
174
|
+
const noop = () => void 0;
|
|
175
|
+
return { el, current: () => null, next: () => null, prev: () => null, submit: noop, destroy: noop };
|
|
176
|
+
}
|
|
177
|
+
const host = el;
|
|
178
|
+
host.__icenKbReview?.destroy();
|
|
179
|
+
const state = {
|
|
180
|
+
tasks: opts.queue.map(cloneTask),
|
|
181
|
+
configs: opts.configs,
|
|
182
|
+
assignees: opts.assignees ?? [],
|
|
183
|
+
onAction: opts.onAction,
|
|
184
|
+
filter: "",
|
|
185
|
+
idx: 0,
|
|
186
|
+
keysOpen: false
|
|
187
|
+
};
|
|
188
|
+
let destroyed = false;
|
|
189
|
+
const fire = (action) => {
|
|
190
|
+
state.onAction?.(action);
|
|
191
|
+
};
|
|
192
|
+
const visible = () => state.tasks.filter((t) => !state.filter || t.assignee === state.filter);
|
|
193
|
+
const getScore = (task, name) => task.scores?.find((s) => s.name === name)?.value;
|
|
194
|
+
const setScore = (task, name, value) => {
|
|
195
|
+
if (!task.scores) task.scores = [];
|
|
196
|
+
const entry = task.scores.find((s) => s.name === name);
|
|
197
|
+
if (entry) entry.value = value;
|
|
198
|
+
else task.scores.push({ name, value });
|
|
199
|
+
};
|
|
200
|
+
const emitScore = (task, name, value) => {
|
|
201
|
+
emitIcen(host, "icen:kb-review-score", { taskId: task.id, name, value });
|
|
202
|
+
fire({ type: "score", taskId: task.id, name, value });
|
|
203
|
+
};
|
|
204
|
+
const score = (task, name, value) => {
|
|
205
|
+
setScore(task, name, value);
|
|
206
|
+
emitScore(task, name, value);
|
|
207
|
+
render();
|
|
208
|
+
};
|
|
209
|
+
const assign = (task, assignee) => {
|
|
210
|
+
task.assignee = assignee || void 0;
|
|
211
|
+
emitIcen(host, "icen:kb-review-assign", { taskId: task.id, assignee });
|
|
212
|
+
fire({ type: "assign", taskId: task.id, value: assignee });
|
|
213
|
+
render();
|
|
214
|
+
};
|
|
215
|
+
const selectIdx = (i) => {
|
|
216
|
+
const vis = visible();
|
|
217
|
+
if (!vis.length) {
|
|
218
|
+
state.idx = 0;
|
|
219
|
+
render();
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
const len = vis.length;
|
|
223
|
+
state.idx = (i % len + len) % len;
|
|
224
|
+
const task = vis[state.idx];
|
|
225
|
+
render();
|
|
226
|
+
fire({ type: "select", taskId: task.id });
|
|
227
|
+
return task;
|
|
228
|
+
};
|
|
229
|
+
const next = () => destroyed ? null : selectIdx(state.idx + 1);
|
|
230
|
+
const prev = () => destroyed ? null : selectIdx(state.idx - 1);
|
|
231
|
+
const submit = () => {
|
|
232
|
+
if (destroyed) return;
|
|
233
|
+
const task = visible()[state.idx];
|
|
234
|
+
if (!task) return;
|
|
235
|
+
task.status = "done";
|
|
236
|
+
emitIcen(host, "icen:kb-review-submit", { taskId: task.id });
|
|
237
|
+
fire({ type: "submit", taskId: task.id });
|
|
238
|
+
next();
|
|
239
|
+
};
|
|
240
|
+
const onKeyDown = (e) => {
|
|
241
|
+
if (destroyed) return;
|
|
242
|
+
const target = e.target;
|
|
243
|
+
if (!(target instanceof Element)) return;
|
|
244
|
+
if (!target.closest(".kb-review-card")) return;
|
|
245
|
+
if (target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) return;
|
|
246
|
+
const isRange = target instanceof HTMLInputElement && target.type === "range";
|
|
247
|
+
if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
|
|
248
|
+
if (isRange) return;
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
if (e.key === "ArrowRight") next();
|
|
251
|
+
else prev();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
submit();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (e.key === "?") {
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
state.keysOpen = !state.keysOpen;
|
|
262
|
+
render();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (e.key === "Escape" && state.keysOpen) {
|
|
266
|
+
state.keysOpen = false;
|
|
267
|
+
render();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (!isRange && e.key >= "1" && e.key <= "9") {
|
|
271
|
+
const n = Number(e.key);
|
|
272
|
+
const rubricRow = target.closest(".kb-review-rubric");
|
|
273
|
+
let cfg;
|
|
274
|
+
if (rubricRow) cfg = state.configs.find((c) => c.name === rubricRow.dataset.configName);
|
|
275
|
+
cfg ??= state.configs.find((c) => c.type === "categorical");
|
|
276
|
+
if (!cfg || cfg.type !== "categorical") return;
|
|
277
|
+
const opt = (cfg.categories ?? [])[n - 1];
|
|
278
|
+
const task = visible()[state.idx];
|
|
279
|
+
if (!opt || !task) return;
|
|
280
|
+
e.preventDefault();
|
|
281
|
+
score(task, cfg.name, opt);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
host.addEventListener("keydown", onKeyDown);
|
|
285
|
+
const render = () => {
|
|
286
|
+
if (destroyed || typeof document === "undefined") return;
|
|
287
|
+
const doc = host.ownerDocument;
|
|
288
|
+
const active = doc.activeElement;
|
|
289
|
+
const hadFocus = active instanceof HTMLElement && host.contains(active);
|
|
290
|
+
host.className = "kb-review";
|
|
291
|
+
host.replaceChildren();
|
|
292
|
+
const queueCol = h("div", "kb-review-queue");
|
|
293
|
+
const filterWrap = h("div", "kb-review-filter");
|
|
294
|
+
filterWrap.append(h("span", "kb-review-filter-label", "\u5206\u914D\u4EBA"));
|
|
295
|
+
const filterSel = h("select", "kb-review-filter-select");
|
|
296
|
+
const allOpt = h("option", void 0, "\u5168\u90E8");
|
|
297
|
+
allOpt.value = "";
|
|
298
|
+
filterSel.append(allOpt);
|
|
299
|
+
for (const a of distinctAssignees(state.tasks)) {
|
|
300
|
+
const o = h("option", void 0, a);
|
|
301
|
+
o.value = a;
|
|
302
|
+
filterSel.append(o);
|
|
303
|
+
}
|
|
304
|
+
filterSel.value = state.filter;
|
|
305
|
+
filterSel.addEventListener("change", () => {
|
|
306
|
+
state.filter = filterSel.value;
|
|
307
|
+
state.idx = 0;
|
|
308
|
+
render();
|
|
309
|
+
});
|
|
310
|
+
filterWrap.append(filterSel);
|
|
311
|
+
queueCol.append(filterWrap);
|
|
312
|
+
const vis = visible();
|
|
313
|
+
const listEl = h("div", "kb-review-list");
|
|
314
|
+
if (!vis.length) {
|
|
315
|
+
listEl.append(h("div", "kb-empty", state.filter ? "\u8BE5\u5206\u914D\u4EBA\u4E0B\u6CA1\u6709\u6761\u76EE" : "\u961F\u5217\u4E3A\u7A7A"));
|
|
316
|
+
} else {
|
|
317
|
+
vis.forEach((t, i) => {
|
|
318
|
+
const item = h("button", `kb-review-item${t.status === "done" ? " is-done" : ""}${i === state.idx ? " is-active" : ""}`);
|
|
319
|
+
item.type = "button";
|
|
320
|
+
item.dataset.taskId = t.id;
|
|
321
|
+
const main = h("span", "kb-review-item-main");
|
|
322
|
+
main.append(h("span", "kb-review-item-title", t.target.label || t.target.id));
|
|
323
|
+
item.append(
|
|
324
|
+
h("span", `kb-dot${t.status === "done" ? " is-ok" : ""}`),
|
|
325
|
+
main,
|
|
326
|
+
h("span", "kb-review-item-kind", KIND_LABELS[t.target.kind] ?? t.target.kind),
|
|
327
|
+
h("span", "kb-review-item-assignee", t.assignee ? `@${t.assignee}` : "")
|
|
328
|
+
);
|
|
329
|
+
item.addEventListener("click", () => {
|
|
330
|
+
if (i !== state.idx) selectIdx(i);
|
|
331
|
+
});
|
|
332
|
+
listEl.append(item);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
queueCol.append(listEl);
|
|
336
|
+
const card = h("div", "kb-review-card");
|
|
337
|
+
card.tabIndex = 0;
|
|
338
|
+
const task = vis[state.idx];
|
|
339
|
+
if (!task) {
|
|
340
|
+
card.append(h("div", "kb-empty", "\u6CA1\u6709\u5F85\u5BA1\u67E5\u6761\u76EE"));
|
|
341
|
+
} else {
|
|
342
|
+
const head = h("div", "kb-review-card-head");
|
|
343
|
+
head.append(
|
|
344
|
+
h("span", "kb-review-card-kind", KIND_LABELS[task.target.kind] ?? task.target.kind),
|
|
345
|
+
h("span", "kb-review-card-title", task.target.label || task.target.id)
|
|
346
|
+
);
|
|
347
|
+
if (state.assignees.length) {
|
|
348
|
+
const sel = h("select", "kb-review-assign");
|
|
349
|
+
const placeholder = h("option", void 0, "\u672A\u5206\u914D");
|
|
350
|
+
placeholder.value = "";
|
|
351
|
+
sel.append(placeholder);
|
|
352
|
+
for (const a of state.assignees) {
|
|
353
|
+
const o = h("option", void 0, a);
|
|
354
|
+
o.value = a;
|
|
355
|
+
sel.append(o);
|
|
356
|
+
}
|
|
357
|
+
sel.value = task.assignee ?? "";
|
|
358
|
+
sel.addEventListener("change", () => assign(task, sel.value));
|
|
359
|
+
head.append(sel);
|
|
360
|
+
}
|
|
361
|
+
card.append(head);
|
|
362
|
+
const rubrics = h("div", "kb-review-rubrics");
|
|
363
|
+
for (const cfg of state.configs) {
|
|
364
|
+
const row = h("div", "kb-review-rubric");
|
|
365
|
+
row.dataset.configName = cfg.name;
|
|
366
|
+
const nameEl = h("div", "kb-review-rubric-name", cfg.name);
|
|
367
|
+
if (cfg.description) nameEl.append(h("span", "kb-review-rubric-desc", cfg.description));
|
|
368
|
+
row.append(nameEl);
|
|
369
|
+
if (cfg.type === "categorical") {
|
|
370
|
+
const chips = h("div", "kb-review-chips");
|
|
371
|
+
(cfg.categories ?? []).forEach((opt, oi) => {
|
|
372
|
+
const chip = h("button", `kb-review-chip${getScore(task, cfg.name) === opt ? " is-on" : ""}`);
|
|
373
|
+
chip.type = "button";
|
|
374
|
+
chip.dataset.opt = opt;
|
|
375
|
+
if (oi < 9) chip.append(h("span", "kb-key", String(oi + 1)));
|
|
376
|
+
chip.append(h("span", "kb-review-chip-label", opt));
|
|
377
|
+
chip.addEventListener("click", () => score(task, cfg.name, opt));
|
|
378
|
+
chips.append(chip);
|
|
379
|
+
});
|
|
380
|
+
row.append(chips);
|
|
381
|
+
} else {
|
|
382
|
+
const cur = typeof getScore(task, cfg.name) === "number" ? getScore(task, cfg.name) : 5;
|
|
383
|
+
const sliderRow = h("div", "kb-review-slider-row");
|
|
384
|
+
const input = h("input", "kb-review-slider");
|
|
385
|
+
input.type = "range";
|
|
386
|
+
input.min = "0";
|
|
387
|
+
input.max = "10";
|
|
388
|
+
input.step = "0.5";
|
|
389
|
+
input.value = String(cur);
|
|
390
|
+
input.setAttribute("aria-label", cfg.name);
|
|
391
|
+
const val = h("span", "kb-num kb-review-slider-value", fmtSlider(cur));
|
|
392
|
+
input.addEventListener("input", () => {
|
|
393
|
+
const v = Number(input.value);
|
|
394
|
+
setScore(task, cfg.name, v);
|
|
395
|
+
val.textContent = fmtSlider(v);
|
|
396
|
+
});
|
|
397
|
+
input.addEventListener("change", () => emitScore(task, cfg.name, Number(input.value)));
|
|
398
|
+
sliderRow.append(input, val);
|
|
399
|
+
row.append(sliderRow);
|
|
400
|
+
}
|
|
401
|
+
rubrics.append(row);
|
|
402
|
+
}
|
|
403
|
+
card.append(rubrics);
|
|
404
|
+
const foot = h("div", "kb-review-foot");
|
|
405
|
+
const submitBtn = h("button", "kb-review-submit");
|
|
406
|
+
submitBtn.type = "button";
|
|
407
|
+
submitBtn.append(h("span", void 0, "\u5B8C\u6210\u5E76\u4E0B\u4E00\u6761"), h("span", "kb-key", "\u2318\u21B5"));
|
|
408
|
+
submitBtn.addEventListener("click", () => submit());
|
|
409
|
+
foot.append(submitBtn, h("span", "kb-review-hint", "? \u5FEB\u6377\u952E"));
|
|
410
|
+
card.append(foot);
|
|
411
|
+
}
|
|
412
|
+
const keys = h("div", "kb-review-keys");
|
|
413
|
+
keys.hidden = !state.keysOpen;
|
|
414
|
+
const KEY_ROWS = [
|
|
415
|
+
["\u2190/\u2192", "\u5207\u6362\u6761\u76EE"],
|
|
416
|
+
["1\u20139", "\u9009\u62E9\u9009\u9879"],
|
|
417
|
+
["\u2318/Ctrl \u21B5", "\u5B8C\u6210\u5E76\u4E0B\u4E00\u6761"],
|
|
418
|
+
["?", "\u663E\u793A/\u9690\u85CF\u672C\u63D0\u793A"],
|
|
419
|
+
["Esc", "\u5173\u95ED\u63D0\u793A"]
|
|
420
|
+
];
|
|
421
|
+
for (const [k, label] of KEY_ROWS) {
|
|
422
|
+
const r = h("div", "kb-review-keys-row");
|
|
423
|
+
r.append(h("span", "kb-key", k), h("span", void 0, label));
|
|
424
|
+
keys.append(r);
|
|
425
|
+
}
|
|
426
|
+
card.append(keys);
|
|
427
|
+
host.append(queueCol, card);
|
|
428
|
+
if (hadFocus) card.focus();
|
|
429
|
+
};
|
|
430
|
+
const destroy = () => {
|
|
431
|
+
if (destroyed) return;
|
|
432
|
+
destroyed = true;
|
|
433
|
+
host.removeEventListener("keydown", onKeyDown);
|
|
434
|
+
host.replaceChildren();
|
|
435
|
+
host.classList.remove("kb-review");
|
|
436
|
+
delete host.__icenKbReview;
|
|
437
|
+
};
|
|
438
|
+
host.__icenKbReview = { destroy };
|
|
439
|
+
render();
|
|
440
|
+
return {
|
|
441
|
+
el: host,
|
|
442
|
+
current: () => destroyed ? null : visible()[state.idx] ?? null,
|
|
443
|
+
next,
|
|
444
|
+
prev,
|
|
445
|
+
submit,
|
|
446
|
+
destroy
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
export {
|
|
450
|
+
createKbReview
|
|
451
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { KbSandboxMessage } from './kb-core.js';
|
|
2
|
+
import './ai-core.js';
|
|
3
|
+
|
|
4
|
+
interface KbSandboxOptions {
|
|
5
|
+
/** 通信观测双通道(与 icen:kb-sandbox-message 事件同 payload) */
|
|
6
|
+
onMessage?: (msg: {
|
|
7
|
+
channel: 'in' | 'out';
|
|
8
|
+
payload: KbSandboxMessage;
|
|
9
|
+
}) => void;
|
|
10
|
+
/** 入站 origin 白名单(不传 = 只校验 event.source;沙箱帧 origin 恒为 'null') */
|
|
11
|
+
allowedOrigins?: string[];
|
|
12
|
+
/** 单次 call() 超时(默认 8000ms) */
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
/** 'ui://' 声明式资源解析器:返回 html 字符串(同步或 Promise);null/undefined/抛错 = 拒绝 */
|
|
15
|
+
resolve?: (uri: string) => string | null | Promise<string | null>;
|
|
16
|
+
}
|
|
17
|
+
interface KbSandboxHandle {
|
|
18
|
+
/** 载入应用:'ui://' 声明式资源 / html 片段 / URL(opts.srcDoc 强制按 html 处理) */
|
|
19
|
+
load: (uriOrHtml: string, opts?: {
|
|
20
|
+
srcDoc?: boolean;
|
|
21
|
+
}) => void;
|
|
22
|
+
/** 调用子帧方法(JSON-RPC 请求→响应);超时 / destroy / 重载时 reject */
|
|
23
|
+
call: (name: string, args?: unknown) => Promise<unknown>;
|
|
24
|
+
/** 摘监听、清 pending、移除 iframe;可重复调用 */
|
|
25
|
+
destroy: () => void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 创建 MCP App 沙箱容器:iframe sandbox="allow-scripts" + JSON-RPC 2.0 双向桥。
|
|
29
|
+
* 三态样式(idle/loading/ready/error)由 data-state 驱动(纯 CSS,见 kb-agent.css)。
|
|
30
|
+
*/
|
|
31
|
+
declare function createKbSandbox(el: HTMLElement, opts?: KbSandboxOptions): KbSandboxHandle;
|
|
32
|
+
|
|
33
|
+
export { type KbSandboxHandle, type KbSandboxOptions, createKbSandbox };
|