@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,587 @@
|
|
|
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
|
+
var KB_FILTER_OPS = ["eq", "ne", "gt", "gte", "lt", "lte", "in", "contains", "exists"];
|
|
150
|
+
function normalizeFilterNode(raw) {
|
|
151
|
+
if (!raw || typeof raw !== "object") return null;
|
|
152
|
+
const r = raw;
|
|
153
|
+
if (r.type === "group") {
|
|
154
|
+
const children = Array.isArray(r.children) ? r.children.map(normalizeFilterNode).filter((c) => c !== null) : [];
|
|
155
|
+
return children.length ? { type: "group", op: r.op === "or" ? "or" : "and", children } : null;
|
|
156
|
+
}
|
|
157
|
+
if (r.type === "rule") {
|
|
158
|
+
const field = String(r.field ?? "").trim();
|
|
159
|
+
const op = KB_FILTER_OPS.includes(r.op) ? r.op : null;
|
|
160
|
+
if (!field || !op) return null;
|
|
161
|
+
let value;
|
|
162
|
+
if (Array.isArray(r.value)) value = r.value.map((v) => typeof v === "number" ? v : String(v));
|
|
163
|
+
else if (typeof r.value === "number" || typeof r.value === "string" || typeof r.value === "boolean") value = r.value;
|
|
164
|
+
return { type: "rule", field, op, value };
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
var MONGO_OP = {
|
|
169
|
+
eq: "$eq",
|
|
170
|
+
ne: "$ne",
|
|
171
|
+
gt: "$gt",
|
|
172
|
+
gte: "$gte",
|
|
173
|
+
lt: "$lt",
|
|
174
|
+
lte: "$lte",
|
|
175
|
+
in: "$in",
|
|
176
|
+
contains: "$regex",
|
|
177
|
+
exists: "$exists"
|
|
178
|
+
};
|
|
179
|
+
function filterToMongo(node) {
|
|
180
|
+
const n = normalizeFilterNode(node);
|
|
181
|
+
if (!n) return null;
|
|
182
|
+
if (n.type === "group") {
|
|
183
|
+
const key = n.op === "or" ? "$or" : "$and";
|
|
184
|
+
return { [key]: n.children.map((c) => filterToMongo(c) ?? {}) };
|
|
185
|
+
}
|
|
186
|
+
if (n.op === "exists") return { [n.field]: { $exists: n.value !== false } };
|
|
187
|
+
if (n.op === "contains") {
|
|
188
|
+
const v = Array.isArray(n.value) ? String(n.value[0] ?? "") : String(n.value ?? "");
|
|
189
|
+
return { [n.field]: { $regex: v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), $options: "i" } };
|
|
190
|
+
}
|
|
191
|
+
const mk = MONGO_OP[n.op];
|
|
192
|
+
if (!mk) return null;
|
|
193
|
+
const value = Array.isArray(n.value) ? n.value : n.value ?? "";
|
|
194
|
+
return { [n.field]: { [mk]: value } };
|
|
195
|
+
}
|
|
196
|
+
function odataQuote(v) {
|
|
197
|
+
return typeof v === "number" ? String(v) : `'${String(v).replace(/'/g, "''")}'`;
|
|
198
|
+
}
|
|
199
|
+
var ODATA_OP = {
|
|
200
|
+
eq: "eq",
|
|
201
|
+
ne: "ne",
|
|
202
|
+
gt: "gt",
|
|
203
|
+
gte: "ge",
|
|
204
|
+
lt: "lt",
|
|
205
|
+
lte: "le",
|
|
206
|
+
in: "in",
|
|
207
|
+
contains: "contains",
|
|
208
|
+
exists: "exists"
|
|
209
|
+
};
|
|
210
|
+
function filterToOData(node) {
|
|
211
|
+
const n = normalizeFilterNode(node);
|
|
212
|
+
if (!n) return "";
|
|
213
|
+
if (n.type === "group") {
|
|
214
|
+
const join = n.op === "or" ? " or " : " and ";
|
|
215
|
+
return n.children.map((c) => `(${filterToOData(c)})`).join(join);
|
|
216
|
+
}
|
|
217
|
+
if (n.op === "contains") {
|
|
218
|
+
const v = Array.isArray(n.value) ? String(n.value[0] ?? "") : String(n.value ?? "");
|
|
219
|
+
return `contains(${n.field},${odataQuote(v)})`;
|
|
220
|
+
}
|
|
221
|
+
if (n.op === "in") {
|
|
222
|
+
const list = Array.isArray(n.value) ? n.value : [typeof n.value === "number" ? n.value : String(n.value ?? "")];
|
|
223
|
+
return `${n.field} in (${list.map(odataQuote).join(",")})`;
|
|
224
|
+
}
|
|
225
|
+
if (n.op === "exists") return n.value === false ? `${n.field} eq null` : `${n.field} ne null`;
|
|
226
|
+
const raw = Array.isArray(n.value) ? n.value[0] : n.value;
|
|
227
|
+
const value = typeof raw === "number" ? raw : String(raw ?? "");
|
|
228
|
+
return `${n.field} ${ODATA_OP[n.op]} ${odataQuote(value)}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/behaviors/events.ts
|
|
232
|
+
function emitIcen(source, type, detail) {
|
|
233
|
+
return source.dispatchEvent(
|
|
234
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/behaviors/kb-filter.ts
|
|
239
|
+
var MAX_GROUP_DEPTH = 2;
|
|
240
|
+
var OP_LABELS = {
|
|
241
|
+
eq: "\u7B49\u4E8E",
|
|
242
|
+
ne: "\u4E0D\u7B49\u4E8E",
|
|
243
|
+
gt: "\u5927\u4E8E",
|
|
244
|
+
gte: "\u5927\u4E8E\u7B49\u4E8E",
|
|
245
|
+
lt: "\u5C0F\u4E8E",
|
|
246
|
+
lte: "\u5C0F\u4E8E\u7B49\u4E8E",
|
|
247
|
+
in: "\u5C5E\u4E8E",
|
|
248
|
+
contains: "\u5305\u542B",
|
|
249
|
+
exists: "\u5B58\u5728"
|
|
250
|
+
};
|
|
251
|
+
var fieldListSeq = 0;
|
|
252
|
+
function numOrStr(s) {
|
|
253
|
+
return /^-?\d+(\.\d+)?$/.test(s) ? Number(s) : s;
|
|
254
|
+
}
|
|
255
|
+
function parseValue(raw, op) {
|
|
256
|
+
if (op === "exists") return true;
|
|
257
|
+
const t = raw.trim();
|
|
258
|
+
if (!t) return void 0;
|
|
259
|
+
if (op === "in") {
|
|
260
|
+
const list = t.split(/[,,]/).map((s) => s.trim()).filter(Boolean).map(numOrStr);
|
|
261
|
+
return list.length ? list : void 0;
|
|
262
|
+
}
|
|
263
|
+
return numOrStr(t);
|
|
264
|
+
}
|
|
265
|
+
function formatValue(value) {
|
|
266
|
+
if (Array.isArray(value)) return value.map(String).join(", ");
|
|
267
|
+
if (value == null) return "";
|
|
268
|
+
if (typeof value === "boolean") return "";
|
|
269
|
+
return String(value);
|
|
270
|
+
}
|
|
271
|
+
function copyText(text) {
|
|
272
|
+
try {
|
|
273
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
274
|
+
void navigator.clipboard.writeText(text);
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
if (typeof document === "undefined") return false;
|
|
278
|
+
const ta = document.createElement("textarea");
|
|
279
|
+
ta.value = text;
|
|
280
|
+
ta.setAttribute("readonly", "");
|
|
281
|
+
ta.style.position = "fixed";
|
|
282
|
+
ta.style.opacity = "0";
|
|
283
|
+
document.body.appendChild(ta);
|
|
284
|
+
ta.select();
|
|
285
|
+
const ok = document.execCommand("copy");
|
|
286
|
+
ta.remove();
|
|
287
|
+
return ok;
|
|
288
|
+
} catch {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function createKbFilter(el, opts = {}) {
|
|
293
|
+
if (typeof document === "undefined" || !el) {
|
|
294
|
+
const stub = {
|
|
295
|
+
get: () => null,
|
|
296
|
+
set: () => {
|
|
297
|
+
},
|
|
298
|
+
serialize: () => "",
|
|
299
|
+
destroy: () => {
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
return stub;
|
|
303
|
+
}
|
|
304
|
+
const marked = el;
|
|
305
|
+
if (marked.__icenKbFilter) return marked.__icenKbFilter;
|
|
306
|
+
const ctrl = new AbortController();
|
|
307
|
+
const listen = (type, fn) => {
|
|
308
|
+
el.addEventListener(type, fn, { signal: ctrl.signal });
|
|
309
|
+
};
|
|
310
|
+
let dslKind = "mongo";
|
|
311
|
+
let destroyed = false;
|
|
312
|
+
let copyTimer = null;
|
|
313
|
+
el.classList.add("kb-filter");
|
|
314
|
+
el.dataset.kbFilter = "";
|
|
315
|
+
el.textContent = "";
|
|
316
|
+
const datalistId = `kb-filter-fields-${++fieldListSeq}`;
|
|
317
|
+
const datalist = h("datalist");
|
|
318
|
+
datalist.id = datalistId;
|
|
319
|
+
for (const f of opts.fields ?? []) datalist.appendChild(h("option", void 0, f));
|
|
320
|
+
const treeBox = h("div", "kb-filter-tree");
|
|
321
|
+
const dslText = h("code", "kb-filter-dsl-text kb-meta");
|
|
322
|
+
const tabMongo = h("button", "kb-filter-dsl-tab is-active", "Mongo");
|
|
323
|
+
tabMongo.type = "button";
|
|
324
|
+
tabMongo.dataset.dsl = "mongo";
|
|
325
|
+
const tabOdata = h("button", "kb-filter-dsl-tab", "OData");
|
|
326
|
+
tabOdata.type = "button";
|
|
327
|
+
tabOdata.dataset.dsl = "odata";
|
|
328
|
+
const dslCopy = h("button", "kb-filter-act kb-filter-dsl-copy", "\u590D\u5236");
|
|
329
|
+
dslCopy.type = "button";
|
|
330
|
+
dslCopy.title = "\u590D\u5236 DSL";
|
|
331
|
+
const dslBar = h("div", "kb-filter-dsl-bar");
|
|
332
|
+
dslBar.append(tabMongo, tabOdata, dslCopy);
|
|
333
|
+
const dslBox = h("div", "kb-filter-dsl");
|
|
334
|
+
dslBox.append(dslBar, dslText);
|
|
335
|
+
el.append(treeBox, dslBox, datalist);
|
|
336
|
+
function buildRuleRow(rule) {
|
|
337
|
+
const row = h("div", "kb-filter-rule");
|
|
338
|
+
const field = h("input", "kb-filter-field");
|
|
339
|
+
field.type = "text";
|
|
340
|
+
field.value = rule.field;
|
|
341
|
+
field.placeholder = "\u5B57\u6BB5";
|
|
342
|
+
field.setAttribute("list", datalistId);
|
|
343
|
+
field.setAttribute("aria-label", "\u8FC7\u6EE4\u5B57\u6BB5");
|
|
344
|
+
const opSel = h("select", "kb-filter-op");
|
|
345
|
+
opSel.setAttribute("aria-label", "\u64CD\u4F5C\u7B26");
|
|
346
|
+
for (const op of KB_FILTER_OPS) {
|
|
347
|
+
const option = h("option", void 0, OP_LABELS[op]);
|
|
348
|
+
option.value = op;
|
|
349
|
+
opSel.appendChild(option);
|
|
350
|
+
}
|
|
351
|
+
opSel.value = rule.op;
|
|
352
|
+
const value = h("input", "kb-filter-value");
|
|
353
|
+
value.type = "text";
|
|
354
|
+
value.value = formatValue(rule.value);
|
|
355
|
+
value.placeholder = rule.op === "in" ? "\u503C 1, \u503C 2\uFF08\u9017\u53F7\u5206\u9694\uFF09" : "\u503C";
|
|
356
|
+
value.setAttribute("aria-label", "\u8FC7\u6EE4\u503C");
|
|
357
|
+
if (rule.op === "exists") value.disabled = true;
|
|
358
|
+
const del = h("button", "kb-filter-act is-danger", "\xD7");
|
|
359
|
+
del.type = "button";
|
|
360
|
+
del.dataset.act = "del-rule";
|
|
361
|
+
del.title = "\u5220\u9664\u89C4\u5219";
|
|
362
|
+
del.setAttribute("aria-label", "\u5220\u9664\u89C4\u5219");
|
|
363
|
+
row.append(field, opSel, value, del);
|
|
364
|
+
return row;
|
|
365
|
+
}
|
|
366
|
+
function buildGroup(node, depth) {
|
|
367
|
+
const box = h("div", "kb-filter-group");
|
|
368
|
+
box.dataset.depth = String(depth);
|
|
369
|
+
const head = h("div", "kb-filter-head");
|
|
370
|
+
const opBtn = h("button", "kb-filter-op-toggle", node.op === "or" ? "OR" : "AND");
|
|
371
|
+
opBtn.type = "button";
|
|
372
|
+
opBtn.dataset.op = node.op;
|
|
373
|
+
opBtn.classList.toggle("is-or", node.op === "or");
|
|
374
|
+
opBtn.title = "\u5207\u6362 AND / OR";
|
|
375
|
+
opBtn.setAttribute("aria-label", `\u7EC4\u5408\u65B9\u5F0F ${node.op === "or" ? "\u6216" : "\u4E14"}`);
|
|
376
|
+
const addRule = h("button", "kb-filter-act", "+ \u89C4\u5219");
|
|
377
|
+
addRule.type = "button";
|
|
378
|
+
addRule.dataset.act = "add-rule";
|
|
379
|
+
const addGroup = h("button", "kb-filter-act", "+ \u7EC4");
|
|
380
|
+
addGroup.type = "button";
|
|
381
|
+
addGroup.dataset.act = "add-group";
|
|
382
|
+
if (depth >= MAX_GROUP_DEPTH) addGroup.disabled = true;
|
|
383
|
+
head.append(opBtn, addRule, addGroup);
|
|
384
|
+
if (depth > 0) {
|
|
385
|
+
const delGroup = h("button", "kb-filter-act is-danger", "\u5220\u9664\u7EC4");
|
|
386
|
+
delGroup.type = "button";
|
|
387
|
+
delGroup.dataset.act = "del-group";
|
|
388
|
+
head.appendChild(delGroup);
|
|
389
|
+
}
|
|
390
|
+
const body = h("div", "kb-filter-body");
|
|
391
|
+
for (const child of node.children) {
|
|
392
|
+
body.appendChild(child.type === "rule" ? buildRuleRow(child) : buildGroup(child, depth + 1));
|
|
393
|
+
}
|
|
394
|
+
box.append(head, body);
|
|
395
|
+
return box;
|
|
396
|
+
}
|
|
397
|
+
function readGroup(groupEl) {
|
|
398
|
+
const opBtn = groupEl.querySelector(":scope > .kb-filter-head > .kb-filter-op-toggle");
|
|
399
|
+
const op = opBtn instanceof HTMLElement && opBtn.dataset.op === "or" ? "or" : "and";
|
|
400
|
+
const children = [];
|
|
401
|
+
const body = groupEl.querySelector(":scope > .kb-filter-body");
|
|
402
|
+
if (body) {
|
|
403
|
+
for (const child of Array.from(body.children)) {
|
|
404
|
+
if (!(child instanceof HTMLElement)) continue;
|
|
405
|
+
if (child.classList.contains("kb-filter-rule")) {
|
|
406
|
+
const fieldEl = child.querySelector(":scope > .kb-filter-field");
|
|
407
|
+
const opEl = child.querySelector(":scope > .kb-filter-op");
|
|
408
|
+
const valueEl = child.querySelector(":scope > .kb-filter-value");
|
|
409
|
+
const field = fieldEl instanceof HTMLInputElement ? fieldEl.value.trim() : "";
|
|
410
|
+
const op2 = opEl instanceof HTMLSelectElement && KB_FILTER_OPS.includes(opEl.value) ? opEl.value : "eq";
|
|
411
|
+
const rawValue = valueEl instanceof HTMLInputElement ? valueEl.value : "";
|
|
412
|
+
children.push({ type: "rule", field, op: op2, value: parseValue(rawValue, op2) });
|
|
413
|
+
} else if (child.classList.contains("kb-filter-group")) {
|
|
414
|
+
children.push(readGroup(child));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return { type: "group", op, children };
|
|
419
|
+
}
|
|
420
|
+
function rootNode() {
|
|
421
|
+
const rootEl = treeBox.querySelector(":scope > .kb-filter-group");
|
|
422
|
+
return rootEl instanceof HTMLElement ? readGroup(rootEl) : { type: "group", op: "and", children: [] };
|
|
423
|
+
}
|
|
424
|
+
function collectFields(node, out) {
|
|
425
|
+
if (node.type === "rule") {
|
|
426
|
+
if (!node.field) out.empty = true;
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
for (const c of node.children) collectFields(c, out);
|
|
430
|
+
}
|
|
431
|
+
function currentNode() {
|
|
432
|
+
return normalizeFilterNode(rootNode());
|
|
433
|
+
}
|
|
434
|
+
function currentValid(node) {
|
|
435
|
+
const flag = { empty: false };
|
|
436
|
+
collectFields(node, flag);
|
|
437
|
+
return !flag.empty;
|
|
438
|
+
}
|
|
439
|
+
function serializeOf(node, kind) {
|
|
440
|
+
if (kind === "odata") return filterToOData(node);
|
|
441
|
+
return JSON.stringify(filterToMongo(node));
|
|
442
|
+
}
|
|
443
|
+
function syncDsl() {
|
|
444
|
+
tabMongo.classList.toggle("is-active", dslKind === "mongo");
|
|
445
|
+
tabOdata.classList.toggle("is-active", dslKind === "odata");
|
|
446
|
+
dslText.textContent = serializeOf(currentNode(), dslKind) || "\uFF08\u65E0\u8FC7\u6EE4\uFF09";
|
|
447
|
+
}
|
|
448
|
+
function notify() {
|
|
449
|
+
const raw = rootNode();
|
|
450
|
+
const valid = currentValid(raw);
|
|
451
|
+
const node = normalizeFilterNode(raw);
|
|
452
|
+
syncDsl();
|
|
453
|
+
if (destroyed) return;
|
|
454
|
+
emitIcen(el, "icen:kb-filter-change", { node, valid });
|
|
455
|
+
opts.onChange?.(node, valid);
|
|
456
|
+
}
|
|
457
|
+
function applyNode(node) {
|
|
458
|
+
const norm = normalizeFilterNode(node);
|
|
459
|
+
treeBox.textContent = "";
|
|
460
|
+
if (!norm) {
|
|
461
|
+
treeBox.appendChild(buildGroup({ type: "group", op: "and", children: [] }, 0));
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (norm.type === "rule") {
|
|
465
|
+
treeBox.appendChild(buildGroup({ type: "group", op: "and", children: [norm] }, 0));
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
treeBox.appendChild(buildGroup(norm, 0));
|
|
469
|
+
}
|
|
470
|
+
applyNode(opts.node ?? null);
|
|
471
|
+
listen("click", (ev) => {
|
|
472
|
+
const target = ev.target;
|
|
473
|
+
if (!(target instanceof Element)) return;
|
|
474
|
+
const tab = target.closest(".kb-filter-dsl-tab");
|
|
475
|
+
if (tab instanceof HTMLElement) {
|
|
476
|
+
const kind = tab.dataset.dsl === "odata" ? "odata" : "mongo";
|
|
477
|
+
if (kind !== dslKind) {
|
|
478
|
+
dslKind = kind;
|
|
479
|
+
syncDsl();
|
|
480
|
+
}
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (target.closest(".kb-filter-dsl-copy")) {
|
|
484
|
+
if (!destroyed && copyText(dslText.textContent === "\uFF08\u65E0\u8FC7\u6EE4\uFF09" ? "" : dslText.textContent ?? "")) {
|
|
485
|
+
dslCopy.classList.add("is-copied");
|
|
486
|
+
dslCopy.textContent = "\u5DF2\u590D\u5236";
|
|
487
|
+
if (copyTimer) clearTimeout(copyTimer);
|
|
488
|
+
copyTimer = setTimeout(() => {
|
|
489
|
+
if (destroyed) return;
|
|
490
|
+
dslCopy.classList.remove("is-copied");
|
|
491
|
+
dslCopy.textContent = "\u590D\u5236";
|
|
492
|
+
}, 1200);
|
|
493
|
+
}
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const actBtn = target.closest(".kb-filter-act");
|
|
497
|
+
if (!(actBtn instanceof HTMLButtonElement) || actBtn.disabled) return;
|
|
498
|
+
const act = actBtn.dataset.act;
|
|
499
|
+
if (!act) return;
|
|
500
|
+
if (act === "add-rule" || act === "add-group") {
|
|
501
|
+
const groupEl = actBtn.closest(".kb-filter-group");
|
|
502
|
+
if (!(groupEl instanceof HTMLElement)) return;
|
|
503
|
+
const body = groupEl.querySelector(":scope > .kb-filter-body");
|
|
504
|
+
if (!body) return;
|
|
505
|
+
if (act === "add-rule") {
|
|
506
|
+
body.appendChild(buildRuleRow({ type: "rule", field: "", op: "eq", value: void 0 }));
|
|
507
|
+
} else {
|
|
508
|
+
const depth = Number(groupEl.dataset.depth) || 0;
|
|
509
|
+
body.appendChild(buildGroup({ type: "group", op: "and", children: [] }, depth + 1));
|
|
510
|
+
}
|
|
511
|
+
notify();
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (act === "del-rule") {
|
|
515
|
+
const row = actBtn.closest(".kb-filter-rule");
|
|
516
|
+
row?.remove();
|
|
517
|
+
notify();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (act === "del-group") {
|
|
521
|
+
const groupEl = actBtn.closest(".kb-filter-group");
|
|
522
|
+
if (groupEl instanceof HTMLElement && (Number(groupEl.dataset.depth) || 0) > 0) {
|
|
523
|
+
groupEl.remove();
|
|
524
|
+
notify();
|
|
525
|
+
}
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
listen("click", (ev) => {
|
|
530
|
+
const target = ev.target;
|
|
531
|
+
if (!(target instanceof Element)) return;
|
|
532
|
+
const opBtn = target.closest(".kb-filter-op-toggle");
|
|
533
|
+
if (!(opBtn instanceof HTMLButtonElement)) return;
|
|
534
|
+
const nowOr = opBtn.dataset.op !== "or";
|
|
535
|
+
opBtn.dataset.op = nowOr ? "or" : "and";
|
|
536
|
+
opBtn.textContent = nowOr ? "OR" : "AND";
|
|
537
|
+
opBtn.classList.toggle("is-or", nowOr);
|
|
538
|
+
opBtn.setAttribute("aria-label", `\u7EC4\u5408\u65B9\u5F0F ${nowOr ? "\u6216" : "\u4E14"}`);
|
|
539
|
+
notify();
|
|
540
|
+
});
|
|
541
|
+
listen("input", (ev) => {
|
|
542
|
+
const target = ev.target;
|
|
543
|
+
if (!(target instanceof HTMLInputElement)) return;
|
|
544
|
+
if (target.classList.contains("kb-filter-field") || target.classList.contains("kb-filter-value")) {
|
|
545
|
+
notify();
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
listen("change", (ev) => {
|
|
549
|
+
const target = ev.target;
|
|
550
|
+
if (!(target instanceof HTMLSelectElement) || !target.classList.contains("kb-filter-op")) return;
|
|
551
|
+
const row = target.closest(".kb-filter-rule");
|
|
552
|
+
const valueEl = row?.querySelector(":scope > .kb-filter-value");
|
|
553
|
+
if (!(valueEl instanceof HTMLInputElement)) return;
|
|
554
|
+
const op = KB_FILTER_OPS.includes(target.value) ? target.value : "eq";
|
|
555
|
+
valueEl.disabled = op === "exists";
|
|
556
|
+
valueEl.placeholder = op === "in" ? "\u503C 1, \u503C 2\uFF08\u9017\u53F7\u5206\u9694\uFF09" : op === "exists" ? "\uFF08\u65E0\u9700\u503C\uFF09" : "\u503C";
|
|
557
|
+
notify();
|
|
558
|
+
});
|
|
559
|
+
syncDsl();
|
|
560
|
+
const handle = {
|
|
561
|
+
get() {
|
|
562
|
+
return currentNode();
|
|
563
|
+
},
|
|
564
|
+
set(node) {
|
|
565
|
+
applyNode(node);
|
|
566
|
+
notify();
|
|
567
|
+
},
|
|
568
|
+
serialize(kind) {
|
|
569
|
+
return serializeOf(currentNode(), kind);
|
|
570
|
+
},
|
|
571
|
+
destroy() {
|
|
572
|
+
if (destroyed) return;
|
|
573
|
+
destroyed = true;
|
|
574
|
+
ctrl.abort();
|
|
575
|
+
if (copyTimer) clearTimeout(copyTimer);
|
|
576
|
+
el.classList.remove("kb-filter");
|
|
577
|
+
delete el.dataset.kbFilter;
|
|
578
|
+
el.textContent = "";
|
|
579
|
+
delete el.__icenKbFilter;
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
marked.__icenKbFilter = handle;
|
|
583
|
+
return handle;
|
|
584
|
+
}
|
|
585
|
+
export {
|
|
586
|
+
createKbFilter
|
|
587
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { KbGapQuery } from './kb-core.js';
|
|
2
|
+
import './ai-core.js';
|
|
3
|
+
|
|
4
|
+
/** 趋势单点(label 如 '10-01',value 为当日未命中次数) */
|
|
5
|
+
interface KbGapTrendPoint {
|
|
6
|
+
label: string;
|
|
7
|
+
value: number;
|
|
8
|
+
}
|
|
9
|
+
/** renderKbGap 入参模型 */
|
|
10
|
+
interface KbGapModel {
|
|
11
|
+
queries: KbGapQuery[];
|
|
12
|
+
trend?: KbGapTrendPoint[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 渲染无答案分析视图(快照:整体替换 el 内容)。返回挂载容器 el。
|
|
16
|
+
* queries 为空时渲染空态「没有未命中查询——覆盖良好」(无死路:覆盖良好也是一等状态)。
|
|
17
|
+
*/
|
|
18
|
+
declare function renderKbGap(el: HTMLElement, model: KbGapModel): HTMLElement;
|
|
19
|
+
/**
|
|
20
|
+
* 初始化无答案分析交互:document 级 click 委托 → 行内行动钮派发
|
|
21
|
+
* icen:kb-gap-action { query, action }。幂等(重复调用安全);返回销毁函数
|
|
22
|
+
* (解绑委托并复位幂等标记,销毁后可重新 init)。委托挂 document 级:动态插入的
|
|
23
|
+
* .kb-gap 自动接管(root 参数仅保持 kb 族 API 对称)。SSR 下 no-op。
|
|
24
|
+
*/
|
|
25
|
+
declare function initKbGap(root?: ParentNode): () => void;
|
|
26
|
+
|
|
27
|
+
export { type KbGapModel, type KbGapTrendPoint, initKbGap, renderKbGap };
|