|
|
@@ -52,8 +52,7 @@ const PIPELINE_STEPS = [
|
|
|
{ id: 'research', label: '1.1 分布式爬取' },
|
|
|
{ id: 'source', label: '1.5 提取数据源' },
|
|
|
{ id: 'generate-case', label: '1.6 生成 case.json' },
|
|
|
- { id: 'workflow-extract', label: '1.6a 工作流提取' },
|
|
|
- { id: 'capability-extract-1', label: '1.6b 原子能力提取' },
|
|
|
+ { id: 'workflow-extract', label: '1.6 提取' },
|
|
|
{ id: 'apply-grounding', label: '1.7 场景映射' },
|
|
|
{ id: 'process-cluster', label: '2.1.1 工序聚类' },
|
|
|
{ id: 'process-score', label: '2.1.2 工序打分' },
|
|
|
@@ -1244,8 +1243,7 @@ async function triggerRun() {
|
|
|
"step_1.1": "research",
|
|
|
"step_1.5": "source",
|
|
|
"step_1.6": "generate-case",
|
|
|
- "step_1.6a": "workflow-extract",
|
|
|
- "step_1.6b": "capability-extract",
|
|
|
+ "step_1.6_extract": "workflow-extract",
|
|
|
"step_1.7": "apply-grounding",
|
|
|
"step_2.1.1": "process-cluster",
|
|
|
"step_2.1.2": "process-score",
|
|
|
@@ -1776,7 +1774,7 @@ function renderPipelineChain() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const LINEAR_PREFIX = ["research", "source", "generate-case", "workflow-extract", "capability-extract-1", "apply-grounding"];
|
|
|
+ const LINEAR_PREFIX = ["research", "source", "generate-case", "workflow-extract", "apply-grounding"];
|
|
|
const BRANCH_21 = ["process-cluster", "process-score"];
|
|
|
const BRANCH_22 = ["capability-extract", "capability-enrich"];
|
|
|
const STRATEGY = "strategy";
|
|
|
@@ -2584,19 +2582,51 @@ window.renderStructuredData = function(items, type, parentItem = null) {
|
|
|
// Render steps array specially
|
|
|
if (item.steps && Array.isArray(item.steps)) {
|
|
|
const allFragments = (parentItem && parentItem.fragments) || [];
|
|
|
-
|
|
|
- const hasInputs = item.steps.some(s => s.inputs && Array.isArray(s.inputs) && s.inputs.length > 0) || allFragments.some(f => f.inputs && Array.isArray(f.inputs) && f.inputs.length > 0);
|
|
|
- const hasOutputs = item.steps.some(s => s.outputs && Array.isArray(s.outputs) && s.outputs.length > 0) || allFragments.some(f => f.outputs && Array.isArray(f.outputs) && f.outputs.length > 0);
|
|
|
- const hasAction = item.steps.some(s => s.action || s.method || s.description) || allFragments.some(f => f.action);
|
|
|
- const hasApplyTo = item.steps.some(s => s.apply_to || s.apply_to_draft || s.apply_to_grounding) || allFragments.some(f => f.apply_to || f.apply_to_draft || f.apply_to_grounding);
|
|
|
- const hasTools = item.steps.some(s => s.tools && Array.isArray(s.tools) && s.tools.length > 0) || allFragments.some(f => f.tools && Array.isArray(f.tools) && f.tools.length > 0);
|
|
|
- const hasRelation = item.steps.some(s => s.relation);
|
|
|
- const hasStepId = item.steps.some(s => s.step_id);
|
|
|
-
|
|
|
- let minWidth = 400;
|
|
|
- if (hasStepId) minWidth += 60;
|
|
|
- if (hasRelation) minWidth += 120;
|
|
|
- minWidth += 400; // base width for Fragments column
|
|
|
+ const escapeHtml = (s) => String(s).replace(/</g, '<').replace(/>/g, '>');
|
|
|
+ const minWidth = 1280;
|
|
|
+ const renderAction = (src) => {
|
|
|
+ if (!src) return '-';
|
|
|
+ if (src.action && src.action.main_action) {
|
|
|
+ const mainAction = escapeHtml(src.action.main_action);
|
|
|
+ const mechanism = src.action.mechanism ? escapeHtml(src.action.mechanism) : '';
|
|
|
+ return `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-right:6px;margin-bottom:2px;display:inline-block;">${mainAction}</span>${mechanism}`;
|
|
|
+ }
|
|
|
+ if (src.method) return escapeHtml(src.method);
|
|
|
+ if (src.description) return escapeHtml(src.description);
|
|
|
+ return '-';
|
|
|
+ };
|
|
|
+ const renderTools = (tools) => {
|
|
|
+ if (!tools || !Array.isArray(tools) || tools.length === 0) return '-';
|
|
|
+ return tools.map(t => `<span class="structured-badge tool-badge" style="display:inline-block; margin:2px;">${escapeHtml(t)}</span>`).join('');
|
|
|
+ };
|
|
|
+ const getStepFragments = (step) => {
|
|
|
+ if (!step || !step.step_id) return [];
|
|
|
+ return allFragments.filter(f => {
|
|
|
+ const refStepId = f.workflow_step_ref && f.workflow_step_ref.step_id;
|
|
|
+ return refStepId === step.step_id || (
|
|
|
+ f.fragment_id && (
|
|
|
+ f.fragment_id === `f_${step.step_id}` ||
|
|
|
+ f.fragment_id.startsWith(`f_${step.step_id}_`)
|
|
|
+ )
|
|
|
+ );
|
|
|
+ });
|
|
|
+ };
|
|
|
+ const matchedFragments = new Set();
|
|
|
+ const renderFragmentColumns = (fragment) => {
|
|
|
+ const applyTo = fragment && (fragment.apply_to_draft || fragment.apply_to_grounding || fragment.apply_to);
|
|
|
+ return `
|
|
|
+ <td class="fragment-cell" style="font-family: monospace; color:#4338ca;">
|
|
|
+ <span class="row-expand-icon">▶</span>
|
|
|
+ ${fragment && fragment.fragment_id ? `<span style="display:inline-block; font-weight:bold; background:#e0e7ff; border:1px solid #c7d2fe; padding:2px 6px; border-radius:4px;">${escapeHtml(fragment.fragment_id)}</span>` : '-'}
|
|
|
+ </td>
|
|
|
+ <td class="fragment-cell"><div class="fragment-clamp">${renderAction(fragment)}</div></td>
|
|
|
+ <td class="fragment-cell"><div class="fragment-clamp">${fragment && fragment.inputs && fragment.inputs.length > 0 ? renderDataObjList(fragment.inputs) : '-'}</div></td>
|
|
|
+ <td class="fragment-cell"><div class="fragment-clamp">${fragment && fragment.outputs && fragment.outputs.length > 0 ? renderDataObjList(fragment.outputs) : '-'}</div></td>
|
|
|
+ <td class="fragment-cell" style="font-size:0.9em;"><div class="fragment-clamp">${applyTo ? renderApplyToVal(applyTo) : '-'}</div></td>
|
|
|
+ <td class="fragment-cell"><div class="fragment-clamp">${fragment ? renderTools(fragment.tools) : '-'}</div></td>
|
|
|
+ <td class="fragment-cell"><div class="fragment-clamp fragment-text">${fragment && fragment.body ? escapeHtml(fragment.body) : '-'}</div></td>
|
|
|
+ `;
|
|
|
+ };
|
|
|
|
|
|
html += `<div class="structured-row">
|
|
|
<div class="structured-label">steps</div>
|
|
|
@@ -2604,140 +2634,62 @@ window.renderStructuredData = function(items, type, parentItem = null) {
|
|
|
<style>
|
|
|
.steps-table tbody tr { cursor: pointer; transition: background 0.2s; }
|
|
|
.steps-table tbody tr:hover { background: rgba(0,0,0,0.02) !important; }
|
|
|
- .steps-table tr.expanded-row .cell-content { max-height: 3000px !important; overflow: visible !important; }
|
|
|
- .steps-table tr .expand-icon { transition: transform 0.2s; display: inline-block; font-size: 0.8em; color: var(--text-muted); }
|
|
|
- .steps-table tr.expanded-row .expand-icon { transform: rotate(90deg); }
|
|
|
- .cell-fade { position: absolute; bottom: 0; left: 0; right: 0; height: 30px; background: linear-gradient(to bottom, rgba(255,255,255,0), white); pointer-events: none; }
|
|
|
- .steps-table tr.expanded-row .cell-fade { display: none; }
|
|
|
.steps-table td { border-bottom: 1px solid rgba(0,0,0,0.05); }
|
|
|
- .steps-table tr.expanded-row .fragments-collapsed-view { display: none !important; }
|
|
|
- .steps-table tr.expanded-row .fragments-expanded-view { display: flex !important; }
|
|
|
+ .steps-table .step-merged-cell { background: #fbfdff; border-right: 1px dashed rgba(0,0,0,0.08); }
|
|
|
+ .steps-table .fragment-cell { padding: 12px 10px; vertical-align: top; line-height: 1.5; color: var(--text-main); }
|
|
|
+ .steps-table .fragment-clamp { max-height: 72px; overflow: hidden; position: relative; }
|
|
|
+ .steps-table .fragment-clamp::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 24px; background: linear-gradient(to bottom, rgba(255,255,255,0), white); pointer-events: none; }
|
|
|
+ .steps-table tr.fragment-expanded .fragment-clamp { max-height: none; overflow: visible; }
|
|
|
+ .steps-table tr.fragment-expanded .fragment-clamp::after { display: none; }
|
|
|
+ .steps-table .row-expand-icon { display: inline-block; margin-right: 6px; color: var(--text-muted); font-size: 0.8em; transition: transform 0.2s; }
|
|
|
+ .steps-table tr.fragment-expanded .row-expand-icon { transform: rotate(90deg); }
|
|
|
+ .steps-table .fragment-text { white-space: pre-wrap; word-break: break-word; }
|
|
|
</style>
|
|
|
<table class="steps-table" style="width: 100%; min-width: ${minWidth}px; border-collapse: collapse; margin-top: 8px; font-size: 0.9em; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
|
|
|
<thead>
|
|
|
<tr style="background: rgba(0,0,0,0.03); border-bottom: 2px solid rgba(0,0,0,0.1); text-align: left;">
|
|
|
<th style="padding: 12px 10px; width: 60px;">序号</th>
|
|
|
- ${hasStepId ? `<th style="padding: 12px 10px; width: 60px;">ID</th>` : ''}
|
|
|
- <th style="padding: 12px 10px; width: 70px;">阶段</th>
|
|
|
- ${hasRelation ? `<th style="padding: 12px 10px; width: 120px;">流转关系</th>` : ''}
|
|
|
- <th style="padding: 12px 10px; width: 280px; border-right: 1px solid rgba(0,0,0,0.05);">做法</th>
|
|
|
- <th style="padding: 12px 10px; width: auto;">原子操作 (Fragments)</th>
|
|
|
+ <th style="padding: 12px 10px; width: 90px;">阶段</th>
|
|
|
+ <th style="padding: 12px 10px; width: 120px;">Fragment ID</th>
|
|
|
+ <th style="padding: 12px 10px; width: 160px;">动作</th>
|
|
|
+ <th style="padding: 12px 10px; width: 180px;">输入</th>
|
|
|
+ <th style="padding: 12px 10px; width: 180px;">输出</th>
|
|
|
+ <th style="padding: 12px 10px; width: 260px;">作用域</th>
|
|
|
+ <th style="padding: 12px 10px; width: 130px;">工具</th>
|
|
|
+ <th style="padding: 12px 10px; width: 280px;">做法</th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody>`;
|
|
|
-
|
|
|
- const escapeHtml = (s) => String(s).replace(/</g, '<').replace(/>/g, '>');
|
|
|
+
|
|
|
item.steps.forEach((step, stepIdx) => {
|
|
|
- const stepFragments = allFragments.filter(f => f.fragment_id && (f.fragment_id === `f_${step.step_id}` || f.fragment_id.startsWith(`f_${step.step_id}_`)));
|
|
|
-
|
|
|
- let fragsToRender = stepFragments.length > 0 ? stepFragments : [step];
|
|
|
- if (stepFragments.length === 0) {
|
|
|
- const hasFragFields = step.inputs || step.outputs || step.action || step.tools || step.apply_to || step.apply_to_draft || step.apply_to_grounding;
|
|
|
- if (!hasFragFields) fragsToRender = [];
|
|
|
- }
|
|
|
-
|
|
|
- let fragmentsHtml = '';
|
|
|
- if (fragsToRender.length > 0) {
|
|
|
- let collapsedIdsHtml = fragsToRender.map(src => {
|
|
|
- return src.fragment_id
|
|
|
- ? `<span style="display: inline-block; font-family: monospace; font-size: 0.85em; font-weight: bold; color: #4338ca; background: #e0e7ff; border: 1px solid #c7d2fe; padding: 2px 6px; border-radius: 4px;">${escapeHtml(src.fragment_id)}</span>`
|
|
|
- : `<span style="display: inline-block; font-size: 0.85em; color: #64748b; background: #f1f5f9; padding: 2px 6px; border-radius: 4px;">含操作详情</span>`;
|
|
|
- }).join(' ');
|
|
|
-
|
|
|
- let expandedCardsHtml = fragsToRender.map(src => {
|
|
|
- let actionText = '未知';
|
|
|
- if (src.action && src.action.main_action) {
|
|
|
- actionText = src.action.mechanism ? `[${src.action.main_action}] ${src.action.mechanism}` : src.action.main_action;
|
|
|
- } else if (src.method) {
|
|
|
- actionText = src.method;
|
|
|
- } else if (src.description) {
|
|
|
- actionText = src.description;
|
|
|
- }
|
|
|
-
|
|
|
- let actionHtml = escapeHtml(actionText);
|
|
|
- if (src.action && src.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(src.action.main_action)}</span>`;
|
|
|
- actionHtml = src.action.mechanism ? badgeHtml + escapeHtml(src.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])}`;
|
|
|
- } else if (actionText === '未知') {
|
|
|
- actionHtml = '';
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let toolsHtml = '';
|
|
|
- if (src.tools && src.tools.length > 0) {
|
|
|
- toolsHtml = src.tools.map(t => `<span class="structured-badge tool-badge" style="display:inline-block; margin:2px;">${escapeHtml(t)}</span>`).join('');
|
|
|
- }
|
|
|
-
|
|
|
- return `<div style="border: 1px solid rgba(0,0,0,0.08); border-radius: 6px; background: white; position: relative; box-shadow: 0 1px 2px rgba(0,0,0,0.02); overflow: hidden;">
|
|
|
- <div style="display: flex; justify-content: space-between; align-items: center; background: #f8fafc; padding: 8px 12px; border-bottom: 1px solid rgba(0,0,0,0.05);">
|
|
|
- <div style="font-weight: bold; color: var(--text-main); font-size: 0.95em;">${actionHtml || '操作'}</div>
|
|
|
- ${src.fragment_id ? `<div style="font-family: monospace; font-size: 0.85em; font-weight: bold; color: #4338ca; background: #e0e7ff; border: 1px solid #c7d2fe; padding: 2px 8px; border-radius: 4px;">${escapeHtml(src.fragment_id)}</div>` : ''}
|
|
|
- </div>
|
|
|
- <div style="padding: 10px 12px; display: flex; flex-direction: column; gap: 8px;">
|
|
|
- ${src.inputs && src.inputs.length > 0 ? `
|
|
|
- <div style="display: flex; align-items: flex-start; gap: 12px;">
|
|
|
- <div style="width: 48px; font-size: 0.8em; color: var(--text-muted); background: #f1f5f9; padding: 3px 6px; border-radius: 4px; text-align: center; flex-shrink: 0; margin-top: 1px;">输入</div>
|
|
|
- <div style="flex-grow: 1; min-width: 0;">${renderDataObjList(src.inputs)}</div>
|
|
|
- </div>` : ''}
|
|
|
-
|
|
|
- ${src.outputs && src.outputs.length > 0 ? `
|
|
|
- <div style="display: flex; align-items: flex-start; gap: 12px;">
|
|
|
- <div style="width: 48px; font-size: 0.8em; color: var(--text-muted); background: #f1f5f9; padding: 3px 6px; border-radius: 4px; text-align: center; flex-shrink: 0; margin-top: 1px;">输出</div>
|
|
|
- <div style="flex-grow: 1; min-width: 0;">${renderDataObjList(src.outputs)}</div>
|
|
|
- </div>` : ''}
|
|
|
-
|
|
|
- ${(src.apply_to_draft || src.apply_to_grounding || src.apply_to) ? `
|
|
|
- <div style="display: flex; align-items: flex-start; gap: 12px;">
|
|
|
- <div style="width: 48px; font-size: 0.8em; color: var(--text-muted); background: #f1f5f9; padding: 3px 6px; border-radius: 4px; text-align: center; flex-shrink: 0; margin-top: 1px;">作用域</div>
|
|
|
- <div style="flex-grow: 1; min-width: 0; font-size: 0.9em; line-height: 1.5; color: var(--text-main); margin-top: 2px;">${renderApplyToVal(src.apply_to_draft || src.apply_to_grounding || src.apply_to)}</div>
|
|
|
- </div>` : ''}
|
|
|
-
|
|
|
- ${toolsHtml ? `
|
|
|
- <div style="display: flex; align-items: flex-start; gap: 12px;">
|
|
|
- <div style="width: 48px; font-size: 0.8em; color: var(--text-muted); background: #f1f5f9; padding: 3px 6px; border-radius: 4px; text-align: center; flex-shrink: 0; margin-top: 1px;">工具</div>
|
|
|
- <div style="flex-grow: 1; min-width: 0; display: flex; flex-wrap: wrap; gap: 4px;">${toolsHtml}</div>
|
|
|
- </div>` : ''}
|
|
|
- </div>
|
|
|
- </div>`;
|
|
|
- }).join('');
|
|
|
-
|
|
|
- fragmentsHtml = `
|
|
|
- <div class="fragments-collapsed-view" style="display: flex; gap: 6px; flex-wrap: wrap; align-items: center; padding-top: 2px;">
|
|
|
- ${collapsedIdsHtml}
|
|
|
- </div>
|
|
|
- <div class="fragments-expanded-view" style="display: none; flex-direction: column; gap: 8px;">
|
|
|
- ${expandedCardsHtml}
|
|
|
- </div>
|
|
|
+ const stepFragments = getStepFragments(step);
|
|
|
+ stepFragments.forEach(fragment => matchedFragments.add(fragment));
|
|
|
+ const fragsToRender = stepFragments.length > 0 ? stepFragments : [null];
|
|
|
+ const rowspan = fragsToRender.length;
|
|
|
+
|
|
|
+ fragsToRender.forEach((fragment, fragmentIdx) => {
|
|
|
+ html += `
|
|
|
+ <tr style="vertical-align: top;" onclick="this.classList.toggle('fragment-expanded')">
|
|
|
+ ${fragmentIdx === 0 ? `
|
|
|
+ <td class="step-merged-cell" rowspan="${rowspan}" style="padding: 14px 10px; font-weight: 600; color: var(--text-muted); text-align: center;">
|
|
|
+ ${step.order || stepIdx + 1}
|
|
|
+ </td>
|
|
|
+ <td class="step-merged-cell" rowspan="${rowspan}" style="padding: 14px 10px;">
|
|
|
+ ${step.phase ? `<span class="structured-badge" style="background:#f1f5f9; color:#475569; font-weight: 500;">${escapeHtml(step.phase)}</span>` : '-'}
|
|
|
+ </td>` : ''}
|
|
|
+ ${renderFragmentColumns(fragment)}
|
|
|
+ </tr>
|
|
|
`;
|
|
|
- } else {
|
|
|
- fragmentsHtml = `<div class="cell-content" style="max-height: 50px; overflow: hidden; color: var(--text-muted);">-</div><div class="cell-fade"></div>`;
|
|
|
- }
|
|
|
-
|
|
|
+ });
|
|
|
+ });
|
|
|
+ allFragments.filter(fragment => !matchedFragments.has(fragment)).forEach(fragment => {
|
|
|
html += `
|
|
|
- <tr style="vertical-align: top; border-bottom: 1px solid rgba(0,0,0,0.05);" onclick="this.classList.toggle('expanded-row')">
|
|
|
- <td style="padding: 14px 10px; font-weight: 500; color: var(--text-muted); text-align: center; border-right: 1px dashed rgba(0,0,0,0.05);">
|
|
|
- <span class="expand-icon">▶</span> ${step.order || stepIdx + 1}
|
|
|
- </td>
|
|
|
- ${hasStepId ? `<td style="padding: 14px 10px; font-family: monospace; color: var(--text-muted); border-right: 1px dashed rgba(0,0,0,0.05);">${escapeHtml(step.step_id || '-')}</td>` : ''}
|
|
|
- <td style="padding: 14px 10px; border-right: 1px dashed rgba(0,0,0,0.05);">
|
|
|
- ${step.phase ? `<span class="structured-badge" style="background:#f1f5f9; color:#475569; font-weight: 500;">${escapeHtml(step.phase)}</span>` : '-'}
|
|
|
- </td>
|
|
|
- ${hasRelation ? `
|
|
|
- <td style="padding: 14px 10px; color: var(--text-secondary); font-size: 0.9em; position: relative; border-right: 1px dashed rgba(0,0,0,0.05);">
|
|
|
- <div class="cell-content" style="max-height: 50px; overflow: hidden; font-family: monospace;">${step.relation ? escapeHtml(step.relation) : '-'}</div>
|
|
|
- <div class="cell-fade"></div>
|
|
|
- </td>` : ''}
|
|
|
- <td style="padding: 14px 10px; color: var(--text-main); font-size: 0.95em; line-height: 1.5; position: relative; border-right: 1px solid rgba(0,0,0,0.05);">
|
|
|
- <div class="cell-content" style="max-height: 50px; overflow: hidden; white-space: pre-wrap;">${step.body ? escapeHtml(step.body) : '-'}</div>
|
|
|
- <div class="cell-fade"></div>
|
|
|
- </td>
|
|
|
- <td style="padding: 14px 10px; position: relative;">
|
|
|
- ${fragmentsHtml}
|
|
|
+ <tr style="vertical-align: top;" onclick="this.classList.toggle('fragment-expanded')">
|
|
|
+ <td class="step-merged-cell" style="padding: 14px 10px; font-weight: 600; color: var(--text-muted); text-align: center;">-</td>
|
|
|
+ <td class="step-merged-cell" style="padding: 14px 10px;">
|
|
|
+ <span class="structured-badge" style="background:#f8fafc; color:#64748b; font-weight: 500;">独立片段</span>
|
|
|
</td>
|
|
|
+ ${renderFragmentColumns(fragment)}
|
|
|
</tr>
|
|
|
`;
|
|
|
});
|