`;
const renderPaneContent = (pList, paneType) => {
let paneHtml = '';
if (paneType === 'total' && typeof totalStatsHtml !== 'undefined' && totalStatsHtml) {
paneHtml += totalStatsHtml;
} else if (paneType === 'filtered_cases' && typeof filteredStatsHtml !== 'undefined' && filteredStatsHtml) {
paneHtml += filteredStatsHtml;
}
let totalCases = 0;
let seenIds = new Set();
let groupedHtml = {};
const getGroupKey = (c, p) => (p === 'filtered_cases' && c.filter_reason) ? `🚫 过滤原因: ${c.filter_reason}` : 'default';
let allCases = [];
pList.forEach(p => {
if (!rawCasesObj[p]) return;
if (rawCasesObj[p].error) {
const safeRaw = rawCasesObj[p].raw_content ? rawCasesObj[p].raw_content.replace(//g, ">") : "文件为空。";
paneHtml += `
⚠️ ${p} JSON 解析失败
${safeRaw}
`;
return;
}
if (rawCasesObj[p].reason && p !== 'filtered_cases') {
paneHtml += `
🛑 ${p} 提示: ${rawCasesObj[p].reason}
`;
}
let cases = [];
if (Array.isArray(rawCasesObj[p])) {
cases = rawCasesObj[p];
} else if (rawCasesObj[p].cases) {
cases = rawCasesObj[p].cases;
} else if (rawCasesObj[p].sources) {
cases = rawCasesObj[p].sources;
} else if (rawCasesObj[p].by_reason) {
Object.entries(rawCasesObj[p].by_reason).forEach(([reasonKey, reasonObj]) => {
if (reasonObj.sources && Array.isArray(reasonObj.sources)) {
reasonObj.sources.forEach(src => {
if (!src.filter_reason) src.filter_reason = reasonKey;
cases.push(src);
});
}
});
}
if (cases.length > 0) {
if (!rawCasesObj['source'] && p !== 'source_ex' && p !== 'filtered_cases' && p !== 'source') {
paneHtml += `
📝 ${p} 原始爬取数据 (未进行 1.5 数据源提取)
${renderJSON(rawCasesObj[p])}
`;
return;
}
cases.forEach((c, idx) => {
allCases.push({ c: c, p: p, originalIdx: idx });
});
}
});
allCases.sort((aObj, bObj) => {
const getScore = (item) => {
const iId = item.case_id || (item._raw && item._raw.case_id) || (item.post && item.post.channel_content_id);
const iUrl = item.source_url || item.url || (item.post && item.post.link);
const mapped = sourceMap[iId] || sourceMap[iUrl] || (item._raw && sourceMap[item._raw.case_id]) || item;
return mapped.evaluation && mapped.evaluation.quality ? (mapped.evaluation.quality.overall_score || 0) : 0;
};
return getScore(bObj.c) - getScore(aObj.c);
});
allCases.forEach(itemObj => {
const c = itemObj.c;
const p = itemObj.p;
const idx = itemObj.originalIdx;
totalCases++;
const cId = c.case_id || (c._raw && c._raw.case_id) || (c.post && c.post.channel_content_id) || `temp_${p}_${idx}`;
const cUrl = c.source_url || c.url || (c.post && c.post.link) || '';
const groupKey = getGroupKey(c, p);
if (!groupedHtml[groupKey]) groupedHtml[groupKey] = '';
if (cId || cUrl || c.post) {
const mappedS = sourceMap[cId] || sourceMap[cUrl] || (c._raw && sourceMap[c._raw.case_id]);
if (p !== 'filtered_cases' && p !== 'source' && p !== 'source_ex' && !mappedS) return;
if (cId && seenIds.has(cId)) return;
if (cId) seenIds.add(cId);
const s = mappedS || c;
const post = s.post || s || {};
const images = post.images || [];
const xImages = post.image_url_list || [];
const ytThumbnails = post.thumbnails && post.thumbnails.length > 0 ? [post.thumbnails[post.thumbnails.length - 1].url] : [];
const allImages = [...images, ...xImages.map(img => img.image_url), ...ytThumbnails].filter(Boolean);
const coverImgUrl = allImages.length > 0 ? `/output/${reqId}/raw_cases/images/${cId}/00.jpg` : '';
const fallbackImgUrl = allImages.length > 0 ? allImages[0] : '';
const title = post.title || c.title || post.desc || (post.body_text ? post.body_text.substring(0, 30) + '...' : '') || cId || '无标题';
const author = post.channel_account_name || s.author || '-';
const likes = post.like_count !== undefined ? post.like_count : (post.likes !== undefined ? post.likes : '-');
const snippetStr = (post.body_text || post.body || '').substring(0, 100);
const snippetHtml = snippetStr ? `
${snippetStr.replace(//g, ">")}...
` : '';
const platBadge = p.startsWith('case_') ? `
${p.replace('case_', '').toUpperCase()} ` : '';
const score = s.evaluation && s.evaluation.quality ? s.evaluation.quality.overall_score : null;
let scoreBadge = '';
if (score !== null && score !== undefined) {
let color = score >= 80 ? '#10b981' : (score >= 60 ? '#f59e0b' : '#ef4444');
scoreBadge = `
⭐️ ${score}
`;
}
let actionBtn = '';
if (p === 'source_ex') {
const isImported = !!sourceMap[cId] || !!sourceMap[cUrl];
if (isImported) {
actionBtn = `
✅ 已导入 `;
} else {
actionBtn = `
📥 导入 Source `;
}
}
groupedHtml[groupKey] += `
${platBadge}
${scoreBadge}
${actionBtn}
${allImages.length > 0 ? `
` : ''}
${title}
${allImages.length === 0 ? snippetHtml : ''}
`;
} else {
groupedHtml[groupKey] += `
📝 旧版格式 / 解析失败 点击查看详情
`;
}
});
if (totalCases === 0 && pList.length > 0 && !paneHtml.includes('解析失败') && !paneHtml.includes('未进行')) {
paneHtml += `
暂无数据
`;
} else {
Object.entries(groupedHtml).forEach(([groupName, gHtml]) => {
if (gHtml) {
if (groupName !== 'default') {
paneHtml += `
${groupName} `;
}
paneHtml += `
${gHtml}
`;
}
});
}
return paneHtml;
};
html += `
${renderPaneContent([...channelPlatforms, 'source'], 'total')}
`;
if (hasFiltered) html += `
${renderPaneContent(['filtered_cases'], 'filtered_cases')}
`;
if (hasExternal) {
html += `
`;
html += `
📥 全部导入 Source
`;
html += renderPaneContent(['source_ex'], 'source_ex');
html += `
`;
}
html += `
`;
return html;
}
window.importExternalCase = async function (e, caseId) {
e.stopPropagation();
if (!confirm('确定要将该外部数据导入到 source 吗?')) return;
try {
const res = await fetch(`/api/requirements/${currentSelectedIndex}/import_source_ex`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ case_id: caseId })
});
const data = await res.json();
if (data.status === 'success') {
fetchRequirementData(currentSelectedIndex); // Refresh data
} else {
alert('导入失败: ' + (data.detail || data.message || JSON.stringify(data)));
}
} catch (err) {
alert('导入出错: ' + err);
}
};
window.importAllExternalCases = async function () {
if (!confirm('确定要将所有外部数据全部导入到 source 吗?')) return;
try {
const res = await fetch(`/api/requirements/${currentSelectedIndex}/import_all_source_ex`, {
method: 'POST'
});
const data = await res.json();
if (data.status === 'success') {
alert(`成功导入 ${data.count} 条数据!`);
fetchRequirementData(currentSelectedIndex); // Refresh data
} else {
alert('全部导入失败: ' + (data.detail || data.message || JSON.stringify(data)));
}
} catch (err) {
alert('导入出错: ' + err);
}
};
function selectSubTab(tabName) {
document.querySelectorAll('#json-raw .sub-tab-btn').forEach(btn => {
btn.classList.remove('active');
});
const activeBtn = document.querySelector(`#json-raw .sub-tab-btn[data-target="sub-tab-${tabName}"]`);
if (activeBtn) activeBtn.classList.add('active');
document.querySelectorAll('#json-raw .sub-tab-pane').forEach(pane => {
pane.classList.add('hidden');
});
document.getElementById(`sub-tab-${tabName}`).classList.remove('hidden');
}
window.deleteClusterItems = async function (indices) {
if (currentSelectedIndex === null) return;
const data = window.dataCache[currentSelectedIndex]?.cluster;
if (!data) return;
let newData = Array.isArray(data) ? [...data] : { ...data };
let count = 0;
// Sort indices descending to avoid shifting issues if it's an array
indices.sort((a, b) => b - a);
if (Array.isArray(newData)) {
indices.forEach(i => { newData.splice(i, 1); count++; });
} else {
const keys = Object.keys(newData);
indices.forEach(i => { delete newData[keys[i]]; count++; });
}
if (!confirm(`确定要删除选中的 ${count} 个项目吗?`)) return;
try {
const res = await fetch(`/api/requirements/${currentSelectedIndex}/save_cluster`, {
method: 'POST',
body: JSON.stringify(newData),
headers: { 'Content-Type': 'application/json' }
});
if (res.ok) {
fetchRequirementData(currentSelectedIndex);
} else {
alert('删除失败');
}
} catch (e) {
console.error(e);
alert('删除失败');
}
};
window.deleteSingleCluster = function (idx) {
deleteClusterItems([idx]);
};
window.deleteSelectedClusters = function () {
const checkboxes = document.querySelectorAll('.cluster-checkbox:checked');
const indices = Array.from(checkboxes).map(cb => parseInt(cb.dataset.idx));
if (indices.length === 0) {
alert('请先选择要删除的项目');
return;
}
deleteClusterItems(indices);
};
window.clearAllClusters = async function () {
if (currentSelectedIndex === null) return;
if (!confirm('确定要清空全部聚类结果吗?')) return;
try {
const res = await fetch(`/api/requirements/${currentSelectedIndex}/save_cluster`, {
method: 'POST',
body: JSON.stringify(Array.isArray(window.dataCache[currentSelectedIndex]?.cluster) ? [] : {}),
headers: { 'Content-Type': 'application/json' }
});
if (res.ok) {
fetchRequirementData(currentSelectedIndex);
} else {
alert('清空失败');
}
} catch (e) {
console.error(e);
alert('清空失败');
}
};
function renderClusterDeletable(clusterData) {
if (!clusterData || (Array.isArray(clusterData) && clusterData.length === 0) || (typeof clusterData === 'object' && Object.keys(clusterData).length === 0)) {
return ``;
let inputs = f.inputs ? f.inputs.map(i => i.modality || '').filter(Boolean).join('+') : '';
let outputs = f.outputs ? f.outputs.map(o => o.modality || '').filter(Boolean).join('+') : '';
let ioStr = (inputs && outputs) ? `${inputs} → ${outputs}` : (f.action && f.action.description ? f.action.description : '转换');
html += `
${f._caseId ? f._caseId.split('-').pop() : 'case'}
${f._workflowId || 'w'}
${f.capability_id || 'unknown'}
`;
html += `
I/O 模态 `;
html += `
${ioStr}
`;
if (f.inputs && f.inputs.length > 0) {
html += `
IN ${f.inputs.map(i => `${i.description || '输入'} [${i.modality || '未知'}] `).join('')}
`;
}
if (f.outputs && f.outputs.length > 0) {
html += `
OUT ${f.outputs.map(o => `${o.description || '输出'} [${o.modality || '未知'}] `).join('')}
`;
}
if (f.body || f.body_excerpt || f.rationale) {
html += `
BODY `;
html += `
${f.body || f.body_excerpt || f.rationale}
`;
}
if (f.apply_to) {
html += `
APPLY TO `;
Object.keys(f.apply_to).forEach(k => {
f.apply_to[k].forEach(item => {
html += `
${item.category_path || k}
${item.rationale || item.body_excerpt || '无推理说明'}
`;
});
});
}
if (f.suggest_apply_to && f.suggest_apply_to.length > 0) {
html += `
SUGGEST APPLY TO (建议新增节点) `;
f.suggest_apply_to.forEach(item => {
html += `
${item.path || '新节点'}
${item.rationale || item.body_excerpt || '无推理说明'}
`;
});
}
if (f.effects && f.effects.length > 0) {
html += `
EFFECTS `;
f.effects.forEach((eff, i) => {
html += `
#${i} ${eff.statement || 'Effect'}
判定标准: ${eff.criteria || '无'}
判定方式: ${eff.judge_method || '未知'}
`;
if (eff.negative_examples && eff.negative_examples.length > 0) {
html += `
反例: ${eff.negative_examples.map(ex => `${ex} `).join('')} `;
}
html += `
`;
});
}
html += `
其他 `;
let tools = f.tools && f.tools.length > 0 ? f.tools.join(', ') : '无';
html += `
tools: ${tools}
`;
if (f.artifact_type) html += `
artifact_type: ${f.artifact_type}
`;
if (f.control_target) html += `
control_target: ${Array.isArray(f.control_target) ? f.control_target.join(', ') : f.control_target}
`;
html += `
`;
body.innerHTML = html;
modal.classList.remove('hidden');
};
function renderBlueprint(bpObj) {
if (!bpObj || (!bpObj.blueprints && !bpObj.clusters)) {
return `
🧠 推理逻辑 (Reasoning)
${bp.reasoning || ''}
📍 阶段 (Phases)
`;
if (bp.phases) bp.phases.forEach(ph => {
html += `
${ph.phase || ''}
${ph.description || ''}
`;
});
html += `
`;
});
return html;
}
function renderStrategy(stratObj) {
if (!stratObj || (!stratObj.strategies && !stratObj.workflow)) {
return `
`;
}
let html = `
🎯 需求描述
${stratObj.requirement || ''}
`;
// New Workflow Format
if (stratObj.workflow) {
stratObj.workflow.forEach(strat => {
if (!strat.cluster_id && !strat.cluster_name && !strat.steps) {
html += `
`;
return;
}
const score = strat.score || 0;
const deg = score * 360;
const scoreClass = score >= 0.8 ? '' : (score >= 0.5 ? 'medium' : 'low');
let scoreHtml = `
${Math.round(score * 100)}%
匹配得分 ${strat.explanation || ''}
`;
html += `
${scoreHtml}
${(strat.关联案例 && strat.关联案例.length > 0) ? `
📌 关联案例
${renderCaseTags(strat.关联案例)}
` : ''}
🧱 工作流步骤
`;
if (strat.steps) strat.steps.forEach(step => {
html += `
步骤 ${step.步骤序号}
${step.步骤描述}
`;
});
html += `
`;
});
return html;
}
if (stratObj.strategies) {
stratObj.strategies.sort((a, b) => (b.is_selected === true) - (a.is_selected === true));
stratObj.strategies.forEach(strat => {
if (!strat.name && !strat.workflow_outline) {
html += `
`;
return;
}
const isSelected = strat.is_selected;
const icon = isSelected ? '🎯' : '🥈';
const badge = isSelected ? '
⭐ 被选中的策略 ' : '
备选策略 ';
let scoreHtml = '';
if (strat.coverage_score !== undefined) {
const score = strat.coverage_score;
const deg = score * 360;
const scoreClass = score >= 0.8 ? '' : (score >= 0.5 ? 'medium' : 'low');
scoreHtml = `
${Math.round(score * 100)}%
覆盖率得分 ${strat.coverage_explanation || ''}
`;
}
html += `
${scoreHtml}
📥 来源: ${strat.source || 'N/A'}
`;
if (strat.reasoning) html += `
🧠 推理逻辑 (Reasoning)
${strat.reasoning}
`;
if (strat.why_not) html += `
`;
if (strat.workflow_outline && strat.workflow_outline.length > 0) {
html += `
🧱 工作流大纲
`;
strat.workflow_outline.forEach(wo => {
html += `
${wo.phase}
${wo.description}
`;
if (wo.capabilities) {
wo.capabilities.forEach(cap => html += `⚡ ${cap.name} `);
}
html += `
`;
});
html += `
`;
}
html += `
`;
});
}
if (stratObj.uncovered_requirements && stratObj.uncovered_requirements.length > 0) {
html += `
`;
stratObj.uncovered_requirements.forEach(req => html += `${req} `);
html += ` `;
}
return html;
}