Spaces:
Sleeping
Sleeping
fix
Browse files- backend/api/demo.py +12 -4
- frontend/src/components/ChatPanel.tsx +90 -46
- frontend/src/components/DemoMode.tsx +3 -4
- frontend/src/lib/api.ts +3 -2
backend/api/demo.py
CHANGED
|
@@ -649,26 +649,34 @@ class FeedbackRequest(BaseModel):
|
|
| 649 |
question: str
|
| 650 |
sql: str
|
| 651 |
correct: bool
|
|
|
|
| 652 |
|
| 653 |
|
| 654 |
@router.post("/feedback")
|
| 655 |
async def submit_feedback(req: FeedbackRequest):
|
| 656 |
gepa = get_gepa()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 657 |
gepa.record_result(QueryResult(
|
| 658 |
question=req.question,
|
| 659 |
final_sql=req.sql,
|
| 660 |
attempts=1,
|
| 661 |
success=req.correct,
|
| 662 |
-
errors=
|
| 663 |
timestamp=time.time(),
|
| 664 |
))
|
| 665 |
|
| 666 |
result = None
|
| 667 |
if not req.correct and gepa.should_optimize():
|
|
|
|
|
|
|
|
|
|
| 668 |
try:
|
| 669 |
-
result = await gepa.run_optimization_cycle(
|
| 670 |
-
user_feedback_context=f"User marked query as incorrect.\nQuestion: {req.question}\nSQL: {req.sql}"
|
| 671 |
-
)
|
| 672 |
except Exception:
|
| 673 |
pass
|
| 674 |
|
|
|
|
| 649 |
question: str
|
| 650 |
sql: str
|
| 651 |
correct: bool
|
| 652 |
+
remark: Optional[str] = None # user's free-text explanation of what was wrong
|
| 653 |
|
| 654 |
|
| 655 |
@router.post("/feedback")
|
| 656 |
async def submit_feedback(req: FeedbackRequest):
|
| 657 |
gepa = get_gepa()
|
| 658 |
+
errors = []
|
| 659 |
+
if not req.correct:
|
| 660 |
+
errors.append("User marked as incorrect")
|
| 661 |
+
if req.remark:
|
| 662 |
+
errors.append(f"User remark: {req.remark}")
|
| 663 |
+
|
| 664 |
gepa.record_result(QueryResult(
|
| 665 |
question=req.question,
|
| 666 |
final_sql=req.sql,
|
| 667 |
attempts=1,
|
| 668 |
success=req.correct,
|
| 669 |
+
errors=errors,
|
| 670 |
timestamp=time.time(),
|
| 671 |
))
|
| 672 |
|
| 673 |
result = None
|
| 674 |
if not req.correct and gepa.should_optimize():
|
| 675 |
+
feedback_ctx = f"User marked query as incorrect.\nQuestion: {req.question}\nSQL: {req.sql}"
|
| 676 |
+
if req.remark:
|
| 677 |
+
feedback_ctx += f"\nUser explanation: {req.remark}"
|
| 678 |
try:
|
| 679 |
+
result = await gepa.run_optimization_cycle(user_feedback_context=feedback_ctx)
|
|
|
|
|
|
|
| 680 |
except Exception:
|
| 681 |
pass
|
| 682 |
|
frontend/src/components/ChatPanel.tsx
CHANGED
|
@@ -201,10 +201,12 @@ function MessageCard({
|
|
| 201 |
onRetry,
|
| 202 |
}: {
|
| 203 |
msg: ChatMessage
|
| 204 |
-
onFeedback: (id: string, correct: boolean) => Promise<void>
|
| 205 |
onRetry: (q: string, previousSql?: string) => void
|
| 206 |
}) {
|
| 207 |
const [sqlOpen, setSqlOpen] = useState(true)
|
|
|
|
|
|
|
| 208 |
|
| 209 |
return (
|
| 210 |
<div className="flex flex-col gap-2.5">
|
|
@@ -306,53 +308,95 @@ function MessageCard({
|
|
| 306 |
|
| 307 |
{/* Feedback */}
|
| 308 |
{msg.status === 'done' && msg.attempts > 0 && (
|
| 309 |
-
<div className="flex
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
className={`text-xs flex items-center gap-1.5 ${
|
| 313 |
-
msg.feedback === 'correct' ?
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
<
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
<button
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
className="flex items-center gap-1 px-2 py-1 text-[10px] font-medium rounded-lg border border-green-500/25 bg-green-500/8 text-green-400 hover:bg-green-500/15 transition-all disabled:opacity-40"
|
| 330 |
>
|
| 331 |
-
<
|
| 332 |
-
|
| 333 |
</button>
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
>
|
| 339 |
-
<
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
</div>
|
| 357 |
)}
|
| 358 |
</div>
|
|
@@ -380,12 +424,12 @@ export function ChatPanel() {
|
|
| 380 |
}, [messages.length])
|
| 381 |
|
| 382 |
const handleFeedback = useCallback(
|
| 383 |
-
async (id: string, correct: boolean) => {
|
| 384 |
const msg = messages.find((m) => m.id === id)
|
| 385 |
if (!msg) return
|
| 386 |
updateMessage(id, { feedbackSending: true })
|
| 387 |
try {
|
| 388 |
-
await submitFeedback(msg.question, msg.sql, correct)
|
| 389 |
updateMessage(id, { feedback: correct ? 'correct' : 'wrong', feedbackSending: false })
|
| 390 |
} catch {
|
| 391 |
updateMessage(id, { feedbackSending: false })
|
|
|
|
| 201 |
onRetry,
|
| 202 |
}: {
|
| 203 |
msg: ChatMessage
|
| 204 |
+
onFeedback: (id: string, correct: boolean, remark?: string) => Promise<void>
|
| 205 |
onRetry: (q: string, previousSql?: string) => void
|
| 206 |
}) {
|
| 207 |
const [sqlOpen, setSqlOpen] = useState(true)
|
| 208 |
+
const [wrongOpen, setWrongOpen] = useState(false)
|
| 209 |
+
const [remark, setRemark] = useState('')
|
| 210 |
|
| 211 |
return (
|
| 212 |
<div className="flex flex-col gap-2.5">
|
|
|
|
| 308 |
|
| 309 |
{/* Feedback */}
|
| 310 |
{msg.status === 'done' && msg.attempts > 0 && (
|
| 311 |
+
<div className="flex flex-col gap-2">
|
| 312 |
+
<div className="flex items-center gap-2">
|
| 313 |
+
{msg.feedback ? (
|
| 314 |
+
<div className={`text-xs flex items-center gap-1.5 ${msg.feedback === 'correct' ? 'text-green-400' : 'text-red-400'}`}>
|
| 315 |
+
{msg.feedback === 'correct' ? <CheckCircle2 size={12} /> : <XCircle size={12} />}
|
| 316 |
+
Marked as {msg.feedback}
|
| 317 |
+
</div>
|
| 318 |
+
) : (
|
| 319 |
+
<>
|
| 320 |
+
<span className="text-[10px] text-gray-600 mr-0.5">Was this correct?</span>
|
| 321 |
+
<button
|
| 322 |
+
disabled={msg.feedbackSending}
|
| 323 |
+
onClick={() => onFeedback(msg.id, true)}
|
| 324 |
+
className="flex items-center gap-1 px-2 py-1 text-[10px] font-medium rounded-lg border border-green-500/25 bg-green-500/8 text-green-400 hover:bg-green-500/15 transition-all disabled:opacity-40"
|
| 325 |
+
>
|
| 326 |
+
<CheckCircle2 size={10} />
|
| 327 |
+
Correct
|
| 328 |
+
</button>
|
| 329 |
+
<button
|
| 330 |
+
disabled={msg.feedbackSending}
|
| 331 |
+
onClick={() => setWrongOpen((v) => !v)}
|
| 332 |
+
className={`flex items-center gap-1 px-2 py-1 text-[10px] font-medium rounded-lg border transition-all disabled:opacity-40 ${
|
| 333 |
+
wrongOpen
|
| 334 |
+
? 'border-red-500/40 bg-red-500/15 text-red-300'
|
| 335 |
+
: 'border-red-500/25 bg-red-500/8 text-red-400 hover:bg-red-500/15'
|
| 336 |
+
}`}
|
| 337 |
+
>
|
| 338 |
+
<XCircle size={10} />
|
| 339 |
+
Wrong
|
| 340 |
+
<ChevronDown size={9} className={`transition-transform ${wrongOpen ? 'rotate-180' : ''}`} />
|
| 341 |
+
</button>
|
| 342 |
+
</>
|
| 343 |
+
)}
|
| 344 |
+
{(msg.status === 'done' || msg.status === 'error') && (
|
| 345 |
<button
|
| 346 |
+
onClick={() => onRetry(msg.question, msg.feedback === 'wrong' ? msg.sql : undefined)}
|
| 347 |
+
className="ml-auto flex items-center gap-1 text-[10px] text-gray-600 hover:text-gray-400 transition-colors"
|
|
|
|
| 348 |
>
|
| 349 |
+
<RefreshCw size={10} />
|
| 350 |
+
{msg.feedback === 'wrong' ? 'Retry differently' : 'Retry'}
|
| 351 |
</button>
|
| 352 |
+
)}
|
| 353 |
+
</div>
|
| 354 |
+
|
| 355 |
+
{/* Remarks dropdown for Wrong */}
|
| 356 |
+
<AnimatePresence>
|
| 357 |
+
{wrongOpen && !msg.feedback && (
|
| 358 |
+
<motion.div
|
| 359 |
+
initial={{ height: 0, opacity: 0 }}
|
| 360 |
+
animate={{ height: 'auto', opacity: 1 }}
|
| 361 |
+
exit={{ height: 0, opacity: 0 }}
|
| 362 |
+
transition={{ duration: 0.15 }}
|
| 363 |
+
className="overflow-hidden"
|
| 364 |
>
|
| 365 |
+
<div className="flex flex-col gap-2 p-3 rounded-xl border border-red-500/20 bg-red-500/5">
|
| 366 |
+
<p className="text-[10px] text-red-400/80 font-medium">
|
| 367 |
+
What was wrong? <span className="text-gray-600">(optional — helps improve the prompt)</span>
|
| 368 |
+
</p>
|
| 369 |
+
<textarea
|
| 370 |
+
value={remark}
|
| 371 |
+
onChange={(e) => setRemark(e.target.value)}
|
| 372 |
+
placeholder="e.g. wrong JOIN logic, missing GROUP BY, incorrect column name…"
|
| 373 |
+
rows={2}
|
| 374 |
+
className="w-full px-3 py-2 text-xs rounded-lg border border-white/10 bg-black/20 text-gray-300 placeholder-gray-600 resize-none focus:outline-none focus:border-red-500/40 transition-colors"
|
| 375 |
+
/>
|
| 376 |
+
<div className="flex items-center gap-2 justify-end">
|
| 377 |
+
<button
|
| 378 |
+
onClick={() => { setWrongOpen(false); setRemark('') }}
|
| 379 |
+
className="text-[10px] text-gray-600 hover:text-gray-400 transition-colors px-2 py-1"
|
| 380 |
+
>
|
| 381 |
+
Cancel
|
| 382 |
+
</button>
|
| 383 |
+
<button
|
| 384 |
+
disabled={msg.feedbackSending}
|
| 385 |
+
onClick={() => {
|
| 386 |
+
void onFeedback(msg.id, false, remark.trim() || undefined)
|
| 387 |
+
setWrongOpen(false)
|
| 388 |
+
setRemark('')
|
| 389 |
+
}}
|
| 390 |
+
className="flex items-center gap-1 px-3 py-1 text-[10px] font-semibold rounded-lg bg-red-500/20 border border-red-500/30 text-red-300 hover:bg-red-500/30 transition-all disabled:opacity-40"
|
| 391 |
+
>
|
| 392 |
+
{msg.feedbackSending ? <Loader2 size={9} className="animate-spin" /> : <XCircle size={9} />}
|
| 393 |
+
Submit feedback
|
| 394 |
+
</button>
|
| 395 |
+
</div>
|
| 396 |
+
</div>
|
| 397 |
+
</motion.div>
|
| 398 |
+
)}
|
| 399 |
+
</AnimatePresence>
|
| 400 |
</div>
|
| 401 |
)}
|
| 402 |
</div>
|
|
|
|
| 424 |
}, [messages.length])
|
| 425 |
|
| 426 |
const handleFeedback = useCallback(
|
| 427 |
+
async (id: string, correct: boolean, remark?: string) => {
|
| 428 |
const msg = messages.find((m) => m.id === id)
|
| 429 |
if (!msg) return
|
| 430 |
updateMessage(id, { feedbackSending: true })
|
| 431 |
try {
|
| 432 |
+
await submitFeedback(msg.question, msg.sql, correct, remark)
|
| 433 |
updateMessage(id, { feedback: correct ? 'correct' : 'wrong', feedbackSending: false })
|
| 434 |
} catch {
|
| 435 |
updateMessage(id, { feedbackSending: false })
|
frontend/src/components/DemoMode.tsx
CHANGED
|
@@ -783,11 +783,10 @@ export function DemoMode({ onClose }: { onClose: () => void }) {
|
|
| 783 |
</AnimatePresence>
|
| 784 |
|
| 785 |
{appState === 'done' && (
|
| 786 |
-
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="border border-
|
| 787 |
-
<div className="text-3xl font-bold text-
|
| 788 |
-
<div className="text-sm font-semibold text-white mb-1">Demo complete</div>
|
| 789 |
<div className="text-xs text-gray-500 max-w-xs mx-auto leading-relaxed">
|
| 790 |
-
Agent improved from 42% → 91% accuracy through RL
|
| 791 |
</div>
|
| 792 |
</motion.div>
|
| 793 |
)}
|
|
|
|
| 783 |
</AnimatePresence>
|
| 784 |
|
| 785 |
{appState === 'done' && (
|
| 786 |
+
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="border border-violet-500/20 rounded-2xl p-5 bg-violet-500/5 text-center mt-2">
|
| 787 |
+
<div className="text-3xl font-bold text-violet-300 mb-1 tabular-nums">91%</div>
|
|
|
|
| 788 |
<div className="text-xs text-gray-500 max-w-xs mx-auto leading-relaxed">
|
| 789 |
+
Agent improved from 42% → 91% accuracy through RL repair strategies and two GEPA prompt evolution cycles.
|
| 790 |
</div>
|
| 791 |
</motion.div>
|
| 792 |
)}
|
frontend/src/lib/api.ts
CHANGED
|
@@ -84,12 +84,13 @@ export async function fetchSchemaGraph(): Promise<SchemaGraph> {
|
|
| 84 |
export async function submitFeedback(
|
| 85 |
question: string,
|
| 86 |
sql: string,
|
| 87 |
-
correct: boolean
|
|
|
|
| 88 |
): Promise<void> {
|
| 89 |
await fetch(`${BASE_URL}/api/feedback`, {
|
| 90 |
method: 'POST',
|
| 91 |
headers: { 'Content-Type': 'application/json' },
|
| 92 |
-
body: JSON.stringify({ question, sql, correct }),
|
| 93 |
})
|
| 94 |
}
|
| 95 |
|
|
|
|
| 84 |
export async function submitFeedback(
|
| 85 |
question: string,
|
| 86 |
sql: string,
|
| 87 |
+
correct: boolean,
|
| 88 |
+
remark?: string,
|
| 89 |
): Promise<void> {
|
| 90 |
await fetch(`${BASE_URL}/api/feedback`, {
|
| 91 |
method: 'POST',
|
| 92 |
headers: { 'Content-Type': 'application/json' },
|
| 93 |
+
body: JSON.stringify({ question, sql, correct, remark }),
|
| 94 |
})
|
| 95 |
}
|
| 96 |
|