Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import QueryInput from './components/QueryInput';
import CouncilMemberCard from './components/CouncilMemberCard';
import SynthesisPanel from './components/SynthesisPanel';
import HistoryBrowser from './components/HistoryBrowser';
import ThemeToggle from './components/ThemeToggle';

export default function App() {
const {
Expand Down Expand Up @@ -83,25 +84,28 @@ export default function App() {
const councilMembersList = Object.keys(displayResponses);

return (
<div className="min-h-screen bg-[#0a0e1a]">
<div className="min-h-screen bg-gray-50 dark:bg-[#0a0e1a] transition-colors duration-200">
{/* Header */}
<header className="bg-[#1a2332] shadow-lg border-b border-gray-700">
<header className="bg-white dark:bg-[#1a2332] shadow-lg border-b border-gray-200 dark:border-gray-700 transition-colors duration-200">
<div className="max-w-7xl mx-auto px-4 py-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
LLM Council
</h1>
<p className="text-sm text-gray-400">
<p className="text-sm text-gray-500 dark:text-gray-400">
Multi-Model AI Decision Framework
</p>
</div>
<button
onClick={() => setShowHistory(!showHistory)}
className="btn-secondary"
>
{showHistory ? 'Hide History' : 'Show History'}
</button>
<div className="flex items-center gap-4">
<ThemeToggle />
<button
onClick={() => setShowHistory(!showHistory)}
className="btn-secondary"
>
{showHistory ? 'Hide History' : 'Show History'}
</button>
</div>
</div>
</div>
</header>
Expand All @@ -110,15 +114,15 @@ export default function App() {
<main className="max-w-7xl mx-auto px-4 py-8">
{/* Error Display */}
{error && (
<div className="mb-4 p-4 bg-red-900/30 border border-red-700 rounded-lg">
<p className="text-red-400">Error: {error}</p>
<div className="mb-4 p-4 bg-red-100 dark:bg-red-900/30 border border-red-200 dark:border-red-700 rounded-lg">
<p className="text-red-700 dark:text-red-400">Error: {error}</p>
</div>
)}

{/* Decision ID Display */}
{displayDecisionId && (
<div className="mb-4 p-3 bg-blue-900/30 border border-blue-700 rounded-lg">
<p className="text-blue-400 text-sm">
<div className="mb-4 p-3 bg-blue-100 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-700 rounded-lg">
<p className="text-blue-700 dark:text-blue-400 text-sm">
{historicalData ? '📜 Historical ' : ''}Decision ID: <span className="font-mono font-bold">#{displayDecisionId}</span>
{historicalData && (
<button
Expand Down Expand Up @@ -158,7 +162,7 @@ export default function App() {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Council Member Responses - Left Column (2/3) */}
<div className="lg:col-span-2 space-y-4">
<h2 className="text-2xl font-bold text-gray-100">Council Responses</h2>
<h2 className="text-2xl font-bold text-gray-900 dark:text-gray-100">Council Responses</h2>
{councilMembersList.map(modelId => (
<CouncilMemberCard
key={modelId}
Expand All @@ -182,14 +186,14 @@ export default function App() {
{modelsLoading && (
<div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto"></div>
<p className="mt-4 text-gray-400">Loading models...</p>
<p className="mt-4 text-gray-500 dark:text-gray-400">Loading models...</p>
</div>
)}
</main>

{/* Footer */}
<footer className="mt-12 py-6 border-t border-gray-700">
<div className="max-w-7xl mx-auto px-4 text-center text-sm text-gray-400">
<footer className="mt-12 py-6 border-t border-gray-200 dark:border-gray-700 transition-colors duration-200">
<div className="max-w-7xl mx-auto px-4 text-center text-sm text-gray-500 dark:text-gray-400">
<p className="mb-2">Multi-Model AI Decision Framework • LLM Council v1.0.0 • MIT License</p>
<div className="flex justify-center space-x-4">
<span>&copy; 2025 Barnaby Jeans</span>
Comment thread
bjeans marked this conversation as resolved.
Expand All @@ -198,7 +202,7 @@ export default function App() {
href="https://github.com/bjeans/Multi-AI-Chat"
target="_blank"
rel="noopener noreferrer"
className="hover:text-blue-400 transition-colors"
className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
>
GitHub
</a>
Expand All @@ -207,7 +211,7 @@ export default function App() {
href="https://hub.docker.com/r/bjeans/multi-ai-chat"
target="_blank"
rel="noopener noreferrer"
className="hover:text-blue-400 transition-colors"
className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
>
DockerHub
</a>
Expand Down
22 changes: 11 additions & 11 deletions frontend/src/components/CouncilMemberCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ export default function CouncilMemberCard({ modelId, response, isChairman }) {
const getStatusColor = () => {
switch (response.status) {
case 'pending':
return 'bg-gray-700 text-gray-400';
return 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400';
case 'streaming':
return 'bg-blue-900/30 text-blue-300';
return 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300';
case 'complete':
return 'bg-green-900/30 text-green-300';
return 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300';
case 'error':
return 'bg-red-900/30 text-red-300';
return 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300';
default:
return 'bg-gray-700 text-gray-400';
return 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400';
}
};

Expand All @@ -35,11 +35,11 @@ export default function CouncilMemberCard({ modelId, response, isChairman }) {
<div className="card">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center space-x-2">
<h3 className="font-semibold text-lg text-gray-100 truncate" title={modelId}>
<h3 className="font-semibold text-lg text-gray-900 dark:text-gray-100 truncate" title={modelId}>
{modelId}
</h3>
{isChairman && (
<span className="px-2 py-1 bg-purple-900/30 text-purple-300 text-xs rounded-full">
<span className="px-2 py-1 bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 text-xs rounded-full">
Chairman
</span>
)}
Expand All @@ -49,18 +49,18 @@ export default function CouncilMemberCard({ modelId, response, isChairman }) {
</span>
</div>

<div className="bg-[#0f1623] rounded-lg p-3 min-h-[150px] max-h-[400px] overflow-y-auto">
<div className="bg-gray-50 dark:bg-[#0f1623] rounded-lg p-3 min-h-[150px] max-h-[400px] overflow-y-auto transition-colors duration-200">
{response.status === 'error' ? (
<p className="text-red-400">{response.error}</p>
<p className="text-red-600 dark:text-red-400">{response.error}</p>
) : response.text ? (
<div className="prose prose-invert prose-sm max-w-none">
<div className="prose dark:prose-invert prose-sm max-w-none">
<ReactMarkdown>{response.text}</ReactMarkdown>
{response.status === 'streaming' && (
<span className="inline-block w-2 h-4 bg-blue-500 ml-1 animate-pulse"></span>
)}
</div>
) : (
<p className="text-gray-400 italic">Waiting for response...</p>
<p className="text-gray-500 dark:text-gray-400 italic">Waiting for response...</p>
)}
</div>
</div>
Expand Down
20 changes: 10 additions & 10 deletions frontend/src/components/HistoryBrowser.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,52 +31,52 @@ export default function HistoryBrowser({ onSelectDecision }) {
if (loading) {
return (
<div className="card">
<h3 className="text-xl font-bold mb-4 text-gray-100">Decision History</h3>
<p className="text-gray-400">Loading...</p>
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-gray-100">Decision History</h3>
<p className="text-gray-500 dark:text-gray-400">Loading...</p>
</div>
);
}

if (error) {
return (
<div className="card">
<h3 className="text-xl font-bold mb-4 text-gray-100">Decision History</h3>
<p className="text-red-400">{error}</p>
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-gray-100">Decision History</h3>
<p className="text-red-600 dark:text-red-400">{error}</p>
</div>
);
}

return (
<div className="card">
<div className="flex items-center justify-between mb-4">
<h3 className="text-xl font-bold text-gray-100">Decision History</h3>
<h3 className="text-xl font-bold text-gray-900 dark:text-gray-100">Decision History</h3>
<button onClick={fetchHistory} className="btn-secondary text-sm">
Refresh
</button>
</div>

{decisions.length === 0 ? (
<p className="text-gray-400 italic">No decisions yet. Start research to create one!</p>
<p className="text-gray-500 dark:text-gray-400 italic">No decisions yet. Start research to create one!</p>
) : (
<div className="space-y-2 max-h-96 overflow-y-auto">
{decisions.map(decision => (
<div
key={decision.id}
className="border border-gray-700 rounded-lg p-3 hover:bg-[#0f1623] cursor-pointer transition-colors"
className="border border-gray-200 dark:border-gray-700 rounded-lg p-3 hover:bg-gray-50 dark:hover:bg-[#0f1623] cursor-pointer transition-colors"
onClick={() => onSelectDecision(decision.id)}
>
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-200 truncate" title={decision.query}>
<p className="text-sm font-medium text-gray-800 dark:text-gray-200 truncate" title={decision.query}>
{decision.query}
</p>
<div className="flex items-center space-x-2 mt-1 text-xs text-gray-400">
<div className="flex items-center space-x-2 mt-1 text-xs text-gray-500 dark:text-gray-400">
<span>Chairman: {decision.chairman_model}</span>
<span>•</span>
<span>{decision.response_count} responses</span>
</div>
</div>
<span className="text-xs text-gray-500 whitespace-nowrap ml-2">
<span className="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap ml-2">
{formatDate(decision.created_at)}
</span>
</div>
Expand Down
20 changes: 10 additions & 10 deletions frontend/src/components/QueryInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default function QueryInput({
<form onSubmit={handleSubmit} className="space-y-6">
{/* Research Prompt */}
<div>
<label className="block text-sm font-medium mb-3 text-gray-300">
<label className="block text-sm font-medium mb-3 text-gray-700 dark:text-gray-300">
Research Prompt
</label>
<textarea
Expand All @@ -99,7 +99,7 @@ export default function QueryInput({

{/* Quick Prompts */}
<div className="flex gap-2 mt-3">
<span className="text-xs text-gray-400 self-center mr-1">Quick prompts:</span>
<span className="text-xs text-gray-500 dark:text-gray-400 self-center mr-1">Quick prompts:</span>
{Object.keys(QUICK_PROMPTS).map(prompt => (
<button
key={prompt}
Expand All @@ -122,10 +122,10 @@ export default function QueryInput({
{/* Council Members */}
<div>
<div className="flex justify-between items-center mb-3">
<label className="block text-sm font-medium text-gray-300">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Council Members
</label>
<span className="text-xs text-gray-400">
<span className="text-xs text-gray-500 dark:text-gray-400">
Select 2+ models for best results ({activeSelectedModels.length} selected)
</span>
</div>
Expand All @@ -145,7 +145,7 @@ export default function QueryInput({
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{availableModels.length === 0 ? (
<p className="text-gray-500 col-span-full">No models available</p>
<p className="text-gray-500 dark:text-gray-400 col-span-full">No models available</p>
) : (
availableModels.map(model => {
const modelId = model.id || model.name;
Expand All @@ -167,13 +167,13 @@ export default function QueryInput({
disabled={disabled}
tabIndex={-1}
aria-hidden="true"
className="mt-1 rounded text-blue-600 focus:ring-blue-500 bg-transparent border-gray-600"
className="mt-1 rounded text-blue-600 focus:ring-blue-500 bg-white dark:bg-transparent border-gray-300 dark:border-gray-600"
/>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-gray-100 truncate">
<div className="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
{model.name || model.display_name}
</div>
<div className="text-xs text-gray-400 mt-1">
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
{getModelProviderLabel(modelId)}
</div>
</div>
Expand All @@ -188,8 +188,8 @@ export default function QueryInput({

{/* Chairman Model */}
<div>
<label className="block text-sm font-medium mb-3 text-gray-300">
Chairman Model <span className="text-xs text-gray-400 font-normal">Synthesizes the final answer</span>
<label className="block text-sm font-medium mb-3 text-gray-700 dark:text-gray-300">
Chairman Model <span className="text-xs text-gray-500 dark:text-gray-400 font-normal">Synthesizes the final answer</span>
</label>
<select
value={chairman}
Expand Down
34 changes: 17 additions & 17 deletions frontend/src/components/SynthesisPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export default function SynthesisPanel({ synthesis }) {
if (!synthesis) {
return (
<div className="card">
<h3 className="text-xl font-bold mb-4 text-gray-100">Synthesis</h3>
<p className="text-gray-400 italic">
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-gray-100">Synthesis</h3>
<p className="text-gray-500 dark:text-gray-400 italic">
Synthesis will appear here after all council members respond...
</p>
</div>
Expand All @@ -15,10 +15,10 @@ export default function SynthesisPanel({ synthesis }) {
if (synthesis.status === 'generating') {
return (
<div className="card">
<h3 className="text-xl font-bold mb-4 text-gray-100">Synthesis</h3>
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-gray-100">Synthesis</h3>
<div className="flex items-center space-x-2">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-500"></div>
<p className="text-blue-400">Chairman is synthesizing responses...</p>
<p className="text-blue-600 dark:text-blue-400">Chairman is synthesizing responses...</p>
</div>
</div>
);
Expand All @@ -27,29 +27,29 @@ export default function SynthesisPanel({ synthesis }) {
if (synthesis.status === 'error') {
return (
<div className="card">
<h3 className="text-xl font-bold mb-4 text-gray-100">Synthesis</h3>
<p className="text-red-400">{synthesis.error}</p>
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-gray-100">Synthesis</h3>
<p className="text-red-600 dark:text-red-400">{synthesis.error}</p>
</div>
);
}

return (
<div className="card space-y-6">
<h3 className="text-xl font-bold text-gray-100">Synthesis</h3>
<h3 className="text-xl font-bold text-gray-900 dark:text-gray-100">Synthesis</h3>

{/* Consensus Section */}
{synthesis.consensus && synthesis.consensus.length > 0 && (
<div>
<h4 className="text-lg font-semibold text-green-400 mb-2">
<h4 className="text-lg font-semibold text-green-600 dark:text-green-400 mb-2">
✓ Consensus
</h4>
<ul className="space-y-2">
{synthesis.consensus.map((item, idx) => (
<li
key={idx}
className="bg-green-900/20 border-l-4 border-green-500 p-3 rounded"
className="bg-green-100 dark:bg-green-900/20 border-l-4 border-green-500 p-3 rounded"
>
<div className="text-sm text-gray-300 prose prose-invert prose-sm max-w-none">
<div className="text-sm prose dark:prose-invert prose-sm max-w-none">
<ReactMarkdown>{item}</ReactMarkdown>
</div>
</li>
Expand All @@ -61,20 +61,20 @@ export default function SynthesisPanel({ synthesis }) {
{/* Debates Section */}
{synthesis.debates && synthesis.debates.length > 0 && (
<div>
<h4 className="text-lg font-semibold text-orange-400 mb-2">
<h4 className="text-lg font-semibold text-orange-600 dark:text-orange-400 mb-2">
⚡ Debates
</h4>
<ul className="space-y-2">
{synthesis.debates.map((debate, idx) => (
<li
key={idx}
className="bg-orange-900/20 border-l-4 border-orange-500 p-3 rounded"
className="bg-orange-100 dark:bg-orange-900/20 border-l-4 border-orange-500 p-3 rounded"
>
<div className="font-medium text-sm mb-1 text-gray-200 prose prose-invert prose-sm max-w-none">
<div className="font-medium text-sm mb-1 prose dark:prose-invert prose-sm max-w-none">
<ReactMarkdown>{debate.topic}</ReactMarkdown>
</div>
{debate.positions && (
<div className="text-sm text-gray-300 prose prose-invert prose-sm max-w-none">
<div className="text-sm prose dark:prose-invert prose-sm max-w-none">
<ReactMarkdown>{debate.positions}</ReactMarkdown>
</div>
)}
Expand All @@ -86,11 +86,11 @@ export default function SynthesisPanel({ synthesis }) {

{/* Chairman's Synthesis */}
<div>
<h4 className="text-lg font-semibold text-purple-400 mb-2">
<h4 className="text-lg font-semibold text-purple-600 dark:text-purple-400 mb-2">
Chairman's Synthesis
</h4>
<div className="bg-purple-900/20 border-l-4 border-purple-500 p-4 rounded">
<div className="prose prose-invert prose-sm max-w-none">
<div className="bg-purple-100 dark:bg-purple-900/20 border-l-4 border-purple-500 p-4 rounded">
<div className="prose dark:prose-invert prose-sm max-w-none">
<ReactMarkdown>{synthesis.text}</ReactMarkdown>
</div>
</div>
Expand Down
Loading
Loading