|
|
@@ -971,7 +971,7 @@ function renderAggregatedPerCaseData(cases, type) {
|
|
|
</div>`;
|
|
|
|
|
|
// Always Expanded Structured Data
|
|
|
- contentHtml += window.renderStructuredData(items, type);
|
|
|
+ contentHtml += window.renderStructuredData(items, type, c);
|
|
|
|
|
|
// Add JSON toggle at the bottom of the case section
|
|
|
const caseJsonStr = JSON.stringify(c, null, 2).replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
|
@@ -1308,6 +1308,39 @@ function setupEventListeners() {
|
|
|
memo.classList.toggle('hidden');
|
|
|
});
|
|
|
|
|
|
+ // Refresh Data without changing page position
|
|
|
+ const btnRefresh = document.getElementById('btn-refresh-data');
|
|
|
+ if (btnRefresh) {
|
|
|
+ btnRefresh.addEventListener('click', async () => {
|
|
|
+ if (currentSelectedIndex === null) {
|
|
|
+ alert("请先选择一个需求项目!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const oldText = btnRefresh.innerHTML;
|
|
|
+ btnRefresh.innerHTML = '🔄 刷新中...';
|
|
|
+ btnRefresh.disabled = true;
|
|
|
+
|
|
|
+ const modalCaseDetail = document.getElementById('case-detail-modal');
|
|
|
+ const isModalOpen = modalCaseDetail && !modalCaseDetail.classList.contains('hidden');
|
|
|
+ const activeSidebarItem = document.querySelector('.modal-sidebar-item.active');
|
|
|
+ const activeCaseIdx = activeSidebarItem ? parseInt(activeSidebarItem.id.replace('sidebar-item-', '')) : null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ await fetchRequirementData(currentSelectedIndex);
|
|
|
+
|
|
|
+ if (isModalOpen && activeCaseIdx !== null && typeof window.renderSingleCaseDetail === 'function') {
|
|
|
+ window.renderSingleCaseDetail(activeCaseIdx);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error("Failed to refresh data", e);
|
|
|
+ } finally {
|
|
|
+ btnRefresh.innerHTML = oldText;
|
|
|
+ btnRefresh.disabled = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
// Tabs
|
|
|
document.querySelectorAll('.tab-btn-pill').forEach(btn => {
|
|
|
btn.addEventListener('click', () => {
|
|
|
@@ -1960,7 +1993,7 @@ window.renderSingleCaseDetail = function(idx) {
|
|
|
${btnWorkflowHtml}
|
|
|
</div>
|
|
|
<div class="hidden" style="padding-top: 1.2rem;">
|
|
|
- ${window.renderStructuredData(wf && wf.workflow ? [wf.workflow] : null, 'workflow')}
|
|
|
+ ${window.renderStructuredData(wf && wf.workflow ? [wf.workflow] : null, 'workflow', wf)}
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -1973,7 +2006,7 @@ window.renderSingleCaseDetail = function(idx) {
|
|
|
${btnCapabilityHtml}
|
|
|
</div>
|
|
|
<div class="hidden" style="padding-top: 1.2rem;">
|
|
|
- ${window.renderStructuredData(wf && wf.capabilities ? wf.capabilities : null, 'capabilities')}
|
|
|
+ ${window.renderStructuredData(wf && wf.capabilities ? wf.capabilities : null, 'capabilities', wf)}
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
@@ -1989,7 +2022,7 @@ window.switchDetailTab = function(tabId) {
|
|
|
document.getElementById(`tab-content-${tabId}`).style.display = 'block';
|
|
|
};
|
|
|
|
|
|
-window.renderStructuredData = function(items, type) {
|
|
|
+window.renderStructuredData = function(items, type, parentItem = null) {
|
|
|
if (!items || items.length === 0) {
|
|
|
return `<div style="color:var(--text-muted); padding: 1rem;">暂无${type === 'workflow' ? '工序' : '能力'}数据</div>`;
|
|
|
}
|
|
|
@@ -2090,7 +2123,7 @@ window.renderStructuredData = function(items, type) {
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
- const renderApplyToVal = (valObj) => {
|
|
|
+ const renderApplyToVal = (valObj, isIdeal = false) => {
|
|
|
if (!valObj || typeof valObj !== 'object') return '-';
|
|
|
let res = '<div style="display:flex; flex-direction:column; gap:6px;">';
|
|
|
Object.entries(valObj).forEach(([k, v]) => {
|
|
|
@@ -2098,6 +2131,9 @@ window.renderStructuredData = function(items, type) {
|
|
|
res += `<div class="apply-to-subrow">
|
|
|
<span class="apply-to-key-badge" style="background: rgba(0,0,0,0.05); color: #475569;">${k}</span>
|
|
|
<div class="apply-to-values" style="display:flex; flex-wrap:wrap; gap:4px;">`;
|
|
|
+
|
|
|
+ let idealBadges = [];
|
|
|
+
|
|
|
v.forEach(pathObj => {
|
|
|
let pathStr = '';
|
|
|
let elementStr = '';
|
|
|
@@ -2134,7 +2170,54 @@ window.renderStructuredData = function(items, type) {
|
|
|
if (htmlParts) {
|
|
|
res += `<span class="apply-to-path-item has-tooltip">${htmlParts}${tooltipHtml}</span>`;
|
|
|
}
|
|
|
+
|
|
|
+ if (typeof pathObj === 'object' && pathObj !== null && pathObj.ideal_path) {
|
|
|
+ let fullNormalPath = pathStr || '';
|
|
|
+ if (elementStr) {
|
|
|
+ if (!fullNormalPath.endsWith('/')) fullNormalPath += '/';
|
|
|
+ fullNormalPath += elementStr;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (fullNormalPath !== pathObj.ideal_path) {
|
|
|
+ const normalParts = fullNormalPath.split('/');
|
|
|
+ const idealParts = pathObj.ideal_path.split('/');
|
|
|
+ const idealLeaf = idealParts.pop();
|
|
|
+
|
|
|
+ let prefixHtml = '';
|
|
|
+ if (idealParts.length > 0) {
|
|
|
+ prefixHtml += `<span class="apply-to-path-prefix">`;
|
|
|
+ for (let i = 0; i < idealParts.length; i++) {
|
|
|
+ if (i === 0 && idealParts[0] === '') {
|
|
|
+ prefixHtml += '/';
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ const p = idealParts[i];
|
|
|
+ const isNew = i >= normalParts.length || p !== normalParts[i];
|
|
|
+ if (isNew) {
|
|
|
+ prefixHtml += `<span style="color:#3b82f6; font-weight:600;">${p}</span>/`;
|
|
|
+ } else {
|
|
|
+ prefixHtml += `${p}/`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ prefixHtml += `</span>`;
|
|
|
+ }
|
|
|
+
|
|
|
+ const isLeafNew = idealParts.length >= normalParts.length || idealLeaf !== normalParts[idealParts.length];
|
|
|
+
|
|
|
+ const leafHtml = isLeafNew
|
|
|
+ ? `<span class="apply-to-path-leaf" style="background:#eff6ff; color:#2563eb; border:1px solid #bfdbfe;">${idealLeaf}</span>`
|
|
|
+ : `<span class="apply-to-path-leaf" style="background:#f1f5f9; color:#475569; border:1px solid #cbd5e1;">${idealLeaf}</span>`;
|
|
|
+
|
|
|
+ const idealHtml = `${prefixHtml}${leafHtml}`;
|
|
|
+ idealBadges.push(`<span class="apply-to-path-item has-tooltip" style="border: 2px dashed #94a3b8; background: transparent; margin-top: 4px;">${idealHtml}${tooltipHtml}</span>`);
|
|
|
+ }
|
|
|
+ }
|
|
|
});
|
|
|
+
|
|
|
+ if (idealBadges.length > 0) {
|
|
|
+ res += `<div style="flex-basis: 100%; height: 0; margin: 0;"></div>` + idealBadges.join('');
|
|
|
+ }
|
|
|
+
|
|
|
res += `</div></div>`;
|
|
|
}
|
|
|
});
|
|
|
@@ -2162,6 +2245,79 @@ window.renderStructuredData = function(items, type) {
|
|
|
</div>
|
|
|
</div>`;
|
|
|
}
|
|
|
+
|
|
|
+ // Render confidence fields
|
|
|
+ const formatDate = (ts) => {
|
|
|
+ if (!ts) return '-';
|
|
|
+ if (typeof ts === 'string' && ts.includes('-')) return ts;
|
|
|
+ const num = Number(ts);
|
|
|
+ if (isNaN(num) || num <= 0) return '-';
|
|
|
+ const d = new Date(num > 10000000000 ? num : num * 1000);
|
|
|
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
|
+ };
|
|
|
+
|
|
|
+ html += `<div class="structured-row">
|
|
|
+ <div class="structured-label" style="display:flex; align-items:center; gap:4px;">
|
|
|
+ 置信度
|
|
|
+ </div>
|
|
|
+ <div class="structured-value" style="display: flex; flex-wrap: wrap; gap: 8px; font-size: 0.85em;">
|
|
|
+ <div style="background: rgba(0,0,0,0.03); border: 1px solid rgba(0,0,0,0.06); padding: 4px 8px; border-radius: 6px; display:flex; gap: 6px;">
|
|
|
+ <span style="color: var(--text-muted);">Maturity:</span>
|
|
|
+ <span style="font-weight: 500; color: var(--text-main);">${String((item.maturity || (parentItem && parentItem.maturity)) || '-').replace(/</g, '<').replace(/>/g, '>')}</span>
|
|
|
+ </div>
|
|
|
+ <div style="background: rgba(0,0,0,0.03); border: 1px solid rgba(0,0,0,0.06); padding: 4px 8px; border-radius: 6px; display:flex; gap: 6px;">
|
|
|
+ <span style="color: var(--text-muted);">Validation:</span>
|
|
|
+ <span style="font-weight: 500; color: var(--text-main);">${(item.validation_count !== undefined && item.validation_count !== null) ? String(item.validation_count).replace(/</g, '<').replace(/>/g, '>') : (parentItem && parentItem.validation_count !== undefined && parentItem.validation_count !== null) ? String(parentItem.validation_count).replace(/</g, '<').replace(/>/g, '>') : '-'}</span>
|
|
|
+ </div>
|
|
|
+ <div style="background: rgba(0,0,0,0.03); border: 1px solid rgba(0,0,0,0.06); padding: 4px 8px; border-radius: 6px; display:flex; gap: 6px;">
|
|
|
+ <span style="color: var(--text-muted);">Published:</span>
|
|
|
+ <span style="font-weight: 500; color: var(--text-main);">${formatDate(item.published_at || (parentItem && parentItem.published_at))}</span>
|
|
|
+ </div>
|
|
|
+ <div style="background: rgba(0,0,0,0.03); border: 1px solid rgba(0,0,0,0.06); padding: 4px 8px; border-radius: 6px; display:flex; gap: 6px;">
|
|
|
+ <span style="color: var(--text-muted);">Last Verified:</span>
|
|
|
+ <span style="font-weight: 500; color: var(--text-main);">${formatDate(item.last_verified_at || (parentItem && parentItem.last_verified_at))}</span>
|
|
|
+ </div>
|
|
|
+ <div style="background: rgba(0,0,0,0.03); border: 1px solid rgba(0,0,0,0.06); padding: 4px 8px; border-radius: 6px; display:flex; gap: 6px;">
|
|
|
+ <span style="color: var(--text-muted);">Created:</span>
|
|
|
+ <span style="font-weight: 500; color: var(--text-main);">${formatDate(item.created_at || (parentItem && parentItem.created_at))}</span>
|
|
|
+ </div>
|
|
|
+ <div style="background: rgba(0,0,0,0.03); border: 1px solid rgba(0,0,0,0.06); padding: 4px 8px; border-radius: 6px; display:flex; gap: 6px;">
|
|
|
+ <span style="color: var(--text-muted);">Updated:</span>
|
|
|
+ <span style="font-weight: 500; color: var(--text-main);">${formatDate(item.updated_at || (parentItem && parentItem.updated_at))}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>`;
|
|
|
+
|
|
|
+ // Render feedback if available
|
|
|
+ const feedbackVal = item.feedback || (parentItem && parentItem.feedback);
|
|
|
+ if (feedbackVal) {
|
|
|
+ let feedbackHtml = '';
|
|
|
+ if (typeof feedbackVal === 'object' && feedbackVal !== null) {
|
|
|
+ Object.entries(feedbackVal).forEach(([k, v]) => {
|
|
|
+ if (v !== null && v !== undefined && String(v).trim() !== '') {
|
|
|
+ feedbackHtml += `<div style="background: rgba(0,0,0,0.03); border: 1px solid rgba(0,0,0,0.06); padding: 4px 8px; border-radius: 6px; display:flex; gap: 6px;">
|
|
|
+ <span style="color: var(--text-muted);">${k}:</span>
|
|
|
+ <span style="font-weight: 500; color: var(--text-main);">${v}</span>
|
|
|
+ </div>`;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (feedbackHtml !== '') {
|
|
|
+ feedbackHtml = `<div style="display: flex; flex-wrap: wrap; gap: 8px; font-size: 0.85em;">${feedbackHtml}</div>`;
|
|
|
+ }
|
|
|
+ } else if (typeof feedbackVal === 'string' && feedbackVal.trim() !== '') {
|
|
|
+ feedbackHtml = `<div style="color: var(--text-main); font-size: 0.95em; line-height: 1.5; white-space: pre-wrap; padding-top: 2px;">${String(feedbackVal).replace(/</g, '<').replace(/>/g, '>')}</div>`;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (feedbackHtml !== '') {
|
|
|
+ html += `<div class="structured-row">
|
|
|
+ <div class="structured-label">feedback</div>
|
|
|
+ <div class="structured-value" style="display: flex; align-items: center;">
|
|
|
+ ${feedbackHtml}
|
|
|
+ </div>
|
|
|
+ </div>`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
// Render body
|
|
|
if (item.body && typeof item.body === 'string') {
|
|
|
@@ -2179,12 +2335,12 @@ window.renderStructuredData = function(items, type) {
|
|
|
const desc = isValid(io.description) ? io.description.replace(/</g, '<').replace(/>/g, '>') : '';
|
|
|
const mod = isValid(io.modality) ? io.modality : '';
|
|
|
let content = '';
|
|
|
+ if (mod) {
|
|
|
+ content += `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-right:6px;margin-bottom:2px;display:inline-block;">${mod}</span>`;
|
|
|
+ }
|
|
|
if (desc) {
|
|
|
content += desc;
|
|
|
}
|
|
|
- if (mod) {
|
|
|
- content += `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-left:6px;margin-top:2px;">${mod}</span>`;
|
|
|
- }
|
|
|
if (!content) {
|
|
|
const keys = Object.keys(io);
|
|
|
if (keys.length === 1 && typeof io[keys[0]] === 'string') {
|
|
|
@@ -2217,11 +2373,11 @@ window.renderStructuredData = function(items, type) {
|
|
|
<th style="padding: 12px 10px; width: 60px;">序号</th>
|
|
|
<th style="padding: 12px 10px; width: 70px;">阶段</th>
|
|
|
<th style="display: none;">操作流</th>
|
|
|
- <th style="padding: 12px 10px; width: 200px;">输入 (Inputs)</th>
|
|
|
+ <th style="padding: 12px 10px; width: 200px;">输入</th>
|
|
|
<th style="padding: 12px 10px; width: 120px;">动作</th>
|
|
|
- <th style="padding: 12px 10px; width: 200px;">输出 (Outputs)</th>
|
|
|
- <th style="padding: 12px 10px; width: 200px;">操作对象 (Apply)</th>
|
|
|
- <th style="padding: 12px 10px; width: 280px;">具体做法</th>
|
|
|
+ <th style="padding: 12px 10px; width: 200px;">输出</th>
|
|
|
+ <th style="padding: 12px 10px; width: 200px;">作用域</th>
|
|
|
+ <th style="padding: 12px 10px; width: 280px;">做法</th>
|
|
|
<th style="padding: 12px 10px; width: 100px;">工具</th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
@@ -2248,6 +2404,17 @@ window.renderStructuredData = function(items, type) {
|
|
|
if (!stepTitle || stepTitle === '未知') stepTitle = escapeHtml(`步骤 ${step.order || stepIdx + 1}`);
|
|
|
}
|
|
|
|
|
|
+ let actionHtml = escapeHtml(actionText);
|
|
|
+ if (step.action && step.action.main_action) {
|
|
|
+ const badgeHtml = `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-right:6px;margin-bottom:2px;display:inline-block;">${escapeHtml(step.action.main_action)}</span>`;
|
|
|
+ actionHtml = step.action.mechanism ? badgeHtml + escapeHtml(step.action.mechanism) : badgeHtml;
|
|
|
+ } else {
|
|
|
+ const match = actionText.match(/^\[(.*?)\]\s*(.*)$/);
|
|
|
+ if (match) {
|
|
|
+ actionHtml = `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-right:6px;margin-bottom:2px;display:inline-block;">${escapeHtml(match[1])}</span>${escapeHtml(match[2])}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
let toolsHtml = '';
|
|
|
if (step.tools && step.tools.length > 0) {
|
|
|
toolsHtml = step.tools.map(t => `<span class="structured-badge tool-badge" style="display:inline-block; margin:2px;">${escapeHtml(t)}</span>`).join('');
|
|
|
@@ -2270,8 +2437,8 @@ window.renderStructuredData = function(items, type) {
|
|
|
<div class="cell-content" style="max-height: 50px; overflow: hidden;">${step.inputs && Array.isArray(step.inputs) && step.inputs.length > 0 ? renderDataObjList(step.inputs) : '-'}</div>
|
|
|
<div class="cell-fade"></div>
|
|
|
</td>
|
|
|
- <td style="padding: 14px 10px; font-weight: 600; color: var(--accent-primary);">
|
|
|
- ${escapeHtml(actionText)}
|
|
|
+ <td style="padding: 14px 10px; font-weight: bold; color: var(--text-main); line-height: 1.5;">
|
|
|
+ ${actionHtml}
|
|
|
</td>
|
|
|
<td style="padding: 14px 10px; position: relative;">
|
|
|
<div class="cell-content" style="max-height: 50px; overflow: hidden;">${step.outputs && Array.isArray(step.outputs) && step.outputs.length > 0 ? renderDataObjList(step.outputs) : '-'}</div>
|