git completed flow with streaming api-integrate with front end

This commit is contained in:
Kenil 2025-10-13 08:33:38 +05:30
parent 84a25bf091
commit 0d3f89da0f
6 changed files with 357 additions and 26 deletions

View File

@ -1,7 +1,7 @@
// app/diff-viewer/page.tsx
'use client';
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, Suspense } from 'react';
import { useSearchParams } from 'next/navigation';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -13,9 +13,13 @@ import {
FolderOpen,
Search,
RefreshCw,
ExternalLink
ExternalLink,
Brain,
CheckSquare,
Square
} from 'lucide-react';
import DiffViewer from '@/components/diff-viewer/DiffViewer';
import { authApiClient } from '@/components/apis/authApiClients';
interface Repository {
id: string;
@ -36,7 +40,7 @@ interface Commit {
total_diff_size: number;
}
const DiffViewerPage: React.FC = () => {
const DiffViewerContent: React.FC = () => {
const searchParams = useSearchParams();
const [repositories, setRepositories] = useState<Repository[]>([]);
const [commits, setCommits] = useState<Commit[]>([]);
@ -45,25 +49,29 @@ const DiffViewerPage: React.FC = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState('');
// Handle URL parameters
useEffect(() => {
const repoId = searchParams.get('repo');
if (repoId) {
setSelectedRepository(repoId);
}
}, [searchParams]);
const [selectedCommits, setSelectedCommits] = useState<string[]>([]);
const [bulkAnalysisLoading, setBulkAnalysisLoading] = useState(false);
const [bulkAnalysisError, setBulkAnalysisError] = useState<string | null>(null);
// Load repositories
useEffect(() => {
const loadRepositories = async () => {
try {
setIsLoading(true);
const response = await fetch('/api/diffs/repositories');
const data = await response.json();
const response = await authApiClient.get('/api/diffs/repositories');
const data = response.data;
if (data.success) {
setRepositories(data.data.repositories);
// Handle URL parameters after repositories are loaded
const repoId = searchParams.get('repo') || searchParams.get('repository');
console.log('URL repoId:', repoId);
console.log('Available repositories:', data.data.repositories.map((r: Repository) => ({ id: r.id, name: r.repository_name })));
if (repoId) {
console.log('Setting selected repository to:', repoId);
setSelectedRepository(repoId);
}
} else {
setError(data.message || 'Failed to load repositories');
}
@ -75,7 +83,7 @@ const DiffViewerPage: React.FC = () => {
};
loadRepositories();
}, []);
}, [searchParams]);
// Load commits when repository is selected
useEffect(() => {
@ -83,8 +91,8 @@ const DiffViewerPage: React.FC = () => {
const loadCommits = async () => {
try {
setIsLoading(true);
const response = await fetch(`/api/diffs/repositories/${selectedRepository}/commits`);
const data = await response.json();
const response = await authApiClient.get(`/api/diffs/repositories/${selectedRepository}/commits`);
const data = response.data;
if (data.success) {
setCommits(data.data.commits);
@ -109,6 +117,7 @@ const DiffViewerPage: React.FC = () => {
const handleRepositoryChange = (repositoryId: string) => {
setSelectedRepository(repositoryId);
setSelectedCommit('');
setSelectedCommits([]); // Reset bulk selection
};
const handleCommitChange = (commitId: string) => {
@ -120,8 +129,8 @@ const DiffViewerPage: React.FC = () => {
const loadCommits = async () => {
try {
setIsLoading(true);
const response = await fetch(`/api/diffs/repositories/${selectedRepository}/commits`);
const data = await response.json();
const response = await authApiClient.get(`/api/diffs/repositories/${selectedRepository}/commits`);
const data = response.data;
if (data.success) {
setCommits(data.data.commits);
@ -137,6 +146,60 @@ const DiffViewerPage: React.FC = () => {
}
};
const handleBulkAnalysis = async () => {
if (!selectedRepository || selectedCommits.length === 0) {
setBulkAnalysisError('Please select a repository and at least one commit for bulk analysis.');
return;
}
setBulkAnalysisLoading(true);
setBulkAnalysisError(null);
try {
const response = await authApiClient.post(`/api/ai/repository/${selectedRepository}/bulk-analysis`, {
commit_ids: selectedCommits,
analysis_type: "bulk",
include_content: "true",
stream: "false"
}, {
timeout: 120000 // 2 minutes timeout for bulk analysis
});
const data = response.data;
console.log('Bulk Analysis Result:', data);
// Log user ID from response
if (data.user_id) {
console.log('🔍 [USER-ID] Received user ID in bulk analysis response:', data.user_id);
}
alert(`Bulk AI Analysis completed successfully for ${selectedCommits.length} commits!`);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Bulk AI Analysis failed';
setBulkAnalysisError(errorMessage);
console.error('Bulk Analysis Error:', err);
} finally {
setBulkAnalysisLoading(false);
}
};
const toggleCommitSelection = (commitId: string) => {
setSelectedCommits(prev =>
prev.includes(commitId)
? prev.filter(id => id !== commitId)
: [...prev, commitId]
);
};
const selectAllCommits = () => {
setSelectedCommits(commits.map(commit => commit.id));
};
const clearSelection = () => {
setSelectedCommits([]);
};
const filteredCommits = commits.filter(commit =>
commit.message.toLowerCase().includes(searchQuery.toLowerCase()) ||
commit.author_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
@ -279,6 +342,122 @@ const DiffViewerPage: React.FC = () => {
</Card>
)}
{/* Bulk Analysis Error Display */}
{bulkAnalysisError && (
<Card className="border-destructive">
<CardContent className="pt-6">
<div className="text-center text-destructive">
<p className="font-medium">Bulk Analysis Error</p>
<p className="text-sm mt-2">{bulkAnalysisError}</p>
<Button
variant="outline"
className="mt-4"
onClick={() => setBulkAnalysisError(null)}
>
Dismiss
</Button>
</div>
</CardContent>
</Card>
)}
{/* Bulk Analysis Section */}
{selectedRepository && commits.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Brain className="h-5 w-5" />
<span>Bulk AI Analysis</span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground">
Select multiple commits for bulk AI analysis
</p>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={selectAllCommits}
disabled={commits.length === 0}
>
Select All
</Button>
<Button
variant="outline"
size="sm"
onClick={clearSelection}
disabled={selectedCommits.length === 0}
>
Clear
</Button>
</div>
</div>
{/* Commit Selection List */}
<div className="max-h-60 overflow-y-auto border rounded-lg p-4 space-y-2">
{filteredCommits.map((commit) => (
<div
key={commit.id}
className={`flex items-center space-x-3 p-2 rounded-lg cursor-pointer transition-colors ${
selectedCommits.includes(commit.id)
? 'bg-primary/10 border border-primary/20'
: 'hover:bg-muted/50'
}`}
onClick={() => toggleCommitSelection(commit.id)}
>
<div className="flex-shrink-0">
{selectedCommits.includes(commit.id) ? (
<CheckSquare className="h-4 w-4 text-primary" />
) : (
<Square className="h-4 w-4 text-muted-foreground" />
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2">
<code className="text-xs bg-muted px-2 py-1 rounded">
{commit.commit_sha.substring(0, 8)}
</code>
<span className="text-sm font-medium truncate">
{commit.message}
</span>
</div>
<div className="text-xs text-muted-foreground mt-1">
by {commit.author_name} {new Date(commit.committed_at).toLocaleDateString()}
</div>
</div>
</div>
))}
</div>
{/* Bulk Analysis Button */}
<div className="flex items-center justify-between pt-4 border-t">
<div className="text-sm text-muted-foreground">
{selectedCommits.length} commit{selectedCommits.length !== 1 ? 's' : ''} selected
</div>
<Button
onClick={handleBulkAnalysis}
disabled={selectedCommits.length === 0 || bulkAnalysisLoading}
className="min-w-[140px]"
>
{bulkAnalysisLoading ? (
<>
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
Analyzing...
</>
) : (
<>
<Brain className="h-4 w-4 mr-2" />
Analyze {selectedCommits.length} Commits
</>
)}
</Button>
</div>
</CardContent>
</Card>
)}
{/* Diff Viewer */}
{selectedRepository && selectedCommit && (
<DiffViewer
@ -321,4 +500,19 @@ const DiffViewerPage: React.FC = () => {
);
};
const DiffViewerPage: React.FC = () => {
return (
<Suspense fallback={
<div className="max-w-7xl mx-auto p-6">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
<p>Loading diff viewer...</p>
</div>
</div>
}>
<DiffViewerContent />
</Suspense>
);
};
export default DiffViewerPage;

View File

@ -166,7 +166,32 @@ export default function RepoByIdClient({ repositoryId, initialPath = "" }: { rep
<span className="ml-2 text-white/60">Loading file content...</span>
</div>
) : fileContent ? (
<pre className="whitespace-pre-wrap text-sm text-white/90 bg-black/20 p-4 rounded overflow-auto h-full">{fileContent}</pre>
<div
className="bg-black/20 rounded overflow-auto h-full thin-scrollbar"
style={{
scrollbarWidth: 'thin',
scrollbarColor: '#4B5563 #1F2937'
}}
>
<div className="flex">
{/* Line Numbers Column */}
<div className="bg-gray-800/50 text-gray-400 text-xs font-mono px-3 py-4 select-none border-r border-gray-700/50 min-w-[3rem] flex-shrink-0">
{fileContent.split('\n').map((line, index) => (
<div key={index} className="leading-5 h-5 flex items-center justify-end">
{index + 1}
</div>
))}
</div>
{/* File Content Column */}
<div className="flex-1 p-4">
{fileContent.split('\n').map((line, index) => (
<div key={index} className="leading-5 h-5 text-sm text-white/90 font-mono">
{line || '\u00A0'}
</div>
))}
</div>
</div>
</div>
) : (
<div className="flex items-center justify-center h-full">
<div className="text-center">

View File

@ -16,9 +16,11 @@ import {
Eye,
Code,
Calendar,
GitCompare
GitCompare,
Brain
} from 'lucide-react';
import { getUserRepositories, type GitHubRepoSummary } from '@/lib/api/github';
import { authApiClient } from '@/components/apis/authApiClients';
import Link from 'next/link';
const GitHubReposPage: React.FC = () => {
@ -27,6 +29,8 @@ const GitHubReposPage: React.FC = () => {
const [error, setError] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState('');
const [filter, setFilter] = useState<'all' | 'public' | 'private'>('all');
const [aiAnalysisLoading, setAiAnalysisLoading] = useState<string | null>(null);
const [aiAnalysisError, setAiAnalysisError] = useState<string | null>(null);
// Load repositories
useEffect(() => {
@ -59,6 +63,33 @@ const GitHubReposPage: React.FC = () => {
}
};
const handleAiAnalysis = async (repositoryId: string) => {
try {
setAiAnalysisLoading(repositoryId);
setAiAnalysisError(null);
const response = await authApiClient.get(`/api/ai/repository/${repositoryId}/ai-stream`);
const data = response.data;
console.log('AI Analysis Result:', data);
// Log user ID from response
if (data.user_id) {
console.log('🔍 [USER-ID] Received user ID in response:', data.user_id);
}
// You can add a success notification or modal here
alert('AI Analysis completed successfully!');
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'AI Analysis failed';
setAiAnalysisError(errorMessage);
console.error('AI Analysis Error:', err);
} finally {
setAiAnalysisLoading(null);
}
};
const filteredRepositories = repositories.filter(repo => {
const matchesSearch = repo.full_name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
repo.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
@ -167,6 +198,25 @@ const GitHubReposPage: React.FC = () => {
</Card>
)}
{/* AI Analysis Error Display */}
{aiAnalysisError && (
<Card className="border-destructive">
<CardContent className="pt-6">
<div className="text-center text-destructive">
<p className="font-medium">AI Analysis Error</p>
<p className="text-sm mt-2">{aiAnalysisError}</p>
<Button
variant="outline"
className="mt-4"
onClick={() => setAiAnalysisError(null)}
>
Dismiss
</Button>
</div>
</CardContent>
</Card>
)}
{/* Loading State */}
{isLoading && (
<Card>
@ -242,6 +292,39 @@ const GitHubReposPage: React.FC = () => {
View
</Link>
</Button>
<Button asChild size="sm" variant="outline" className="flex-1">
<Link href={`/diff-viewer?repository=${repo.id}`}>
<GitCompare className="h-4 w-4 mr-2" />
Git Diff
</Link>
</Button>
</div>
{/* AI Analysis Button */}
<div className="pt-2">
<Button
size="sm"
variant="secondary"
className="w-full"
onClick={() => {
if (repo.id) {
handleAiAnalysis(String(repo.id));
}
}}
disabled={aiAnalysisLoading === repo.id}
>
{aiAnalysisLoading === repo.id ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-current mr-2"></div>
Analyzing...
</>
) : (
<>
<Brain className="h-4 w-4 mr-2" />
AI Analysis
</>
)}
</Button>
</div>
</CardContent>
</Card>

View File

@ -1,6 +1,25 @@
@import "tailwindcss";
@import "tw-animate-css";
/* Thin scrollbar styles */
.thin-scrollbar::-webkit-scrollbar {
width: 6px;
}
.thin-scrollbar::-webkit-scrollbar-track {
background: #1F2937;
border-radius: 3px;
}
.thin-scrollbar::-webkit-scrollbar-thumb {
background: #4B5563;
border-radius: 3px;
}
.thin-scrollbar::-webkit-scrollbar-thumb:hover {
background: #6B7280;
}
@custom-variant dark (&:is(.dark *));
@theme inline {

View File

@ -106,6 +106,15 @@ const addAuthTokenInterceptor = (client: typeof authApiClient) => {
config.headers = config.headers || {};
// Header preferred by backend
(config.headers as any)['x-user-id'] = userId;
// Debug: Log user ID being sent (only for AI endpoints)
if (config.url?.includes('/api/ai/')) {
console.log('🔍 [USER-ID] Sending user ID:', userId, 'for request:', config.url);
console.log('🔍 [USER-ID] Request headers:', {
'x-user-id': userId,
'Authorization': config.headers.Authorization ? 'Bearer [token]' : 'None'
});
}
}
}
} catch (_) {}

View File

@ -1,5 +1,6 @@
// components/diff-viewer/DiffViewerContext.tsx
import React, { createContext, useContext, useReducer, useCallback } from 'react';
import { authApiClient } from '@/components/apis/authApiClients';
// Types
export interface DiffFile {
@ -147,10 +148,10 @@ export const DiffViewerProvider: React.FC<{ children: React.ReactNode }> = ({ ch
dispatch({ type: 'SET_ERROR', payload: null });
try {
const response = await fetch(`/api/diffs/commits/${commitId}/diffs`);
const data = await response.json();
const response = await authApiClient.get(`/api/diffs/commits/${commitId}/diffs`);
const data = response.data;
if (!response.ok) {
if (!data.success) {
throw new Error(data.message || 'Failed to load commit diffs');
}
@ -172,10 +173,10 @@ export const DiffViewerProvider: React.FC<{ children: React.ReactNode }> = ({ ch
dispatch({ type: 'SET_ERROR', payload: null });
try {
const response = await fetch(`/api/diffs/repositories/${repositoryId}/commits`);
const data = await response.json();
const response = await authApiClient.get(`/api/diffs/repositories/${repositoryId}/commits`);
const data = response.data;
if (!response.ok) {
if (!data.success) {
throw new Error(data.message || 'Failed to load repository commits');
}