@ohos-cpf/3rdloop 0.0.4 → 0.0.6
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/README.md +119 -128
- package/lib/cli.js +25 -0
- package/lib/serve.js +280 -0
- package/lib/update.js +46 -6
- package/lib/web-ext.js +454 -0
- package/lib/web.js +664 -0
- package/package.json +2 -1
- package/vendor/Server/CLI/opencode/index.js +2 -2
- package/vendor/Server/DbUse/StorageManager.js +7 -1
- package/vendor/Server/Routes/controllers/FlexRunnerController.js +92 -0
- package/vendor/Server/Routes/routes/flexrunner.js +2 -0
- package/vendor/Server/Skills/arkts-code-use/SKILL.md +270 -0
- package/vendor/Server/Skills/arkts-code-use/assets/TEMPLATES.md +367 -0
- package/vendor/Server/Skills/arkts-code-use/references/API_VERIFICATION.md +144 -0
- package/vendor/Server/Skills/arkts-code-use/references/ARKTS_RULES.md +240 -0
- package/vendor/Server/Skills/arkts-code-use/references/CODE_PATTERNS.md +431 -0
- package/vendor/Server/Skills/arkts-code-use/references/SYNTAX_CHECK_GUIDE.md +164 -0
- package/vendor/Server/Skills/arkts-code-use/scripts/verify-arkts.cjs +428 -0
- package/vendor/Server/Skills/gitcode-repo-fork/SKILL.md +310 -0
- package/vendor/Server/Skills/gitcode-repo-fork/assets/FORK_REPORT_TEMPLATE.md +113 -0
- package/vendor/Server/Skills/gitcode-repo-fork/references/FORK_DECISION_GUIDE.md +124 -0
- package/vendor/Server/Skills/gitcode-repo-fork/references/GITCODE_FORK_API.md +95 -0
- package/vendor/Server/Skills/gitcode-repo-fork/scripts/gitcode-fork.cjs +285 -0
- package/vendor/VERSION +3 -3
- package/web/css/arktslibrarycheck.css +322 -0
- package/web/css/codecheck.css +464 -0
- package/web/css/flutterlibrarycheck.css +322 -0
- package/web/css/knowledge.css +332 -0
- package/web/css/loop.css +578 -0
- package/web/css/md-reader.css +240 -0
- package/web/css/rnlibrarycheck.css +322 -0
- package/web/css/theme.css +702 -0
- package/web/index.html +713 -0
- package/web/js/arktslibrarycheck.js +1495 -0
- package/web/js/codecheck.js +1098 -0
- package/web/js/flutterlibrarycheck.js +1447 -0
- package/web/js/health.js +69 -0
- package/web/js/knowledge.js +358 -0
- package/web/js/loop.js +1102 -0
- package/web/js/md-reader.js +435 -0
- package/web/js/navigation.js +238 -0
- package/web/js/rnlibrarycheck.js +1462 -0
- package/web/js/stats.js +110 -0
- package/web/js/theme.js +46 -0
- package/web/js/utils.js +228 -0
- package/web/knowledge.html +146 -0
- package/web/loop.html +219 -0
package/web/js/health.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2
|
+
* health.js — 后端健康检查(共享模块)
|
|
3
|
+
*
|
|
4
|
+
* 所有面板共用一个顶栏健康指示器,由本模块统一管理。
|
|
5
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
var Health = {
|
|
9
|
+
_timer: null,
|
|
10
|
+
_intervalMs: 30000,
|
|
11
|
+
|
|
12
|
+
/** 执行一次健康检查,同时更新顶栏指示器和健康面板输出 */
|
|
13
|
+
check: async function () {
|
|
14
|
+
var dot = $('health-dot');
|
|
15
|
+
var label = $('health-label');
|
|
16
|
+
if (!dot) return;
|
|
17
|
+
dot.className = '';
|
|
18
|
+
if (label) label.textContent = '检测中…';
|
|
19
|
+
|
|
20
|
+
var out = $('health-out');
|
|
21
|
+
if (out) { out.classList.remove('empty'); out.textContent = '正在检测后端服务…'; }
|
|
22
|
+
|
|
23
|
+
var result = null, ok = false;
|
|
24
|
+
try {
|
|
25
|
+
var d = await API.get('/api/health');
|
|
26
|
+
result = d;
|
|
27
|
+
ok = d && d.status === 'ok';
|
|
28
|
+
dot.className = ok ? 'ok' : 'error';
|
|
29
|
+
if (label) label.textContent = ok ? '服务正常' : '服务异常';
|
|
30
|
+
} catch (err) {
|
|
31
|
+
result = { error: err.message };
|
|
32
|
+
dot.className = 'error';
|
|
33
|
+
if (label) label.textContent = '后端不可达';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (out) {
|
|
37
|
+
var ts = new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
|
38
|
+
var json = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
39
|
+
out.innerHTML = '<div style="color:var(--text3);margin-bottom:6px">' + ts + ' ' +
|
|
40
|
+
(ok ? '<span style="color:var(--green)">● 服务正常</span>' : '<span style="color:var(--red)">● 服务异常</span>') +
|
|
41
|
+
'</div><pre style="margin:0;font-family:var(--mono);font-size:12px;white-space:pre-wrap;word-break:break-all;">' +
|
|
42
|
+
escapeHtml(json) + '</pre>';
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/** 启动定时健康检查 */
|
|
47
|
+
startPolling: function () {
|
|
48
|
+
this.check();
|
|
49
|
+
var self = this;
|
|
50
|
+
this._timer = setInterval(function () { self.check(); }, this._intervalMs);
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/** 停止定时健康检查 */
|
|
54
|
+
stopPolling: function () {
|
|
55
|
+
if (this._timer) { clearInterval(this._timer); this._timer = null; }
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
window.Health = Health;
|
|
60
|
+
|
|
61
|
+
/** 健康面板按钮调用 */
|
|
62
|
+
function doHealthCheck() { Health.check(); }
|
|
63
|
+
|
|
64
|
+
/** 顶栏健康指示器点击触发检测 */
|
|
65
|
+
window.addEventListener('load', function () {
|
|
66
|
+
var tag = $('serverTag');
|
|
67
|
+
if (tag) tag.style.cursor = 'pointer';
|
|
68
|
+
if (tag) tag.onclick = function () { Health.check(); };
|
|
69
|
+
});
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2
|
+
* 知识导入页面 — 主脚本
|
|
3
|
+
* 共享工具函数($, escapeHtml, toast, API 等)由 utils.js 统一提供
|
|
4
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
/* ─── knowledge 专属工具函数 ────────────────────────────────────────── */
|
|
8
|
+
function importStatusLabel(s) {
|
|
9
|
+
var m = { running: '运行中', completed: '已完成', failed: '失败', error: '错误', pending: '待执行' };
|
|
10
|
+
return m[s] || s || '—';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* ─── 全局状态 ──────────────────────────────────────────────────────── */
|
|
14
|
+
var KnState = {
|
|
15
|
+
experiences: [],
|
|
16
|
+
filtered: [],
|
|
17
|
+
selectedId: null,
|
|
18
|
+
selectedExp: null,
|
|
19
|
+
pollTimer: null,
|
|
20
|
+
currentImportId: null
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/* ─── 加载知识列表 ──────────────────────────────────────────────────── */
|
|
24
|
+
async function loadKnowledgeList() {
|
|
25
|
+
var listEl = $('knList');
|
|
26
|
+
listEl.innerHTML = '<div class="detail-empty">加载中...</div>';
|
|
27
|
+
try {
|
|
28
|
+
var d = await API.get('/api/knowledge/list');
|
|
29
|
+
if (d && d.status === 'ok') {
|
|
30
|
+
KnState.experiences = d.experiences || [];
|
|
31
|
+
filterKnowledge();
|
|
32
|
+
} else {
|
|
33
|
+
KnState.experiences = [];
|
|
34
|
+
listEl.innerHTML = '<div class="detail-empty">加载失败: ' + escapeHtml(d && d.message || '未知错误') + '</div>';
|
|
35
|
+
$('knCount').textContent = '0';
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
KnState.experiences = [];
|
|
39
|
+
listEl.innerHTML = '<div class="detail-empty">后端不可达: ' + escapeHtml(err.message) + '</div>';
|
|
40
|
+
$('knCount').textContent = '0';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function filterKnowledge() {
|
|
45
|
+
var q = $('knSearch').value.trim().toLowerCase();
|
|
46
|
+
KnState.filtered = KnState.experiences.filter(function (e) {
|
|
47
|
+
var id = (e.id || '').toLowerCase();
|
|
48
|
+
var tid = (e.taskId || '').toLowerCase();
|
|
49
|
+
var desc = (e.taskDescription || '').toLowerCase();
|
|
50
|
+
return !q || id.indexOf(q) !== -1 || tid.indexOf(q) !== -1 || desc.indexOf(q) !== -1;
|
|
51
|
+
});
|
|
52
|
+
renderKnowledgeList();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function renderKnowledgeList() {
|
|
56
|
+
var listEl = $('knList');
|
|
57
|
+
$('knCount').textContent = KnState.filtered.length;
|
|
58
|
+
if (KnState.filtered.length === 0) {
|
|
59
|
+
listEl.innerHTML = '<div class="detail-empty">暂无知识条目</div>';
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
listEl.innerHTML = KnState.filtered.map(function (e) {
|
|
63
|
+
var active = e.id === KnState.selectedId;
|
|
64
|
+
return '<div class="kn-card ' + (active ? 'active' : '') + '" onclick="selectKnowledge(\'' + escapeHtml(e.id) + '\')">' +
|
|
65
|
+
'<div class="kn-card-id">' + escapeHtml(e.id) + '</div>' +
|
|
66
|
+
'<div class="kn-card-desc">' + escapeHtml(truncate(e.taskDescription, 120)) + '</div>' +
|
|
67
|
+
'<div class="kn-card-meta">' +
|
|
68
|
+
(e.taskId ? '<span class="kn-card-task">' + escapeHtml(e.taskId) + '</span>' : '') +
|
|
69
|
+
'<span class="kn-card-time">' + formatTime(e.createdAt) + '</span>' +
|
|
70
|
+
'</div></div>';
|
|
71
|
+
}).join('');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ─── 选中知识 → 渲染详情 ───────────────────────────────────────────── */
|
|
75
|
+
function selectKnowledge(id) {
|
|
76
|
+
KnState.selectedId = id;
|
|
77
|
+
var exp = KnState.experiences.find(function (e) { return e.id === id; });
|
|
78
|
+
KnState.selectedExp = exp || null;
|
|
79
|
+
renderKnowledgeList();
|
|
80
|
+
renderDetail();
|
|
81
|
+
showImportPanel();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function renderDetail() {
|
|
85
|
+
var exp = KnState.selectedExp;
|
|
86
|
+
if (!exp) {
|
|
87
|
+
$('knDetailCard').style.display = 'none';
|
|
88
|
+
$('knEmpty').style.display = 'flex';
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
$('knEmpty').style.display = 'none';
|
|
92
|
+
$('knDetailCard').style.display = 'block';
|
|
93
|
+
|
|
94
|
+
$('knTitle').textContent = truncate(exp.taskDescription, 60) || '(无描述)';
|
|
95
|
+
$('knDetailId').textContent = exp.id;
|
|
96
|
+
|
|
97
|
+
var meta = '';
|
|
98
|
+
if (exp.taskId) meta += '<div class="meta-item"><span class="meta-label">来源任务</span><span class="meta-value">' + escapeHtml(exp.taskId) + '</span></div>';
|
|
99
|
+
if (exp.stepId != null) meta += '<div class="meta-item"><span class="meta-label">来源步骤</span><span class="meta-value">' + escapeHtml(exp.stepId) + '</span></div>';
|
|
100
|
+
if (exp.bigLoopIndex != null) meta += '<div class="meta-item"><span class="meta-label">大循环</span><span class="meta-value">' + escapeHtml(exp.bigLoopIndex) + '</span></div>';
|
|
101
|
+
if (exp.retryCount != null) meta += '<div class="meta-item"><span class="meta-label">重试次数</span><span class="meta-value">' + escapeHtml(exp.retryCount) + '</span></div>';
|
|
102
|
+
if (exp.skillDir) meta += '<div class="meta-item"><span class="meta-label">SKILL 目录</span><span class="meta-value">' + escapeHtml(exp.skillDir) + '</span></div>';
|
|
103
|
+
if (exp.createdAt) meta += '<div class="meta-item"><span class="meta-label">创建时间</span><span class="meta-value">' + formatTime(exp.createdAt) + '</span></div>';
|
|
104
|
+
if (exp.updatedAt) meta += '<div class="meta-item"><span class="meta-label">更新时间</span><span class="meta-value">' + formatTime(exp.updatedAt) + '</span></div>';
|
|
105
|
+
if (exp.tags && exp.tags.length > 0) meta += '<div class="meta-item"><span class="meta-label">标签</span><span class="meta-value">' + escapeHtml(exp.tags.join(', ')) + '</span></div>';
|
|
106
|
+
$('knDetailMeta').innerHTML = meta;
|
|
107
|
+
|
|
108
|
+
$('knDetailDesc').textContent = exp.taskDescription || '(无任务描述)';
|
|
109
|
+
|
|
110
|
+
$('knImportTarget').textContent = '目标: ' + exp.id;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function showImportPanel() {
|
|
114
|
+
if (!KnState.selectedExp) return;
|
|
115
|
+
$('knImportPanel').style.display = 'block';
|
|
116
|
+
$('btnImportSubmit').disabled = false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ─── 查看经验文档 ──────────────────────────────────────────────────── */
|
|
120
|
+
async function viewDoc() {
|
|
121
|
+
var exp = KnState.selectedExp;
|
|
122
|
+
if (!exp) { toast('warn', '请先选择一条知识'); return; }
|
|
123
|
+
|
|
124
|
+
var title = '经验文档 - ' + exp.id;
|
|
125
|
+
MarkdownReader.open(title, '加载中...');
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
var d = await API.get('/api/knowledge/doc?id=' + encodeURIComponent(exp.id));
|
|
129
|
+
if (d && d.status === 'ok') {
|
|
130
|
+
MarkdownReader.open(title, d.content || '(文档内容为空)');
|
|
131
|
+
} else {
|
|
132
|
+
MarkdownReader.open(title, '加载失败: ' + (d && d.message || d && d.error || '未知错误'));
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
MarkdownReader.open(title, '后端不可达: ' + err.message);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* ─── 执行知识导入 ──────────────────────────────────────────────────── */
|
|
140
|
+
async function submitImport() {
|
|
141
|
+
var exp = KnState.selectedExp;
|
|
142
|
+
if (!exp) { toast('warn', '请先选择一条知识'); return; }
|
|
143
|
+
|
|
144
|
+
var userPrompt = $('knUserPrompt').value.trim();
|
|
145
|
+
var workDir = $('knWorkDir').value.trim();
|
|
146
|
+
var skillDir = $('knSkillDir').value.trim();
|
|
147
|
+
|
|
148
|
+
var body = { knowledgeId: exp.id };
|
|
149
|
+
if (userPrompt) body.userPrompt = userPrompt;
|
|
150
|
+
if (workDir) body.workDir = workDir;
|
|
151
|
+
if (skillDir) body.skillDir = skillDir;
|
|
152
|
+
|
|
153
|
+
var btn = $('btnImportSubmit');
|
|
154
|
+
btn.disabled = true;
|
|
155
|
+
|
|
156
|
+
btn.textContent = '🔄 异步导入中...';
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
var d = await API.post('/api/knowledge/import-async', body);
|
|
160
|
+
btn.disabled = false;
|
|
161
|
+
btn.textContent = '📥 执行导入';
|
|
162
|
+
|
|
163
|
+
if (d.status === 'ok') {
|
|
164
|
+
toast('success', '异步导入已启动: ' + d.importId);
|
|
165
|
+
KnState.currentImportId = d.importId;
|
|
166
|
+
startImportPolling(d.importId);
|
|
167
|
+
} else {
|
|
168
|
+
toast('error', d.error || d.message || '导入失败');
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
btn.disabled = false;
|
|
172
|
+
btn.textContent = '📥 执行导入';
|
|
173
|
+
toast('error', '请求失败: ' + err.message);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* ─── 异步导入状态轮询 ──────────────────────────────────────────────── */
|
|
178
|
+
function startImportPolling(importId) {
|
|
179
|
+
stopImportPolling();
|
|
180
|
+
$('knImportStatus').style.display = 'block';
|
|
181
|
+
KnState.pollTimer = setInterval(function () { pollImportStatus(importId); }, 3000);
|
|
182
|
+
pollImportStatus(importId);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function stopImportPolling() {
|
|
186
|
+
if (KnState.pollTimer) { clearInterval(KnState.pollTimer); KnState.pollTimer = null; }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function pollImportStatus(importId) {
|
|
190
|
+
try {
|
|
191
|
+
var d = await API.get('/api/knowledge/import-status?importId=' + encodeURIComponent(importId));
|
|
192
|
+
if (d.status !== 'ok' || !d.import) {
|
|
193
|
+
renderImportStatus(null);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
renderImportStatus(d.import);
|
|
197
|
+
|
|
198
|
+
var st = d.import.status;
|
|
199
|
+
if (st === 'completed') {
|
|
200
|
+
stopImportPolling();
|
|
201
|
+
toast('success', '异步导入完成');
|
|
202
|
+
// 导入成功后刷新列表(原知识已自动删除)
|
|
203
|
+
if (d.import.result && d.import.result.success) {
|
|
204
|
+
loadKnowledgeList();
|
|
205
|
+
}
|
|
206
|
+
} else if (st === 'failed' || st === 'error') {
|
|
207
|
+
stopImportPolling();
|
|
208
|
+
toast('error', '异步导入失败: ' + (d.import.error || d.import.result && d.import.result.message || ''));
|
|
209
|
+
}
|
|
210
|
+
} catch (err) {
|
|
211
|
+
$('knStatusBody').innerHTML = '<div style="color:var(--red);font-size:12px;">轮询失败: ' + escapeHtml(err.message) + '</div>';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function renderImportStatus(imp) {
|
|
216
|
+
if (!imp) {
|
|
217
|
+
$('knStatusBody').innerHTML = '<div class="muted">无状态数据</div>';
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
var st = imp.status || 'unknown';
|
|
221
|
+
var badge = '<span class="kn-import-badge ' + st + '">' + importStatusLabel(st) + '</span>';
|
|
222
|
+
|
|
223
|
+
var rows = '';
|
|
224
|
+
rows += '<div class="kn-status-row"><span class="k">导入 ID</span><span class="v">' + escapeHtml(imp.importId) + '</span></div>';
|
|
225
|
+
rows += '<div class="kn-status-row"><span class="k">状态</span><span class="v">' + badge + '</span></div>';
|
|
226
|
+
rows += '<div class="kn-status-row"><span class="k">知识 ID</span><span class="v">' + escapeHtml(imp.knowledgeInfo && imp.knowledgeInfo.id || '—') + '</span></div>';
|
|
227
|
+
rows += '<div class="kn-status-row"><span class="k">工作目录</span><span class="v">' + escapeHtml(imp.workDir || '—') + '</span></div>';
|
|
228
|
+
rows += '<div class="kn-status-row"><span class="k">结果文件</span><span class="v">' + escapeHtml(imp.resultFilePath || '—') + '</span></div>';
|
|
229
|
+
rows += '<div class="kn-status-row"><span class="k">Session ID</span><span class="v">' + escapeHtml(imp.sessionId || '—') + '</span></div>';
|
|
230
|
+
rows += '<div class="kn-status-row"><span class="k">开始时间</span><span class="v">' + formatTime(imp.startTime) + '</span></div>';
|
|
231
|
+
if (imp.endTime) rows += '<div class="kn-status-row"><span class="k">结束时间</span><span class="v">' + formatTime(imp.endTime) + '</span></div>';
|
|
232
|
+
|
|
233
|
+
if (imp.result) {
|
|
234
|
+
rows += '<div class="kn-status-row"><span class="k">成功</span><span class="v">' + (imp.result.success ? '✅ 是' : '❌ 否') + '</span></div>';
|
|
235
|
+
if (imp.result.message) rows += '<div class="kn-status-row"><span class="k">消息</span><span class="v">' + escapeHtml(imp.result.message) + '</span></div>';
|
|
236
|
+
}
|
|
237
|
+
if (imp.error) {
|
|
238
|
+
rows += '<div class="kn-status-row"><span class="k">错误</span><span class="v" style="color:var(--red);">' + escapeHtml(imp.error) + '</span></div>';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 结果内容预览
|
|
242
|
+
var resultPreview = '';
|
|
243
|
+
if (imp.result && imp.result.resultContent) {
|
|
244
|
+
var content = imp.result.resultContent;
|
|
245
|
+
var preview = content.length > 2000 ? content.substring(0, 2000) + '\n...(已截断)' : content;
|
|
246
|
+
resultPreview = '<div class="kn-log-box">' + escapeHtml(preview) + '</div>';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
$('knStatusBody').innerHTML = rows + resultPreview;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* ─── 删除知识 ──────────────────────────────────────────────────────── */
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 打开删除确认弹窗(替代原生 confirm,风格与站点模态一致)
|
|
256
|
+
*/
|
|
257
|
+
function openDeleteConfirm() {
|
|
258
|
+
var exp = KnState.selectedExp;
|
|
259
|
+
if (!exp) { toast('warn', '请先选择一条知识'); return; }
|
|
260
|
+
|
|
261
|
+
$('knDelTitle').textContent = truncate(exp.taskDescription, 60) || '(无描述)';
|
|
262
|
+
$('knDelId').textContent = exp.id;
|
|
263
|
+
$('knDeleteOverlay').classList.add('show');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function closeDeleteConfirm() {
|
|
267
|
+
$('knDeleteOverlay').classList.remove('show');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function deleteKnowledge() {
|
|
271
|
+
var exp = KnState.selectedExp;
|
|
272
|
+
if (!exp) { toast('warn', '请先选择一条知识'); return; }
|
|
273
|
+
|
|
274
|
+
var btn = $('btnDeleteConfirm');
|
|
275
|
+
btn.disabled = true;
|
|
276
|
+
btn.textContent = '🗑️ 删除中...';
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
var d = await API.del('/api/knowledge/delete?id=' + encodeURIComponent(exp.id));
|
|
280
|
+
btn.disabled = false;
|
|
281
|
+
btn.textContent = '🗑️ 确认删除';
|
|
282
|
+
|
|
283
|
+
if (d.status === 'ok') {
|
|
284
|
+
closeDeleteConfirm();
|
|
285
|
+
toast('success', d.message || '知识已删除');
|
|
286
|
+
// 清除选中状态,刷新列表
|
|
287
|
+
KnState.selectedId = null;
|
|
288
|
+
KnState.selectedExp = null;
|
|
289
|
+
$('knDetailCard').style.display = 'none';
|
|
290
|
+
$('knImportPanel').style.display = 'none';
|
|
291
|
+
$('knEmpty').style.display = 'flex';
|
|
292
|
+
loadKnowledgeList();
|
|
293
|
+
} else {
|
|
294
|
+
toast('error', d.error || d.message || '删除失败');
|
|
295
|
+
}
|
|
296
|
+
} catch (err) {
|
|
297
|
+
btn.disabled = false;
|
|
298
|
+
btn.textContent = '🗑️ 确认删除';
|
|
299
|
+
toast('error', '请求失败: ' + err.message);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
/* ─── 事件绑定 & 初始化 ──────────────────────────────────────────────── */
|
|
305
|
+
$('knSearch').oninput = filterKnowledge;
|
|
306
|
+
$('btnKnRefresh').onclick = loadKnowledgeList;
|
|
307
|
+
|
|
308
|
+
$('btnStartImport').onclick = function () {
|
|
309
|
+
showImportPanel();
|
|
310
|
+
$('knImportPanel').scrollIntoView({ behavior: 'smooth' });
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
$('btnViewDoc').onclick = viewDoc;
|
|
314
|
+
|
|
315
|
+
$('btnDeleteKn').onclick = openDeleteConfirm;
|
|
316
|
+
$('btnDeleteCancel').onclick = closeDeleteConfirm;
|
|
317
|
+
$('btnDeleteConfirm').onclick = deleteKnowledge;
|
|
318
|
+
$('knDeleteOverlay').onclick = function (e) {
|
|
319
|
+
if (e.target === $('knDeleteOverlay')) closeDeleteConfirm();
|
|
320
|
+
};
|
|
321
|
+
document.addEventListener('keydown', function (e) {
|
|
322
|
+
if (e.key === 'Escape' && $('knDeleteOverlay').classList.contains('show')) {
|
|
323
|
+
closeDeleteConfirm();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
$('btnImportCancel').onclick = function () {
|
|
328
|
+
$('knImportPanel').style.display = 'none';
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
$('btnImportSubmit').onclick = submitImport;
|
|
332
|
+
|
|
333
|
+
// 高级选项折叠
|
|
334
|
+
$('knAdvancedToggle').onclick = function () {
|
|
335
|
+
$('knAdvancedSection').classList.toggle('show');
|
|
336
|
+
$('knAdvancedToggle').classList.toggle('open');
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// 停止轮询
|
|
340
|
+
$('btnStopPoll').onclick = function () {
|
|
341
|
+
stopImportPolling();
|
|
342
|
+
toast('info', '已停止轮询');
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// 弹窗关闭
|
|
346
|
+
$('docClose').onclick = function () { $('docOverlay').classList.remove('show'); };
|
|
347
|
+
$('docOverlay').onclick = function (e) { if (e.target === $('docOverlay')) $('docOverlay').classList.remove('show'); };
|
|
348
|
+
|
|
349
|
+
// 暴露内联调用
|
|
350
|
+
window.selectKnowledge = selectKnowledge;
|
|
351
|
+
|
|
352
|
+
/* ─── 知识管理面板懒加载(首次切到知识管理视图时由 ViewMode 触发加载) ── */
|
|
353
|
+
var _knInited = false;
|
|
354
|
+
function initKnowledgePanel() {
|
|
355
|
+
if (_knInited) return;
|
|
356
|
+
_knInited = true;
|
|
357
|
+
loadKnowledgeList();
|
|
358
|
+
}
|