elksmmx 1 день назад
Родитель
Сommit
ebe8a04dfa
2 измененных файлов с 97 добавлено и 158 удалено
  1. 91 139
      examples/process_pipeline/ui/app.js
  2. 6 19
      examples/process_pipeline/ui/index.html

+ 91 - 139
examples/process_pipeline/ui/app.js

@@ -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, '&lt;').replace(/>/g, '&gt;');
+            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, '&lt;').replace(/>/g, '&gt;');
+
             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>
                 `;
             });

+ 6 - 19
examples/process_pipeline/ui/index.html

@@ -227,8 +227,7 @@
                             <option value="step_1.1">1.1 分布式爬取 (仅爬取选定平台)</option>
                             <option value="step_1.5">1.5 提取数据源 (生成 source.json)</option>
                             <option value="step_1.6">1.6 格式标准化 (生成 case.json)</option>
-                            <option value="step_1.6a">1.6a 提取工作流 (修改 case.json)</option>
-                            <option value="step_1.6b">1.6b 提取能力 (完善 case.json)</option>
+                            <option value="step_1.6_extract">1.6 提取 (修改 case.json)</option>
                             <option value="step_1.7">1.7 场景映射 (修改 case.json)</option>
                             <!-- <option value="step_2.1.1">2.1.1 工序聚类 (生成 blueprint_temp.json)</option>
                             <option value="step_2.1.2">2.1.2 工序打分 (生成 process.json)</option>
@@ -261,11 +260,7 @@
                         </div>
                         <div class="dag-arrow">↓</div>
                         <div class="dag-row">
-                            <div class="chain-node" data-id="workflow-extract" id="node-workflow-extract">1.6a 工作流提取</div>
-                        </div>
-                        <div class="dag-arrow">↓</div>
-                        <div class="dag-row">
-                            <div class="chain-node" data-id="capability-extract-1" id="node-capability-extract-1">1.6b 原子能力提取</div>
+                            <div class="chain-node" data-id="workflow-extract" id="node-workflow-extract">1.6 提取</div>
                         </div>
                         <div class="dag-arrow">↓</div>
                         <div class="dag-row">
@@ -381,18 +376,10 @@
                             <div class="chain-node prompt-node" data-prompt="generate_case.prompt" style="font-size: 0.85em; padding: 8px;">1.5.5 格式标准化 (generate_case)</div>
                         </div>
                         <div class="dag-arrow">↓</div>
-                        <div class="dag-branch-split" style="margin: 10px 0;">
-                            <div class="dag-branch">
-                                <div class="dag-branch-arrow-left">↙</div>
-                                <div class="chain-node prompt-node" data-prompt="extract_workflow.prompt" style="font-size: 0.85em; padding: 8px;">1.6 工作流提取<br>(extract_workflow)</div>
-                                <div class="dag-branch-arrow-left" style="transform: translateX(10px);">↘</div>
-                            </div>
-                            <div class="dag-branch">
-                                <div class="dag-branch-arrow-right">↘</div>
-                                <div class="chain-node prompt-node" data-prompt="extract_capability.prompt" style="font-size: 0.85em; padding: 8px;">1.6 能力提取<br>(extract_capability)</div>
-                                <div class="dag-branch-arrow-right" style="transform: translateX(-10px);">↙</div>
-                            </div>
+                        <div class="dag-row">
+                            <div class="chain-node prompt-node" data-prompt="extract_workflow.prompt" style="font-size: 0.85em; padding: 8px;">1.6 提取<br>(extract_workflow)</div>
                         </div>
+                        <div class="dag-arrow">↓</div>
                         
                         <div class="dag-row">
                             <div class="chain-node prompt-node" data-prompt="apply_to_grounding.prompt" style="font-size: 0.85em; padding: 8px;">1.7 场景落地<br>(apply_to_grounding)</div>
@@ -485,4 +472,4 @@
     <script src="/static/app.js?v=11"></script>
 </body>
 
-</html>
+</html>