diff --git a/backend/main.py b/backend/main.py index c06590b..2c6c4ca 100644 --- a/backend/main.py +++ b/backend/main.py @@ -413,13 +413,16 @@ async def websocket_index(websocket: WebSocket, repo_id: str): # Index with progress callback async def progress_callback(files_processed: int, functions_indexed: int, total_files: int): - await websocket.send_json({ - "type": "progress", - "files_processed": files_processed, - "functions_indexed": functions_indexed, - "total_files": total_files, - "progress_pct": int((files_processed / total_files) * 100) if total_files > 0 else 0 - }) + try: + await websocket.send_json({ + "type": "progress", + "files_processed": files_processed, + "functions_indexed": functions_indexed, + "total_files": total_files, + "progress_pct": int((files_processed / total_files) * 100) if total_files > 0 else 0 + }) + except Exception: + pass # Client disconnected, continue indexing anyway # Index repository with progress total_functions = await indexer.index_repository_with_progress( @@ -432,18 +435,27 @@ async def progress_callback(files_processed: int, functions_indexed: int, total_ repo_manager.update_file_count(repo_id, total_functions) # Send completion - await websocket.send_json({ - "type": "complete", - "total_functions": total_functions - }) + try: + await websocket.send_json({ + "type": "complete", + "total_functions": total_functions + }) + except Exception: + pass # Client disconnected except WebSocketDisconnect: print(f"WebSocket disconnected for repo {repo_id}") except Exception as e: - await websocket.send_json({"type": "error", "message": str(e)}) + try: + await websocket.send_json({"type": "error", "message": str(e)}) + except Exception: + pass # Connection already closed repo_manager.update_status(repo_id, "error") finally: - await websocket.close() + try: + await websocket.close() + except Exception: + pass # Already closed @app.post("/api/repos/{repo_id}/index") diff --git a/backend/services/supabase_service.py b/backend/services/supabase_service.py index a776812..75ecd67 100644 --- a/backend/services/supabase_service.py +++ b/backend/services/supabase_service.py @@ -17,10 +17,11 @@ class SupabaseService: def __init__(self): supabase_url = os.getenv("SUPABASE_URL") - supabase_key = os.getenv("SUPABASE_KEY") + # Use service role key to bypass RLS for backend operations + supabase_key = os.getenv("SUPABASE_SERVICE_ROLE_KEY") or os.getenv("SUPABASE_KEY") if not supabase_url or not supabase_key: - raise ValueError("SUPABASE_URL and SUPABASE_KEY must be set") + raise ValueError("SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY (or SUPABASE_KEY) must be set") # Create client with options to avoid auth cleanup issues options = ClientOptions( diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4dbf1ba..5617641 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -37,7 +37,7 @@ "autoprefixer": "^10.4.16", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", - "typescript": "^5.2.2", + "typescript": "^5.9.3", "vite": "^4.5.0" } }, diff --git a/frontend/package.json b/frontend/package.json index 4cac773..40ea0b0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -38,7 +38,7 @@ "autoprefixer": "^10.4.16", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", - "typescript": "^5.2.2", + "typescript": "^5.9.3", "vite": "^4.5.0" } } diff --git a/frontend/src/components/RepoOverview.tsx b/frontend/src/components/RepoOverview.tsx index f4729be..f33bafa 100644 --- a/frontend/src/components/RepoOverview.tsx +++ b/frontend/src/components/RepoOverview.tsx @@ -1,7 +1,8 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef } from 'react' import { toast } from 'sonner' import { Progress } from '@/components/ui/progress' import type { Repository } from '../types' +import { WS_URL } from '../config/api' interface RepoOverviewProps { repo: Repository @@ -10,38 +11,112 @@ interface RepoOverviewProps { apiKey: string } +interface IndexProgress { + files_processed: number + functions_indexed: number + total_files: number + progress_pct: number +} + export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewProps) { const [indexing, setIndexing] = useState(false) - const [progress, setProgress] = useState(0) + const [progress, setProgress] = useState(null) + const wsRef = useRef(null) + const completedRef = useRef(false) // Track if indexing completed successfully + + // Cleanup WebSocket on unmount + useEffect(() => { + return () => { + if (wsRef.current) { + wsRef.current.close() + } + } + }, []) const handleReindex = async () => { setIndexing(true) - setProgress(10) - toast.loading('Starting re-index...', { id: 'reindex' }) + setProgress({ files_processed: 0, functions_indexed: 0, total_files: 0, progress_pct: 0 }) + completedRef.current = false + + // Connect to WebSocket for real-time progress + const wsUrl = `${WS_URL}/ws/index/${repo.id}?token=${apiKey}` + + try { + const ws = new WebSocket(wsUrl) + wsRef.current = ws + + ws.onopen = () => { + toast.loading('Indexing started...', { id: 'reindex' }) + } + + ws.onmessage = (event) => { + const data = JSON.parse(event.data) + + if (data.type === 'progress') { + setProgress({ + files_processed: data.files_processed, + functions_indexed: data.functions_indexed, + total_files: data.total_files, + progress_pct: data.progress_pct + }) + } else if (data.type === 'complete') { + completedRef.current = true + toast.success(`Indexing complete! ${data.total_functions} functions indexed.`, { id: 'reindex' }) + setIndexing(false) + setProgress(null) + onReindex() + } else if (data.type === 'error') { + completedRef.current = true + toast.error(`Indexing failed: ${data.message}`, { id: 'reindex' }) + setIndexing(false) + setProgress(null) + } + } + + ws.onerror = () => { + if (!completedRef.current) { + toast.dismiss('reindex') + fallbackToHttp() + } + } + + ws.onclose = () => { + // Only fallback if we didn't complete successfully + if (!completedRef.current) { + fallbackToHttp() + } + } + + } catch { + fallbackToHttp() + } + } + + const fallbackToHttp = async () => { + if (completedRef.current) return // Already completed + + toast.loading('Using fallback indexing...', { id: 'reindex' }) try { await onReindex() - toast.success('Re-indexing started!', { - id: 'reindex', - description: 'Using incremental mode - 100x faster!' - }) + toast.success('Re-indexing started!', { id: 'reindex' }) - // Simulate progress + let pct = 10 const interval = setInterval(() => { - setProgress(prev => { - if (prev >= 90) return prev - return prev + 10 - }) + pct = Math.min(pct + 10, 90) + setProgress(prev => prev ? { ...prev, progress_pct: pct } : null) }, 1000) setTimeout(() => { clearInterval(interval) - setProgress(100) + setProgress(null) setIndexing(false) + completedRef.current = true }, 8000) - } catch (error) { + } catch { setIndexing(false) + setProgress(null) toast.error('Failed to start re-indexing', { id: 'reindex' }) } } @@ -80,17 +155,18 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr - {/* Indexing Progress */} - {indexing && ( + {/* Indexing Progress - only show when indexing AND progress exists */} + {indexing && progress && (

🔄 Indexing in Progress

- {progress}% + {progress.progress_pct}% +
+ +
+ Files: {progress.files_processed}/{progress.total_files || '?'} + Functions: {progress.functions_indexed}
- -

- Incremental mode - only processing changed files for 100x faster updates -

)} diff --git a/frontend/src/config/api.ts b/frontend/src/config/api.ts index 5e91bbc..a9fbb01 100644 --- a/frontend/src/config/api.ts +++ b/frontend/src/config/api.ts @@ -2,12 +2,11 @@ * API Configuration * * Centralizes API URL configuration for all frontend components. - * - * - Production: Set VITE_API_URL in Vercel dashboard to Railway backend URL - * - Development: Defaults to localhost:8000 (Docker Compose) - * - Local dev without Docker: Can override with .env.local */ const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' -export { API_URL } +// WebSocket URL - convert http(s) to ws(s) +const WS_URL = API_URL.replace(/^http/, 'ws') + +export { API_URL, WS_URL }