Skip to content

Commit be459a9

Browse files
committed
feat(frontend): Add WebSocket for real-time indexing progress (#17)
- Connect to /ws/index/{repo_id} with JWT token - Show real-time files processed, functions indexed, progress % - Graceful fallback to HTTP if WebSocket fails - Add WS_URL helper in config/api.ts Closes #17
1 parent 047dc32 commit be459a9

4 files changed

Lines changed: 96 additions & 28 deletions

File tree

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: 90 additions & 21 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,37 +11,104 @@ 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+
26+
// Cleanup WebSocket on unmount
27+
useEffect(() => {
28+
return () => {
29+
if (wsRef.current) {
30+
wsRef.current.close()
31+
}
32+
}
33+
}, [])
1634

1735
const handleReindex = async () => {
1836
setIndexing(true)
19-
setProgress(10)
20-
toast.loading('Starting re-index...', { id: 'reindex' })
37+
setProgress({ files_processed: 0, functions_indexed: 0, total_files: 0, progress_pct: 0 })
38+
39+
// Connect to WebSocket for real-time progress
40+
const wsUrl = `${WS_URL}/ws/index/${repo.id}?token=${apiKey}`
41+
42+
try {
43+
const ws = new WebSocket(wsUrl)
44+
wsRef.current = ws
45+
46+
ws.onopen = () => {
47+
toast.loading('Indexing started...', { id: 'reindex' })
48+
}
49+
50+
ws.onmessage = (event) => {
51+
const data = JSON.parse(event.data)
52+
53+
if (data.type === 'progress') {
54+
setProgress({
55+
files_processed: data.files_processed,
56+
functions_indexed: data.functions_indexed,
57+
total_files: data.total_files,
58+
progress_pct: data.progress_pct
59+
})
60+
} else if (data.type === 'complete') {
61+
setProgress(prev => prev ? { ...prev, progress_pct: 100 } : null)
62+
toast.success(`Indexing complete! ${data.total_functions} functions indexed.`, { id: 'reindex' })
63+
setIndexing(false)
64+
onReindex() // Refresh repo data
65+
} else if (data.type === 'error') {
66+
toast.error(`Indexing failed: ${data.message}`, { id: 'reindex' })
67+
setIndexing(false)
68+
}
69+
}
70+
71+
ws.onerror = () => {
72+
// WebSocket error - fall back to HTTP
73+
toast.dismiss('reindex')
74+
fallbackToHttp()
75+
}
76+
77+
ws.onclose = (event) => {
78+
if (event.code !== 1000 && indexing) {
79+
// Abnormal close while still indexing - fall back to HTTP
80+
fallbackToHttp()
81+
}
82+
}
83+
84+
} catch {
85+
// WebSocket connection failed - fall back to HTTP
86+
fallbackToHttp()
87+
}
88+
}
89+
90+
const fallbackToHttp = async () => {
91+
// Fallback: Use HTTP endpoint with simulated progress
92+
toast.loading('Using fallback indexing...', { id: 'reindex' })
2193

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

29-
// Simulate progress
98+
// Simulate progress for HTTP fallback
99+
let pct = 10
30100
const interval = setInterval(() => {
31-
setProgress(prev => {
32-
if (prev >= 90) return prev
33-
return prev + 10
34-
})
101+
pct = Math.min(pct + 10, 90)
102+
setProgress(prev => prev ? { ...prev, progress_pct: pct } : null)
35103
}, 1000)
36104

37105
setTimeout(() => {
38106
clearInterval(interval)
39-
setProgress(100)
107+
setProgress(prev => prev ? { ...prev, progress_pct: 100 } : null)
40108
setIndexing(false)
41109
}, 8000)
42110

43-
} catch (error) {
111+
} catch {
44112
setIndexing(false)
45113
toast.error('Failed to start re-indexing', { id: 'reindex' })
46114
}
@@ -81,16 +149,17 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr
81149
</div>
82150

83151
{/* Indexing Progress */}
84-
{indexing && (
152+
{indexing && progress && (
85153
<div className="card p-6 border-2 border-blue-500 bg-blue-50">
86154
<div className="flex items-center justify-between mb-3">
87155
<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>
156+
<span className="text-sm font-mono text-blue-600">{progress.progress_pct}%</span>
157+
</div>
158+
<Progress value={progress.progress_pct} className="h-2" />
159+
<div className="flex justify-between text-xs text-gray-600 mt-2">
160+
<span>Files: {progress.files_processed}/{progress.total_files || '?'}</span>
161+
<span>Functions: {progress.functions_indexed}</span>
89162
</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>
94163
</div>
95164
)}
96165

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)