Spaces:
Sleeping
Sleeping
fix: light mode contrast, collapse queries on demo completion
Browse files- frontend/src/components/DemoMode.tsx +89 -47
- frontend/src/index.css +15 -0
frontend/src/components/DemoMode.tsx
CHANGED
|
@@ -176,9 +176,9 @@ function GithubDiff({ fromIdx, toIdx }: { fromIdx: number; toIdx: number }) {
|
|
| 176 |
const removed = lines.filter((l) => l.type === 'remove').length
|
| 177 |
|
| 178 |
return (
|
| 179 |
-
<div className="rounded-xl border
|
| 180 |
{/* Header */}
|
| 181 |
-
<div className="flex items-center gap-2 px-3 py-2
|
| 182 |
<GitCommitHorizontal size={11} className="text-gray-500" />
|
| 183 |
<span className="text-gray-400 font-semibold">system_prompt.txt</span>
|
| 184 |
<span className="ml-auto flex items-center gap-2 text-[10px]">
|
|
@@ -187,7 +187,7 @@ function GithubDiff({ fromIdx, toIdx }: { fromIdx: number; toIdx: number }) {
|
|
| 187 |
</span>
|
| 188 |
</div>
|
| 189 |
{/* Diff lines */}
|
| 190 |
-
<div className="max-h-52 overflow-y-auto">
|
| 191 |
{lines.map((line, i) => {
|
| 192 |
const bg = line.type === 'add' ? 'bg-green-500/10' : line.type === 'remove' ? 'bg-red-500/10' : ''
|
| 193 |
const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' '
|
|
@@ -270,14 +270,14 @@ function RightPanel({ gen, score, rewardPoints, latestDiff }: RightPanelProps) {
|
|
| 270 |
</div>
|
| 271 |
|
| 272 |
{/* Score bar */}
|
| 273 |
-
<div className="
|
| 274 |
<div className="flex items-end gap-2 mb-2">
|
| 275 |
<span className="text-2xl font-bold text-green-400 tabular-nums leading-none">
|
| 276 |
{(score * 100).toFixed(0)}%
|
| 277 |
</span>
|
| 278 |
<span className="text-[10px] text-gray-600 mb-0.5">benchmark score</span>
|
| 279 |
</div>
|
| 280 |
-
<div className="h-1.5
|
| 281 |
<motion.div
|
| 282 |
className="h-full rounded-full"
|
| 283 |
style={{ background: 'linear-gradient(90deg,#8b5cf6,#22c55e)' }}
|
|
@@ -290,9 +290,9 @@ function RightPanel({ gen, score, rewardPoints, latestDiff }: RightPanelProps) {
|
|
| 290 |
{SCORES.slice(0, gen + 1).map((s, i) => (
|
| 291 |
<div key={i} className="flex items-center gap-2 text-[10px]">
|
| 292 |
<span className="text-gray-600 w-10 shrink-0">Gen {i}</span>
|
| 293 |
-
<div className="flex-1 h-1
|
| 294 |
<div className="h-full rounded-full transition-all duration-700"
|
| 295 |
-
style={{ width: `${s * 100}%`, background: i === gen ? 'linear-gradient(90deg,#8b5cf6,#22c55e)' : '
|
| 296 |
</div>
|
| 297 |
<span className={`font-bold tabular-nums ${i === gen ? 'text-green-400' : 'text-gray-600'}`}>
|
| 298 |
{(s * 100).toFixed(0)}%
|
|
@@ -379,13 +379,13 @@ function Bubble({ b }: { b: BubbleData }) {
|
|
| 379 |
)
|
| 380 |
|
| 381 |
if (b.type === 'sql_stream') return (
|
| 382 |
-
<div className="border
|
| 383 |
-
<div className="px-3 py-1.5
|
| 384 |
<span className="text-[10px] font-semibold text-gray-500 uppercase tracking-wider">SQL</span>
|
| 385 |
<span className="text-[10px] text-gray-600">attempt {b.attempt}</span>
|
| 386 |
<Loader2 size={9} className="animate-spin text-violet-400 ml-auto" />
|
| 387 |
</div>
|
| 388 |
-
<pre className="px-3 py-2 text-xs leading-relaxed whitespace-pre-wrap" style={{
|
| 389 |
<HighlightSQL sql={b.sql} />
|
| 390 |
<span className="inline-block w-0.5 h-[1em] bg-violet-400 animate-pulse align-bottom ml-0.5" />
|
| 391 |
</pre>
|
|
@@ -394,12 +394,12 @@ function Bubble({ b }: { b: BubbleData }) {
|
|
| 394 |
|
| 395 |
if (b.type === 'sql_err') return (
|
| 396 |
<div className="flex flex-col gap-1.5">
|
| 397 |
-
<div className="border
|
| 398 |
-
<div className="px-3 py-1.5
|
| 399 |
<span className="text-[10px] font-semibold text-gray-500 uppercase tracking-wider">SQL</span>
|
| 400 |
<span className="text-[10px] text-gray-600">attempt {b.attempt}</span>
|
| 401 |
</div>
|
| 402 |
-
<pre className="px-3 py-2 text-xs leading-relaxed whitespace-pre-wrap" style={{
|
| 403 |
<HighlightSQL sql={b.sql} />
|
| 404 |
</pre>
|
| 405 |
</div>
|
|
@@ -417,15 +417,15 @@ function Bubble({ b }: { b: BubbleData }) {
|
|
| 417 |
|
| 418 |
if (b.type === 'sql_ok') return (
|
| 419 |
<div className="flex flex-col gap-1.5">
|
| 420 |
-
<div className="border
|
| 421 |
-
<div className="px-3 py-1.5
|
| 422 |
<span className="text-[10px] font-semibold text-gray-500 uppercase tracking-wider">SQL</span>
|
| 423 |
{b.firstTry && (
|
| 424 |
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-green-500/15 border border-green-500/25 text-green-400 font-semibold">first try ✓</span>
|
| 425 |
)}
|
| 426 |
<span className="ml-auto text-[11px] font-bold text-green-400">+{b.reward.toFixed(2)}</span>
|
| 427 |
</div>
|
| 428 |
-
<pre className="px-3 py-2 text-xs leading-relaxed whitespace-pre-wrap" style={{
|
| 429 |
<HighlightSQL sql={b.sql} />
|
| 430 |
</pre>
|
| 431 |
</div>
|
|
@@ -434,10 +434,10 @@ function Bubble({ b }: { b: BubbleData }) {
|
|
| 434 |
<span className="text-green-400 font-semibold">Success</span>
|
| 435 |
<span className="text-gray-600">· {b.rows.length}+ rows returned</span>
|
| 436 |
</div>
|
| 437 |
-
<div className="rounded-xl border
|
| 438 |
<table className="w-full">
|
| 439 |
<thead>
|
| 440 |
-
<tr
|
| 441 |
{Object.keys(b.rows[0] ?? {}).map((k) => (
|
| 442 |
<th key={k} className="px-2 py-1.5 text-left font-semibold text-gray-500 whitespace-nowrap">{k}</th>
|
| 443 |
))}
|
|
@@ -445,9 +445,9 @@ function Bubble({ b }: { b: BubbleData }) {
|
|
| 445 |
</thead>
|
| 446 |
<tbody>
|
| 447 |
{b.rows.map((row, i) => (
|
| 448 |
-
<tr key={i}
|
| 449 |
{Object.values(row).map((v, j) => (
|
| 450 |
-
<td key={j} className="px-2 py-1 text-
|
| 451 |
))}
|
| 452 |
</tr>
|
| 453 |
))}
|
|
@@ -483,7 +483,7 @@ function Bubble({ b }: { b: BubbleData }) {
|
|
| 483 |
<GitCommitHorizontal size={10} />
|
| 484 |
Prompt diff (see sidebar for full view)
|
| 485 |
</div>
|
| 486 |
-
<div className="rounded-lg border
|
| 487 |
{diffPrompts(b.fromGen, b.toGen).filter((l) => l.type !== 'same').map((line, i) => (
|
| 488 |
<div key={i} className={`flex gap-2 px-2 py-0.5 ${line.type === 'add' ? 'bg-green-500/10' : 'bg-red-500/10'}`}>
|
| 489 |
<span className={`shrink-0 ${line.type === 'add' ? 'text-green-400' : 'text-red-400'}`}>{line.type === 'add' ? '+' : '-'}</span>
|
|
@@ -496,22 +496,25 @@ function Bubble({ b }: { b: BubbleData }) {
|
|
| 496 |
)
|
| 497 |
|
| 498 |
if (b.type === 'group') return (
|
| 499 |
-
<div className="border
|
| 500 |
<button
|
| 501 |
onClick={() => setOpen((v) => !v)}
|
| 502 |
-
className="w-full flex items-center gap-2 px-3 py-2.5
|
|
|
|
|
|
|
|
|
|
| 503 |
>
|
| 504 |
{b.success
|
| 505 |
? <CheckCircle2 size={12} className="text-green-400 shrink-0" />
|
| 506 |
: <XCircle size={12} className="text-red-400 shrink-0" />}
|
| 507 |
-
<span className="text-xs text-
|
| 508 |
<span className="text-[10px] text-gray-600">{b.attempts} attempt{b.attempts !== 1 ? 's' : ''}</span>
|
| 509 |
{open ? <ChevronUp size={11} className="text-gray-600 shrink-0" /> : <ChevronDown size={11} className="text-gray-600 shrink-0" />}
|
| 510 |
</button>
|
| 511 |
<AnimatePresence>
|
| 512 |
{open && (
|
| 513 |
<motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ duration: 0.15 }} className="overflow-hidden">
|
| 514 |
-
<div className="p-3 flex flex-col gap-2.5 border-t border-
|
| 515 |
{b.children.map((c) => <Bubble key={c.id} b={c} />)}
|
| 516 |
</div>
|
| 517 |
</motion.div>
|
|
@@ -535,6 +538,9 @@ export function DemoMode({ onClose }: { onClose: () => void }) {
|
|
| 535 |
const stepRef = useRef(0)
|
| 536 |
const cancel = useRef(false)
|
| 537 |
const bottomRef = useRef<HTMLDivElement>(null)
|
|
|
|
|
|
|
|
|
|
| 538 |
|
| 539 |
const scroll = useCallback(() => {
|
| 540 |
setTimeout(() => bottomRef.current?.scrollIntoView({ behavior: 'smooth' }), 60)
|
|
@@ -603,17 +609,6 @@ export function DemoMode({ onClose }: { onClose: () => void }) {
|
|
| 603 |
return children
|
| 604 |
}, [push, scroll, typeUser, streamSQL, addReward])
|
| 605 |
|
| 606 |
-
const collapseQuery = useCallback((def: QueryDef, children: BubbleData[]) => {
|
| 607 |
-
const lastAtt = def.attempts[def.attempts.length - 1]
|
| 608 |
-
setBubbles((prev) => {
|
| 609 |
-
const userIdx = [...prev].reverse().findIndex((b) => b.type === 'user' && (b as UserBubble).text === def.question)
|
| 610 |
-
if (userIdx < 0) return prev
|
| 611 |
-
const fromIdx = prev.length - 1 - userIdx
|
| 612 |
-
const group: GroupBubble = { id: uid(), type: 'group', question: def.question, success: !lastAtt.error, attempts: def.attempts.length, children }
|
| 613 |
-
return [...prev.slice(0, fromIdx), group]
|
| 614 |
-
})
|
| 615 |
-
}, [])
|
| 616 |
-
|
| 617 |
const playGepa = useCallback(async (fromGen: number, toGen: number) => {
|
| 618 |
const steps = ['Analyzing failure patterns…', 'Identifying missing rules from errors…', 'Rewriting system prompt…', 'Benchmarking candidate prompt…']
|
| 619 |
for (const label of steps) {
|
|
@@ -637,12 +632,16 @@ export function DemoMode({ onClose }: { onClose: () => void }) {
|
|
| 637 |
setGen(toGen)
|
| 638 |
setLatestDiff({ from: fromGen, to: toGen })
|
| 639 |
|
| 640 |
-
|
|
|
|
|
|
|
| 641 |
scroll(); await sleep(1000)
|
| 642 |
}, [push, scroll])
|
| 643 |
|
| 644 |
const autoPlay = useCallback(async () => {
|
| 645 |
cancel.current = false
|
|
|
|
|
|
|
| 646 |
setBubbles([]); setGen(0); setScore(SCORES[0]); setRewardPoints([]); setLatestDiff(null)
|
| 647 |
stepRef.current = 0; setAppState('running')
|
| 648 |
await sleep(300)
|
|
@@ -650,28 +649,71 @@ export function DemoMode({ onClose }: { onClose: () => void }) {
|
|
| 650 |
for (const id of ROUND_1) {
|
| 651 |
if (cancel.current) break
|
| 652 |
const children = await playQuery(QUERIES[id])
|
| 653 |
-
if (cancel.current)
|
| 654 |
-
|
|
|
|
|
|
|
| 655 |
}
|
| 656 |
if (!cancel.current) { await playGepa(0, 1); await sleep(500) }
|
| 657 |
|
| 658 |
for (const id of ROUND_2) {
|
| 659 |
if (cancel.current) break
|
| 660 |
const children = await playQuery(QUERIES[id])
|
| 661 |
-
if (cancel.current)
|
| 662 |
-
|
|
|
|
|
|
|
| 663 |
}
|
| 664 |
if (!cancel.current) { await playGepa(1, 2); await sleep(500) }
|
| 665 |
|
| 666 |
for (const id of ROUND_3) {
|
| 667 |
if (cancel.current) break
|
| 668 |
const children = await playQuery(QUERIES[id])
|
| 669 |
-
if (cancel.current)
|
| 670 |
-
|
|
|
|
|
|
|
| 671 |
}
|
| 672 |
|
| 673 |
-
if (!cancel.current)
|
| 674 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 675 |
|
| 676 |
useEffect(() => () => { cancel.current = true }, [])
|
| 677 |
|
|
@@ -682,7 +724,7 @@ export function DemoMode({ onClose }: { onClose: () => void }) {
|
|
| 682 |
style={{ background: 'var(--bg-primary)' }}
|
| 683 |
>
|
| 684 |
{/* Header */}
|
| 685 |
-
<div className="shrink-0 flex items-center justify-between px-4 py-3 border-b
|
| 686 |
<div className="flex items-center gap-3">
|
| 687 |
<div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-violet-500/15 border border-violet-500/25">
|
| 688 |
<Play size={9} className="text-violet-400" fill="currentColor" />
|
|
@@ -756,7 +798,7 @@ export function DemoMode({ onClose }: { onClose: () => void }) {
|
|
| 756 |
</div>
|
| 757 |
|
| 758 |
{/* Right panel */}
|
| 759 |
-
<aside className="hidden lg:flex flex-col w-72 border-l
|
| 760 |
<RightPanel gen={gen} score={score} rewardPoints={rewardPoints} latestDiff={latestDiff} />
|
| 761 |
</aside>
|
| 762 |
</div>
|
|
|
|
| 176 |
const removed = lines.filter((l) => l.type === 'remove').length
|
| 177 |
|
| 178 |
return (
|
| 179 |
+
<div className="rounded-xl border overflow-hidden text-[11px] font-mono" style={{ borderColor: 'var(--border-color)' }}>
|
| 180 |
{/* Header */}
|
| 181 |
+
<div className="flex items-center gap-2 px-3 py-2 border-b" style={{ background: 'var(--bg-tertiary)', borderColor: 'var(--border-color)' }}>
|
| 182 |
<GitCommitHorizontal size={11} className="text-gray-500" />
|
| 183 |
<span className="text-gray-400 font-semibold">system_prompt.txt</span>
|
| 184 |
<span className="ml-auto flex items-center gap-2 text-[10px]">
|
|
|
|
| 187 |
</span>
|
| 188 |
</div>
|
| 189 |
{/* Diff lines */}
|
| 190 |
+
<div className="max-h-52 overflow-y-auto" style={{ background: 'var(--bg-secondary)' }}>
|
| 191 |
{lines.map((line, i) => {
|
| 192 |
const bg = line.type === 'add' ? 'bg-green-500/10' : line.type === 'remove' ? 'bg-red-500/10' : ''
|
| 193 |
const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' '
|
|
|
|
| 270 |
</div>
|
| 271 |
|
| 272 |
{/* Score bar */}
|
| 273 |
+
<div className="border rounded-xl p-3" style={{ background: 'var(--bg-tertiary)', borderColor: 'var(--border-color)' }}>
|
| 274 |
<div className="flex items-end gap-2 mb-2">
|
| 275 |
<span className="text-2xl font-bold text-green-400 tabular-nums leading-none">
|
| 276 |
{(score * 100).toFixed(0)}%
|
| 277 |
</span>
|
| 278 |
<span className="text-[10px] text-gray-600 mb-0.5">benchmark score</span>
|
| 279 |
</div>
|
| 280 |
+
<div className="h-1.5 rounded-full overflow-hidden" style={{ background: 'var(--border-color)' }}>
|
| 281 |
<motion.div
|
| 282 |
className="h-full rounded-full"
|
| 283 |
style={{ background: 'linear-gradient(90deg,#8b5cf6,#22c55e)' }}
|
|
|
|
| 290 |
{SCORES.slice(0, gen + 1).map((s, i) => (
|
| 291 |
<div key={i} className="flex items-center gap-2 text-[10px]">
|
| 292 |
<span className="text-gray-600 w-10 shrink-0">Gen {i}</span>
|
| 293 |
+
<div className="flex-1 h-1 rounded-full overflow-hidden" style={{ background: 'var(--border-color)' }}>
|
| 294 |
<div className="h-full rounded-full transition-all duration-700"
|
| 295 |
+
style={{ width: `${s * 100}%`, background: i === gen ? 'linear-gradient(90deg,#8b5cf6,#22c55e)' : 'var(--text-dim)' }} />
|
| 296 |
</div>
|
| 297 |
<span className={`font-bold tabular-nums ${i === gen ? 'text-green-400' : 'text-gray-600'}`}>
|
| 298 |
{(s * 100).toFixed(0)}%
|
|
|
|
| 379 |
)
|
| 380 |
|
| 381 |
if (b.type === 'sql_stream') return (
|
| 382 |
+
<div className="border rounded-xl overflow-hidden" style={{ borderColor: 'var(--border-color)' }}>
|
| 383 |
+
<div className="px-3 py-1.5 flex items-center gap-2" style={{ background: 'var(--bg-tertiary)' }}>
|
| 384 |
<span className="text-[10px] font-semibold text-gray-500 uppercase tracking-wider">SQL</span>
|
| 385 |
<span className="text-[10px] text-gray-600">attempt {b.attempt}</span>
|
| 386 |
<Loader2 size={9} className="animate-spin text-violet-400 ml-auto" />
|
| 387 |
</div>
|
| 388 |
+
<pre className="px-3 py-2 text-xs leading-relaxed whitespace-pre-wrap" style={{ background: 'rgba(139,92,246,0.05)' }}>
|
| 389 |
<HighlightSQL sql={b.sql} />
|
| 390 |
<span className="inline-block w-0.5 h-[1em] bg-violet-400 animate-pulse align-bottom ml-0.5" />
|
| 391 |
</pre>
|
|
|
|
| 394 |
|
| 395 |
if (b.type === 'sql_err') return (
|
| 396 |
<div className="flex flex-col gap-1.5">
|
| 397 |
+
<div className="border rounded-xl overflow-hidden" style={{ borderColor: 'var(--border-color)' }}>
|
| 398 |
+
<div className="px-3 py-1.5 flex items-center gap-2" style={{ background: 'var(--bg-tertiary)' }}>
|
| 399 |
<span className="text-[10px] font-semibold text-gray-500 uppercase tracking-wider">SQL</span>
|
| 400 |
<span className="text-[10px] text-gray-600">attempt {b.attempt}</span>
|
| 401 |
</div>
|
| 402 |
+
<pre className="px-3 py-2 text-xs leading-relaxed whitespace-pre-wrap" style={{ background: 'rgba(139,92,246,0.05)' }}>
|
| 403 |
<HighlightSQL sql={b.sql} />
|
| 404 |
</pre>
|
| 405 |
</div>
|
|
|
|
| 417 |
|
| 418 |
if (b.type === 'sql_ok') return (
|
| 419 |
<div className="flex flex-col gap-1.5">
|
| 420 |
+
<div className="border rounded-xl overflow-hidden" style={{ borderColor: 'var(--border-color)' }}>
|
| 421 |
+
<div className="px-3 py-1.5 flex items-center gap-2" style={{ background: 'var(--bg-tertiary)' }}>
|
| 422 |
<span className="text-[10px] font-semibold text-gray-500 uppercase tracking-wider">SQL</span>
|
| 423 |
{b.firstTry && (
|
| 424 |
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-green-500/15 border border-green-500/25 text-green-400 font-semibold">first try ✓</span>
|
| 425 |
)}
|
| 426 |
<span className="ml-auto text-[11px] font-bold text-green-400">+{b.reward.toFixed(2)}</span>
|
| 427 |
</div>
|
| 428 |
+
<pre className="px-3 py-2 text-xs leading-relaxed whitespace-pre-wrap" style={{ background: 'rgba(139,92,246,0.05)' }}>
|
| 429 |
<HighlightSQL sql={b.sql} />
|
| 430 |
</pre>
|
| 431 |
</div>
|
|
|
|
| 434 |
<span className="text-green-400 font-semibold">Success</span>
|
| 435 |
<span className="text-gray-600">· {b.rows.length}+ rows returned</span>
|
| 436 |
</div>
|
| 437 |
+
<div className="rounded-xl border overflow-hidden text-[10px]" style={{ borderColor: 'var(--border-color)' }}>
|
| 438 |
<table className="w-full">
|
| 439 |
<thead>
|
| 440 |
+
<tr style={{ background: 'var(--bg-tertiary)' }}>
|
| 441 |
{Object.keys(b.rows[0] ?? {}).map((k) => (
|
| 442 |
<th key={k} className="px-2 py-1.5 text-left font-semibold text-gray-500 whitespace-nowrap">{k}</th>
|
| 443 |
))}
|
|
|
|
| 445 |
</thead>
|
| 446 |
<tbody>
|
| 447 |
{b.rows.map((row, i) => (
|
| 448 |
+
<tr key={i} style={i % 2 === 0 ? { background: 'var(--bg-hover)' } : {}}>
|
| 449 |
{Object.values(row).map((v, j) => (
|
| 450 |
+
<td key={j} className="px-2 py-1 theme-text-secondary whitespace-nowrap">{String(v)}</td>
|
| 451 |
))}
|
| 452 |
</tr>
|
| 453 |
))}
|
|
|
|
| 483 |
<GitCommitHorizontal size={10} />
|
| 484 |
Prompt diff (see sidebar for full view)
|
| 485 |
</div>
|
| 486 |
+
<div className="rounded-lg border overflow-hidden text-[10px] font-mono max-h-24 overflow-y-auto" style={{ borderColor: 'var(--border-color)', background: 'var(--bg-secondary)' }}>
|
| 487 |
{diffPrompts(b.fromGen, b.toGen).filter((l) => l.type !== 'same').map((line, i) => (
|
| 488 |
<div key={i} className={`flex gap-2 px-2 py-0.5 ${line.type === 'add' ? 'bg-green-500/10' : 'bg-red-500/10'}`}>
|
| 489 |
<span className={`shrink-0 ${line.type === 'add' ? 'text-green-400' : 'text-red-400'}`}>{line.type === 'add' ? '+' : '-'}</span>
|
|
|
|
| 496 |
)
|
| 497 |
|
| 498 |
if (b.type === 'group') return (
|
| 499 |
+
<div className="border rounded-2xl overflow-hidden" style={{ borderColor: 'var(--border-color)' }}>
|
| 500 |
<button
|
| 501 |
onClick={() => setOpen((v) => !v)}
|
| 502 |
+
className="w-full flex items-center gap-2 px-3 py-2.5 transition-colors text-left"
|
| 503 |
+
style={{ background: 'var(--bg-tertiary)' }}
|
| 504 |
+
onMouseEnter={(e) => (e.currentTarget.style.background = 'var(--bg-hover-strong)')}
|
| 505 |
+
onMouseLeave={(e) => (e.currentTarget.style.background = 'var(--bg-tertiary)')}
|
| 506 |
>
|
| 507 |
{b.success
|
| 508 |
? <CheckCircle2 size={12} className="text-green-400 shrink-0" />
|
| 509 |
: <XCircle size={12} className="text-red-400 shrink-0" />}
|
| 510 |
+
<span className="text-xs theme-text-secondary flex-1 truncate">{b.question}</span>
|
| 511 |
<span className="text-[10px] text-gray-600">{b.attempts} attempt{b.attempts !== 1 ? 's' : ''}</span>
|
| 512 |
{open ? <ChevronUp size={11} className="text-gray-600 shrink-0" /> : <ChevronDown size={11} className="text-gray-600 shrink-0" />}
|
| 513 |
</button>
|
| 514 |
<AnimatePresence>
|
| 515 |
{open && (
|
| 516 |
<motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ duration: 0.15 }} className="overflow-hidden">
|
| 517 |
+
<div className="p-3 flex flex-col gap-2.5 border-t" style={{ borderColor: 'var(--border-color)' }}>
|
| 518 |
{b.children.map((c) => <Bubble key={c.id} b={c} />)}
|
| 519 |
</div>
|
| 520 |
</motion.div>
|
|
|
|
| 538 |
const stepRef = useRef(0)
|
| 539 |
const cancel = useRef(false)
|
| 540 |
const bottomRef = useRef<HTMLDivElement>(null)
|
| 541 |
+
// Track all played queries and GEPA bubbles for end-of-demo collapse
|
| 542 |
+
const allQueriesRef = useRef<Array<{ def: QueryDef; children: BubbleData[] }>>([])
|
| 543 |
+
const gepaBubblesRef = useRef<BubbleData[]>([])
|
| 544 |
|
| 545 |
const scroll = useCallback(() => {
|
| 546 |
setTimeout(() => bottomRef.current?.scrollIntoView({ behavior: 'smooth' }), 60)
|
|
|
|
| 609 |
return children
|
| 610 |
}, [push, scroll, typeUser, streamSQL, addReward])
|
| 611 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
const playGepa = useCallback(async (fromGen: number, toGen: number) => {
|
| 613 |
const steps = ['Analyzing failure patterns…', 'Identifying missing rules from errors…', 'Rewriting system prompt…', 'Benchmarking candidate prompt…']
|
| 614 |
for (const label of steps) {
|
|
|
|
| 632 |
setGen(toGen)
|
| 633 |
setLatestDiff({ from: fromGen, to: toGen })
|
| 634 |
|
| 635 |
+
const gepaBubble: GepaBubble = { id: uid(), type: 'gepa', fromGen, toGen, scoreFrom: from, scoreTo: to }
|
| 636 |
+
gepaBubblesRef.current.push(gepaBubble)
|
| 637 |
+
push(gepaBubble)
|
| 638 |
scroll(); await sleep(1000)
|
| 639 |
}, [push, scroll])
|
| 640 |
|
| 641 |
const autoPlay = useCallback(async () => {
|
| 642 |
cancel.current = false
|
| 643 |
+
allQueriesRef.current = []
|
| 644 |
+
gepaBubblesRef.current = []
|
| 645 |
setBubbles([]); setGen(0); setScore(SCORES[0]); setRewardPoints([]); setLatestDiff(null)
|
| 646 |
stepRef.current = 0; setAppState('running')
|
| 647 |
await sleep(300)
|
|
|
|
| 649 |
for (const id of ROUND_1) {
|
| 650 |
if (cancel.current) break
|
| 651 |
const children = await playQuery(QUERIES[id])
|
| 652 |
+
if (!cancel.current) {
|
| 653 |
+
allQueriesRef.current.push({ def: QUERIES[id], children })
|
| 654 |
+
await sleep(600)
|
| 655 |
+
}
|
| 656 |
}
|
| 657 |
if (!cancel.current) { await playGepa(0, 1); await sleep(500) }
|
| 658 |
|
| 659 |
for (const id of ROUND_2) {
|
| 660 |
if (cancel.current) break
|
| 661 |
const children = await playQuery(QUERIES[id])
|
| 662 |
+
if (!cancel.current) {
|
| 663 |
+
allQueriesRef.current.push({ def: QUERIES[id], children })
|
| 664 |
+
await sleep(600)
|
| 665 |
+
}
|
| 666 |
}
|
| 667 |
if (!cancel.current) { await playGepa(1, 2); await sleep(500) }
|
| 668 |
|
| 669 |
for (const id of ROUND_3) {
|
| 670 |
if (cancel.current) break
|
| 671 |
const children = await playQuery(QUERIES[id])
|
| 672 |
+
if (!cancel.current) {
|
| 673 |
+
allQueriesRef.current.push({ def: QUERIES[id], children })
|
| 674 |
+
await sleep(600)
|
| 675 |
+
}
|
| 676 |
}
|
| 677 |
|
| 678 |
+
if (!cancel.current) {
|
| 679 |
+
// Collapse all queries at once, rebuilding the bubble list
|
| 680 |
+
const queries = allQueriesRef.current
|
| 681 |
+
const gepas = gepaBubblesRef.current
|
| 682 |
+
const result: BubbleData[] = []
|
| 683 |
+
|
| 684 |
+
// Round 1 groups
|
| 685 |
+
for (let i = 0; i < ROUND_1.length && i < queries.length; i++) {
|
| 686 |
+
const { def, children } = queries[i]
|
| 687 |
+
const lastAtt = def.attempts[def.attempts.length - 1]
|
| 688 |
+
result.push({ id: uid(), type: 'group', question: def.question, success: !lastAtt.error, attempts: def.attempts.length, children })
|
| 689 |
+
}
|
| 690 |
+
// GEPA 0→1
|
| 691 |
+
if (gepas[0]) result.push(gepas[0])
|
| 692 |
+
// Round 2 groups
|
| 693 |
+
for (let i = 0; i < ROUND_2.length; i++) {
|
| 694 |
+
const qi = ROUND_1.length + i
|
| 695 |
+
if (qi >= queries.length) break
|
| 696 |
+
const { def, children } = queries[qi]
|
| 697 |
+
const lastAtt = def.attempts[def.attempts.length - 1]
|
| 698 |
+
result.push({ id: uid(), type: 'group', question: def.question, success: !lastAtt.error, attempts: def.attempts.length, children })
|
| 699 |
+
}
|
| 700 |
+
// GEPA 1→2
|
| 701 |
+
if (gepas[1]) result.push(gepas[1])
|
| 702 |
+
// Round 3 groups
|
| 703 |
+
for (let i = 0; i < ROUND_3.length; i++) {
|
| 704 |
+
const qi = ROUND_1.length + ROUND_2.length + i
|
| 705 |
+
if (qi >= queries.length) break
|
| 706 |
+
const { def, children } = queries[qi]
|
| 707 |
+
const lastAtt = def.attempts[def.attempts.length - 1]
|
| 708 |
+
result.push({ id: uid(), type: 'group', question: def.question, success: !lastAtt.error, attempts: def.attempts.length, children })
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
setBubbles(result)
|
| 712 |
+
await sleep(300)
|
| 713 |
+
scroll()
|
| 714 |
+
setAppState('done')
|
| 715 |
+
}
|
| 716 |
+
}, [playQuery, playGepa, scroll])
|
| 717 |
|
| 718 |
useEffect(() => () => { cancel.current = true }, [])
|
| 719 |
|
|
|
|
| 724 |
style={{ background: 'var(--bg-primary)' }}
|
| 725 |
>
|
| 726 |
{/* Header */}
|
| 727 |
+
<div className="shrink-0 flex items-center justify-between px-4 py-3 border-b theme-border" style={{ background: 'var(--bg-secondary)' }}>
|
| 728 |
<div className="flex items-center gap-3">
|
| 729 |
<div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-violet-500/15 border border-violet-500/25">
|
| 730 |
<Play size={9} className="text-violet-400" fill="currentColor" />
|
|
|
|
| 798 |
</div>
|
| 799 |
|
| 800 |
{/* Right panel */}
|
| 801 |
+
<aside className="hidden lg:flex flex-col w-72 border-l theme-border overflow-hidden shrink-0" style={{ background: 'var(--bg-secondary)' }}>
|
| 802 |
<RightPanel gen={gen} score={score} rewardPoints={rewardPoints} latestDiff={latestDiff} />
|
| 803 |
</aside>
|
| 804 |
</div>
|
frontend/src/index.css
CHANGED
|
@@ -177,7 +177,10 @@ body {
|
|
| 177 |
[data-theme="light"] .text-violet-300 { color: #7c3aed !important; }
|
| 178 |
[data-theme="light"] .text-violet-400 { color: #7c3aed !important; }
|
| 179 |
[data-theme="light"] .text-green-400 { color: #15803d !important; }
|
|
|
|
| 180 |
[data-theme="light"] .text-red-400 { color: #b91c1c !important; }
|
|
|
|
|
|
|
| 181 |
[data-theme="light"] pre {
|
| 182 |
background-color: var(--bg-tertiary) !important;
|
| 183 |
color: #374151 !important;
|
|
@@ -185,3 +188,15 @@ body {
|
|
| 185 |
[data-theme="light"] .recharts-cartesian-grid line {
|
| 186 |
stroke: rgba(0, 0, 0, 0.06) !important;
|
| 187 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
[data-theme="light"] .text-violet-300 { color: #7c3aed !important; }
|
| 178 |
[data-theme="light"] .text-violet-400 { color: #7c3aed !important; }
|
| 179 |
[data-theme="light"] .text-green-400 { color: #15803d !important; }
|
| 180 |
+
[data-theme="light"] .text-green-300 { color: #16a34a !important; }
|
| 181 |
[data-theme="light"] .text-red-400 { color: #b91c1c !important; }
|
| 182 |
+
[data-theme="light"] .text-red-300 { color: #dc2626 !important; }
|
| 183 |
+
[data-theme="light"] .text-orange-400 { color: #c2410c !important; }
|
| 184 |
[data-theme="light"] pre {
|
| 185 |
background-color: var(--bg-tertiary) !important;
|
| 186 |
color: #374151 !important;
|
|
|
|
| 188 |
[data-theme="light"] .recharts-cartesian-grid line {
|
| 189 |
stroke: rgba(0, 0, 0, 0.06) !important;
|
| 190 |
}
|
| 191 |
+
/* Make borders using white opacity visible in light mode */
|
| 192 |
+
[data-theme="light"] [class*="border-white/"] {
|
| 193 |
+
border-color: var(--border-color) !important;
|
| 194 |
+
}
|
| 195 |
+
/* Make subtle white-opacity section headers visible in light mode */
|
| 196 |
+
[data-theme="light"] [class*="bg-white/[0.0"] {
|
| 197 |
+
background-color: rgba(0, 0, 0, 0.035) !important;
|
| 198 |
+
}
|
| 199 |
+
/* Hover states on white-opacity backgrounds */
|
| 200 |
+
[data-theme="light"] [class*="hover:bg-white/"]:hover {
|
| 201 |
+
background-color: var(--bg-hover-strong) !important;
|
| 202 |
+
}
|