ar9avg commited on
Commit
b00a200
·
1 Parent(s): c07b98d
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=[] if req.correct else ["User marked as incorrect"],
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 items-center gap-2">
310
- {msg.feedback ? (
311
- <div
312
- className={`text-xs flex items-center gap-1.5 ${
313
- msg.feedback === 'correct' ? 'text-green-400' : 'text-red-400'
314
- }`}
315
- >
316
- {msg.feedback === 'correct' ? (
317
- <CheckCircle2 size={12} />
318
- ) : (
319
- <XCircle size={12} />
320
- )}
321
- Marked as {msg.feedback}
322
- </div>
323
- ) : (
324
- <>
325
- <span className="text-[10px] text-gray-600 mr-0.5">Was this correct?</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  <button
327
- disabled={msg.feedbackSending}
328
- onClick={() => onFeedback(msg.id, true)}
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
- <CheckCircle2 size={10} />
332
- Correct
333
  </button>
334
- <button
335
- disabled={msg.feedbackSending}
336
- onClick={() => onFeedback(msg.id, false)}
337
- className="flex items-center gap-1 px-2 py-1 text-[10px] font-medium rounded-lg border border-red-500/25 bg-red-500/8 text-red-400 hover:bg-red-500/15 transition-all disabled:opacity-40"
 
 
 
 
 
 
 
 
338
  >
339
- <XCircle size={10} />
340
- Wrong
341
- </button>
342
- </>
343
- )}
344
- {(msg.status === 'done' || msg.status === 'error') && (
345
- <button
346
- onClick={() => onRetry(
347
- msg.question,
348
- msg.feedback === 'wrong' ? msg.sql : undefined
349
- )}
350
- className="ml-auto flex items-center gap-1 text-[10px] text-gray-600 hover:text-gray-400 transition-colors"
351
- >
352
- <RefreshCw size={10} />
353
- {msg.feedback === 'wrong' ? 'Retry differently' : 'Retry'}
354
- </button>
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-green-500/25 rounded-2xl p-5 bg-green-500/5 text-center mt-2">
787
- <div className="text-3xl font-bold text-green-400 mb-1 tabular-nums">91%</div>
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-driven repair strategies and two GEPA prompt evolution cycles.
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