window.openCaseDetail = function (p, initialIdx) {
if (!window._currentRawCasesContext) return;
const ctx = window._currentRawCasesContext;
// Determine the list of platforms to aggregate.
let platformsToAggregate = [p];
if (p !== 'filtered_cases' && p !== 'source_ex') {
const crawlerPlatforms = Object.keys(ctx.rawCasesObj).filter(k => k !== 'source' && k !== 'case_detailed' && k !== 'case' && k !== 'images' && k !== 'filtered_cases' && k !== 'source_ex');
platformsToAggregate = [...crawlerPlatforms, 'source'];
}
let casesList = [];
let globalInitialIdx = 0;
const seenIds = new Set();
platformsToAggregate.forEach(plat => {
if (!ctx.rawCasesObj[plat]) return;
let platCases = [];
if (Array.isArray(ctx.rawCasesObj[plat])) {
platCases = ctx.rawCasesObj[plat];
} else if (ctx.rawCasesObj[plat].cases) {
platCases = ctx.rawCasesObj[plat].cases;
} else if (ctx.rawCasesObj[plat].sources) {
platCases = ctx.rawCasesObj[plat].sources;
} else if (ctx.rawCasesObj[plat].by_reason) {
Object.entries(ctx.rawCasesObj[plat].by_reason).forEach(([reasonKey, reasonObj]) => {
if (reasonObj.sources && Array.isArray(reasonObj.sources)) {
reasonObj.sources.forEach(src => {
if (!src.filter_reason) src.filter_reason = reasonKey;
platCases.push(src);
});
}
});
}
platCases.forEach((c, idx) => {
const cId = c.case_id || (c._raw && c._raw.case_id) || (c.post && c.post.channel_content_id) || `temp_${plat}_${idx}`;
const cUrl = c.source_url || c.url || (c.post && c.post.link) || '';
const mappedS = ctx.sourceMap[cId] || ctx.sourceMap[cUrl] || (c._raw && ctx.sourceMap[c._raw.case_id]);
if (plat !== 'filtered_cases' && plat !== 'source' && plat !== 'source_ex' && !mappedS) return;
if (seenIds.has(cId)) return;
seenIds.add(cId);
const augmentedC = { ...c, _actualPlatform: plat };
if (plat === p && idx === initialIdx) {
globalInitialIdx = casesList.length;
}
casesList.push(augmentedC);
});
});
window._currentModalCases = casesList;
window._currentModalPlatform = p;
window._currentModalContext = ctx;
window._currentModalIdx = globalInitialIdx;
// Build Sidebar
let sidebarHtml = '
';
// Build Main Content Skeleton
const mainHtml = `
`;
const renderApplyToVal = (valObj, suggestApplyTo = null) => {
if (!valObj || typeof valObj !== 'object') return '-';
const escapeApplyToText = (s) => String(s).replace(//g, '>');
const renderPathParts = (pathValue, highlight = false) => {
const pathStr = String(pathValue || '').trim();
if (!pathStr) return '';
const parts = pathStr.split('/');
const leaf = parts.pop();
const prefix = parts.length > 0 ? parts.join('/') + '/' : '';
const leafStyle = highlight
? 'background:#eff6ff; color:#2563eb; border:1px solid #bfdbfe;'
: '';
return `
${prefix ? `
${escapeApplyToText(prefix)}` : ''}
${escapeApplyToText(leaf)}
`;
};
const renderTooltip = (pathObj) => {
if (
typeof pathObj !== 'object'
|| pathObj === null
|| !(pathObj.rationale || pathObj.body_excerpt || pathObj.body_excerpt_note || pathObj.body_excerpt_type || pathObj.category_id)
) {
return '';
}
return `
${pathObj.category_id ? `id: ${pathObj.category_id}` : ''}
${pathObj.rationale ? `${escapeApplyToText(pathObj.rationale)}` : ''}
${pathObj.body_excerpt ? `${escapeApplyToText(pathObj.body_excerpt)}` : ''}
${pathObj.body_excerpt_note ? `${escapeApplyToText(pathObj.body_excerpt_note)}` : ''}
${pathObj.body_excerpt_type ? `type: ${escapeApplyToText(pathObj.body_excerpt_type)}` : ''}
`;
};
const renderEvidence = (pathObj) => {
if (typeof pathObj !== 'object' || pathObj === null) return '';
if (!('body_excerpt' in pathObj) && !('body_excerpt_note' in pathObj) && !('body_excerpt_type' in pathObj)) return '';
const excerpt = pathObj.body_excerpt || '';
const note = pathObj.body_excerpt_note || '';
const excerptType = pathObj.body_excerpt_type || '';
const excerptKey = excerpt ? makeExcerptKey(excerpt) : '';
return `
关联做法 ${excerptType ? escapeApplyToText(excerptType) : 'type: 空'}
${note ? escapeApplyToText(note) : '空'}
`;
};
let res = '
';
let hasRows = false;
Object.entries(valObj).forEach(([k, v]) => {
if (Array.isArray(v) && v.length > 0) {
hasRows = true;
res += `
${escapeApplyToText(k)}
`;
v.forEach(pathObj => {
let pathStr = '';
let elementStr = '';
if (typeof pathObj === 'object' && pathObj !== null) {
pathStr = pathObj.category_path || pathObj.path || '';
elementStr = pathObj.element || '';
} else {
pathStr = String(pathObj);
}
let tooltipHtml = renderTooltip(pathObj);
let htmlParts = '';
if (pathStr && elementStr) {
htmlParts = `${escapeApplyToText(pathStr)}${escapeApplyToText(elementStr)}`;
} else if (pathStr) {
htmlParts = renderPathParts(pathStr);
}
if (htmlParts) {
const evidenceHtml = renderEvidence(pathObj);
res += `${htmlParts}${tooltipHtml}${evidenceHtml}`;
}
});
res += `
`;
}
});
const suggestItems = Array.isArray(suggestApplyTo)
? suggestApplyTo
: (typeof suggestApplyTo === 'string' && suggestApplyTo.trim() ? [{ path: suggestApplyTo }] : []);
if (suggestItems.length > 0) {
hasRows = true;
res += `
建议
`;
suggestItems.forEach(item => {
const pathStr = typeof item === 'object' && item !== null ? item.path : String(item || '');
const tooltipHtml = renderTooltip(item);
if (pathStr) {
const evidenceHtml = renderEvidence(item);
res += `${renderPathParts(pathStr, true)}${tooltipHtml}${evidenceHtml}`;
}
});
res += `
`;
}
res += `
`;
return hasRows ? res : '-';
};
// Render apply_to / apply_to_grounding at workflow level (if it exists)
if (applyToData && typeof applyToData.val === 'object' && Object.keys(applyToData.val).length > 0) {
html += `
${applyToData.key}
${renderApplyToVal(applyToData.val, applyToData.suggest)}
`;
}
if (item.action && typeof item.action === 'object' && (item.action.description || item.action.reasoning)) {
const actionDescription = item.action.description ? String(item.action.description).replace(//g, '>') : '';
const actionReasoning = item.action.reasoning ? String(item.action.reasoning).replace(//g, '>') : '';
html += `
action
${actionDescription ? `
${actionDescription}
${actionReasoning ? `${actionReasoning}` : ''}
` : ''}
`;
}
// Stage rendering removed per request
// Render effects
if (item.effects && Array.isArray(item.effects) && item.effects.length > 0) {
let effectsHtml = '';
item.effects.forEach(effectItem => {
if (typeof effectItem === 'string') {
effectsHtml += `
${effectItem.replace(//g, '>')}`;
} else if (typeof effectItem === 'object' && effectItem !== null) {
const stmt = effectItem.statement ? effectItem.statement.replace(//g, '>') : 'Effect';
let detailsHtml = '';
const excludeKeys = ['statement'];
Object.entries(effectItem).forEach(([k, v]) => {
if (!excludeKeys.includes(k) && v !== null && v !== undefined && v !== '' && (!Array.isArray(v) || v.length > 0)) {
let valStr = '';
if (Array.isArray(v)) {
valStr = `
` + v.map(vi => `- ${String(vi).replace(//g, '>')}
`).join('') + `
`;
} else if (typeof v === 'object') {
valStr = JSON.stringify(v);
} else {
valStr = String(v).replace(//g, '>');
}
if (Array.isArray(v)) {
detailsHtml += `
${k.replace(/_/g, ' ')}:
${valStr}
`;
} else {
detailsHtml += `
${k.replace(/_/g, ' ')}:
${valStr}
`;
}
}
});
effectsHtml += `
${stmt}
${detailsHtml}
`;
}
});
html += `
`;
}
// 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 += `
置信度
Maturity:
${String((item.maturity || (parentItem && parentItem.maturity)) || '-').replace(//g, '>')}
Validation:
${(item.validation_count !== undefined && item.validation_count !== null) ? String(item.validation_count).replace(//g, '>') : (parentItem && parentItem.validation_count !== undefined && parentItem.validation_count !== null) ? String(parentItem.validation_count).replace(//g, '>') : '-'}
Published:
${formatDate(item.published_at || (parentItem && parentItem.published_at))}
Last Verified:
${formatDate(item.last_verified_at || (parentItem && parentItem.last_verified_at))}
Created:
${formatDate(item.created_at || (parentItem && parentItem.created_at))}
Updated:
${formatDate(item.updated_at || (parentItem && parentItem.updated_at))}
`;
// 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 += `
${k}:
${v}
`;
}
});
if (feedbackHtml !== '') {
feedbackHtml = `
${feedbackHtml}
`;
}
} else if (typeof feedbackVal === 'string' && feedbackVal.trim() !== '') {
feedbackHtml = `
${String(feedbackVal).replace(//g, '>')}
`;
}
if (feedbackHtml !== '') {
html += `
`;
}
}
// Render body
if (item.body && typeof item.body === 'string') {
html += `
body
${item.body.replace(//g, '>')}
`;
}
// Helper for inputs/outputs (Moved up so it can be used by steps)
const renderDataObjList = (list) => {
const isValid = (v) => v !== null && v !== undefined && String(v).toLowerCase() !== 'null' && String(v).toLowerCase() !== 'none' && String(v).trim() !== '';
const escapeHtml = (s) => String(s).replace(//g, '>');
return list.map(io => {
if (!io) return '';
if (typeof io === 'string') return `
${escapeHtml(io)}
`;
const desc = isValid(io.description) ? io.description.replace(//g, '>') : '';
const mod = isValid(io.modality) ? io.modality : '';
const relation = isValid(io.relation) ? io.relation.replace(//g, '>') : '';
let modalityIcon = '';
let modBorder = '#e2e8f0';
let modColor = '#64748b';
let modBg = '#ffffff';
if (mod) {
if (mod === '文本') { modalityIcon = 'T'; }
else if (mod === '图片') { modalityIcon = '🖼️'; modColor = '#10b981'; }
else if (mod === '视频') { modalityIcon = '▶️'; modColor = '#ef4444'; }
else if (mod === '音频') { modalityIcon = '🎵'; modColor = '#ec4899'; }
else if (mod === '参数') { modalityIcon = '⚙️'; modBg = '#f8fafc'; }
else { modalityIcon = escapeHtml(mod[0] || '?'); }
}
const modalityHtml = mod ? `
${modalityIcon}` : '';
let relationHtml = '';
if (relation) {
const relStr = String(relation);
const relMatch = relStr.match(/\[(来源|去向)\.(.+)\]/);
if (relMatch) {
const dir = relMatch[1];
const target = relMatch[2];
let targetLabel = target;
let targetStepId = '';
let targetDirection = '';
let badgeBg = '#eff6ff'; // blue-50
let badgeColor = '#3b82f6'; // blue-500
if (target === '原始输入') {
targetLabel = '初始 ①';
badgeBg = '#eef2ff'; // indigo-50
badgeColor = '#6366f1'; // indigo-500
}
else if (target === '最终输出') {
targetLabel = '最终输出';
badgeBg = '#f0fdfa'; // teal-50
badgeColor = '#14b8a6'; // teal-500
}
else if (target.startsWith('s') && target.length >= 3) {
const stepNumMatch = target.match(/s(\d+)([IO]?)/);
if (stepNumMatch) {
targetLabel = `步骤${stepNumMatch[1]}`;
targetStepId = `s${stepNumMatch[1]}`;
targetDirection = stepNumMatch[2] === 'I' ? 'input' : (stepNumMatch[2] === 'O' ? 'output' : '');
}
}
let clickAttr = targetStepId ? `onclick="event.stopPropagation(); window.jumpToWorkflowStep('${scopeId}', '${targetStepId}', '${targetDirection}')" onmouseenter="this.style.filter='brightness(0.95)'; window.hoverWorkflowStep('${scopeId}', '${targetStepId}', '${targetDirection}')" onmouseleave="this.style.filter='none'; window.unhoverWorkflowStep('${scopeId}', '${targetStepId}', '${targetDirection}')" style="cursor:pointer; ` : `onmouseenter="this.style.filter='brightness(0.95)'" onmouseleave="this.style.filter='none'" style="`;
relationHtml = `
${targetLabel}`;
} else {
relationHtml = `
${escapeHtml(relation)}`;
}
}
let content = desc;
if (!content) {
const keys = Object.keys(io).filter(k => k !== 'modality' && k !== 'relation');
if (keys.length > 0) {
content = keys.map(k => `
${k}${io[k]}`).join(' ');
}
}
return `
${modalityHtml}${relationHtml}${content}
`;
}).join('');
};
// Render steps array specially
if (item.steps && Array.isArray(item.steps)) {
const allCapabilities = (item && item.capability) || (parentItem && parentItem.capability) || [];
const escapeHtml = (s) => String(s).replace(//g, '>');
const minWidth = 1250;
const renderAction = (src) => {
if (!src) return '-';
if (src.action && src.action.description) {
const description = escapeHtml(src.action.description);
const reasoning = src.action.reasoning ? escapeHtml(src.action.reasoning) : '';
return `
${description}
${reasoning ? `${reasoning}` : ''}
`;
}
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 => `
${escapeHtml(t)}`).join('');
};
const renderEffects = (effects) => {
if (!effects || !Array.isArray(effects) || effects.length === 0) return '-';
const renderKeyTag = (keyText) => `
${keyText}`;
return `
${effects.map(effect => {
if (typeof effect === 'string') {
return `
${renderKeyTag('效果')}${escapeHtml(effect)}
`;
}
if (typeof effect !== 'object' || effect === null) return '';
const statement = effect.statement ? escapeHtml(effect.statement) : '效果';
const criteria = effect.criteria ? escapeHtml(effect.criteria) : '';
const judgeMethod = effect.judge_method ? escapeHtml(effect.judge_method) : '';
const negativeExamples = Array.isArray(effect.negative_examples) && effect.negative_examples.length > 0
? `
` +
effect.negative_examples.map(ex => `${escapeHtml(ex)}`).join('') +
`
`
: '';
return `
${statement}
${criteria ? `
${renderKeyTag('判断标准')}${criteria}
` : ''}
${judgeMethod ? `
${renderKeyTag('评判方式')}${judgeMethod}
` : ''}
${negativeExamples ? `
${renderKeyTag('负面示例')}${negativeExamples}
` : ''}
`;
}).join('')}
`;
};
const getStepCapabilities = (step) => {
if (!step || !step.step_id) return [];
const workflowId = item && item.workflow_id;
return allCapabilities.filter(capability => {
const ref = capability.workflow_step_ref || {};
const refStepId = ref.step_id;
const refWorkflowId = ref.workflow_id;
if (refStepId === step.step_id && (!workflowId || !refWorkflowId || refWorkflowId === workflowId)) {
return true;
}
return capability.capability_id && new RegExp(`^c_${workflowId || 'w[0-9]+'}_${step.step_id}_[0-9]+$`).test(capability.capability_id);
});
};
const matchedCapabilities = new Set();
const collectBodyExcerpts = (applyTo, suggestApplyTo) => {
const excerpts = [];
const addExcerpt = (entry) => {
if (entry && typeof entry === 'object' && typeof entry.body_excerpt === 'string' && entry.body_excerpt.trim()) {
excerpts.push(entry.body_excerpt.trim());
}
};
if (applyTo && typeof applyTo === 'object') {
Object.values(applyTo).forEach(items => {
if (Array.isArray(items)) items.forEach(addExcerpt);
});
}
if (Array.isArray(suggestApplyTo)) {
suggestApplyTo.forEach(addExcerpt);
}
return [...new Set(excerpts)].sort((a, b) => b.length - a.length);
};
const renderBodyWithExcerptHighlights = (body, applyTo, suggestApplyTo) => {
if (!body) return '-';
const text = String(body);
const excerpts = collectBodyExcerpts(applyTo, suggestApplyTo);
if (excerpts.length === 0) return escapeHtml(text);
let out = '';
let i = 0;
while (i < text.length) {
const match = excerpts.find(excerpt => text.startsWith(excerpt, i));
if (match) {
out += `
${escapeHtml(match)}`;
i += match.length;
} else {
out += escapeHtml(text[i]);
i += 1;
}
}
return out;
};
const renderCapabilityColumns = (capability, step) => {
const applyTo = capability && (capability.apply_to_draft || capability.apply_to_grounding || capability.apply_to);
const suggestApplyTo = capability && capability.apply_to_draft ? null : capability && capability.suggest_apply_to;
const bodyHtml = capability && capability.body ? renderBodyWithExcerptHighlights(capability.body, applyTo, suggestApplyTo) : '-';
return `
▶
${capability && capability.capability_id ? `${escapeHtml(capability.capability_id)}` : '-'}
|
${capability && capability.inputs && capability.inputs.length > 0 ? renderDataObjList(capability.inputs) : '-'} |
${renderAction(capability)} |
${capability && capability.outputs && capability.outputs.length > 0 ? renderDataObjList(capability.outputs) : '-'} |
${applyTo ? renderApplyToVal(applyTo, suggestApplyTo) : '-'} |
${bodyHtml} |
${capability ? renderEffects(capability.effects) : '-'} |
${capability ? renderTools(capability.tools) : '-'} |
`;
};
html += `
steps
| Id |
步骤名称 |
动作 |
角色 |
目的 |
步骤输入 |
工具 |
步骤输出 |
来源 |
| 模态 |
领域类型 |
来源 |
value |
# |
模态 |
领域类型 |
value |
`;
const CIRCLE_NUMS = ['①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩'];
const ROW_COLORS = ['#4f46e5', '#0d9488', '#e11d48', '#7c3aed', '#0284c7', '#d97706', '#65a30d', '#db2777'];
const stepSourceMap = {};
let initCount = 0;
// First pass: collect outputs and init inputs
item.steps.forEach((step, sIdx) => {
if (step.outputs) {
step.outputs.forEach((out, outIdx) => {
if (out.id) {
stepSourceMap[out.id] = { step_id: step.step_id, order: sIdx, index: outIdx, type: out.type, color: ROW_COLORS[sIdx % ROW_COLORS.length], hasMultiple: step.outputs.length > 1 };
}
});
}
if (step.inputs) {
step.inputs.forEach(inp => {
if (inp.source_id && inp.source_id.startsWith('init_input')) {
if (!stepSourceMap[inp.source_id]) {
stepSourceMap[inp.source_id] = { isInit: true, order: initCount++ };
}
}
});
}
});
const getModalityHtml = (mod) => {
let modalityIcon = escapeHtml(mod ? mod[0] : '?');
let modBorder = '#e2e8f0';
let modColor = '#64748b';
let modBg = '#ffffff';
if (mod === '文本') { modalityIcon = 'T'; modColor = '#3b82f6'; modBg = '#eff6ff'; modBorder = '#bfdbfe'; }
else if (mod === '图片') { modalityIcon = '🖼️'; modColor = '#10b981'; modBg = '#ecfdf5'; modBorder = '#a7f3d0'; }
else if (mod === '视频' || mod === '视频片断') { modalityIcon = '▶️'; modColor = '#ef4444'; modBg = '#fef2f2'; modBorder = '#fecaca'; }
else if (mod === '音频') { modalityIcon = '🎵'; modColor = '#ec4899'; modBg = '#fdf2f8'; modBorder = '#fbcfe8'; }
else if (mod === '图片内容组') { modalityIcon = '🖼️'; modColor = '#10b981'; modBg = '#ecfdf5'; modBorder = '#a7f3d0'; }
return `${modalityIcon}`;
};
item.steps.forEach((step, stepIdx) => {
const sColor = ROW_COLORS[stepIdx % ROW_COLORS.length];
const inputs = step.inputs || [];
const outputs = step.outputs || [];
const maxRows = Math.max(inputs.length, outputs.length, 1);
const hoverAttr = `onmouseenter="this.style.background='rgba(0,0,0,0.03)';" onmouseleave="this.style.background='white';"`;
for (let i = 0; i < maxRows; i++) {
const rowBorderBottom = (i === maxRows - 1) ? `2px solid ${sColor}` : `1px dashed #e5e7eb`;
html += ``;
if (i === 0) {
const renderTagList = (items, cssClass) => {
if (!items || !Array.isArray(items) || items.length === 0) return '—';
const tags = items.map(t => `${escapeHtml(t)}`).join('');
return `${tags}
`;
};
const rolesHtml = step.roles ? renderTagList(step.roles, 'tag-role') : (step.stage ? `${escapeHtml(step.stage)}
` : '—');
const actionsHtml = step.actions ? renderTagList(step.actions, 'tag-action') : '—';
const purposesHtml = step.purposes ? renderTagList(step.purposes, 'tag-purpose') : '—';
const stepIdAttr = `id="step-s${escapeHtml(step.step_id || '')}"`;
html += `
${escapeHtml(step.step_id || (stepIdx + 1))} |
${escapeHtml(step.name || '')} |
${actionsHtml} |
${rolesHtml} |
${purposesHtml} |
`;
}
const inp = inputs[i];
if (inp) {
// resolve modality/category based on source
const sInfo = stepSourceMap[inp.source_id];
let modalityHtml = getModalityHtml(inp.modality || inp.type);
let categoryHtml = inp.category ? `${escapeHtml(inp.category)}` : '—';
let sourceHtml = escapeHtml(inp.source_id || '-');
if (sInfo) {
if (sInfo.isInit) {
sourceHtml = `初始${CIRCLE_NUMS[sInfo.order % 10]}`;
} else {
const bdColor = sInfo.color + '40';
const clickAttr = `onclick="event.stopPropagation(); window.jumpToWorkflowStep('${scopeId}', 's${sInfo.step_id}', ${sInfo.index})" onmouseenter="window.hoverWorkflowStep('${scopeId}', 's${sInfo.step_id}', ${sInfo.index})" onmouseleave="window.unhoverWorkflowStep('${scopeId}', 's${sInfo.step_id}', ${sInfo.index})"`;
const circleHtml = sInfo.hasMultiple ? `${CIRCLE_NUMS[sInfo.index % 10]}` : '';
sourceHtml = `步骤${sInfo.step_id}${circleHtml}`;
// Source dictates modality & category overrides if available
const sourceOutputObj = item.steps[sInfo.order] && item.steps[sInfo.order].outputs && item.steps[sInfo.order].outputs[sInfo.index];
if (sourceOutputObj) {
modalityHtml = getModalityHtml(sourceOutputObj.modality || sourceOutputObj.type || inp.modality || inp.type);
if (sourceOutputObj.category) {
categoryHtml = `${escapeHtml(sourceOutputObj.category)}`;
}
}
}
}
html += `
${modalityHtml} |
${categoryHtml} |
${sourceHtml} |
${inp.value ? escapeHtml(inp.value) : '—'} |
`;
} else {
html += ` | | | | `;
}
if (i === 0) {
let toolHtml = '—';
if (step.tool_name || step.tool_method) {
let parts = [];
if (step.tool_name) parts.push(`${escapeHtml(step.tool_name)}
`);
if (step.tool_method) parts.push(`${escapeHtml(step.tool_method)}
`);
toolHtml = parts.join('');
} else if (step.tool) {
toolHtml = `${escapeHtml(step.tool)}
`;
}
html += `${toolHtml} | `;
}
const out = outputs[i];
if (out) {
const typeHtml = getModalityHtml(out.modality || out.type);
let categoryHtml = out.category ? `${escapeHtml(out.category)}` : '—';
const cNum = CIRCLE_NUMS[i % 10];
html += `
${cNum} |
${typeHtml} |
${categoryHtml} |
${out.value ? escapeHtml(out.value) : '—'} |
`;
} else {
html += ` | | | | `;
}
if (i === 0) {
html += `${escapeHtml(step.source_ref || '')} | `;
}
html += `
`;
}
});
html += `
`;
}
// Render inputs
if (item.inputs && Array.isArray(item.inputs) && item.inputs.length > 0) {
html += `
inputs
${renderDataObjList(item.inputs)}
`;
} else if (item.inputs && typeof item.inputs === 'object' && Object.keys(item.inputs).length > 0 && !Array.isArray(item.inputs)) {
// Fallback for old schema
html += `
inputs
`;
Object.entries(item.inputs).forEach(([k, v]) => {
html += `
${k}${v}
`;
});
html += `
`;
}
// Render outputs
if (item.outputs && Array.isArray(item.outputs) && item.outputs.length > 0) {
html += `
outputs
${renderDataObjList(item.outputs)}
`;
} else if (item.outputs && typeof item.outputs === 'object' && Object.keys(item.outputs).length > 0 && !Array.isArray(item.outputs)) {
// Fallback for old schema
html += `
outputs
`;
Object.entries(item.outputs).forEach(([k, v]) => {
html += `
${k}${v}
`;
});
html += `
`;
}
// Render tools (for non-step items like capabilities)
if (item.tools && Array.isArray(item.tools) && item.tools.length > 0) {
html += `
tools
${item.tools.map(t => `${t}`).join('')}
`;
}
// Dynamic fallback for any other unhandled keys
const handledKeys = [
'method', 'name', 'action', 'unstructured_what', 'apply_to_grounding', 'apply_to_draft', 'apply_to',
'suggest_apply_to', 'stage', 'effects', 'body', 'steps', 'inputs', 'outputs', 'tools',
'workflow_id', 'capability', 'source', 'channel_content_id'
];
Object.keys(item).forEach(k => {
if (!handledKeys.includes(k)) {
let v = item[k];
if (v === null || v === undefined || v === '') return;
let displayHtml = '';
if (typeof v === 'object') {
if (Array.isArray(v)) {
if (v.length === 0) return;
if (typeof v[0] === 'object') {
displayHtml = `
${JSON.stringify(v, null, 2).replace(//g, '>')}`;
} else {
displayHtml = v.map(vi => `
${String(vi).replace(//g, '>')}`).join('');
}
} else {
if (Object.keys(v).length === 0) return;
displayHtml = `
${JSON.stringify(v, null, 2).replace(//g, '>')}`;
}
} else {
displayHtml = String(v).replace(//g, '>');
}
html += `
`;
}
});
html += `
`;
});
return html;
};