import React, { useState, useRef, useEffect } from 'react';
import { marked } from 'marked';
import {
Send, Bot, User, Shield, Layers, Terminal, Play,
HelpCircle, Cpu, Award, Activity, ChevronRight
} from 'lucide-react';
import { apiUrl } from '../api';
const SUGGESTED = [
'How does authentication work?',
'What is the architecture pattern?',
'Which API endpoints exist?',
'What are the main dependencies?',
'Where should I start reading code?',
];
const getAgentIcon = (name) => {
const map = {
PlannerAgent: ,
ArchitectureAgent: ,
SecurityAgent: ,
ApiAgent: ,
DependencyAgent: ,
QualityAgent: ,
OnboardingAgent: ,
};
return map[name] || ;
};
const getAgentLabel = (name) => name.replace('Agent', ' Agent');
const renderMarkdown = (text) => {
try { return { __html: marked.parse(text || '') }; }
catch (e) { return { __html: text || '' }; }
};
export default function RepositoryAssistant({ repo_id, apiKey }) {
const [messages, setMessages] = useState([{
id: 'welcome',
role: 'assistant',
content: 'Hello! Ask me anything about this codebase — architecture, security, APIs, dependencies, or how to get started.',
timeline: null, planner_decision: null, confidence: null,
total_time_ms: null, references: [], retrieved_context: []
}]);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const [sessionId, setSessionId] = useState(null);
const [selectedId, setSelectedId] = useState('welcome');
const chatEndRef = useRef(null);
useEffect(() => { chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages, loading]);
const sendMessage = async (text) => {
if (!text.trim() || loading) return;
setLoading(true);
const userMsg = { id: `u-${Date.now()}`, role: 'user', content: text.trim() };
setMessages(prev => [...prev, userMsg]);
setSelectedId(userMsg.id);
setInput('');
const headers = { 'Content-Type': 'application/json' };
if (apiKey) headers['x-gemini-key'] = apiKey;
try {
const res = await fetch(apiUrl('/api/chat'), {
method: 'POST',
headers,
body: JSON.stringify({
repo_id,
question: text.trim(),
session_id: sessionId,
}),
});
const raw = await res.text();
let data;
try {
data = raw ? JSON.parse(raw) : {};
} catch {
throw new Error(raw || `Server error (${res.status})`);
}
if (!res.ok) throw new Error(data.detail || 'Agent orchestration failed.');
if (data.session_id) setSessionId(data.session_id);
const assistMsg = {
id: `a-${Date.now()}`,
role: 'assistant',
content: data.answer || data.summary || (data.agent_contributions || []).join('\n\n') || 'No answer returned from agent.',
summary: data.summary,
agents_used: data.agents_used,
confidence: data.confidence,
references: data.references || [],
agent_contributions: data.agent_contributions,
planner_decision: data.planner_decision,
timeline: data.timeline,
total_time_ms: data.total_time_ms,
retrieved_context: data.retrieved_context || [],
rag_latency_ms: data.rag_latency_ms,
session_id: data.session_id,
};
setMessages(prev => [...prev, assistMsg]);
setSelectedId(assistMsg.id);
} catch (err) {
const errMsg = { id: `e-${Date.now()}`, role: 'assistant', content: `**Error:** ${err.message}`, isError: true };
setMessages(prev => [...prev, errMsg]);
} finally {
setLoading(false);
}
};
const handleSubmit = (e) => { e.preventDefault(); sendMessage(input); };
const activeMsg = messages.find(m => m.id === selectedId && m.role === 'assistant')
|| [...messages].reverse().find(m => m.role === 'assistant' && !m.isError)
|| messages[0];
const confLevel = (c) => c >= 0.75 ? 'high' : c >= 0.5 ? 'medium' : 'low';
return (
{/* ── Chat Column ── */}
AI Repository Assistant
Multi-Agent Online
{/* Suggested questions — shown only when just the welcome message exists */}
{messages.length === 1 && (
🤖
{messages[0].content}
Try one of these questions:
{SUGGESTED.map(q => (
))}
)}
{messages.slice(1).map(msg => (
msg.role === 'assistant' && setSelectedId(msg.id)}
style={{ cursor: msg.role === 'assistant' ? 'pointer' : 'default' }}
>
{msg.role === 'assistant' ? (
) : (
{msg.content}
)}
{msg.role === 'assistant' && msg.confidence != null && (
{Math.round(msg.confidence * 100)}% confidence
)}
{msg.role === 'assistant' && msg.total_time_ms && (
{msg.total_time_ms}ms
)}
{msg.role === 'assistant' && msg.agents_used?.length > 0 && (
{msg.agents_used.map(a => (
{getAgentIcon(a)} {getAgentLabel(a)}
))}
)}
{msg.role === 'assistant' && msg.retrieved_context?.length > 0 && (
{msg.retrieved_context.length} RAG chunks
)}
{/* References */}
{msg.references?.length > 0 && (
{msg.references.map((r, i) => (
{r}
))}
)}
))}
{loading && (
)}
{/* ── Observability Panel ── */}
Orchestration Observability
{activeMsg && activeMsg.id !== 'welcome' && !activeMsg.isError ? (
{/* Stats */}
Time
{activeMsg.total_time_ms}ms
Confidence
{activeMsg.confidence != null ? `${Math.round(activeMsg.confidence * 100)}%` : '—'}
{/* RAG context */}
{activeMsg.retrieved_context?.length > 0 && (
RAG Context ({activeMsg.retrieved_context.length} chunks)
{activeMsg.retrieved_context.slice(0, 3).map((c, i) => (
{c.metadata?.category || 'chunk'}
{Math.round(c.similarity * 100)}%
))}
{activeMsg.rag_latency_ms && (
Retrieved in {activeMsg.rag_latency_ms}ms
)}
)}
{/* Planner Decision */}
{activeMsg.planner_decision && (
Planner Decision
{activeMsg.planner_decision.reasoning}
{activeMsg.planner_decision.execution_order?.flat().map(a => (
{getAgentIcon(a)} {getAgentLabel(a)}
))}
)}
{/* Agent Timeline */}
{activeMsg.timeline?.length > 0 && (
Agent Timeline
{activeMsg.timeline.map((step, i) => (
{getAgentIcon(step.agent)} {getAgentLabel(step.agent)}
{step.execution_time_ms}ms
{step.confidence != null && step.agent !== 'PlannerAgent' && (
)}
))}
)}
{/* Contributions */}
{activeMsg.agent_contributions?.length > 0 && (
{activeMsg.agent_contributions.map((c, i) => (
-
•
{c}
))}
)}
) : (
Click any response to see agent timeline & orchestration details.
)}
);
}