feat: Add review events persistence, version display, and auto-versioning system

This commit is contained in:
Primakov Alexandr Alexandrovich
2025-10-13 14:18:37 +03:00
parent cfba28f913
commit 2db1225618
56 changed files with 750 additions and 436 deletions

View File

@@ -7,6 +7,7 @@ import ReviewDetail from './pages/ReviewDetail';
import Organizations from './pages/Organizations';
import Tasks from './pages/Tasks';
import WebSocketStatus from './components/WebSocketStatus';
import Footer from './components/Footer';
const queryClient = new QueryClient({
defaultOptions: {
@@ -65,7 +66,7 @@ function Navigation() {
function AppContent() {
return (
<div className="min-h-screen bg-dark-bg">
<div className="min-h-screen bg-dark-bg pb-12">
<Navigation />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
@@ -78,6 +79,8 @@ function AppContent() {
<Route path="/reviews/:id" element={<ReviewDetail />} />
</Routes>
</main>
<Footer />
</div>
);
}

View File

@@ -68,5 +68,25 @@ export const getReviewStats = async () => {
return response.data;
};
export interface ReviewEvent {
id: number;
review_id: number;
event_type: string;
step?: string;
message?: string;
data?: any;
created_at: string;
}
export const getReviewEvents = async (reviewId: number) => {
const response = await api.get<ReviewEvent[]>(`/reviews/${reviewId}/events`);
return response.data;
};
export const getBackendVersion = async () => {
const response = await api.get<{ version: string }>('/version');
return response.data;
};
export default api;

View File

@@ -0,0 +1,39 @@
/**
* Footer component with version info
*/
import { useQuery } from '@tanstack/react-query';
import { getBackendVersion } from '../api/client';
export default function Footer() {
const { data: versionData } = useQuery({
queryKey: ['backendVersion'],
queryFn: getBackendVersion,
staleTime: 60000, // Cache for 1 minute
refetchInterval: 300000, // Refetch every 5 minutes
});
return (
<footer className="fixed bottom-0 left-0 right-0 bg-dark-card border-t border-dark-border py-2 px-4 z-10">
<div className="container mx-auto flex items-center justify-between text-xs text-dark-text-muted">
<div>
AI Code Review Agent
</div>
<div className="flex items-center gap-4">
<span>
Backend v{versionData?.version || '...'}
</span>
<a
href="https://github.com/yourusername/ai-review-agent"
target="_blank"
rel="noopener noreferrer"
className="hover:text-dark-text-secondary transition-colors"
>
GitHub
</a>
</div>
</div>
</footer>
);
}

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import { WS_URL } from '../api/websocket';
import { getReviewEvents, ReviewEvent } from '../api/client';
interface StreamEvent {
type: string;
@@ -36,6 +37,41 @@ export const ReviewStream: React.FC<ReviewStreamProps> = ({ reviewId }) => {
console.log('🔌 Connecting to WebSocket:', WS_URL);
console.log('👀 Watching for review ID:', reviewId);
// Load historical events from database
const loadHistoricalEvents = async () => {
try {
console.log('📥 Loading historical events from DB...');
const historicalEvents = await getReviewEvents(reviewId);
console.log(`✅ Loaded ${historicalEvents.length} historical events`);
// Convert DB events to stream events format
const streamEvents: StreamEvent[] = historicalEvents.map((dbEvent: ReviewEvent) => ({
type: dbEvent.event_type,
review_id: dbEvent.review_id,
pr_number: 0, // Not stored in DB
timestamp: dbEvent.created_at,
data: {
type: dbEvent.event_type,
step: dbEvent.step,
message: dbEvent.message,
data: dbEvent.data
}
}));
setEvents(streamEvents);
// Set current step from last event
const lastAgentStep = streamEvents.reverse().find(e => e.type === 'agent_step');
if (lastAgentStep && lastAgentStep.data.step) {
setCurrentStep(lastAgentStep.data.step);
}
} catch (error) {
console.error('❌ Error loading historical events:', error);
}
};
loadHistoricalEvents();
const ws = new WebSocket(WS_URL);
let pingInterval: number;