// ==========================================
// FLOWCHART RENDERING (D3.js)
// ==========================================
function renderFlowchartForTask(sessionId, task) {
// Will render on section expand
}
function renderFlowchart(containerId, steps) {
if (!steps || steps.length === 0) return;
if (typeof d3 === 'undefined') {
document.getElementById(containerId).innerHTML = '
D3.js not loaded
';
return;
}
const container = document.getElementById(containerId);
const width = container.clientWidth || 500;
const nodeHeight = 50;
const nodeWidth = Math.min(width - 40, 300);
const padding = 15;
const height = steps.length * (nodeHeight + padding) + padding * 2;
// Clear existing content
container.innerHTML = '';
const svg = d3.select('#' + containerId)
.append('svg')
.attr('width', width)
.attr('height', height)
.attr('class', 'flowchart-svg');
// Arrow marker
svg.append('defs').append('marker')
.attr('id', 'arrow-' + containerId)
.attr('viewBox', '0 -5 10 10')
.attr('refX', 8)
.attr('refY', 0)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', 'hsl(var(--border))');
// Draw arrows
for (let i = 0; i < steps.length - 1; i++) {
const y1 = padding + i * (nodeHeight + padding) + nodeHeight;
const y2 = padding + (i + 1) * (nodeHeight + padding);
svg.append('line')
.attr('x1', width / 2)
.attr('y1', y1)
.attr('x2', width / 2)
.attr('y2', y2)
.attr('stroke', 'hsl(var(--border))')
.attr('stroke-width', 2)
.attr('marker-end', 'url(#arrow-' + containerId + ')');
}
// Draw nodes
const nodes = svg.selectAll('.node')
.data(steps)
.enter()
.append('g')
.attr('class', 'flowchart-node')
.attr('transform', (d, i) => `translate(${(width - nodeWidth) / 2}, ${padding + i * (nodeHeight + padding)})`);
// Node rectangles
nodes.append('rect')
.attr('width', nodeWidth)
.attr('height', nodeHeight)
.attr('rx', 6)
.attr('fill', (d, i) => i === 0 ? 'hsl(var(--primary))' : 'hsl(var(--card))')
.attr('stroke', 'hsl(var(--border))')
.attr('stroke-width', 1);
// Step number circle
nodes.append('circle')
.attr('cx', 20)
.attr('cy', nodeHeight / 2)
.attr('r', 12)
.attr('fill', (d, i) => i === 0 ? 'rgba(255,255,255,0.2)' : 'hsl(var(--muted))');
nodes.append('text')
.attr('x', 20)
.attr('y', nodeHeight / 2)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'central')
.attr('font-size', '11px')
.attr('fill', (d, i) => i === 0 ? 'white' : 'hsl(var(--muted-foreground))')
.text((d, i) => i + 1);
// Node text (step name)
nodes.append('text')
.attr('x', 45)
.attr('y', nodeHeight / 2)
.attr('dominant-baseline', 'central')
.attr('fill', (d, i) => i === 0 ? 'white' : 'hsl(var(--foreground))')
.attr('font-size', '12px')
.text(d => {
const text = d.step || d.action || 'Step';
return text.length > 35 ? text.substring(0, 32) + '...' : text;
});
}
function renderFullFlowchart(flowControl) {
if (!flowControl) return;
const container = document.getElementById('flowchartContainer');
if (!container) return;
const preAnalysis = Array.isArray(flowControl.pre_analysis) ? flowControl.pre_analysis : [];
const implSteps = Array.isArray(flowControl.implementation_approach) ? flowControl.implementation_approach : [];
if (preAnalysis.length === 0 && implSteps.length === 0) {
container.innerHTML = 'No flowchart data available
';
return;
}
const width = container.clientWidth || 500;
const nodeHeight = 90;
const nodeWidth = Math.min(width - 40, 420);
const nodeGap = 45;
const sectionGap = 30;
// Calculate total nodes and height
const totalPreNodes = preAnalysis.length;
const totalImplNodes = implSteps.length;
const hasBothSections = totalPreNodes > 0 && totalImplNodes > 0;
const height = (totalPreNodes + totalImplNodes) * (nodeHeight + nodeGap) +
(hasBothSections ? sectionGap + 60 : 0) + 60;
// Clear existing
d3.select('#flowchartContainer').selectAll('*').remove();
const svg = d3.select('#flowchartContainer')
.append('svg')
.attr('width', '100%')
.attr('height', height)
.attr('viewBox', `0 0 ${width} ${height}`);
// Add arrow markers
const defs = svg.append('defs');
defs.append('marker')
.attr('id', 'arrowhead-pre')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 8)
.attr('refY', 0)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#f59e0b');
defs.append('marker')
.attr('id', 'arrowhead-impl')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 8)
.attr('refY', 0)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', 'hsl(var(--primary))');
let currentY = 20;
// Render Pre-Analysis section
if (totalPreNodes > 0) {
// Section label
svg.append('text')
.attr('x', 20)
.attr('y', currentY)
.attr('fill', '#f59e0b')
.attr('font-weight', 'bold')
.attr('font-size', '13px')
.text('📋 Pre-Analysis Steps');
currentY += 25;
preAnalysis.forEach((step, idx) => {
const x = (width - nodeWidth) / 2;
// Connection line to next node
if (idx < preAnalysis.length - 1) {
svg.append('line')
.attr('x1', width / 2)
.attr('y1', currentY + nodeHeight)
.attr('x2', width / 2)
.attr('y2', currentY + nodeHeight + nodeGap - 10)
.attr('stroke', '#f59e0b')
.attr('stroke-width', 2)
.attr('marker-end', 'url(#arrowhead-pre)');
}
// Node group
const nodeG = svg.append('g')
.attr('class', 'flowchart-node')
.attr('transform', `translate(${x}, ${currentY})`);
// Node rectangle (pre-analysis style - amber/orange)
nodeG.append('rect')
.attr('width', nodeWidth)
.attr('height', nodeHeight)
.attr('rx', 10)
.attr('fill', 'hsl(var(--card))')
.attr('stroke', '#f59e0b')
.attr('stroke-width', 2)
.attr('stroke-dasharray', '5,3');
// Step badge
nodeG.append('circle')
.attr('cx', 25)
.attr('cy', 25)
.attr('r', 15)
.attr('fill', '#f59e0b');
nodeG.append('text')
.attr('x', 25)
.attr('y', 30)
.attr('text-anchor', 'middle')
.attr('fill', 'white')
.attr('font-weight', 'bold')
.attr('font-size', '11px')
.text('P' + (idx + 1));
// Step name
const stepName = step.step || step.action || 'Pre-step ' + (idx + 1);
nodeG.append('text')
.attr('x', 50)
.attr('y', 28)
.attr('fill', 'hsl(var(--foreground))')
.attr('font-weight', '600')
.attr('font-size', '13px')
.text(truncateText(stepName, 40));
// Action description
if (step.action && step.action !== stepName) {
nodeG.append('text')
.attr('x', 15)
.attr('y', 52)
.attr('fill', 'hsl(var(--muted-foreground))')
.attr('font-size', '11px')
.text(truncateText(step.action, 50));
}
// Output indicator
if (step.output_to) {
nodeG.append('text')
.attr('x', 15)
.attr('y', 75)
.attr('fill', '#f59e0b')
.attr('font-size', '10px')
.text('→ ' + truncateText(step.output_to, 45));
}
currentY += nodeHeight + nodeGap;
});
}
// Section divider if both sections exist
if (hasBothSections) {
currentY += 10;
svg.append('line')
.attr('x1', 40)
.attr('y1', currentY)
.attr('x2', width - 40)
.attr('y2', currentY)
.attr('stroke', 'hsl(var(--border))')
.attr('stroke-width', 1)
.attr('stroke-dasharray', '4,4');
// Connecting arrow from pre-analysis to implementation
svg.append('line')
.attr('x1', width / 2)
.attr('y1', currentY - nodeGap + 5)
.attr('x2', width / 2)
.attr('y2', currentY + sectionGap - 5)
.attr('stroke', 'hsl(var(--primary))')
.attr('stroke-width', 2)
.attr('marker-end', 'url(#arrowhead-impl)');
currentY += sectionGap;
}
// Render Implementation section
if (totalImplNodes > 0) {
// Section label
svg.append('text')
.attr('x', 20)
.attr('y', currentY)
.attr('fill', 'hsl(var(--primary))')
.attr('font-weight', 'bold')
.attr('font-size', '13px')
.text('🔧 Implementation Steps');
currentY += 25;
implSteps.forEach((step, idx) => {
const x = (width - nodeWidth) / 2;
// Connection line to next node
if (idx < implSteps.length - 1) {
svg.append('line')
.attr('x1', width / 2)
.attr('y1', currentY + nodeHeight)
.attr('x2', width / 2)
.attr('y2', currentY + nodeHeight + nodeGap - 10)
.attr('stroke', 'hsl(var(--primary))')
.attr('stroke-width', 2)
.attr('marker-end', 'url(#arrowhead-impl)');
}
// Node group
const nodeG = svg.append('g')
.attr('class', 'flowchart-node')
.attr('transform', `translate(${x}, ${currentY})`);
// Node rectangle (implementation style - blue)
nodeG.append('rect')
.attr('width', nodeWidth)
.attr('height', nodeHeight)
.attr('rx', 10)
.attr('fill', 'hsl(var(--card))')
.attr('stroke', 'hsl(var(--primary))')
.attr('stroke-width', 2);
// Step badge
nodeG.append('circle')
.attr('cx', 25)
.attr('cy', 25)
.attr('r', 15)
.attr('fill', 'hsl(var(--primary))');
nodeG.append('text')
.attr('x', 25)
.attr('y', 30)
.attr('text-anchor', 'middle')
.attr('fill', 'white')
.attr('font-weight', 'bold')
.attr('font-size', '12px')
.text(step.step || idx + 1);
// Step title
nodeG.append('text')
.attr('x', 50)
.attr('y', 28)
.attr('fill', 'hsl(var(--foreground))')
.attr('font-weight', '600')
.attr('font-size', '13px')
.text(truncateText(step.title || 'Step ' + (idx + 1), 40));
// Description
if (step.description) {
nodeG.append('text')
.attr('x', 15)
.attr('y', 52)
.attr('fill', 'hsl(var(--muted-foreground))')
.attr('font-size', '11px')
.text(truncateText(step.description, 50));
}
// Output/depends indicator
if (step.depends_on?.length) {
nodeG.append('text')
.attr('x', 15)
.attr('y', 75)
.attr('fill', 'var(--warning-color)')
.attr('font-size', '10px')
.text('← Depends: ' + step.depends_on.join(', '));
}
currentY += nodeHeight + nodeGap;
});
}
}
// D3.js Vertical Flowchart for Implementation Approach (legacy)
function renderImplementationFlowchart(steps) {
if (!Array.isArray(steps) || steps.length === 0) return;
const container = document.getElementById('flowchartContainer');
if (!container) return;
const width = container.clientWidth || 500;
const nodeHeight = 100;
const nodeWidth = Math.min(width - 40, 400);
const nodeGap = 50;
const height = steps.length * (nodeHeight + nodeGap) + 40;
// Clear existing
d3.select('#flowchartContainer').selectAll('*').remove();
const svg = d3.select('#flowchartContainer')
.append('svg')
.attr('width', '100%')
.attr('height', height)
.attr('viewBox', `0 0 ${width} ${height}`);
// Add arrow marker
svg.append('defs').append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 8)
.attr('refY', 0)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', 'hsl(var(--primary))');
// Draw nodes and connections
steps.forEach((step, idx) => {
const y = idx * (nodeHeight + nodeGap) + 20;
const x = (width - nodeWidth) / 2;
// Connection line to next node
if (idx < steps.length - 1) {
svg.append('line')
.attr('x1', width / 2)
.attr('y1', y + nodeHeight)
.attr('x2', width / 2)
.attr('y2', y + nodeHeight + nodeGap - 10)
.attr('stroke', 'hsl(var(--primary))')
.attr('stroke-width', 2)
.attr('marker-end', 'url(#arrowhead)');
}
// Node group
const nodeG = svg.append('g')
.attr('class', 'flowchart-node')
.attr('transform', `translate(${x}, ${y})`);
// Node rectangle with gradient
nodeG.append('rect')
.attr('width', nodeWidth)
.attr('height', nodeHeight)
.attr('rx', 10)
.attr('fill', 'hsl(var(--card))')
.attr('stroke', 'hsl(var(--primary))')
.attr('stroke-width', 2)
.attr('filter', 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))');
// Step number badge
nodeG.append('circle')
.attr('cx', 25)
.attr('cy', 25)
.attr('r', 15)
.attr('fill', 'hsl(var(--primary))');
nodeG.append('text')
.attr('x', 25)
.attr('y', 30)
.attr('text-anchor', 'middle')
.attr('fill', 'white')
.attr('font-weight', 'bold')
.attr('font-size', '12px')
.text(step.step || idx + 1);
// Step title
nodeG.append('text')
.attr('x', 50)
.attr('y', 30)
.attr('fill', 'hsl(var(--foreground))')
.attr('font-weight', '600')
.attr('font-size', '14px')
.text(truncateText(step.title || 'Step ' + (idx + 1), 35));
// Step description (if available)
if (step.description) {
nodeG.append('text')
.attr('x', 15)
.attr('y', 55)
.attr('fill', 'hsl(var(--muted-foreground))')
.attr('font-size', '12px')
.text(truncateText(step.description, 45));
}
// Output indicator
if (step.output) {
nodeG.append('text')
.attr('x', 15)
.attr('y', 80)
.attr('fill', 'var(--success-color)')
.attr('font-size', '11px')
.text('→ ' + truncateText(step.output, 40));
}
});
}