From 9adb24799b9b17135914002b38e651cda8e9dd1d Mon Sep 17 00:00:00 2001 From: Pradeep Date: Tue, 28 Oct 2025 09:19:21 +0530 Subject: [PATCH] implemented webshocket for ai analysis --- src/app/github/repos/page.tsx | 262 ++++++++- src/app/sse-test/page.tsx | 111 ++++ .../ai/AIAnalysisProgressTracker.tsx | 511 ++++++++++++++++++ src/components/apis/authApiClients.tsx | 2 +- src/config/backend.ts | 4 +- 5 files changed, 879 insertions(+), 11 deletions(-) create mode 100644 src/app/sse-test/page.tsx create mode 100644 src/components/ai/AIAnalysisProgressTracker.tsx diff --git a/src/app/github/repos/page.tsx b/src/app/github/repos/page.tsx index 8b77f03..7453015 100644 --- a/src/app/github/repos/page.tsx +++ b/src/app/github/repos/page.tsx @@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Github, Gitlab, @@ -28,6 +29,7 @@ import { authApiClient } from '@/components/apis/authApiClients'; import Link from 'next/link'; import RepositoryAnalysis from '@/components/repository-analysis'; import { useAuth } from '@/contexts/auth-context'; +import AIAnalysisProgressTracker from '@/components/ai/AIAnalysisProgressTracker'; const GitHubReposPage: React.FC = () => { const [repositories, setRepositories] = useState([]); @@ -40,6 +42,12 @@ const GitHubReposPage: React.FC = () => { const [selectedRepoId, setSelectedRepoId] = useState(null); const [analysisResults, setAnalysisResults] = useState<{[key: string]: any}>({}); const { user } = useAuth(); + + // Progress tracking dialog state + const [showProgressDialog, setShowProgressDialog] = useState(false); + const [currentAnalysisId, setCurrentAnalysisId] = useState(null); + const [currentRepoName, setCurrentRepoName] = useState(''); + const [currentRepoId, setCurrentRepoId] = useState(null); // Load repositories useEffect(() => { @@ -80,34 +88,178 @@ const GitHubReposPage: React.FC = () => { } }; - const handleAiAnalysis = async (repositoryId: string) => { + const handleAiAnalysis = async (repositoryId: string, repoName: string) => { try { + console.log('🚀 [DEBUG] Starting AI Analysis for repository:', repositoryId); + console.log('🚀 [DEBUG] Repository name:', repoName); + console.log('🚀 [DEBUG] User:', user); + setAiAnalysisLoading(repositoryId); setAiAnalysisError(null); // Get user ID from auth context - const userId = user?.id; + let userId = user?.id; + + // Fallback: try to get user ID from localStorage if context is not loaded if (!userId) { - throw new Error('User not authenticated'); + try { + const storedUser = localStorage.getItem('codenuk_user'); + if (storedUser) { + const userData = JSON.parse(storedUser); + userId = userData?.id; + console.log('🔄 [DEBUG] Using user ID from localStorage:', userId); + } + } catch (error) { + console.error('❌ [DEBUG] Failed to parse user data from localStorage:', error); + } + } + + if (!userId) { + console.error('❌ [DEBUG] User not authenticated'); + console.error('❌ [DEBUG] User object:', user); + console.error('❌ [DEBUG] User from localStorage:', localStorage.getItem('codenuk_user')); + throw new Error('User not authenticated. Please sign in again.'); + } + + // Check if user has valid authentication token + const token = localStorage.getItem('accessToken'); + if (!token) { + console.error('❌ [DEBUG] No authentication token found'); + throw new Error('Authentication token not found. Please sign in again.'); } console.log('🚀 Starting AI Analysis for repository:', repositoryId); console.log('🔍 User ID:', userId); + console.log('🔍 Auth Token exists:', !!token); + console.log('🔍 Token length:', token?.length || 0); + console.log('🔍 User data from localStorage:', localStorage.getItem('codenuk_user')); + console.log('🔍 Access token from localStorage:', localStorage.getItem('accessToken')); + + // Test the getAccessToken function directly + const { getAccessToken } = await import('@/components/apis/authApiClients'); + const testToken = getAccessToken(); + console.log('🔍 Test token from getAccessToken:', !!testToken, testToken?.length || 0); + + // Call the new AI Analysis Service endpoint first + console.log('📡 [DEBUG] Making API request to:', '/api/ai-analysis/analyze-repository'); + console.log('📡 [DEBUG] Request payload:', { + repository_id: repositoryId, + user_id: userId, + output_format: 'pdf', + max_files: 0, + analysis_type: 'full' + }); + + // Add request interceptor to log the actual request being sent + const requestInterceptor = authApiClient.interceptors.request.use( + (config) => { + console.log('📡 [DEBUG] Request interceptor called'); + console.log('📡 [DEBUG] Actual request config:', { + url: config.url, + method: config.method, + headers: config.headers, + data: config.data, + baseURL: config.baseURL, + timeout: config.timeout + }); + + // Manually add headers if they're missing (fallback) + if (!config.headers.Authorization && token) { + console.log('🔧 [DEBUG] Manually adding Authorization header'); + config.headers.Authorization = `Bearer ${token}`; + } + if (!config.headers['x-user-id'] && userId) { + console.log('🔧 [DEBUG] Manually adding x-user-id header'); + config.headers['x-user-id'] = userId; + } + + // Check if headers are properly set after manual addition + if (!config.headers.Authorization) { + console.error('❌ [DEBUG] No Authorization header found in request'); + console.error('❌ [DEBUG] Available headers:', Object.keys(config.headers || {})); + } + if (!config.headers['x-user-id']) { + console.error('❌ [DEBUG] No x-user-id header found in request'); + console.error('❌ [DEBUG] Available headers:', Object.keys(config.headers || {})); + } + + return config; + }, + (error) => { + console.error('📡 [DEBUG] Request interceptor error:', error); + return Promise.reject(error); + } + ); + + // Add response interceptor to log the response + const responseInterceptor = authApiClient.interceptors.response.use( + (response) => { + console.log('📡 [DEBUG] Response received:', { + status: response.status, + statusText: response.statusText, + headers: response.headers, + data: response.data + }); + return response; + }, + (error) => { + console.error('📡 [DEBUG] Response error:', { + message: error.message, + code: error.code, + status: error.response?.status, + statusText: error.response?.statusText, + data: error.response?.data, + config: error.config + }); + return Promise.reject(error); + } + ); + + console.log('📡 [DEBUG] About to make API request...'); - // Call the new AI Analysis Service endpoint const response = await authApiClient.post('/api/ai-analysis/analyze-repository', { repository_id: repositoryId, user_id: userId, output_format: 'pdf', max_files: 0, // 0 = unlimited files analysis_type: 'full' // Use full AI analysis for detailed results + }, { + timeout: 600000, // 10 minute timeout for AI analysis + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + 'x-user-id': userId + } }); + console.log('📡 [DEBUG] API request completed successfully'); + + // Remove the interceptors after the request + authApiClient.interceptors.request.eject(requestInterceptor); + authApiClient.interceptors.response.eject(responseInterceptor); + + console.log('📡 [DEBUG] API Response:', response); + const data = response.data; console.log('✅ AI Analysis Result:', data); + console.log('🔍 [DEBUG] Setting showProgressDialog to true'); + console.log('🔍 [DEBUG] Analysis ID:', data.analysis_id); if (data.success) { + // Set analysis ID for progress tracking BEFORE opening dialog + setCurrentAnalysisId(data.analysis_id); + setCurrentRepoId(repositoryId); // Save repo ID for completion callback + + // Show progress dialog with analysis ID + setCurrentRepoName(repoName); + setShowProgressDialog(true); + + // Clear loading state since modal is now open + setAiAnalysisLoading(null); + + console.log('✅ [DEBUG] Modal should now be visible'); + // Store analysis results in state setAnalysisResults(prev => ({ ...prev, @@ -118,15 +270,40 @@ const GitHubReposPage: React.FC = () => { timestamp: new Date().toISOString() } })); - - // Show success message - alert(`AI Analysis completed successfully!\n\nAnalysis ID: ${data.analysis_id}\nTotal Files: ${data.stats?.total_files || 'N/A'}\nCode Quality: ${data.stats?.code_quality_score || 'N/A'}/10`); } else { + console.log('❌ [DEBUG] Analysis failed:', data.message); throw new Error(data.message || 'Analysis failed'); } } catch (err: any) { console.error('❌ AI Analysis Error:', err); + console.error('❌ Error details:', { + message: err.message, + response: err.response?.data, + status: err.response?.status, + config: err.config, + code: err.code, + stack: err.stack + }); + + // Additional debugging for network issues + if (err.code === 'ERR_NETWORK' || err.message?.includes('Network Error')) { + console.error('🌐 Network Error - Check if backend services are running'); + console.error('🌐 Expected AI Analysis Service URL: http://localhost:8022'); + console.error('🌐 Expected API Gateway URL: http://localhost:8000'); + } + + // Check for authentication issues + if (err.response?.status === 401) { + console.error('🔐 Authentication Error - User may need to sign in again'); + console.error('🔐 Current user:', user); + console.error('🔐 Auth token exists:', !!localStorage.getItem('accessToken')); + } + + // Check for CORS issues + if (err.message?.includes('CORS') || err.message?.includes('cross-origin')) { + console.error('🌐 CORS Error - Check API Gateway CORS configuration'); + } let errorMessage = 'AI Analysis failed'; @@ -146,11 +323,42 @@ const GitHubReposPage: React.FC = () => { } setAiAnalysisError(errorMessage); + setShowProgressDialog(false); alert(`AI Analysis failed: ${errorMessage}`); } finally { setAiAnalysisLoading(null); } }; + + const handleAnalysisComplete = (completionData: any) => { + console.log('✅ Analysis completed, full data:', completionData); + + // Use the tracked repository ID + if (currentRepoId) { + // Save complete analysis results to state + setAnalysisResults(prev => ({ + ...prev, + [currentRepoId]: { + analysis_id: completionData.analysis_id || currentAnalysisId || '', + report_path: completionData.report_path || '', + stats: completionData.stats || {}, + timestamp: new Date().toISOString() + } + })); + console.log('📊 Saved analysis results for repo:', currentRepoId); + console.log('📄 Report path:', completionData.report_path); + console.log('📈 Stats:', completionData.stats); + } else { + console.error('❌ No repository ID tracked for this analysis'); + } + + // Keep dialog open so user can see completion status + }; + + const handleAnalysisError = (error: string) => { + console.error('❌ Analysis error:', error); + setAiAnalysisError(error); + }; const downloadAnalysisReport = async (repositoryId: string) => { const analysis = analysisResults[repositoryId]; @@ -477,8 +685,13 @@ const GitHubReposPage: React.FC = () => { variant="secondary" className="w-full" onClick={() => { + console.log('🔘 [DEBUG] AI Analysis button clicked for repo:', repo); + console.log('🔘 [DEBUG] Repository ID:', repo.id); + console.log('🔘 [DEBUG] Repository name:', repo.repository_name || repo.name); if (repo.id) { - handleAiAnalysis(String(repo.id)); + handleAiAnalysis(String(repo.id), repo.repository_name || repo.name || 'Repository'); + } else { + console.error('❌ [DEBUG] No repository ID available'); } }} disabled={aiAnalysisLoading === repo.id} @@ -622,6 +835,39 @@ const GitHubReposPage: React.FC = () => { )} + + {/* AI Analysis Progress Dialog */} + + + + AI Repository Analysis in Progress + + Analyzing {currentRepoName} with AI-powered insights + + + +
+ {currentAnalysisId ? ( + + ) : ( +
+
+ Initializing analysis... +
+ )} +
+ +
+ +
+
+
); }; diff --git a/src/app/sse-test/page.tsx b/src/app/sse-test/page.tsx new file mode 100644 index 0000000..00670f0 --- /dev/null +++ b/src/app/sse-test/page.tsx @@ -0,0 +1,111 @@ +'use client'; + +import React, { useEffect, useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; + +export default function SSETestPage() { + const [isConnected, setIsConnected] = useState(false); + const [events, setEvents] = useState([]); + const [error, setError] = useState(null); + const [analysisId, setAnalysisId] = useState('test_analysis_id'); + + const connectSSE = () => { + const apiGatewayUrl = process.env.NEXT_PUBLIC_API_GATEWAY_URL || 'http://localhost:8000'; + const eventSourceUrl = `${apiGatewayUrl}/api/ai-analysis/progress/${analysisId}`; + + console.log('🔌 Connecting to SSE:', eventSourceUrl); + + const eventSource = new EventSource(eventSourceUrl); + + eventSource.onopen = () => { + console.log('✅ SSE connection established'); + setIsConnected(true); + setError(null); + setEvents(prev => [...prev, 'Connection established']); + }; + + eventSource.onmessage = (event) => { + console.log('📥 SSE Event:', event.data); + setEvents(prev => [...prev, `Message: ${event.data}`]); + }; + + eventSource.onerror = (err) => { + console.error('❌ SSE connection error:', err); + setIsConnected(false); + setError('Connection error occurred'); + setEvents(prev => [...prev, 'Connection error']); + }; + + return eventSource; + }; + + const [eventSource, setEventSource] = useState(null); + + const handleConnect = () => { + if (eventSource) { + eventSource.close(); + } + const newEventSource = connectSSE(); + setEventSource(newEventSource); + }; + + const handleDisconnect = () => { + if (eventSource) { + eventSource.close(); + setEventSource(null); + setIsConnected(false); + setEvents(prev => [...prev, 'Connection closed']); + } + }; + + useEffect(() => { + return () => { + if (eventSource) { + eventSource.close(); + } + }; + }, [eventSource]); + + return ( +
+ + + SSE Connection Test + + +
+
+ Status: {isConnected ? 'Connected' : 'Disconnected'} +
+ +
+ + +
+ + {error && ( +
+ Error: {error} +
+ )} + +
+

Events:

+
+ {events.map((event, index) => ( +
+ {event} +
+ ))} +
+
+ + +
+ ); +} diff --git a/src/components/ai/AIAnalysisProgressTracker.tsx b/src/components/ai/AIAnalysisProgressTracker.tsx new file mode 100644 index 0000000..27e9046 --- /dev/null +++ b/src/components/ai/AIAnalysisProgressTracker.tsx @@ -0,0 +1,511 @@ +'use client'; + +import React, { useEffect, useState, useCallback, useRef } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Progress } from '@/components/ui/progress'; +import { Badge } from '@/components/ui/badge'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { CheckCircle2, Circle, XCircle, Loader2, FileCode, FolderTree, FileCheck } from 'lucide-react'; + +interface ProgressEvent { + analysis_id: string; + event: string; + data: { + message: string; + file_path?: string; + current?: number; + total?: number; + percent?: number; + quality_score?: number; + issues_count?: number; + total_files?: number; + error?: string; + // Batch event properties + batch?: number; + total_batches?: number; + files_processed?: number; + // Smart Batching properties + files?: string[]; + batch_size?: number; + processing_mode?: string; + // Analysis completion properties + report_path?: string; + analysis_id?: string; + stats?: any; + timestamp?: string; + }; + timestamp: string; +} + +interface FileProgress { + path: string; + status: 'pending' | 'analyzing' | 'completed' | 'error'; + qualityScore?: number; + issuesCount?: number; +} + +interface AIAnalysisProgressTrackerProps { + analysisId: string; + onComplete?: (completionData: any) => void; + onError?: (error: string) => void; +} + +export const AIAnalysisProgressTracker: React.FC = ({ + analysisId, + onComplete, + onError, +}) => { + const [events, setEvents] = useState([]); + const [files, setFiles] = useState>(new Map()); + const [currentFile, setCurrentFile] = useState(''); + const [progress, setProgress] = useState(0); + const [totalFiles, setTotalFiles] = useState(0); + const [currentFileNum, setCurrentFileNum] = useState(0); + const [phase, setPhase] = useState('Starting...'); + const [isConnected, setIsConnected] = useState(false); + const [error, setError] = useState(null); + const currentFileRef = useRef(null); + + const handleProgressEvent = useCallback((event: ProgressEvent) => { + setEvents((prev) => [...prev, event]); + + switch (event.event) { + case 'analysis_started': + setPhase('Analysis Started'); + setProgress(0); + break; + + case 'files_discovered': + setTotalFiles(event.data.total_files || 0); + setPhase(`Found ${event.data.total_files} files`); + // Initialize file map + setFiles(new Map()); + break; + + case 'file_analysis_started': + if (event.data.file_path) { + setCurrentFile(event.data.file_path); + setCurrentFileNum(event.data.current || 0); + setProgress(event.data.percent || 0); + setPhase(`Analyzing file ${event.data.current}/${event.data.total}`); + + setFiles((prev) => { + const newMap = new Map(prev); + newMap.set(event.data.file_path!, { + path: event.data.file_path!, + status: 'analyzing', + }); + return newMap; + }); + + // Auto-scroll to current file + setTimeout(() => { + currentFileRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + }, 100); + } + break; + + case 'file_analysis_completed': + if (event.data.file_path) { + setFiles((prev) => { + const newMap = new Map(prev); + newMap.set(event.data.file_path!, { + path: event.data.file_path!, + status: 'completed', + qualityScore: event.data.quality_score, + issuesCount: event.data.issues_count, + }); + return newMap; + }); + } + break; + + case 'file_analysis_error': + if (event.data.file_path) { + setFiles((prev) => { + const newMap = new Map(prev); + newMap.set(event.data.file_path!, { + path: event.data.file_path!, + status: 'error', + }); + return newMap; + }); + } + break; + + case 'smart_batch_started': + // Smart Batching: Multiple files in single API call + if (event.data.files && Array.isArray(event.data.files)) { + setPhase(`Smart Batch: Processing ${event.data.files.length} files in single API call`); + // Mark all files in batch as analyzing + event.data.files.forEach((filePath: string) => { + setFiles((prev) => { + const newMap = new Map(prev); + newMap.set(filePath, { + path: filePath, + status: 'analyzing', + }); + return newMap; + }); + }); + } + break; + + case 'smart_batch_completed': + // Smart Batching: Batch completed + setProgress(event.data.percent || 70); + setCurrentFileNum(event.data.files_processed || 0); + if (event.data.processing_mode === 'smart_batching') { + setPhase(`Smart Batch ${event.data.batch || 0}/${event.data.total_batches || 0} completed (${event.data.files_processed || 0}/${event.data.total_files || 0} files)`); + } + break; + + case 'batch_completed': + // Update progress but don't override phase if we're showing individual files + setProgress(event.data.percent || 70); + setCurrentFileNum(event.data.files_processed || 0); + // Only update phase if no current file is being shown + if (!currentFile) { + setPhase(`Completed batch ${event.data.batch || 0}/${event.data.total_batches || 0} (${event.data.files_processed || 0}/${event.data.total_files || 0} files)`); + } + break; + + case 'repository_analysis_started': + setPhase('Repository-level Analysis'); + setProgress(event.data.percent || 70); + setCurrentFile(''); + break; + + case 'report_generation_started': + setPhase('Generating PDF Report'); + setProgress(event.data.percent || 85); + break; + + case 'analysis_completed': + setPhase('Analysis Completed'); + setProgress(100); + if (onComplete) { + // Pass the full completion data, not just report_path + onComplete(event.data as any); + } + break; + + case 'analysis_error': + setPhase('Analysis Failed'); + setError(event.data.error || event.data.message); + if (onError) { + onError(event.data.error || event.data.message); + } + break; + } + }, [onComplete, onError]); + + useEffect(() => { + if (!analysisId) { + console.log('⚠️ No analysis ID provided'); + return; + } + + const apiGatewayUrl = process.env.NEXT_PUBLIC_API_GATEWAY_URL || 'http://localhost:8000'; + const eventSourceUrl = `${apiGatewayUrl}/api/ai-analysis/progress/${analysisId}`; + + console.log('🔌 Connecting to SSE:', eventSourceUrl); + console.log('🔌 Analysis ID:', analysisId); + + // Reset state when starting new connection + setEvents([]); + setFiles(new Map()); + setError(null); + setIsConnected(false); + + // Create EventSource with error handling + // Note: EventSource doesn't support custom headers, but the API Gateway + // is configured to allow unauthenticated access for AI analysis + const eventSource = new EventSource(eventSourceUrl); + + eventSource.onopen = () => { + console.log('✅ SSE connection established'); + setIsConnected(true); + setError(null); // Clear any previous errors + }; + + eventSource.onmessage = (event) => { + try { + console.log('📥 Raw SSE Event:', event.data); + + // Handle keepalive pings + if (event.data.startsWith(': ')) { + console.log('💓 SSE Keepalive ping received'); + return; + } + + // Mark that we've received events + hasReceivedEvents = true; + + const data = JSON.parse(event.data) as ProgressEvent; + console.log('📥 Parsed SSE Event:', data.event, data.data); + handleProgressEvent(data); + + // Add to events list for debugging + setEvents(prev => [...prev, event.data]); + + // Reset connection state on successful message + if (!isConnected) { + console.log('🔄 SSE connection restored'); + setIsConnected(true); + setError(null); + } + } catch (err) { + console.error('❌ Error parsing SSE event:', err); + console.error('❌ Raw event data:', event.data); + } + }; + + eventSource.onerror = (err) => { + console.log('🔄 SSE connection state changed:', eventSource.readyState); + + // Only set disconnected if the connection is actually closed + if (eventSource.readyState === EventSource.CLOSED) { + setIsConnected(false); + // Don't show error if progress is 100% (analysis completed successfully) + if (progress < 100) { + setError('Connection closed. Analysis may have completed or failed.'); + console.log('❌ SSE connection closed before completion'); + } else { + console.log('✅ SSE connection closed after successful completion'); + } + } else if (eventSource.readyState === EventSource.CONNECTING) { + console.log('🔄 SSE reconnecting...'); + } + }; + + // Add timeout to handle cases where analysis completes very quickly + // Use a ref to track if we've received any events to avoid race conditions + let hasReceivedEvents = false; + + const timeoutId = setTimeout(() => { + console.log(`⏰ Timeout check: isConnected=${isConnected}, hasReceivedEvents=${hasReceivedEvents}`); + if (!isConnected && !hasReceivedEvents) { + console.log('⏰ SSE connection timeout - no events received and not connected'); + setError('Analysis may have completed already. Please check the results.'); + } else if (isConnected && !hasReceivedEvents) { + console.log('⏰ SSE connection timeout - connected but no events received'); + setError('Analysis is taking longer than expected. Please wait...'); + } else if (isConnected && hasReceivedEvents) { + console.log('⏰ SSE connection timeout - connected and events received, analysis may be continuing'); + // Don't set error if we're connected and have received events + } + }, 120000); // Increased to 2 minutes for real analysis + + return () => { + console.log('🔌 Closing SSE connection'); + clearTimeout(timeoutId); + eventSource.close(); + setIsConnected(false); + }; + }, [analysisId, handleProgressEvent]); + + const fileList = Array.from(files.values()); + const completedFiles = Math.max(fileList.filter((f) => f.status === 'completed').length, currentFileNum); + const errorFiles = fileList.filter((f) => f.status === 'error').length; + + return ( +
+ {/* Connection Status */} +
+
+
+ {isConnected && ( +
+ )} +
+ + {isConnected ? '● Live Analysis' : 'Connecting...'} + +
+ + {/* Main Progress Card */} + + + + {progress < 100 ? ( +
+ +
+
+ ) : ( +
+ +
+
+ )} + {phase} +
+ + {currentFile && ( + + + {currentFile} + + )} + +
+ + {/* Progress Bar */} +
+
+ {Math.round(progress)}% + {totalFiles > 0 && ( + + {currentFileNum}/{totalFiles} files + + )} +
+
+
+
+
+
+
+ + {/* Statistics */} + {totalFiles > 0 && ( +
+
+ + {completedFiles} completed +
+ {errorFiles > 0 && ( +
+ + {errorFiles} errors +
+ )} +
+ )} + + {/* Error Display */} + {error && ( +
+
+
+ +
+
+ Error: +
+

{error}

+
+ )} +
+
+ + {/* File List */} + {totalFiles > 0 && ( + + + +
+ +
+
+ Files Analysis Progress + + {fileList.length}/{totalFiles} + +
+
+ + + {fileList.length === 0 ? ( +
+ + Preparing files for analysis... +
+ ) : ( +
+ {fileList.map((file, index) => ( +
+
+ {file.status === 'pending' && ( +
+ +
+ )} + {file.status === 'analyzing' && ( +
+ +
+
+ )} + {file.status === 'completed' && ( +
+ +
+
+ )} + {file.status === 'error' && ( + + )} + + + {file.path} + +
+ +
+ {file.qualityScore !== undefined && ( + = 8 ? 'default' : file.qualityScore >= 5 ? 'secondary' : 'destructive'} + className={`font-bold ${ + file.qualityScore >= 8 ? 'bg-orange-600 text-white border border-orange-700' : + file.qualityScore >= 5 ? 'bg-orange-500/50 text-orange-200 border border-orange-500' : + 'bg-red-600 text-white border border-red-700' + }`} + > + {file.qualityScore.toFixed(1)}/10 + + )} + {file.issuesCount !== undefined && file.issuesCount > 0 && ( + + {file.issuesCount} {file.issuesCount === 1 ? 'issue' : 'issues'} + + )} +
+
+ ))} +
+ )} +
+
+
+ )} +
+ ); +}; + +export default AIAnalysisProgressTracker; + diff --git a/src/components/apis/authApiClients.tsx b/src/components/apis/authApiClients.tsx index 3c0b214..b7d6427 100644 --- a/src/components/apis/authApiClients.tsx +++ b/src/components/apis/authApiClients.tsx @@ -108,7 +108,7 @@ const addAuthTokenInterceptor = (client: typeof authApiClient) => { (config.headers as any)['x-user-id'] = userId; // Debug: Log user ID being sent (only for AI endpoints) - if (config.url?.includes('/api/ai/')) { + if (config.url?.includes('/api/ai/') || config.url?.includes('/api/ai-analysis/')) { console.log('🔍 [USER-ID] Sending user ID:', userId, 'for request:', config.url); console.log('🔍 [USER-ID] Request headers:', { 'x-user-id': userId, diff --git a/src/config/backend.ts b/src/config/backend.ts index 9891df8..d370941 100644 --- a/src/config/backend.ts +++ b/src/config/backend.ts @@ -1,6 +1,6 @@ -// -export const BACKEND_URL = 'http://localhost:8000'; +// Backend Configuration +export const BACKEND_URL = process.env.NEXT_PUBLIC_API_GATEWAY_URL || 'http://localhost:8000'; export const SOCKET_URL = BACKEND_URL;