* 知识导入页面 — 主脚本
* 共享工具函数($, escapeHtml, toast, API 等)由 utils.js 统一提供
* ════════════════════════════════════════════════════════════════════ */
'use strict';
function importStatusLabel(s) {
var m = { running: '运行中', completed: '已完成', failed: '失败', error: '错误', pending: '待执行' };
return m[s] || s || '—';
}
var KnState = {
experiences: [],
filtered: [],
selectedId: null,
selectedExp: null,
pollTimer: null,
currentImportId: null
};
async function loadKnowledgeList() {
var listEl = $('knList');
listEl.innerHTML = '<div class="detail-empty">加载中...</div>';
try {
var d = await API.get('/api/knowledge/list');
if (d && d.status === 'ok') {
KnState.experiences = d.experiences || [];
filterKnowledge();
} else {
KnState.experiences = [];
listEl.innerHTML = '<div class="detail-empty">加载失败: ' + escapeHtml(d && d.message || '未知错误') + '</div>';
$('knCount').textContent = '0';
}
} catch (err) {
KnState.experiences = [];
listEl.innerHTML = '<div class="detail-empty">后端不可达: ' + escapeHtml(err.message) + '</div>';
$('knCount').textContent = '0';
}
}
function filterKnowledge() {
var q = $('knSearch').value.trim().toLowerCase();
KnState.filtered = KnState.experiences.filter(function (e) {
var id = (e.id || '').toLowerCase();
var tid = (e.taskId || '').toLowerCase();
var desc = (e.taskDescription || '').toLowerCase();
return !q || id.indexOf(q) !== -1 || tid.indexOf(q) !== -1 || desc.indexOf(q) !== -1;
});
renderKnowledgeList();
}
function renderKnowledgeList() {
var listEl = $('knList');
$('knCount').textContent = KnState.filtered.length;
if (KnState.filtered.length === 0) {
listEl.innerHTML = '<div class="detail-empty">暂无知识条目</div>';
return;
}
listEl.innerHTML = KnState.filtered.map(function (e) {
var active = e.id === KnState.selectedId;
return '<div class="kn-card ' + (active ? 'active' : '') + '" onclick="selectKnowledge(\'' + escapeHtml(e.id) + '\')">' +
'<div class="kn-card-id">' + escapeHtml(e.id) + '</div>' +
'<div class="kn-card-desc">' + escapeHtml(truncate(e.taskDescription, 120)) + '</div>' +
'<div class="kn-card-meta">' +
(e.taskId ? '<span class="kn-card-task">' + escapeHtml(e.taskId) + '</span>' : '') +
'<span class="kn-card-time">' + formatTime(e.createdAt) + '</span>' +
'</div></div>';
}).join('');
}
function selectKnowledge(id) {
KnState.selectedId = id;
var exp = KnState.experiences.find(function (e) { return e.id === id; });
KnState.selectedExp = exp || null;
renderKnowledgeList();
renderDetail();
showImportPanel();
}
function renderDetail() {
var exp = KnState.selectedExp;
if (!exp) {
$('knDetailCard').style.display = 'none';
$('knEmpty').style.display = 'flex';
return;
}
$('knEmpty').style.display = 'none';
$('knDetailCard').style.display = 'block';
$('knTitle').textContent = truncate(exp.taskDescription, 60) || '(无描述)';
$('knDetailId').textContent = exp.id;
var meta = '';
if (exp.taskId) meta += '<div class="meta-item"><span class="meta-label">来源任务</span><span class="meta-value">' + escapeHtml(exp.taskId) + '</span></div>';
if (exp.stepId != null) meta += '<div class="meta-item"><span class="meta-label">来源步骤</span><span class="meta-value">' + escapeHtml(exp.stepId) + '</span></div>';
if (exp.bigLoopIndex != null) meta += '<div class="meta-item"><span class="meta-label">大循环</span><span class="meta-value">' + escapeHtml(exp.bigLoopIndex) + '</span></div>';
if (exp.retryCount != null) meta += '<div class="meta-item"><span class="meta-label">重试次数</span><span class="meta-value">' + escapeHtml(exp.retryCount) + '</span></div>';
if (exp.skillDir) meta += '<div class="meta-item"><span class="meta-label">SKILL 目录</span><span class="meta-value">' + escapeHtml(exp.skillDir) + '</span></div>';
if (exp.createdAt) meta += '<div class="meta-item"><span class="meta-label">创建时间</span><span class="meta-value">' + formatTime(exp.createdAt) + '</span></div>';
if (exp.updatedAt) meta += '<div class="meta-item"><span class="meta-label">更新时间</span><span class="meta-value">' + formatTime(exp.updatedAt) + '</span></div>';
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>';
$('knDetailMeta').innerHTML = meta;
$('knDetailDesc').textContent = exp.taskDescription || '(无任务描述)';
$('knImportTarget').textContent = '目标: ' + exp.id;
}
function showImportPanel() {
if (!KnState.selectedExp) return;
$('knImportPanel').style.display = 'block';
$('btnImportSubmit').disabled = false;
}
async function viewDoc() {
var exp = KnState.selectedExp;
if (!exp) { toast('warn', '请先选择一条知识'); return; }
var title = '经验文档 - ' + exp.id;
MarkdownReader.open(title, '加载中...');
try {
var d = await API.get('/api/knowledge/doc?id=' + encodeURIComponent(exp.id));
if (d && d.status === 'ok') {
MarkdownReader.open(title, d.content || '(文档内容为空)');
} else {
MarkdownReader.open(title, '加载失败: ' + (d && d.message || d && d.error || '未知错误'));
}
} catch (err) {
MarkdownReader.open(title, '后端不可达: ' + err.message);
}
}
async function submitImport() {
var exp = KnState.selectedExp;
if (!exp) { toast('warn', '请先选择一条知识'); return; }
var userPrompt = $('knUserPrompt').value.trim();
var workDir = $('knWorkDir').value.trim();
var skillDir = $('knSkillDir').value.trim();
var body = { knowledgeId: exp.id };
if (userPrompt) body.userPrompt = userPrompt;
if (workDir) body.workDir = workDir;
if (skillDir) body.skillDir = skillDir;
var btn = $('btnImportSubmit');
btn.disabled = true;
btn.textContent = '🔄 异步导入中...';
try {
var d = await API.post('/api/knowledge/import-async', body);
btn.disabled = false;
btn.textContent = '📥 执行导入';
if (d.status === 'ok') {
toast('success', '异步导入已启动: ' + d.importId);
KnState.currentImportId = d.importId;
startImportPolling(d.importId);
} else {
toast('error', d.error || d.message || '导入失败');
}
} catch (err) {
btn.disabled = false;
btn.textContent = '📥 执行导入';
toast('error', '请求失败: ' + err.message);
}
}
function startImportPolling(importId) {
stopImportPolling();
$('knImportStatus').style.display = 'block';
KnState.pollTimer = setInterval(function () { pollImportStatus(importId); }, 3000);
pollImportStatus(importId);
}
function stopImportPolling() {
if (KnState.pollTimer) { clearInterval(KnState.pollTimer); KnState.pollTimer = null; }
}
async function pollImportStatus(importId) {
try {
var d = await API.get('/api/knowledge/import-status?importId=' + encodeURIComponent(importId));
if (d.status !== 'ok' || !d.import) {
renderImportStatus(null);
return;
}
renderImportStatus(d.import);
var st = d.import.status;
if (st === 'completed') {
stopImportPolling();
toast('success', '异步导入完成');
if (d.import.result && d.import.result.success) {
loadKnowledgeList();
}
} else if (st === 'failed' || st === 'error') {
stopImportPolling();
toast('error', '异步导入失败: ' + (d.import.error || d.import.result && d.import.result.message || ''));
}
} catch (err) {
$('knStatusBody').innerHTML = '<div style="color:var(--red);font-size:12px;">轮询失败: ' + escapeHtml(err.message) + '</div>';
}
}
function renderImportStatus(imp) {
if (!imp) {
$('knStatusBody').innerHTML = '<div class="muted">无状态数据</div>';
return;
}
var st = imp.status || 'unknown';
var badge = '<span class="kn-import-badge ' + st + '">' + importStatusLabel(st) + '</span>';
var rows = '';
rows += '<div class="kn-status-row"><span class="k">导入 ID</span><span class="v">' + escapeHtml(imp.importId) + '</span></div>';
rows += '<div class="kn-status-row"><span class="k">状态</span><span class="v">' + badge + '</span></div>';
rows += '<div class="kn-status-row"><span class="k">知识 ID</span><span class="v">' + escapeHtml(imp.knowledgeInfo && imp.knowledgeInfo.id || '—') + '</span></div>';
rows += '<div class="kn-status-row"><span class="k">工作目录</span><span class="v">' + escapeHtml(imp.workDir || '—') + '</span></div>';
rows += '<div class="kn-status-row"><span class="k">结果文件</span><span class="v">' + escapeHtml(imp.resultFilePath || '—') + '</span></div>';
rows += '<div class="kn-status-row"><span class="k">Session ID</span><span class="v">' + escapeHtml(imp.sessionId || '—') + '</span></div>';
rows += '<div class="kn-status-row"><span class="k">开始时间</span><span class="v">' + formatTime(imp.startTime) + '</span></div>';
if (imp.endTime) rows += '<div class="kn-status-row"><span class="k">结束时间</span><span class="v">' + formatTime(imp.endTime) + '</span></div>';
if (imp.result) {
rows += '<div class="kn-status-row"><span class="k">成功</span><span class="v">' + (imp.result.success ? '✅ 是' : '❌ 否') + '</span></div>';
if (imp.result.message) rows += '<div class="kn-status-row"><span class="k">消息</span><span class="v">' + escapeHtml(imp.result.message) + '</span></div>';
}
if (imp.error) {
rows += '<div class="kn-status-row"><span class="k">错误</span><span class="v" style="color:var(--red);">' + escapeHtml(imp.error) + '</span></div>';
}
var resultPreview = '';
if (imp.result && imp.result.resultContent) {
var content = imp.result.resultContent;
var preview = content.length > 2000 ? content.substring(0, 2000) + '\n...(已截断)' : content;
resultPreview = '<div class="kn-log-box">' + escapeHtml(preview) + '</div>';
}
$('knStatusBody').innerHTML = rows + resultPreview;
}
* 打开删除确认弹窗(替代原生 confirm,风格与站点模态一致)
*/
function openDeleteConfirm() {
var exp = KnState.selectedExp;
if (!exp) { toast('warn', '请先选择一条知识'); return; }
$('knDelTitle').textContent = truncate(exp.taskDescription, 60) || '(无描述)';
$('knDelId').textContent = exp.id;
$('knDeleteOverlay').classList.add('show');
}
function closeDeleteConfirm() {
$('knDeleteOverlay').classList.remove('show');
}
async function deleteKnowledge() {
var exp = KnState.selectedExp;
if (!exp) { toast('warn', '请先选择一条知识'); return; }
var btn = $('btnDeleteConfirm');
btn.disabled = true;
btn.textContent = '🗑️ 删除中...';
try {
var d = await API.del('/api/knowledge/delete?id=' + encodeURIComponent(exp.id));
btn.disabled = false;
btn.textContent = '🗑️ 确认删除';
if (d.status === 'ok') {
closeDeleteConfirm();
toast('success', d.message || '知识已删除');
KnState.selectedId = null;
KnState.selectedExp = null;
$('knDetailCard').style.display = 'none';
$('knImportPanel').style.display = 'none';
$('knEmpty').style.display = 'flex';
loadKnowledgeList();
} else {
toast('error', d.error || d.message || '删除失败');
}
} catch (err) {
btn.disabled = false;
btn.textContent = '🗑️ 确认删除';
toast('error', '请求失败: ' + err.message);
}
}
$('knSearch').oninput = filterKnowledge;
$('btnKnRefresh').onclick = loadKnowledgeList;
$('btnStartImport').onclick = function () {
showImportPanel();
$('knImportPanel').scrollIntoView({ behavior: 'smooth' });
};
$('btnViewDoc').onclick = viewDoc;
$('btnDeleteKn').onclick = openDeleteConfirm;
$('btnDeleteCancel').onclick = closeDeleteConfirm;
$('btnDeleteConfirm').onclick = deleteKnowledge;
$('knDeleteOverlay').onclick = function (e) {
if (e.target === $('knDeleteOverlay')) closeDeleteConfirm();
};
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && $('knDeleteOverlay').classList.contains('show')) {
closeDeleteConfirm();
}
});
$('btnImportCancel').onclick = function () {
$('knImportPanel').style.display = 'none';
};
$('btnImportSubmit').onclick = submitImport;
$('knAdvancedToggle').onclick = function () {
$('knAdvancedSection').classList.toggle('show');
$('knAdvancedToggle').classList.toggle('open');
};
$('btnStopPoll').onclick = function () {
stopImportPolling();
toast('info', '已停止轮询');
};
$('docClose').onclick = function () { $('docOverlay').classList.remove('show'); };
$('docOverlay').onclick = function (e) { if (e.target === $('docOverlay')) $('docOverlay').classList.remove('show'); };
window.selectKnowledge = selectKnowledge;
var _knInited = false;
function initKnowledgePanel() {
if (_knInited) return;
_knInited = true;
loadKnowledgeList();
}