vn6295337 Claude Opus 4.5 commited on
Commit
de4ad41
Β·
1 Parent(s): 53fe655

Frontend: Remove Editor Agent from process visualization

Browse files

- Remove Editor node from ProcessFlow SVG
- Update workflow to show Critic β†’ Analyzer revision loop
- Narrow SVG width (6 nodes instead of 7)
- Update AGENTS_GROUP to span only Analyzer + Critic
- Remove editor from stepOrder in App.tsx
- Update WorkflowStatus type in api.ts
- Update Storybook stories

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

frontend/src/App.tsx CHANGED
@@ -166,7 +166,7 @@ const Index = () => {
166
  if (status.provider_used) setLlmProvider(status.provider_used)
167
 
168
  // Update completed steps - accumulate rather than recalculate to handle loops
169
- const stepOrder = ['input', 'cache', 'researcher', 'analyzer', 'critic', 'editor', 'output']
170
  setCompletedSteps(prev => {
171
  const newCompleted = new Set(prev)
172
  const currentIdx = stepOrder.indexOf(status.current_step)
@@ -176,11 +176,6 @@ const Index = () => {
176
  newCompleted.add(stepOrder[i])
177
  }
178
 
179
- // Handle Critic ↔ Editor loop: keep editor completed when looping back to critic
180
- if (status.current_step === 'critic' && status.revision_count > 0) {
181
- newCompleted.add('editor')
182
- }
183
-
184
  return Array.from(newCompleted)
185
  })
186
 
@@ -211,11 +206,7 @@ const Index = () => {
211
 
212
  // Normal flow - all steps completed
213
  // Set completed steps BEFORE the async fetch to prevent output from glowing prematurely
214
- // Only mark 'editor' as completed if revisions actually occurred
215
- const finalSteps = status.revision_count > 0
216
- ? stepOrder
217
- : stepOrder.filter(s => s !== 'editor')
218
- setCompletedSteps(finalSteps)
219
  setCurrentStep('completed')
220
  const result = await getWorkflowResult(workflowIdToUse)
221
  setAnalysisResult(result)
 
166
  if (status.provider_used) setLlmProvider(status.provider_used)
167
 
168
  // Update completed steps - accumulate rather than recalculate to handle loops
169
+ const stepOrder = ['input', 'cache', 'researcher', 'analyzer', 'critic', 'output']
170
  setCompletedSteps(prev => {
171
  const newCompleted = new Set(prev)
172
  const currentIdx = stepOrder.indexOf(status.current_step)
 
176
  newCompleted.add(stepOrder[i])
177
  }
178
 
 
 
 
 
 
179
  return Array.from(newCompleted)
180
  })
181
 
 
206
 
207
  // Normal flow - all steps completed
208
  // Set completed steps BEFORE the async fetch to prevent output from glowing prematurely
209
+ setCompletedSteps(stepOrder)
 
 
 
 
210
  setCurrentStep('completed')
211
  const result = await getWorkflowResult(workflowIdToUse)
212
  setAnalysisResult(result)
frontend/src/components/ProcessFlow.tsx CHANGED
@@ -6,9 +6,7 @@ import {
6
  Search,
7
  Brain,
8
  MessageSquare,
9
- Edit3,
10
  FileOutput,
11
- Server,
12
  Loader2,
13
  Network,
14
  GitBranch,
@@ -58,9 +56,9 @@ const ROW2_Y = ROW1_Y + ROW_GAP
58
  const ROW3_Y = ROW2_Y + ROW_GAP
59
  // SVG dimensions
60
  const SVG_HEIGHT = 218 // Exact content height - scales to fill container
61
- const NODE_COUNT = 7
62
  const FLOW_WIDTH = GAP * (NODE_COUNT - 1) + NODE_SIZE
63
- const SVG_WIDTH = 530 // Wide enough for MCP group with increased spacing
64
  const FLOW_START_X = NODE_SIZE / 2 // Left-aligned with half-node margin
65
 
66
  const NODES = {
@@ -69,8 +67,7 @@ const NODES = {
69
  a2a: { x: FLOW_START_X + GAP * 2, y: ROW1_Y },
70
  analyzer: { x: FLOW_START_X + GAP * 3, y: ROW1_Y },
71
  critic: { x: FLOW_START_X + GAP * 4, y: ROW1_Y },
72
- editor: { x: FLOW_START_X + GAP * 5, y: ROW1_Y },
73
- output: { x: FLOW_START_X + GAP * 6, y: ROW1_Y },
74
  exchange: { x: FLOW_START_X, y: ROW2_Y },
75
  researcher: { x: FLOW_START_X + GAP * 2, y: ROW3_Y },
76
  }
@@ -86,7 +83,7 @@ const MCP_SERVERS = [
86
  { id: 'sentiment', label: 'Sentiment', icon: MessageSquare, x: MCP_START_X + MCP_GAP * 5 },
87
  ]
88
 
89
- const AGENTS_CENTER_X = (NODES.analyzer.x + NODES.editor.x) / 2
90
  const LLM_GAP = 68 // LLM_WIDTH (64) + 4px spacing
91
  const LLM_PROVIDERS = [
92
  { id: 'groq', name: 'Groq', x: AGENTS_CENTER_X - LLM_GAP },
@@ -97,7 +94,7 @@ const LLM_PROVIDERS = [
97
  const AGENTS_GROUP = {
98
  x: NODES.analyzer.x - NODE_SIZE / 2 - GROUP_PAD,
99
  y: ROW1_Y - NODE_SIZE / 2 - GROUP_PAD,
100
- width: NODES.editor.x - NODES.analyzer.x + NODE_SIZE + GROUP_PAD * 2,
101
  height: NODE_SIZE + GROUP_PAD * 2,
102
  }
103
 
@@ -133,7 +130,7 @@ function getNodeStatus(
133
  const normalizedCompleted = completedSteps.map(normalizeStep)
134
 
135
  // On cache hit, intermediate steps stay idle (not completed)
136
- if (cacheHit && ['researcher', 'analyzer', 'critic', 'editor', 'a2a'].includes(stepId)) {
137
  return 'idle'
138
  }
139
 
@@ -291,9 +288,6 @@ export function ProcessFlow({
291
  const criticStatus = isAborted
292
  ? (completedSteps.includes('critic') ? 'completed' : 'idle')
293
  : getNodeStatus('critic', currentStep, completedSteps, cacheHit)
294
- const editorStatus = isAborted
295
- ? (completedSteps.includes('editor') ? 'completed' : 'idle')
296
- : getNodeStatus('editor', currentStep, completedSteps, cacheHit)
297
  const outputStatus = isAborted
298
  ? (completedSteps.includes('output') ? 'completed' : 'idle')
299
  : getNodeStatus('output', currentStep, completedSteps, cacheHit)
@@ -311,7 +305,6 @@ export function ProcessFlow({
311
  }, [currentStep, completedSteps, cacheHit])
312
 
313
  // Completion halo: workflow completed successfully
314
- // Editor is optional (only runs if score < 7), so we check for essential steps + output
315
  const allDone = useMemo(() => {
316
  const normalizedCompleted = completedSteps.map(normalizeStep)
317
  const essentialSteps = ['input', 'cache', 'researcher', 'analyzer', 'critic', 'output']
@@ -368,33 +361,20 @@ export function ProcessFlow({
368
  <line x1={nodeRight(NODES.analyzer)} y1={ROW1_Y} x2={nodeLeft(NODES.critic)} y2={ROW1_Y}
369
  strokeWidth={1.4} markerEnd={`url(#arrow-${conn(analyzerStatus, criticStatus)})`}
370
  className={cn("pf-connector", `pf-connector-${conn(analyzerStatus, criticStatus)}`)} />
371
- {/* Critic β†’ Editor connector - only lights up when editor actually runs */}
372
- <line x1={nodeRight(NODES.critic)} y1={ROW1_Y} x2={nodeLeft(NODES.editor)} y2={ROW1_Y}
373
- strokeWidth={1.4} markerEnd={`url(#arrow-${editorStatus === 'executing' || editorStatus === 'completed' ? conn(criticStatus, editorStatus) : 'idle'})`}
374
- className={cn("pf-connector", `pf-connector-${editorStatus === 'executing' || editorStatus === 'completed' ? conn(criticStatus, editorStatus) : 'idle'}`)} />
375
- {/* Editor β†’ Critic loop (curved path below) - shows when revision loop is active */}
376
  <path
377
- d={`M ${NODES.editor.x} ${nodeBottom(NODES.editor)}
378
- Q ${NODES.editor.x} ${ROW1_Y + 38} ${(NODES.critic.x + NODES.editor.x) / 2} ${ROW1_Y + 38}
379
- Q ${NODES.critic.x} ${ROW1_Y + 38} ${NODES.critic.x} ${nodeBottom(NODES.critic)}`}
380
  fill="none"
381
  strokeWidth={1.4}
382
- markerEnd={`url(#arrow-${revisionCount > 0 && (editorStatus === 'completed' || criticStatus === 'executing') ? 'completed' : 'idle'})`}
383
- className={cn("pf-connector", `pf-connector-${revisionCount > 0 && (editorStatus === 'completed' || criticStatus === 'executing') ? 'completed' : 'idle'}`)}
384
- />
385
- {/* Editor β†’ Output connector - only lights up when editor ran */}
386
- <line x1={nodeRight(NODES.editor)} y1={ROW1_Y} x2={nodeLeft(NODES.output)} y2={ROW1_Y}
387
- strokeWidth={1.4} markerEnd={`url(#arrow-${editorStatus === 'completed' ? conn(editorStatus, outputStatus) : 'idle'})`}
388
- className={cn("pf-connector", `pf-connector-${editorStatus === 'completed' ? conn(editorStatus, outputStatus) : 'idle'}`)} />
389
- {/* Critic β†’ Output direct path (curved above) - shows when editor is skipped */}
390
- <path
391
- d={`M ${nodeRight(NODES.critic)} ${ROW1_Y - 8}
392
- Q ${(NODES.critic.x + NODES.output.x) / 2} ${ROW1_Y - 28} ${nodeLeft(NODES.output)} ${ROW1_Y - 8}`}
393
- fill="none"
394
- strokeWidth={1.4}
395
- markerEnd={`url(#arrow-${editorStatus === 'idle' && criticStatus === 'completed' ? conn(criticStatus, outputStatus) : 'idle'})`}
396
- className={cn("pf-connector", `pf-connector-${editorStatus === 'idle' && criticStatus === 'completed' ? conn(criticStatus, outputStatus) : 'idle'}`)}
397
  />
 
 
 
 
398
 
399
  {/* Researcher ↔ MCP block connector (bidirectional) */}
400
  <line x1={nodeRight(NODES.researcher)} y1={ROW3_Y} x2={MCP_GROUP.x - 2} y2={ROW3_Y}
@@ -420,9 +400,9 @@ export function ProcessFlow({
420
 
421
  {/* Agent Group ↔ LLM Group (Orchestration connector) */}
422
  <line x1={AGENTS_CENTER_X} y1={AGENTS_GROUP.y + AGENTS_GROUP.height + 2} x2={AGENTS_CENTER_X} y2={LLM_GROUP.y - 2}
423
- markerStart={`url(#arrow-start-${analyzerStatus === 'executing' || criticStatus === 'executing' || editorStatus === 'executing' ? 'executing' : analyzerStatus === 'completed' ? 'completed' : 'idle'})`}
424
- markerEnd={`url(#arrow-${analyzerStatus === 'executing' || criticStatus === 'executing' || editorStatus === 'executing' ? 'executing' : analyzerStatus === 'completed' ? 'completed' : 'idle'})`}
425
- className={cn("pf-connector pf-orchestration", `pf-connector-${analyzerStatus === 'executing' || criticStatus === 'executing' || editorStatus === 'executing' ? 'executing' : analyzerStatus === 'completed' ? 'completed' : 'idle'}`)} />
426
 
427
  {/* Row 1 Nodes - labels above */}
428
  <SVGNode x={NODES.input.x} y={NODES.input.y} icon={User} label="User Input" status={inputStatus} labelPosition="above" />
@@ -430,7 +410,6 @@ export function ProcessFlow({
430
  <SVGNode x={NODES.a2a.x} y={NODES.a2a.y} icon={Network} label="A2A client" status={a2aStatus} labelPosition="above" />
431
  <SVGNode x={NODES.analyzer.x} y={NODES.analyzer.y} icon={Brain} label="Analyzer" label2="Agent" status={analyzerStatus} isAgent labelPosition="above" />
432
  <SVGNode x={NODES.critic.x} y={NODES.critic.y} icon={MessageSquare} label="Critic" label2="Agent" status={criticStatus} isAgent labelPosition="above" />
433
- <SVGNode x={NODES.editor.x} y={NODES.editor.y} icon={Edit3} label="Editor" label2="Agent" status={editorStatus} isAgent labelPosition="above" />
434
  <SVGNode x={NODES.output.x} y={NODES.output.y} icon={FileOutput} label="Output" status={outputStatus} labelPosition="above" flipIcon />
435
 
436
  {/* Row 2 & 3 Nodes - labels below */}
@@ -445,7 +424,7 @@ export function ProcessFlow({
445
  const isProviderCompleted = providerStatus === 'completed';
446
 
447
  // Only show executing if agents are active AND this provider hasn't failed/completed yet
448
- const agentsActive = analyzerStatus === 'executing' || criticStatus === 'executing' || editorStatus === 'executing';
449
  const isActive = agentsActive && !isFailed && !isProviderCompleted;
450
 
451
  // Only the actually used provider shows as completed (from backend llmStatus)
 
6
  Search,
7
  Brain,
8
  MessageSquare,
 
9
  FileOutput,
 
10
  Loader2,
11
  Network,
12
  GitBranch,
 
56
  const ROW3_Y = ROW2_Y + ROW_GAP
57
  // SVG dimensions
58
  const SVG_HEIGHT = 218 // Exact content height - scales to fill container
59
+ const NODE_COUNT = 6 // Reduced: removed Editor node
60
  const FLOW_WIDTH = GAP * (NODE_COUNT - 1) + NODE_SIZE
61
+ const SVG_WIDTH = 480 // Narrower now without Editor
62
  const FLOW_START_X = NODE_SIZE / 2 // Left-aligned with half-node margin
63
 
64
  const NODES = {
 
67
  a2a: { x: FLOW_START_X + GAP * 2, y: ROW1_Y },
68
  analyzer: { x: FLOW_START_X + GAP * 3, y: ROW1_Y },
69
  critic: { x: FLOW_START_X + GAP * 4, y: ROW1_Y },
70
+ output: { x: FLOW_START_X + GAP * 5, y: ROW1_Y }, // Moved up (was editor position)
 
71
  exchange: { x: FLOW_START_X, y: ROW2_Y },
72
  researcher: { x: FLOW_START_X + GAP * 2, y: ROW3_Y },
73
  }
 
83
  { id: 'sentiment', label: 'Sentiment', icon: MessageSquare, x: MCP_START_X + MCP_GAP * 5 },
84
  ]
85
 
86
+ const AGENTS_CENTER_X = (NODES.analyzer.x + NODES.critic.x) / 2 // Now between Analyzer and Critic only
87
  const LLM_GAP = 68 // LLM_WIDTH (64) + 4px spacing
88
  const LLM_PROVIDERS = [
89
  { id: 'groq', name: 'Groq', x: AGENTS_CENTER_X - LLM_GAP },
 
94
  const AGENTS_GROUP = {
95
  x: NODES.analyzer.x - NODE_SIZE / 2 - GROUP_PAD,
96
  y: ROW1_Y - NODE_SIZE / 2 - GROUP_PAD,
97
+ width: NODES.critic.x - NODES.analyzer.x + NODE_SIZE + GROUP_PAD * 2, // Now only Analyzer + Critic
98
  height: NODE_SIZE + GROUP_PAD * 2,
99
  }
100
 
 
130
  const normalizedCompleted = completedSteps.map(normalizeStep)
131
 
132
  // On cache hit, intermediate steps stay idle (not completed)
133
+ if (cacheHit && ['researcher', 'analyzer', 'critic', 'a2a'].includes(stepId)) {
134
  return 'idle'
135
  }
136
 
 
288
  const criticStatus = isAborted
289
  ? (completedSteps.includes('critic') ? 'completed' : 'idle')
290
  : getNodeStatus('critic', currentStep, completedSteps, cacheHit)
 
 
 
291
  const outputStatus = isAborted
292
  ? (completedSteps.includes('output') ? 'completed' : 'idle')
293
  : getNodeStatus('output', currentStep, completedSteps, cacheHit)
 
305
  }, [currentStep, completedSteps, cacheHit])
306
 
307
  // Completion halo: workflow completed successfully
 
308
  const allDone = useMemo(() => {
309
  const normalizedCompleted = completedSteps.map(normalizeStep)
310
  const essentialSteps = ['input', 'cache', 'researcher', 'analyzer', 'critic', 'output']
 
361
  <line x1={nodeRight(NODES.analyzer)} y1={ROW1_Y} x2={nodeLeft(NODES.critic)} y2={ROW1_Y}
362
  strokeWidth={1.4} markerEnd={`url(#arrow-${conn(analyzerStatus, criticStatus)})`}
363
  className={cn("pf-connector", `pf-connector-${conn(analyzerStatus, criticStatus)}`)} />
364
+ {/* Critic β†’ Analyzer revision loop (curved path below) - shows when revision loop is active */}
 
 
 
 
365
  <path
366
+ d={`M ${NODES.critic.x} ${nodeBottom(NODES.critic)}
367
+ Q ${NODES.critic.x} ${ROW1_Y + 38} ${(NODES.analyzer.x + NODES.critic.x) / 2} ${ROW1_Y + 38}
368
+ Q ${NODES.analyzer.x} ${ROW1_Y + 38} ${NODES.analyzer.x} ${nodeBottom(NODES.analyzer)}`}
369
  fill="none"
370
  strokeWidth={1.4}
371
+ markerEnd={`url(#arrow-${revisionCount > 0 && (analyzerStatus === 'executing' || criticStatus === 'completed') ? 'completed' : 'idle'})`}
372
+ className={cn("pf-connector", `pf-connector-${revisionCount > 0 && (analyzerStatus === 'executing' || criticStatus === 'completed') ? 'completed' : 'idle'}`)}
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  />
374
+ {/* Critic β†’ Output connector */}
375
+ <line x1={nodeRight(NODES.critic)} y1={ROW1_Y} x2={nodeLeft(NODES.output)} y2={ROW1_Y}
376
+ strokeWidth={1.4} markerEnd={`url(#arrow-${conn(criticStatus, outputStatus)})`}
377
+ className={cn("pf-connector", `pf-connector-${conn(criticStatus, outputStatus)}`)} />
378
 
379
  {/* Researcher ↔ MCP block connector (bidirectional) */}
380
  <line x1={nodeRight(NODES.researcher)} y1={ROW3_Y} x2={MCP_GROUP.x - 2} y2={ROW3_Y}
 
400
 
401
  {/* Agent Group ↔ LLM Group (Orchestration connector) */}
402
  <line x1={AGENTS_CENTER_X} y1={AGENTS_GROUP.y + AGENTS_GROUP.height + 2} x2={AGENTS_CENTER_X} y2={LLM_GROUP.y - 2}
403
+ markerStart={`url(#arrow-start-${analyzerStatus === 'executing' || criticStatus === 'executing' ? 'executing' : analyzerStatus === 'completed' ? 'completed' : 'idle'})`}
404
+ markerEnd={`url(#arrow-${analyzerStatus === 'executing' || criticStatus === 'executing' ? 'executing' : analyzerStatus === 'completed' ? 'completed' : 'idle'})`}
405
+ className={cn("pf-connector pf-orchestration", `pf-connector-${analyzerStatus === 'executing' || criticStatus === 'executing' ? 'executing' : analyzerStatus === 'completed' ? 'completed' : 'idle'}`)} />
406
 
407
  {/* Row 1 Nodes - labels above */}
408
  <SVGNode x={NODES.input.x} y={NODES.input.y} icon={User} label="User Input" status={inputStatus} labelPosition="above" />
 
410
  <SVGNode x={NODES.a2a.x} y={NODES.a2a.y} icon={Network} label="A2A client" status={a2aStatus} labelPosition="above" />
411
  <SVGNode x={NODES.analyzer.x} y={NODES.analyzer.y} icon={Brain} label="Analyzer" label2="Agent" status={analyzerStatus} isAgent labelPosition="above" />
412
  <SVGNode x={NODES.critic.x} y={NODES.critic.y} icon={MessageSquare} label="Critic" label2="Agent" status={criticStatus} isAgent labelPosition="above" />
 
413
  <SVGNode x={NODES.output.x} y={NODES.output.y} icon={FileOutput} label="Output" status={outputStatus} labelPosition="above" flipIcon />
414
 
415
  {/* Row 2 & 3 Nodes - labels below */}
 
424
  const isProviderCompleted = providerStatus === 'completed';
425
 
426
  // Only show executing if agents are active AND this provider hasn't failed/completed yet
427
+ const agentsActive = analyzerStatus === 'executing' || criticStatus === 'executing';
428
  const isActive = agentsActive && !isFailed && !isProviderCompleted;
429
 
430
  // Only the actually used provider shows as completed (from backend llmStatus)
frontend/src/lib/api.ts CHANGED
@@ -56,7 +56,7 @@ export interface LLMStatus {
56
  // Workflow status with activity log and MCP status
57
  export interface WorkflowStatus {
58
  status: 'starting' | 'running' | 'completed' | 'error' | 'aborted'
59
- current_step: 'input' | 'cache' | 'researcher' | 'analyzer' | 'critic' | 'editor' | 'output' | 'completed'
60
  revision_count: number
61
  score: number
62
  activity_log: ActivityLogEntry[]
 
56
  // Workflow status with activity log and MCP status
57
  export interface WorkflowStatus {
58
  status: 'starting' | 'running' | 'completed' | 'error' | 'aborted'
59
+ current_step: 'input' | 'cache' | 'researcher' | 'analyzer' | 'critic' | 'output' | 'completed'
60
  revision_count: number
61
  score: number
62
  activity_log: ActivityLogEntry[]
frontend/src/stories/ActivityLog.stories.tsx CHANGED
@@ -52,8 +52,8 @@ const sampleEntries: ActivityLogEntry[] = [
52
  },
53
  {
54
  timestamp: new Date(Date.now() + 12000).toISOString(),
55
- step: 'editor',
56
- message: 'Formatting final output',
57
  },
58
  {
59
  timestamp: new Date(Date.now() + 14000).toISOString(),
 
52
  },
53
  {
54
  timestamp: new Date(Date.now() + 12000).toISOString(),
55
+ step: 'analyzer',
56
+ message: 'Revision #1 completed',
57
  },
58
  {
59
  timestamp: new Date(Date.now() + 14000).toISOString(),
frontend/src/stories/ProcessFlow.stories.tsx CHANGED
@@ -95,19 +95,10 @@ export const CriticActive: Story = {
95
  },
96
  }
97
 
98
- export const EditorActive: Story = {
99
- args: {
100
- currentStep: 'editor',
101
- completedSteps: ['input', 'cache', 'researcher', 'analyzer', 'critic'],
102
- mcpStatus: completedMcpStatus,
103
- llmProvider: 'Claude 3.5',
104
- },
105
- }
106
-
107
  export const Completed: Story = {
108
  args: {
109
  currentStep: 'output',
110
- completedSteps: ['input', 'cache', 'researcher', 'analyzer', 'critic', 'editor', 'output'],
111
  mcpStatus: completedMcpStatus,
112
  llmProvider: 'Claude 3.5',
113
  },
 
95
  },
96
  }
97
 
 
 
 
 
 
 
 
 
 
98
  export const Completed: Story = {
99
  args: {
100
  currentStep: 'output',
101
+ completedSteps: ['input', 'cache', 'researcher', 'analyzer', 'critic', 'output'],
102
  mcpStatus: completedMcpStatus,
103
  llmProvider: 'Claude 3.5',
104
  },