From 77be59bc0a6353e98846b9c9bfa2d566efea8b1f Mon Sep 17 00:00:00 2001
From: YurenHao0426 <blackhao0426@gmail.com>
Date: Fri, 13 Feb 2026 21:43:34 +0000
Subject: Add LLM Council mode for multi-model consensus

3-stage council orchestration: parallel model queries (Stage 1),
anonymous peer ranking (Stage 2), and streamed chairman synthesis
(Stage 3). Includes scope-aware file resolution for Google/Claude
providers so upstream file attachments are visible to all providers.

- Backend: council.py orchestrator, /api/run_council_stream endpoint,
  query_model_full() non-streaming wrapper, resolve_provider() helper,
  resolve_scoped_file_ids() for Google/Claude scope parity with OpenAI
- Frontend: council toggle UI, model checkbox selector, chairman picker,
  SSE event parsing, tabbed Stage 1/2/3 response display
- Canvas: amber council node indicator with Users icon

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 frontend/src/components/nodes/LLMNode.tsx | 36 +++++++++++++++++++++----------
 1 file changed, 25 insertions(+), 11 deletions(-)

(limited to 'frontend/src/components/nodes/LLMNode.tsx')

diff --git a/frontend/src/components/nodes/LLMNode.tsx b/frontend/src/components/nodes/LLMNode.tsx
index 7542860..adeb076 100644
--- a/frontend/src/components/nodes/LLMNode.tsx
+++ b/frontend/src/components/nodes/LLMNode.tsx
@@ -1,7 +1,7 @@
 import { useEffect, useState } from 'react';
 import { Handle, Position, type NodeProps, useUpdateNodeInternals, useEdges } from 'reactflow';
 import type { NodeData, MergedTrace, Trace } from '../../store/flowStore';
-import { Loader2, MessageSquare } from 'lucide-react';
+import { Loader2, MessageSquare, Users } from 'lucide-react';
 import useFlowStore from '../../store/flowStore';
 
 const LLMNode = ({ id, data, selected }: NodeProps<NodeData>) => {
@@ -96,13 +96,21 @@ const LLMNode = ({ id, data, selected }: NodeProps<NodeData>) => {
           ? isDark
             ? 'bg-gray-800 border-gray-600 opacity-50 cursor-not-allowed'
             : 'bg-gray-100 border-gray-300 opacity-50 cursor-not-allowed'
-          : selected
-            ? isDark
-              ? 'bg-gray-800 border-blue-400'
-              : 'bg-white border-blue-500'
-            : isDark
-              ? 'bg-gray-800 border-gray-600'
-              : 'bg-white border-gray-200'
+          : data.councilMode
+            ? selected
+              ? isDark
+                ? 'bg-gray-800 border-amber-400'
+                : 'bg-white border-amber-500'
+              : isDark
+                ? 'bg-gray-800 border-amber-600/60'
+                : 'bg-white border-amber-300'
+            : selected
+              ? isDark
+                ? 'bg-gray-800 border-blue-400'
+                : 'bg-white border-blue-500'
+              : isDark
+                ? 'bg-gray-800 border-gray-600'
+                : 'bg-white border-gray-200'
       }`}
       style={{ pointerEvents: isDisabled ? 'none' : 'auto', minHeight: minHandleHeight }}
       onMouseEnter={() => setShowPreview(true)}
@@ -134,10 +142,12 @@ const LLMNode = ({ id, data, selected }: NodeProps<NodeData>) => {
         }`}>
           {data.status === 'loading' ? (
             <Loader2 className="w-4 h-4 animate-spin text-blue-500" />
+          ) : data.councilMode ? (
+            <Users className={`w-4 h-4 ${isDark ? 'text-amber-400' : 'text-amber-600'}`} />
           ) : (
             <MessageSquare className={`w-4 h-4 ${
-              isDisabled 
-                ? 'text-gray-500' 
+              isDisabled
+                ? 'text-gray-500'
                 : isDark ? 'text-gray-400' : 'text-gray-600'
             }`} />
           )}
@@ -151,7 +161,11 @@ const LLMNode = ({ id, data, selected }: NodeProps<NodeData>) => {
             {data.label}
             {isDisabled && <span className="text-xs ml-1">(disabled)</span>}
           </div>
-          <div className={`text-xs ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>{data.model}</div>
+          <div className={`text-xs ${isDark ? 'text-gray-400' : 'text-gray-500'}`}>
+            {data.councilMode
+              ? `Council (${(data.councilModels || []).length})`
+              : data.model}
+          </div>
         </div>
       </div>
 
-- 
cgit v1.2.3

