ar9avg commited on
Commit
17e7bd7
·
1 Parent(s): 24ef2cf
backend/api/demo.py CHANGED
@@ -442,6 +442,55 @@ async def execute_query_stream(req: ExecuteQueryRequest):
442
  return EventSourceResponse(event_generator())
443
 
444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  # ─── /api/benchmark-questions ────────────────────────────────────
446
 
447
  @router.get("/benchmark-questions")
 
442
  return EventSourceResponse(event_generator())
443
 
444
 
445
+ # ─── /api/suggest-questions ──────────────────────────────────────
446
+
447
+ @router.get("/suggest-questions")
448
+ async def suggest_questions():
449
+ """
450
+ Generate example questions based on the currently active database schema.
451
+ Returns up to 5 short natural-language questions the user might want to ask.
452
+ """
453
+ from env.sql_env import _make_client, _MODEL
454
+ from env.database import get_schema_info as _get_schema
455
+
456
+ schema = _get_schema()
457
+ client = _make_client()
458
+ try:
459
+ resp = await client.chat.completions.create(
460
+ model=_MODEL,
461
+ messages=[
462
+ {
463
+ "role": "system",
464
+ "content": (
465
+ "You are a helpful data analyst. Given a database schema, "
466
+ "return ONLY a JSON array of 5 short natural-language questions "
467
+ "(5-10 words each) a user might want to ask about the data. "
468
+ "No markdown, no explanation — just the JSON array."
469
+ ),
470
+ },
471
+ {
472
+ "role": "user",
473
+ "content": f"Schema:\n{schema}\n\nGenerate 5 example questions.",
474
+ },
475
+ ],
476
+ temperature=0.7,
477
+ max_tokens=250,
478
+ )
479
+ raw = (resp.choices[0].message.content or "").strip()
480
+ # Strip markdown fences if present
481
+ if raw.startswith("```"):
482
+ raw = raw.split("```")[1]
483
+ if raw.startswith("json"):
484
+ raw = raw[4:]
485
+ questions = json.loads(raw)
486
+ if not isinstance(questions, list):
487
+ questions = []
488
+ return {"questions": [str(q) for q in questions[:5]]}
489
+ except Exception as e:
490
+ logger.error("suggest-questions failed: %s", e)
491
+ return {"questions": []}
492
+
493
+
494
  # ─── /api/benchmark-questions ────────────────────────────────────
495
 
496
  @router.get("/benchmark-questions")
