Add provider selector to LiveCompare + fix UI to support all 12 providers
Browse files- web/src/components/tabs/LiveCompare.tsx +142 -109
web/src/components/tabs/LiveCompare.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
-
import { useState } from "react";
|
| 4 |
import {
|
| 5 |
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip,
|
| 6 |
ResponsiveContainer, Legend,
|
|
@@ -15,6 +15,16 @@ interface PipelineResult {
|
|
| 15 |
relations: string[];
|
| 16 |
}
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
interface ComparisonState {
|
| 19 |
loading: boolean;
|
| 20 |
query: string;
|
|
@@ -23,6 +33,9 @@ interface ComparisonState {
|
|
| 23 |
complexity: number;
|
| 24 |
queryType: string;
|
| 25 |
recommended: string;
|
|
|
|
|
|
|
|
|
|
| 26 |
}
|
| 27 |
|
| 28 |
const EXAMPLES = [
|
|
@@ -33,83 +46,114 @@ const EXAMPLES = [
|
|
| 33 |
"What is the capital of the country where the Eiffel Tower is located?",
|
| 34 |
];
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
export function LiveCompare() {
|
|
|
|
| 37 |
const [state, setState] = useState<ComparisonState>({
|
| 38 |
-
loading: false,
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
graphrag: null,
|
| 42 |
-
complexity: 0,
|
| 43 |
-
queryType: "",
|
| 44 |
-
recommended: "",
|
| 45 |
});
|
| 46 |
-
|
| 47 |
const [adaptiveRouting, setAdaptiveRouting] = useState(true);
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
const runComparison = async () => {
|
| 51 |
if (!state.query.trim()) return;
|
| 52 |
-
setState(
|
| 53 |
-
|
| 54 |
try {
|
| 55 |
const res = await fetch("/api/compare", {
|
| 56 |
method: "POST",
|
| 57 |
headers: { "Content-Type": "application/json" },
|
| 58 |
body: JSON.stringify({
|
| 59 |
-
query: state.query,
|
| 60 |
-
|
| 61 |
}),
|
| 62 |
});
|
| 63 |
const data = await res.json();
|
| 64 |
-
setState(
|
| 65 |
-
...s,
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
graphrag: data.graphrag,
|
| 69 |
-
complexity: data.complexity ?? 0,
|
| 70 |
-
queryType: data.queryType ?? "",
|
| 71 |
recommended: data.recommended ?? "",
|
|
|
|
| 72 |
}));
|
| 73 |
} catch {
|
| 74 |
-
|
| 75 |
-
setState((s) => ({
|
| 76 |
-
...s,
|
| 77 |
-
loading: false,
|
| 78 |
-
baseline: {
|
| 79 |
-
answer: "Based on the context, both Scott Derrickson and Ed Wood were American, so yes, they shared the same nationality.",
|
| 80 |
-
tokens: 847,
|
| 81 |
-
latencyMs: 1240,
|
| 82 |
-
costUsd: 0.000203,
|
| 83 |
-
entities: [],
|
| 84 |
-
relations: [],
|
| 85 |
-
},
|
| 86 |
-
graphrag: {
|
| 87 |
-
answer: "Yes. Scott Derrickson (born in Denver, Colorado, USA) and Ed Wood (born in Poughkeepsie, New York, USA) were both American. Following the NATIONALITY relationships in the knowledge graph confirms they share the same nationality.",
|
| 88 |
-
tokens: 2134,
|
| 89 |
-
latencyMs: 3820,
|
| 90 |
-
costUsd: 0.000518,
|
| 91 |
-
entities: ["Scott Derrickson", "Ed Wood", "United States", "Denver", "Poughkeepsie"],
|
| 92 |
-
relations: [
|
| 93 |
-
"Scott Derrickson -[BORN_IN]-> Denver, Colorado",
|
| 94 |
-
"Denver -[LOCATED_IN]-> United States",
|
| 95 |
-
"Ed Wood -[BORN_IN]-> Poughkeepsie, New York",
|
| 96 |
-
"Poughkeepsie -[LOCATED_IN]-> United States",
|
| 97 |
-
],
|
| 98 |
-
},
|
| 99 |
-
complexity: 0.72,
|
| 100 |
-
queryType: "comparison",
|
| 101 |
-
recommended: "graphrag",
|
| 102 |
-
}));
|
| 103 |
}
|
| 104 |
};
|
| 105 |
|
| 106 |
const chartData = state.baseline && state.graphrag ? [
|
| 107 |
{ name: "Tokens", Baseline: state.baseline.tokens, GraphRAG: state.graphrag.tokens },
|
| 108 |
-
{ name: "Latency (ms)", Baseline: state.baseline.latencyMs, GraphRAG: state.graphrag.latencyMs },
|
| 109 |
] : [];
|
| 110 |
|
| 111 |
return (
|
| 112 |
<div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
{/* Query Input */}
|
| 114 |
<div className="card mb-6">
|
| 115 |
<div className="display-sm mb-4">Ask a question</div>
|
|
@@ -119,61 +163,59 @@ export function LiveCompare() {
|
|
| 119 |
className="input textarea"
|
| 120 |
placeholder="e.g., Were Scott Derrickson and Ed Wood of the same nationality?"
|
| 121 |
value={state.query}
|
| 122 |
-
onChange={
|
| 123 |
-
onKeyDown={
|
| 124 |
rows={3}
|
| 125 |
/>
|
| 126 |
</div>
|
| 127 |
<div className="flex flex-col gap-3 lg:w-48">
|
| 128 |
<label className="flex items-center gap-2 cursor-pointer caption">
|
| 129 |
-
<input
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
onChange={(e) => setAdaptiveRouting(e.target.checked)}
|
| 133 |
-
className="w-4 h-4 accent-[#FF6B00]"
|
| 134 |
-
/>
|
| 135 |
🧠 Adaptive Routing
|
| 136 |
</label>
|
| 137 |
-
<button
|
| 138 |
-
|
| 139 |
-
onClick={runComparison}
|
| 140 |
-
disabled={state.loading || !state.query.trim()}
|
| 141 |
-
>
|
| 142 |
{state.loading ? (
|
| 143 |
<span className="flex items-center gap-2">
|
| 144 |
<span className="animate-spin inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full" />
|
| 145 |
Running…
|
| 146 |
</span>
|
| 147 |
-
) :
|
| 148 |
-
"▶ Compare"
|
| 149 |
-
)}
|
| 150 |
</button>
|
| 151 |
</div>
|
| 152 |
</div>
|
| 153 |
|
| 154 |
-
{/*
|
| 155 |
<div className="flex flex-wrap gap-2 mt-4">
|
| 156 |
<span className="caption">Try:</span>
|
| 157 |
{EXAMPLES.slice(0, 3).map((q, i) => (
|
| 158 |
-
<button
|
| 159 |
-
|
| 160 |
-
className="badge-outline cursor-pointer hover:bg-surface-soft transition-colors"
|
| 161 |
-
style={{ fontSize: "0.75rem" }}
|
| 162 |
-
onClick={() => setState((s) => ({ ...s, query: q }))}
|
| 163 |
-
>
|
| 164 |
{q.slice(0, 50)}…
|
| 165 |
</button>
|
| 166 |
))}
|
| 167 |
</div>
|
| 168 |
</div>
|
| 169 |
|
| 170 |
-
{/*
|
| 171 |
-
{state.
|
| 172 |
-
<div className="card-cream mb-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
<span className="badge-orange">🧠 Adaptive Router</span>
|
| 174 |
<span className="body-sm">
|
| 175 |
-
Complexity: <strong>{state.complexity.toFixed(2)}</strong> · Type:{" "}
|
| 176 |
-
<strong>{state.queryType}</strong> · Recommended:{" "}
|
| 177 |
<strong style={{ color: state.recommended === "graphrag" ? "#FF6B00" : "#0072CE" }}>
|
| 178 |
{state.recommended === "graphrag" ? "GraphRAG" : "Baseline RAG"}
|
| 179 |
</strong>
|
|
@@ -185,16 +227,14 @@ export function LiveCompare() {
|
|
| 185 |
{state.baseline && state.graphrag && (
|
| 186 |
<>
|
| 187 |
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
| 188 |
-
{/* Baseline
|
| 189 |
<div className="card pipeline-baseline">
|
| 190 |
<div className="flex items-center gap-2 mb-4">
|
| 191 |
<div className="w-3 h-3 rounded-full" style={{ background: "#0072CE" }} />
|
| 192 |
<span className="title-md">Baseline RAG</span>
|
| 193 |
<span className="badge-blue ml-auto">Pipeline A</span>
|
| 194 |
</div>
|
| 195 |
-
<p className="body-md mb-4" style={{ minHeight: "80px" }}>
|
| 196 |
-
{state.baseline.answer}
|
| 197 |
-
</p>
|
| 198 |
<div className="grid grid-cols-3 gap-4 pt-4" style={{ borderTop: "1px solid var(--color-hairline)" }}>
|
| 199 |
<div>
|
| 200 |
<div className="metric-value-sm" style={{ color: "#0072CE", fontFamily: "var(--font-mono)" }}>
|
|
@@ -217,16 +257,14 @@ export function LiveCompare() {
|
|
| 217 |
</div>
|
| 218 |
</div>
|
| 219 |
|
| 220 |
-
{/* GraphRAG
|
| 221 |
<div className="card pipeline-graphrag">
|
| 222 |
<div className="flex items-center gap-2 mb-4">
|
| 223 |
<div className="w-3 h-3 rounded-full" style={{ background: "#FF6B00" }} />
|
| 224 |
<span className="title-md">GraphRAG</span>
|
| 225 |
<span className="badge-orange ml-auto">Pipeline B</span>
|
| 226 |
</div>
|
| 227 |
-
<p className="body-md mb-4" style={{ minHeight: "80px" }}>
|
| 228 |
-
{state.graphrag.answer}
|
| 229 |
-
</p>
|
| 230 |
<div className="grid grid-cols-3 gap-4 pt-4" style={{ borderTop: "1px solid var(--color-hairline)" }}>
|
| 231 |
<div>
|
| 232 |
<div className="metric-value-sm" style={{ color: "#FF6B00", fontFamily: "var(--font-mono)" }}>
|
|
@@ -272,28 +310,23 @@ export function LiveCompare() {
|
|
| 272 |
</div>
|
| 273 |
</div>
|
| 274 |
|
| 275 |
-
{/*
|
| 276 |
-
|
| 277 |
-
<div className="
|
| 278 |
-
|
| 279 |
-
<
|
| 280 |
-
<
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
contentStyle={{
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
<Bar dataKey="Baseline" fill="#0072CE" radius={[4, 4, 0, 0]} />
|
| 293 |
-
<Bar dataKey="GraphRAG" fill="#FF6B00" radius={[4, 4, 0, 0]} />
|
| 294 |
-
</BarChart>
|
| 295 |
-
</ResponsiveContainer>
|
| 296 |
-
</div>
|
| 297 |
</>
|
| 298 |
)}
|
| 299 |
</div>
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
import { useState, useEffect } from "react";
|
| 4 |
import {
|
| 5 |
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip,
|
| 6 |
ResponsiveContainer, Legend,
|
|
|
|
| 15 |
relations: string[];
|
| 16 |
}
|
| 17 |
|
| 18 |
+
interface ProviderInfo {
|
| 19 |
+
id: string;
|
| 20 |
+
name: string;
|
| 21 |
+
isLocal: boolean;
|
| 22 |
+
hasApiKey: boolean;
|
| 23 |
+
defaultModel: string;
|
| 24 |
+
models: { id: string; name: string; speed: string; quality: string }[];
|
| 25 |
+
notes?: string;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
interface ComparisonState {
|
| 29 |
loading: boolean;
|
| 30 |
query: string;
|
|
|
|
| 33 |
complexity: number;
|
| 34 |
queryType: string;
|
| 35 |
recommended: string;
|
| 36 |
+
provider: string;
|
| 37 |
+
model: string;
|
| 38 |
+
demoMode: boolean;
|
| 39 |
}
|
| 40 |
|
| 41 |
const EXAMPLES = [
|
|
|
|
| 46 |
"What is the capital of the country where the Eiffel Tower is located?",
|
| 47 |
];
|
| 48 |
|
| 49 |
+
const FALLBACK_PROVIDERS: ProviderInfo[] = [
|
| 50 |
+
{ id: "anthropic", name: "Anthropic Claude", isLocal: false, hasApiKey: false, defaultModel: "claude-sonnet-4-20250514", models: [{ id: "claude-sonnet-4-20250514", name: "Claude Sonnet 4", speed: "medium", quality: "high" }] },
|
| 51 |
+
{ id: "openai", name: "OpenAI", isLocal: false, hasApiKey: false, defaultModel: "gpt-4o-mini", models: [{ id: "gpt-4o-mini", name: "GPT-4o Mini", speed: "fast", quality: "medium" }] },
|
| 52 |
+
{ id: "gemini", name: "Google Gemini", isLocal: false, hasApiKey: false, defaultModel: "gemini-2.0-flash", models: [{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash", speed: "fast", quality: "medium" }] },
|
| 53 |
+
{ id: "ollama", name: "Ollama (Local)", isLocal: true, hasApiKey: true, defaultModel: "llama3.2", models: [{ id: "llama3.2", name: "Llama 3.2 3B", speed: "fast", quality: "medium" }] },
|
| 54 |
+
{ id: "groq", name: "Groq", isLocal: false, hasApiKey: false, defaultModel: "llama-3.3-70b-versatile", models: [{ id: "llama-3.3-70b-versatile", name: "Llama 3.3 70B", speed: "fast", quality: "high" }] },
|
| 55 |
+
{ id: "mistral", name: "Mistral AI", isLocal: false, hasApiKey: false, defaultModel: "mistral-large-latest", models: [{ id: "mistral-large-latest", name: "Mistral Large", speed: "medium", quality: "high" }] },
|
| 56 |
+
{ id: "deepseek", name: "DeepSeek", isLocal: false, hasApiKey: false, defaultModel: "deepseek-chat", models: [{ id: "deepseek-chat", name: "DeepSeek V3", speed: "fast", quality: "high" }] },
|
| 57 |
+
{ id: "openrouter", name: "OpenRouter", isLocal: false, hasApiKey: false, defaultModel: "meta-llama/llama-3.3-70b-instruct", models: [{ id: "meta-llama/llama-3.3-70b-instruct", name: "Llama 3.3 70B", speed: "medium", quality: "high" }] },
|
| 58 |
+
{ id: "cohere", name: "Cohere", isLocal: false, hasApiKey: false, defaultModel: "command-r-plus", models: [{ id: "command-r-plus", name: "Command R+", speed: "medium", quality: "high" }] },
|
| 59 |
+
{ id: "xai", name: "xAI Grok", isLocal: false, hasApiKey: false, defaultModel: "grok-3-mini", models: [{ id: "grok-3-mini", name: "Grok 3 Mini", speed: "fast", quality: "medium" }] },
|
| 60 |
+
{ id: "together", name: "Together AI", isLocal: false, hasApiKey: false, defaultModel: "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo", models: [{ id: "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo", name: "Llama 3.1 70B", speed: "fast", quality: "high" }] },
|
| 61 |
+
{ id: "huggingface", name: "HuggingFace", isLocal: false, hasApiKey: false, defaultModel: "meta-llama/Llama-3.3-70B-Instruct", models: [{ id: "meta-llama/Llama-3.3-70B-Instruct", name: "Llama 3.3 70B", speed: "medium", quality: "high" }] },
|
| 62 |
+
];
|
| 63 |
+
|
| 64 |
export function LiveCompare() {
|
| 65 |
+
const [providers, setProviders] = useState<ProviderInfo[]>(FALLBACK_PROVIDERS);
|
| 66 |
const [state, setState] = useState<ComparisonState>({
|
| 67 |
+
loading: false, query: "", baseline: null, graphrag: null,
|
| 68 |
+
complexity: 0, queryType: "", recommended: "",
|
| 69 |
+
provider: "anthropic", model: "", demoMode: false,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
});
|
|
|
|
| 71 |
const [adaptiveRouting, setAdaptiveRouting] = useState(true);
|
| 72 |
+
|
| 73 |
+
// Fetch available providers on mount
|
| 74 |
+
useEffect(() => {
|
| 75 |
+
fetch("/api/providers").then(r => r.json()).then(d => {
|
| 76 |
+
if (d.providers) setProviders(d.providers);
|
| 77 |
+
}).catch(() => {});
|
| 78 |
+
}, []);
|
| 79 |
+
|
| 80 |
+
const selectedProvider = providers.find(p => p.id === state.provider) || providers[0];
|
| 81 |
+
const selectedModel = state.model || selectedProvider?.defaultModel || "";
|
| 82 |
|
| 83 |
const runComparison = async () => {
|
| 84 |
if (!state.query.trim()) return;
|
| 85 |
+
setState(s => ({ ...s, loading: true }));
|
|
|
|
| 86 |
try {
|
| 87 |
const res = await fetch("/api/compare", {
|
| 88 |
method: "POST",
|
| 89 |
headers: { "Content-Type": "application/json" },
|
| 90 |
body: JSON.stringify({
|
| 91 |
+
query: state.query, adaptiveRouting,
|
| 92 |
+
provider: state.provider, model: selectedModel,
|
| 93 |
}),
|
| 94 |
});
|
| 95 |
const data = await res.json();
|
| 96 |
+
setState(s => ({
|
| 97 |
+
...s, loading: false,
|
| 98 |
+
baseline: data.baseline, graphrag: data.graphrag,
|
| 99 |
+
complexity: data.complexity ?? 0, queryType: data.queryType ?? "",
|
|
|
|
|
|
|
|
|
|
| 100 |
recommended: data.recommended ?? "",
|
| 101 |
+
demoMode: data.demoMode ?? false,
|
| 102 |
}));
|
| 103 |
} catch {
|
| 104 |
+
setState(s => ({ ...s, loading: false }));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
}
|
| 106 |
};
|
| 107 |
|
| 108 |
const chartData = state.baseline && state.graphrag ? [
|
| 109 |
{ name: "Tokens", Baseline: state.baseline.tokens, GraphRAG: state.graphrag.tokens },
|
| 110 |
+
{ name: "Latency (ms)", Baseline: Math.round(state.baseline.latencyMs), GraphRAG: Math.round(state.graphrag.latencyMs) },
|
| 111 |
] : [];
|
| 112 |
|
| 113 |
return (
|
| 114 |
<div>
|
| 115 |
+
{/* Provider + Model Selector */}
|
| 116 |
+
<div className="card mb-4" style={{ padding: "16px" }}>
|
| 117 |
+
<div className="flex flex-wrap items-center gap-4">
|
| 118 |
+
<div className="flex-1 min-w-[200px]">
|
| 119 |
+
<label className="caption mb-1 block">LLM Provider</label>
|
| 120 |
+
<select
|
| 121 |
+
className="input"
|
| 122 |
+
value={state.provider}
|
| 123 |
+
onChange={e => setState(s => ({ ...s, provider: e.target.value, model: "" }))}
|
| 124 |
+
>
|
| 125 |
+
{providers.map(p => (
|
| 126 |
+
<option key={p.id} value={p.id}>
|
| 127 |
+
{p.isLocal ? "🦙 " : ""}{p.name} {p.hasApiKey ? "✅" : "🔑"}
|
| 128 |
+
</option>
|
| 129 |
+
))}
|
| 130 |
+
</select>
|
| 131 |
+
</div>
|
| 132 |
+
<div className="flex-1 min-w-[200px]">
|
| 133 |
+
<label className="caption mb-1 block">Model</label>
|
| 134 |
+
<select
|
| 135 |
+
className="input"
|
| 136 |
+
value={selectedModel}
|
| 137 |
+
onChange={e => setState(s => ({ ...s, model: e.target.value }))}
|
| 138 |
+
>
|
| 139 |
+
{selectedProvider?.models.map(m => (
|
| 140 |
+
<option key={m.id} value={m.id}>
|
| 141 |
+
{m.name} ({m.speed}/{m.quality})
|
| 142 |
+
</option>
|
| 143 |
+
))}
|
| 144 |
+
</select>
|
| 145 |
+
</div>
|
| 146 |
+
<div className="flex items-center gap-2 pt-5">
|
| 147 |
+
{selectedProvider?.isLocal && (
|
| 148 |
+
<span className="badge-success" style={{ fontSize: "0.6875rem" }}>Free / Local</span>
|
| 149 |
+
)}
|
| 150 |
+
{!selectedProvider?.hasApiKey && !selectedProvider?.isLocal && (
|
| 151 |
+
<span className="badge-outline" style={{ fontSize: "0.6875rem" }}>Demo Mode</span>
|
| 152 |
+
)}
|
| 153 |
+
</div>
|
| 154 |
+
</div>
|
| 155 |
+
</div>
|
| 156 |
+
|
| 157 |
{/* Query Input */}
|
| 158 |
<div className="card mb-6">
|
| 159 |
<div className="display-sm mb-4">Ask a question</div>
|
|
|
|
| 163 |
className="input textarea"
|
| 164 |
placeholder="e.g., Were Scott Derrickson and Ed Wood of the same nationality?"
|
| 165 |
value={state.query}
|
| 166 |
+
onChange={e => setState(s => ({ ...s, query: e.target.value }))}
|
| 167 |
+
onKeyDown={e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); runComparison(); } }}
|
| 168 |
rows={3}
|
| 169 |
/>
|
| 170 |
</div>
|
| 171 |
<div className="flex flex-col gap-3 lg:w-48">
|
| 172 |
<label className="flex items-center gap-2 cursor-pointer caption">
|
| 173 |
+
<input type="checkbox" checked={adaptiveRouting}
|
| 174 |
+
onChange={e => setAdaptiveRouting(e.target.checked)}
|
| 175 |
+
className="w-4 h-4 accent-[#FF6B00]" />
|
|
|
|
|
|
|
|
|
|
| 176 |
🧠 Adaptive Routing
|
| 177 |
</label>
|
| 178 |
+
<button className="btn btn-primary btn-lg w-full"
|
| 179 |
+
onClick={runComparison} disabled={state.loading || !state.query.trim()}>
|
|
|
|
|
|
|
|
|
|
| 180 |
{state.loading ? (
|
| 181 |
<span className="flex items-center gap-2">
|
| 182 |
<span className="animate-spin inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full" />
|
| 183 |
Running…
|
| 184 |
</span>
|
| 185 |
+
) : "▶ Compare"}
|
|
|
|
|
|
|
| 186 |
</button>
|
| 187 |
</div>
|
| 188 |
</div>
|
| 189 |
|
| 190 |
+
{/* Examples */}
|
| 191 |
<div className="flex flex-wrap gap-2 mt-4">
|
| 192 |
<span className="caption">Try:</span>
|
| 193 |
{EXAMPLES.slice(0, 3).map((q, i) => (
|
| 194 |
+
<button key={i} className="badge-outline cursor-pointer hover:bg-surface-soft transition-colors"
|
| 195 |
+
style={{ fontSize: "0.75rem" }} onClick={() => setState(s => ({ ...s, query: q }))}>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
{q.slice(0, 50)}…
|
| 197 |
</button>
|
| 198 |
))}
|
| 199 |
</div>
|
| 200 |
</div>
|
| 201 |
|
| 202 |
+
{/* Demo Mode Warning */}
|
| 203 |
+
{state.demoMode && state.baseline && (
|
| 204 |
+
<div className="card-cream mb-4 flex items-center gap-3" style={{ padding: "12px 20px" }}>
|
| 205 |
+
<span style={{ fontSize: "1.2rem" }}>ℹ️</span>
|
| 206 |
+
<span className="body-sm">
|
| 207 |
+
<strong>Demo Mode</strong> — No API key for {selectedProvider?.name}. Showing sample data.
|
| 208 |
+
Set <code>{selectedProvider?.id === "ollama" ? "ollama pull llama3.2" : `${providers.find(p=>p.id===state.provider)?.id?.toUpperCase()}_API_KEY`}</code> for live results.
|
| 209 |
+
</span>
|
| 210 |
+
</div>
|
| 211 |
+
)}
|
| 212 |
+
|
| 213 |
+
{/* Adaptive Routing */}
|
| 214 |
+
{state.recommended && !state.demoMode && (
|
| 215 |
+
<div className="card-cream mb-6 flex items-center gap-4 flex-wrap" style={{ padding: "12px 20px" }}>
|
| 216 |
<span className="badge-orange">🧠 Adaptive Router</span>
|
| 217 |
<span className="body-sm">
|
| 218 |
+
Complexity: <strong>{state.complexity.toFixed(2)}</strong> · Type: <strong>{state.queryType}</strong> · Recommended:{" "}
|
|
|
|
| 219 |
<strong style={{ color: state.recommended === "graphrag" ? "#FF6B00" : "#0072CE" }}>
|
| 220 |
{state.recommended === "graphrag" ? "GraphRAG" : "Baseline RAG"}
|
| 221 |
</strong>
|
|
|
|
| 227 |
{state.baseline && state.graphrag && (
|
| 228 |
<>
|
| 229 |
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
| 230 |
+
{/* Baseline */}
|
| 231 |
<div className="card pipeline-baseline">
|
| 232 |
<div className="flex items-center gap-2 mb-4">
|
| 233 |
<div className="w-3 h-3 rounded-full" style={{ background: "#0072CE" }} />
|
| 234 |
<span className="title-md">Baseline RAG</span>
|
| 235 |
<span className="badge-blue ml-auto">Pipeline A</span>
|
| 236 |
</div>
|
| 237 |
+
<p className="body-md mb-4" style={{ minHeight: "80px" }}>{state.baseline.answer}</p>
|
|
|
|
|
|
|
| 238 |
<div className="grid grid-cols-3 gap-4 pt-4" style={{ borderTop: "1px solid var(--color-hairline)" }}>
|
| 239 |
<div>
|
| 240 |
<div className="metric-value-sm" style={{ color: "#0072CE", fontFamily: "var(--font-mono)" }}>
|
|
|
|
| 257 |
</div>
|
| 258 |
</div>
|
| 259 |
|
| 260 |
+
{/* GraphRAG */}
|
| 261 |
<div className="card pipeline-graphrag">
|
| 262 |
<div className="flex items-center gap-2 mb-4">
|
| 263 |
<div className="w-3 h-3 rounded-full" style={{ background: "#FF6B00" }} />
|
| 264 |
<span className="title-md">GraphRAG</span>
|
| 265 |
<span className="badge-orange ml-auto">Pipeline B</span>
|
| 266 |
</div>
|
| 267 |
+
<p className="body-md mb-4" style={{ minHeight: "80px" }}>{state.graphrag.answer}</p>
|
|
|
|
|
|
|
| 268 |
<div className="grid grid-cols-3 gap-4 pt-4" style={{ borderTop: "1px solid var(--color-hairline)" }}>
|
| 269 |
<div>
|
| 270 |
<div className="metric-value-sm" style={{ color: "#FF6B00", fontFamily: "var(--font-mono)" }}>
|
|
|
|
| 310 |
</div>
|
| 311 |
</div>
|
| 312 |
|
| 313 |
+
{/* Chart */}
|
| 314 |
+
{chartData.length > 0 && (
|
| 315 |
+
<div className="card">
|
| 316 |
+
<div className="title-md mb-4">Metrics Comparison</div>
|
| 317 |
+
<ResponsiveContainer width="100%" height={280}>
|
| 318 |
+
<BarChart data={chartData} margin={{ top: 20, right: 30, left: 0, bottom: 0 }}>
|
| 319 |
+
<CartesianGrid strokeDasharray="3 3" stroke="#002B49" strokeOpacity={0.08} />
|
| 320 |
+
<XAxis dataKey="name" tick={{ fill: "#6c6a64", fontSize: 13 }} />
|
| 321 |
+
<YAxis tick={{ fill: "#6c6a64", fontSize: 12 }} />
|
| 322 |
+
<Tooltip contentStyle={{ background: "#faf9f5", border: "1px solid #e6dfd8", borderRadius: "8px" }} />
|
| 323 |
+
<Legend />
|
| 324 |
+
<Bar dataKey="Baseline" fill="#0072CE" radius={[4, 4, 0, 0]} />
|
| 325 |
+
<Bar dataKey="GraphRAG" fill="#FF6B00" radius={[4, 4, 0, 0]} />
|
| 326 |
+
</BarChart>
|
| 327 |
+
</ResponsiveContainer>
|
| 328 |
+
</div>
|
| 329 |
+
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
</>
|
| 331 |
)}
|
| 332 |
</div>
|