git completed flow with streaming api-integrate with front end
This commit is contained in:
parent
84a25bf091
commit
0d3f89da0f
@ -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;
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 (_) {}
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user