frontend/src/App.tsx CHANGED
@@ -28,7 +28,7 @@ export default function App() {
28
  const [demoOpen, setDemoOpen] = useState(false)
29
  const [connectDbOpen, setConnectDbOpen] = useState(false)
30
 
31
- const { theme, setDbSeeded, setTables, setSchemaGraph, setDbLabel, taskDifficulty } = useStore()
32
 
33
  // Apply theme on mount / change
34
  useEffect(() => {
@@ -92,6 +92,11 @@ export default function App() {
92
  setRightOpen(false)
93
  }, [activeTab])
94
 
 
 
 
 
 
95
  return (
96
  <div
97
  className="h-screen flex flex-col overflow-hidden theme-bg-primary theme-text-primary"
@@ -153,7 +158,7 @@ export default function App() {
153
  className="flex items-center gap-1 px-2 sm:px-4 py-2.5 border-b theme-border shrink-0 overflow-x-auto scrollbar-none"
154
  style={{ background: 'var(--bg-secondary)' }}
155
  >
156
- {TABS.map((tab) => (
157
  <button
158
  key={tab.id}
159
  onClick={() => setActiveTab(tab.id)}
 
28
  const [demoOpen, setDemoOpen] = useState(false)
29
  const [connectDbOpen, setConnectDbOpen] = useState(false)
30
 
31
+ const { theme, setDbSeeded, setTables, setSchemaGraph, setDbLabel, taskDifficulty, isCustomDb } = useStore()
32
 
33
  // Apply theme on mount / change
34
  useEffect(() => {
 
92
  setRightOpen(false)
93
  }, [activeTab])
94
 
95
+ // If custom DB is enabled while benchmark tab is active, switch to chat
96
+ useEffect(() => {
97
+ if (isCustomDb && activeTab === 'benchmark') setActiveTab('chat')
98
+ }, [isCustomDb, activeTab])
99
+
100
  return (
101
  <div
102
  className="h-screen flex flex-col overflow-hidden theme-bg-primary theme-text-primary"
 
158
  className="flex items-center gap-1 px-2 sm:px-4 py-2.5 border-b theme-border shrink-0 overflow-x-auto scrollbar-none"
159
  style={{ background: 'var(--bg-secondary)' }}
160
  >
161
+ {TABS.filter((tab) => !(isCustomDb && tab.id === 'benchmark')).map((tab) => (
162
  <button
163
  key={tab.id}
164
  onClick={() => setActiveTab(tab.id)}
frontend/src/components/ChatPanel.tsx CHANGED
@@ -154,9 +154,28 @@ const SUGGESTED: Record<string, string[]> = {
154
  hard: ['Rolling 7-day revenue', 'Seller ranking with rank change', 'Cohort retention analysis'],
155
  }
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  function EmptyState({ onSelect }: { onSelect: (q: string) => void }) {
158
- const { taskDifficulty } = useStore()
159
- const suggestions = SUGGESTED[taskDifficulty] ?? SUGGESTED.easy
160
 
161
  return (
162
  <div className="flex flex-col items-center justify-center h-full gap-6 px-8 text-center">
@@ -176,18 +195,22 @@ function EmptyState({ onSelect }: { onSelect: (q: string) => void }) {
176
 
177
  <div className="flex flex-col gap-2 w-full max-w-sm">
178
  <div className="text-[10px] text-gray-600 uppercase tracking-wider mb-0.5">
179
- Try these queries
180
  </div>
181
- {suggestions.map((q) => (
182
- <button
183
- key={q}
184
- onClick={() => onSelect(q)}
185
- className="flex items-center gap-2 px-3 py-2.5 rounded-xl border border-white/[0.06] bg-white/[0.02] hover:bg-white/[0.05] hover:border-violet-500/30 transition-all text-left group"
186
- >
187
- <span className="text-violet-500 shrink-0 group-hover:text-violet-400">›</span>
188
- <span className="text-xs text-gray-300">{q}</span>
189
- </button>
190
- ))}
 
 
 
 
191
  </div>
192
  </div>
193
  )
@@ -413,6 +436,7 @@ export function ChatPanel() {
413
  taskId, taskDifficulty,
414
  optimizingBanner, setOptimizingBanner,
415
  promptGeneration,
 
416
  } = useStore()
417
 
418
  const [input, setInput] = useState('')
@@ -546,7 +570,9 @@ export function ChatPanel() {
546
  }
547
  }
548
 
549
- const suggestions = SUGGESTED[taskDifficulty] ?? SUGGESTED.easy
 
 
550
 
551
  return (
552
  <div className="flex flex-col h-full">
@@ -615,7 +641,7 @@ export function ChatPanel() {
615
  value={input}
616
  onChange={(e) => setInput(e.target.value)}
617
  onKeyDown={handleKeyDown}
618
- placeholder="Ask about products, orders, sellers..."
619
  disabled={isExecuting}
620
  rows={1}
621
  className="w-full px-3 py-2.5 pr-10 text-sm text-white rounded-xl border border-white/[0.06] bg-white/[0.03] placeholder-gray-600 resize-none focus:outline-none focus:border-violet-500/40 focus:bg-white/[0.05] transition-all disabled:opacity-50"
 
154
  hard: ['Rolling 7-day revenue', 'Seller ranking with rank change', 'Cohort retention analysis'],
155
  }
156
 
157
+ function SuggestionSkeleton() {
158
+ return (
159
+ <div className="flex flex-col gap-2 w-full max-w-sm">
160
+ {[90, 110, 80].map((w, i) => (
161
+ <div
162
+ key={i}
163
+ className="flex items-center gap-2 px-3 py-2.5 rounded-xl border border-white/[0.06] bg-white/[0.02]"
164
+ >
165
+ <div className="w-1 h-3 rounded bg-violet-500/20 animate-pulse shrink-0" />
166
+ <div
167
+ className="h-3 rounded bg-white/8 animate-pulse"
168
+ style={{ width: w }}
169
+ />
170
+ </div>
171
+ ))}
172
+ </div>
173
+ )
174
+ }
175
+
176
  function EmptyState({ onSelect }: { onSelect: (q: string) => void }) {
177
+ const { taskDifficulty, isCustomDb, customDbSuggestions, suggestionsLoading } = useStore()
178
+ const suggestions = isCustomDb ? customDbSuggestions : (SUGGESTED[taskDifficulty] ?? SUGGESTED.easy)
179
 
180
  return (
181
  <div className="flex flex-col items-center justify-center h-full gap-6 px-8 text-center">
 
195
 
196
  <div className="flex flex-col gap-2 w-full max-w-sm">
197
  <div className="text-[10px] text-gray-600 uppercase tracking-wider mb-0.5">
198
+ {isCustomDb && suggestionsLoading ? 'Generating suggestions…' : 'Try these queries'}
199
  </div>
200
+ {isCustomDb && suggestionsLoading ? (
201
+ <SuggestionSkeleton />
202
+ ) : suggestions.length > 0 ? (
203
+ suggestions.map((q) => (
204
+ <button
205
+ key={q}
206
+ onClick={() => onSelect(q)}
207
+ className="flex items-center gap-2 px-3 py-2.5 rounded-xl border border-white/[0.06] bg-white/[0.02] hover:bg-white/[0.05] hover:border-violet-500/30 transition-all text-left group"
208
+ >
209
+ <span className="text-violet-500 shrink-0 group-hover:text-violet-400">›</span>
210
+ <span className="text-xs text-gray-300">{q}</span>
211
+ </button>
212
+ ))
213
+ ) : null}
214
  </div>
215
  </div>
216
  )
 
436
  taskId, taskDifficulty,
437
  optimizingBanner, setOptimizingBanner,
438
  promptGeneration,
439
+ isCustomDb, customDbSuggestions,
440
  } = useStore()
441
 
442
  const [input, setInput] = useState('')
 
570
  }
571
  }
572
 
573
+ const suggestions = isCustomDb
574
+ ? customDbSuggestions
575
+ : (SUGGESTED[taskDifficulty] ?? SUGGESTED.easy)
576
 
577
  return (
578
  <div className="flex flex-col h-full">
 
641
  value={input}
642
  onChange={(e) => setInput(e.target.value)}
643
  onKeyDown={handleKeyDown}
644
+ placeholder={isCustomDb ? 'Ask anything about your data…' : 'Ask about products, orders, sellers...'}
645
  disabled={isExecuting}
646
  rows={1}
647
  className="w-full px-3 py-2.5 pr-10 text-sm text-white rounded-xl border border-white/[0.06] bg-white/[0.03] placeholder-gray-600 resize-none focus:outline-none focus:border-violet-500/40 focus:bg-white/[0.05] transition-all disabled:opacity-50"
frontend/src/components/ConnectDB.tsx CHANGED
@@ -2,7 +2,7 @@ import { useState } from 'react'
2
  import { motion } from 'framer-motion'
3
  import { X, PlugZap, Database, CheckCircle2, XCircle, Loader2, RotateCcw } from 'lucide-react'
4
  import { useStore } from '../store/useStore'
5
- import { connectExternalDb } from '../lib/api'
6
 
7
  interface ConnectDBProps {
8
  onClose: () => void
@@ -21,7 +21,7 @@ const POSTGRES_EXAMPLES = [
21
  ]
22
 
23
  export function ConnectDB({ onClose }: ConnectDBProps) {
24
- const { dbLabel, setDbLabel, setTables, setDbSeeded } = useStore()
25
  const [dbType, setDbType] = useState<DbType>('sqlite')
26
  const [value, setValue] = useState('')
27
  const [status, setStatus] = useState<'idle' | 'connecting' | 'success' | 'error'>('idle')
@@ -51,8 +51,15 @@ export function ConnectDB({ onClose }: ConnectDBProps) {
51
  setDbLabel(res.dbLabel)
52
  setTables(res.tables)
53
  setDbSeeded(true)
 
54
  setStatus('success')
55
  setMessage(res.message)
 
 
 
 
 
 
56
  } else {
57
  setStatus('error')
58
  setMessage(res.message)
@@ -71,6 +78,9 @@ export function ConnectDB({ onClose }: ConnectDBProps) {
71
  setDbLabel(res.dbLabel)
72
  setTables(res.tables)
73
  setDbSeeded(true)
 
 
 
74
  setStatus('success')
75
  setMessage('Reset to built-in benchmark database')
76
  } else {
 
2
  import { motion } from 'framer-motion'
3
  import { X, PlugZap, Database, CheckCircle2, XCircle, Loader2, RotateCcw } from 'lucide-react'
4
  import { useStore } from '../store/useStore'
5
+ import { connectExternalDb, fetchSuggestQuestions } from '../lib/api'
6
 
7
  interface ConnectDBProps {
8
  onClose: () => void
 
21
  ]
22
 
23
  export function ConnectDB({ onClose }: ConnectDBProps) {
24
+ const { dbLabel, setDbLabel, setTables, setDbSeeded, setIsCustomDb, setCustomDbSuggestions, setSuggestionsLoading } = useStore()
25
  const [dbType, setDbType] = useState<DbType>('sqlite')
26
  const [value, setValue] = useState('')
27
  const [status, setStatus] = useState<'idle' | 'connecting' | 'success' | 'error'>('idle')
 
51
  setDbLabel(res.dbLabel)
52
  setTables(res.tables)
53
  setDbSeeded(true)
54
+ setIsCustomDb(true)
55
  setStatus('success')
56
  setMessage(res.message)
57
+ // Start generating suggestions in the background (cached by DSN)
58
+ setCustomDbSuggestions([])
59
+ setSuggestionsLoading(true)
60
+ fetchSuggestQuestions(dsn)
61
+ .then((qs) => { setCustomDbSuggestions(qs); setSuggestionsLoading(false) })
62
+ .catch(() => setSuggestionsLoading(false))
63
  } else {
64
  setStatus('error')
65
  setMessage(res.message)
 
78
  setDbLabel(res.dbLabel)
79
  setTables(res.tables)
80
  setDbSeeded(true)
81
+ setIsCustomDb(false)
82
+ setCustomDbSuggestions([])
83
+ setSuggestionsLoading(false)
84
  setStatus('success')
85
  setMessage('Reset to built-in benchmark database')
86
  } else {
frontend/src/components/Header.tsx CHANGED
@@ -48,9 +48,9 @@ export function Header({ onToggleLeft, onToggleRight, onDemo, onConnectDb }: Hea
48
  <div className="flex items-center gap-2 sm:gap-3">
49
  {/* Connection status */}
50
  {dbSeeded ? (
51
- <div className="hidden sm:flex items-center gap-1.5 text-[10px] text-green-400">
52
- <span className="w-1.5 h-1.5 rounded-full bg-green-400 inline-block" />
53
- benchmark db
54
  </div>
55
  ) : (
56
  <div className="hidden sm:flex items-center gap-1.5 text-[10px] text-amber-400">
 
48
  <div className="flex items-center gap-2 sm:gap-3">
49
  {/* Connection status */}
50
  {dbSeeded ? (
51
+ <div className="hidden sm:flex items-center gap-1.5 text-[10px] text-green-400 max-w-[140px] truncate">
52
+ <span className="w-1.5 h-1.5 rounded-full bg-green-400 inline-block shrink-0" />
53
+ <span className="truncate">{dbLabel}</span>
54
  </div>
55
  ) : (
56
  <div className="hidden sm:flex items-center gap-1.5 text-[10px] text-amber-400">
frontend/src/components/LeftSidebar.tsx CHANGED
@@ -11,42 +11,44 @@ const DIFFICULTY_CONFIG: Record<Difficulty, { label: string; bg: string; text: s
11
  }
12
 
13
  export function LeftSidebar() {
14
- const { tables, taskDifficulty, setTaskDifficulty, dbSeeded } = useStore()
15
  const [tablesExpanded, setTablesExpanded] = useState(true)
16
 
17
  const cfg = DIFFICULTY_CONFIG[taskDifficulty]
18
 
19
  return (
20
  <div className="flex flex-col gap-4 py-1">
21
- {/* Task Difficulty */}
22
- <section>
23
- <div className="text-[10px] font-semibold text-gray-500 uppercase tracking-widest mb-2 flex items-center gap-1.5">
24
- <GitFork size={10} className="text-violet-400" />
25
- Task Difficulty
26
- </div>
27
- <div className="flex flex-col gap-1">
28
- {(Object.keys(DIFFICULTY_CONFIG) as Difficulty[]).map((d) => {
29
- const c = DIFFICULTY_CONFIG[d]
30
- const active = d === taskDifficulty
31
- return (
32
- <button
33
- key={d}
34
- onClick={() => setTaskDifficulty(d)}
35
- className={`flex items-center justify-between px-3 py-2 rounded-lg border text-xs font-medium transition-all ${
36
- active
37
- ? `${c.bg} ${c.text} ${c.border}`
38
- : 'border-transparent text-gray-500 hover:text-gray-300 hover:bg-white/5'
39
- }`}
40
- >
41
- <span>{c.label}</span>
42
- {active && (
43
- <span className={`text-[9px] font-mono ${c.text} opacity-70`}>selected</span>
44
- )}
45
- </button>
46
- )
47
- })}
48
- </div>
49
- </section>
 
 
50
 
51
  {/* Schema Tables */}
52
  <section>
@@ -114,44 +116,55 @@ export function LeftSidebar() {
114
  className="rounded-xl border border-white/[0.05] p-3 text-[11px] text-gray-500 leading-relaxed"
115
  style={{ background: 'var(--bg-card)' }}
116
  >
117
- <p className="mb-2 text-gray-400 font-medium">E-Commerce Marketplace</p>
118
- <p>
119
- Multi-vendor marketplace with products, orders, sellers, users, and reviews.
120
- Supports complex analytical queries across sales, inventory, and user behavior.
121
- </p>
122
- <div className="mt-2 flex flex-wrap gap-1">
123
- {['Products', 'Orders', 'Sellers', 'Users', 'Reviews', 'Categories'].map((t) => (
124
- <span
125
- key={t}
126
- className="text-[9px] px-1.5 py-0.5 rounded border border-white/[0.06] text-gray-600"
127
- >
128
- {t}
129
- </span>
130
- ))}
131
- </div>
 
 
 
 
 
 
 
 
 
132
  </div>
133
  </section>
134
 
135
- {/* Current task badge */}
136
- <section>
137
- <div
138
- className={`rounded-xl border ${cfg.border} ${cfg.bg} p-3 flex flex-col gap-1.5`}
139
- >
140
- <div className="flex items-center justify-between">
141
- <span className={`text-[10px] font-semibold uppercase tracking-wider ${cfg.text}`}>
142
- Current Task
143
- </span>
144
- <span className={`text-[10px] font-mono ${cfg.text}`}>{cfg.label}</span>
 
 
 
 
 
 
 
 
 
145
  </div>
146
- <p className="text-[11px] text-gray-400 leading-relaxed">
147
- {taskDifficulty === 'easy'
148
- ? 'Simple SELECT queries, basic filtering and aggregation'
149
- : taskDifficulty === 'medium'
150
- ? 'Multi-table JOINs, GROUP BY, subqueries, window functions'
151
- : 'Complex CTEs, rolling aggregations, cohort analysis, ranking'}
152
- </p>
153
- </div>
154
- </section>
155
  </div>
156
  )
157
  }
 
11
  }
12
 
13
  export function LeftSidebar() {
14
+ const { tables, taskDifficulty, setTaskDifficulty, dbSeeded, isCustomDb, dbLabel } = useStore()
15
  const [tablesExpanded, setTablesExpanded] = useState(true)
16
 
17
  const cfg = DIFFICULTY_CONFIG[taskDifficulty]
18
 
19
  return (
20
  <div className="flex flex-col gap-4 py-1">
21
+ {/* Task Difficulty — hidden for custom databases */}
22
+ {!isCustomDb && (
23
+ <section>
24
+ <div className="text-[10px] font-semibold text-gray-500 uppercase tracking-widest mb-2 flex items-center gap-1.5">
25
+ <GitFork size={10} className="text-violet-400" />
26
+ Task Difficulty
27
+ </div>
28
+ <div className="flex flex-col gap-1">
29
+ {(Object.keys(DIFFICULTY_CONFIG) as Difficulty[]).map((d) => {
30
+ const c = DIFFICULTY_CONFIG[d]
31
+ const active = d === taskDifficulty
32
+ return (
33
+ <button
34
+ key={d}
35
+ onClick={() => setTaskDifficulty(d)}
36
+ className={`flex items-center justify-between px-3 py-2 rounded-lg border text-xs font-medium transition-all ${
37
+ active
38
+ ? `${c.bg} ${c.text} ${c.border}`
39
+ : 'border-transparent text-gray-500 hover:text-gray-300 hover:bg-white/5'
40
+ }`}
41
+ >
42
+ <span>{c.label}</span>
43
+ {active && (
44
+ <span className={`text-[9px] font-mono ${c.text} opacity-70`}>selected</span>
45
+ )}
46
+ </button>
47
+ )
48
+ })}
49
+ </div>
50
+ </section>
51
+ )}
52
 
53
  {/* Schema Tables */}
54
  <section>
 
116
  className="rounded-xl border border-white/[0.05] p-3 text-[11px] text-gray-500 leading-relaxed"
117
  style={{ background: 'var(--bg-card)' }}
118
  >
119
+ {isCustomDb ? (
120
+ <p className="text-gray-600 italic">
121
+ Connected to <span className="text-violet-400 not-italic font-medium">{dbLabel}</span>.
122
+ Ask questions about your data in natural language.
123
+ </p>
124
+ ) : (
125
+ <>
126
+ <p className="mb-2 text-gray-400 font-medium">E-Commerce Marketplace</p>
127
+ <p>
128
+ Multi-vendor marketplace with products, orders, sellers, users, and reviews.
129
+ Supports complex analytical queries across sales, inventory, and user behavior.
130
+ </p>
131
+ <div className="mt-2 flex flex-wrap gap-1">
132
+ {['Products', 'Orders', 'Sellers', 'Users', 'Reviews', 'Categories'].map((t) => (
133
+ <span
134
+ key={t}
135
+ className="text-[9px] px-1.5 py-0.5 rounded border border-white/[0.06] text-gray-600"
136
+ >
137
+ {t}
138
+ </span>
139
+ ))}
140
+ </div>
141
+ </>
142
+ )}
143
  </div>
144
  </section>
145
 
146
+ {/* Current task badge — hidden for custom databases */}
147
+ {!isCustomDb && (
148
+ <section>
149
+ <div
150
+ className={`rounded-xl border ${cfg.border} ${cfg.bg} p-3 flex flex-col gap-1.5`}
151
+ >
152
+ <div className="flex items-center justify-between">
153
+ <span className={`text-[10px] font-semibold uppercase tracking-wider ${cfg.text}`}>
154
+ Current Task
155
+ </span>
156
+ <span className={`text-[10px] font-mono ${cfg.text}`}>{cfg.label}</span>
157
+ </div>
158
+ <p className="text-[11px] text-gray-400 leading-relaxed">
159
+ {taskDifficulty === 'easy'
160
+ ? 'Simple SELECT queries, basic filtering and aggregation'
161
+ : taskDifficulty === 'medium'
162
+ ? 'Multi-table JOINs, GROUP BY, subqueries, window functions'
163
+ : 'Complex CTEs, rolling aggregations, cohort analysis, ranking'}
164
+ </p>
165
  </div>
166
+ </section>
167
+ )}
 
 
 
 
 
 
 
168
  </div>
169
  )
170
  }
frontend/src/lib/api.ts CHANGED
@@ -108,6 +108,17 @@ export async function fetchBenchmarkQuestions(
108
  return res.json()
109
  }
110
 
 
 
 
 
 
 
 
 
 
 
 
111
  export async function connectExternalDb(path: string): Promise<{ success: boolean; message: string; tables: { name: string; rows: number }[]; dbLabel: string }> {
112
  const res = await fetch(`${BASE_URL}/api/connect-db`, {
113
  method: 'POST',
 
108
  return res.json()
109
  }
110
 
111
+ const _suggestionsCache = new Map<string, string[]>()
112
+
113
+ export async function fetchSuggestQuestions(cacheKey: string): Promise<string[]> {
114
+ if (_suggestionsCache.has(cacheKey)) return _suggestionsCache.get(cacheKey)!
115
+ const res = await fetch(`${BASE_URL}/api/suggest-questions`)
116
+ if (!res.ok) return []
117
+ const data = await res.json() as { questions: string[] }
118
+ _suggestionsCache.set(cacheKey, data.questions ?? [])
119
+ return data.questions ?? []
120
+ }
121
+
122
  export async function connectExternalDb(path: string): Promise<{ success: boolean; message: string; tables: { name: string; rows: number }[]; dbLabel: string }> {
123
  const res = await fetch(`${BASE_URL}/api/connect-db`, {
124
  method: 'POST',
frontend/src/store/useStore.ts CHANGED
@@ -23,6 +23,12 @@ interface Store {
23
  // DB
24
  dbLabel: string
25
  setDbLabel: (label: string) => void
 
 
 
 
 
 
26
 
27
  // Init / DB
28
  dbSeeded: boolean
@@ -104,6 +110,12 @@ export const useStore = create<Store>((set) => ({
104
  // DB
105
  dbLabel: 'benchmark (built-in)',
106
  setDbLabel: (label) => set({ dbLabel: label }),
 
 
 
 
 
 
107
 
108
  // Init
109
  dbSeeded: false,
 
23
  // DB
24
  dbLabel: string
25
  setDbLabel: (label: string) => void
26
+ isCustomDb: boolean
27
+ setIsCustomDb: (v: boolean) => void
28
+ customDbSuggestions: string[]
29
+ setCustomDbSuggestions: (qs: string[]) => void
30
+ suggestionsLoading: boolean
31
+ setSuggestionsLoading: (v: boolean) => void
32
 
33
  // Init / DB
34
  dbSeeded: boolean
 
110
  // DB
111
  dbLabel: 'benchmark (built-in)',
112
  setDbLabel: (label) => set({ dbLabel: label }),
113
+ isCustomDb: false,
114
+ setIsCustomDb: (v) => set({ isCustomDb: v }),
115
+ customDbSuggestions: [],
116
+ setCustomDbSuggestions: (qs) => set({ customDbSuggestions: qs }),
117
+ suggestionsLoading: false,
118
+ setSuggestionsLoading: (v) => set({ suggestionsLoading: v }),
119
 
120
  // Init
121
  dbSeeded: false,