Skip to content

Commit 91de35a

Browse files
authored
Merge pull request #28 from DevanshuNEU/feature/websocket-indexing-progress
feat(frontend): Integrate WebSocket for real-time indexing progress
2 parents 047dc32 + 623d670 commit 91de35a

6 files changed

Lines changed: 132 additions & 44 deletions

File tree

backend/main.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -413,13 +413,16 @@ async def websocket_index(websocket: WebSocket, repo_id: str):
413413

414414
# Index with progress callback
415415
async def progress_callback(files_processed: int, functions_indexed: int, total_files: int):
416-
await websocket.send_json({
417-
"type": "progress",
418-
"files_processed": files_processed,
419-
"functions_indexed": functions_indexed,
420-
"total_files": total_files,
421-
"progress_pct": int((files_processed / total_files) * 100) if total_files > 0 else 0
422-
})
416+
try:
417+
await websocket.send_json({
418+
"type": "progress",
419+
"files_processed": files_processed,
420+
"functions_indexed": functions_indexed,
421+
"total_files": total_files,
422+
"progress_pct": int((files_processed / total_files) * 100) if total_files > 0 else 0
423+
})
424+
except Exception:
425+
pass # Client disconnected, continue indexing anyway
423426

424427
# Index repository with progress
425428
total_functions = await indexer.index_repository_with_progress(
@@ -432,18 +435,27 @@ async def progress_callback(files_processed: int, functions_indexed: int, total_
432435
repo_manager.update_file_count(repo_id, total_functions)
433436

434437
# Send completion
435-
await websocket.send_json({
436-
"type": "complete",
437-
"total_functions": total_functions
438-
})
438+
try:
439+
await websocket.send_json({
440+
"type": "complete",
441+
"total_functions": total_functions
442+
})
443+
except Exception:
444+
pass # Client disconnected
439445

440446
except WebSocketDisconnect:
441447
print(f"WebSocket disconnected for repo {repo_id}")
442448
except Exception as e:
443-
await websocket.send_json({"type": "error", "message": str(e)})
449+
try:
450+
await websocket.send_json({"type": "error", "message": str(e)})
451+
except Exception:
452+
pass # Connection already closed
444453
repo_manager.update_status(repo_id, "error")
445454
finally:
446-
await websocket.close()
455+
try:
456+
await websocket.close()
457+
except Exception:
458+
pass # Already closed
447459

448460

449461
@app.post("/api/repos/{repo_id}/index")

backend/services/supabase_service.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ class SupabaseService:
1717

1818
def __init__(self):
1919
supabase_url = os.getenv("SUPABASE_URL")
20-
supabase_key = os.getenv("SUPABASE_KEY")
20+
# Use service role key to bypass RLS for backend operations
21+
supabase_key = os.getenv("SUPABASE_SERVICE_ROLE_KEY") or os.getenv("SUPABASE_KEY")
2122

2223
if not supabase_url or not supabase_key:
23-
raise ValueError("SUPABASE_URL and SUPABASE_KEY must be set")
24+
raise ValueError("SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY (or SUPABASE_KEY) must be set")
2425

2526
# Create client with options to avoid auth cleanup issues
2627
options = ClientOptions(

frontend/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"autoprefixer": "^10.4.16",
3939
"postcss": "^8.4.32",
4040
"tailwindcss": "^3.4.0",
41-
"typescript": "^5.2.2",
41+
"typescript": "^5.9.3",
4242
"vite": "^4.5.0"
4343
}
4444
}

frontend/src/components/RepoOverview.tsx

Lines changed: 98 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { useState, useEffect } from 'react'
1+
import { useState, useEffect, useRef } from 'react'
22
import { toast } from 'sonner'
33
import { Progress } from '@/components/ui/progress'
44
import type { Repository } from '../types'
5+
import { WS_URL } from '../config/api'
56

67
interface RepoOverviewProps {
78
repo: Repository
@@ -10,38 +11,112 @@ interface RepoOverviewProps {
1011
apiKey: string
1112
}
1213

14+
interface IndexProgress {
15+
files_processed: number
16+
functions_indexed: number
17+
total_files: number
18+
progress_pct: number
19+
}
20+
1321
export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewProps) {
1422
const [indexing, setIndexing] = useState(false)
15-
const [progress, setProgress] = useState(0)
23+
const [progress, setProgress] = useState<IndexProgress | null>(null)
24+
const wsRef = useRef<WebSocket | null>(null)
25+
const completedRef = useRef(false) // Track if indexing completed successfully
26+
27+
// Cleanup WebSocket on unmount
28+
useEffect(() => {
29+
return () => {
30+
if (wsRef.current) {
31+
wsRef.current.close()
32+
}
33+
}
34+
}, [])
1635

1736
const handleReindex = async () => {
1837
setIndexing(true)
19-
setProgress(10)
20-
toast.loading('Starting re-index...', { id: 'reindex' })
38+
setProgress({ files_processed: 0, functions_indexed: 0, total_files: 0, progress_pct: 0 })
39+
completedRef.current = false
40+
41+
// Connect to WebSocket for real-time progress
42+
const wsUrl = `${WS_URL}/ws/index/${repo.id}?token=${apiKey}`
43+
44+
try {
45+
const ws = new WebSocket(wsUrl)
46+
wsRef.current = ws
47+
48+
ws.onopen = () => {
49+
toast.loading('Indexing started...', { id: 'reindex' })
50+
}
51+
52+
ws.onmessage = (event) => {
53+
const data = JSON.parse(event.data)
54+
55+
if (data.type === 'progress') {
56+
setProgress({
57+
files_processed: data.files_processed,
58+
functions_indexed: data.functions_indexed,
59+
total_files: data.total_files,
60+
progress_pct: data.progress_pct
61+
})
62+
} else if (data.type === 'complete') {
63+
completedRef.current = true
64+
toast.success(`Indexing complete! ${data.total_functions} functions indexed.`, { id: 'reindex' })
65+
setIndexing(false)
66+
setProgress(null)
67+
onReindex()
68+
} else if (data.type === 'error') {
69+
completedRef.current = true
70+
toast.error(`Indexing failed: ${data.message}`, { id: 'reindex' })
71+
setIndexing(false)
72+
setProgress(null)
73+
}
74+
}
75+
76+
ws.onerror = () => {
77+
if (!completedRef.current) {
78+
toast.dismiss('reindex')
79+
fallbackToHttp()
80+
}
81+
}
82+
83+
ws.onclose = () => {
84+
// Only fallback if we didn't complete successfully
85+
if (!completedRef.current) {
86+
fallbackToHttp()
87+
}
88+
}
89+
90+
} catch {
91+
fallbackToHttp()
92+
}
93+
}
94+
95+
const fallbackToHttp = async () => {
96+
if (completedRef.current) return // Already completed
97+
98+
toast.loading('Using fallback indexing...', { id: 'reindex' })
2199

22100
try {
23101
await onReindex()
24-
toast.success('Re-indexing started!', {
25-
id: 'reindex',
26-
description: 'Using incremental mode - 100x faster!'
27-
})
102+
toast.success('Re-indexing started!', { id: 'reindex' })
28103

29-
// Simulate progress
104+
let pct = 10
30105
const interval = setInterval(() => {
31-
setProgress(prev => {
32-
if (prev >= 90) return prev
33-
return prev + 10
34-
})
106+
pct = Math.min(pct + 10, 90)
107+
setProgress(prev => prev ? { ...prev, progress_pct: pct } : null)
35108
}, 1000)
36109

37110
setTimeout(() => {
38111
clearInterval(interval)
39-
setProgress(100)
112+
setProgress(null)
40113
setIndexing(false)
114+
completedRef.current = true
41115
}, 8000)
42116

43-
} catch (error) {
117+
} catch {
44118
setIndexing(false)
119+
setProgress(null)
45120
toast.error('Failed to start re-indexing', { id: 'reindex' })
46121
}
47122
}
@@ -80,17 +155,18 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr
80155
</div>
81156
</div>
82157

83-
{/* Indexing Progress */}
84-
{indexing && (
158+
{/* Indexing Progress - only show when indexing AND progress exists */}
159+
{indexing && progress && (
85160
<div className="card p-6 border-2 border-blue-500 bg-blue-50">
86161
<div className="flex items-center justify-between mb-3">
87162
<h3 className="text-base font-semibold text-gray-900">🔄 Indexing in Progress</h3>
88-
<span className="text-sm font-mono text-blue-600">{progress}%</span>
163+
<span className="text-sm font-mono text-blue-600">{progress.progress_pct}%</span>
164+
</div>
165+
<Progress value={progress.progress_pct} className="h-2" />
166+
<div className="flex justify-between text-xs text-gray-600 mt-2">
167+
<span>Files: {progress.files_processed}/{progress.total_files || '?'}</span>
168+
<span>Functions: {progress.functions_indexed}</span>
89169
</div>
90-
<Progress value={progress} className="h-2" />
91-
<p className="text-xs text-gray-600 mt-2">
92-
Incremental mode - only processing changed files for 100x faster updates
93-
</p>
94170
</div>
95171
)}
96172

frontend/src/config/api.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
* API Configuration
33
*
44
* Centralizes API URL configuration for all frontend components.
5-
*
6-
* - Production: Set VITE_API_URL in Vercel dashboard to Railway backend URL
7-
* - Development: Defaults to localhost:8000 (Docker Compose)
8-
* - Local dev without Docker: Can override with .env.local
95
*/
106

117
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
128

13-
export { API_URL }
9+
// WebSocket URL - convert http(s) to ws(s)
10+
const WS_URL = API_URL.replace(/^http/, 'ws')
11+
12+
export { API_URL, WS_URL }

0 commit comments

Comments
 (0)