scaffold react dashboard
This commit is contained in:
45
dashboard/src/api/client.ts
Normal file
45
dashboard/src/api/client.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || '';
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number;
|
||||
|
||||
constructor(status: number, message: string) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiRequest<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {},
|
||||
): Promise<T> {
|
||||
const url = `${API_BASE_URL}${endpoint}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text().catch(() => 'Request failed');
|
||||
throw new ApiError(response.status, message);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export function buildQueryString(
|
||||
params: Record<string, string | number | boolean | undefined>,
|
||||
): string {
|
||||
const filtered = Object.entries(params).filter(
|
||||
([, value]) => value !== undefined,
|
||||
);
|
||||
if (filtered.length === 0) return '';
|
||||
return '?' + new URLSearchParams(
|
||||
filtered.map(([key, value]) => [key, String(value)]),
|
||||
).toString();
|
||||
}
|
||||
45
dashboard/src/api/reviews.ts
Normal file
45
dashboard/src/api/reviews.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiRequest, buildQueryString } from './client';
|
||||
import type {
|
||||
ReviewListResponse,
|
||||
ReviewDetail,
|
||||
DeliberationLogResponse,
|
||||
ReviewFilters,
|
||||
MetricsResponse,
|
||||
} from '../types/api';
|
||||
|
||||
export function useReviews(filters: ReviewFilters = {}) {
|
||||
return useQuery({
|
||||
queryKey: ['reviews', filters],
|
||||
queryFn: () =>
|
||||
apiRequest<ReviewListResponse>(
|
||||
`/api/reviews${buildQueryString(filters as Record<string, string | number | undefined>)}`,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
export function useReview(id: string) {
|
||||
return useQuery({
|
||||
queryKey: ['review', id],
|
||||
queryFn: () => apiRequest<ReviewDetail>(`/api/reviews/${id}`),
|
||||
enabled: Boolean(id),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeliberationLog(reviewId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['deliberation', reviewId],
|
||||
queryFn: () =>
|
||||
apiRequest<DeliberationLogResponse>(
|
||||
`/api/reviews/${reviewId}/deliberation`,
|
||||
),
|
||||
enabled: Boolean(reviewId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMetrics() {
|
||||
return useQuery({
|
||||
queryKey: ['metrics'],
|
||||
queryFn: () => apiRequest<MetricsResponse>('/api/reviews/metrics'),
|
||||
});
|
||||
}
|
||||
5
dashboard/src/index.css
Normal file
5
dashboard/src/index.css
Normal file
@@ -0,0 +1,5 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
body {
|
||||
@apply bg-gray-50 text-gray-900 antialiased;
|
||||
}
|
||||
10
dashboard/src/main.tsx
Normal file
10
dashboard/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
104
dashboard/src/types/api.ts
Normal file
104
dashboard/src/types/api.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
||||
export type Verdict = 'approve' | 'request_changes' | 'comment';
|
||||
export type ReviewStatus = 'pending' | 'running' | 'completed' | 'failed';
|
||||
export type AgentName = 'security' | 'style' | 'complexity';
|
||||
export type ConflictNature = 'contradictory' | 'trade_off' | 'overlapping';
|
||||
export type StepType = 'merge' | 'conflict_detection' | 'synthesis' | 'verdict';
|
||||
|
||||
export interface ReviewSummary {
|
||||
id: string;
|
||||
repository: string;
|
||||
pr_number: number;
|
||||
pr_title: string | null;
|
||||
author: string | null;
|
||||
status: ReviewStatus;
|
||||
verdict: Verdict | null;
|
||||
verdict_confidence: number | null;
|
||||
finding_count: number;
|
||||
critical_count: number;
|
||||
high_count: number;
|
||||
total_cost_usd: number | null;
|
||||
created_at: string;
|
||||
completed_at: string | null;
|
||||
}
|
||||
|
||||
export interface Finding {
|
||||
id: string;
|
||||
agent: AgentName;
|
||||
file: string;
|
||||
line_start: number;
|
||||
line_end: number;
|
||||
severity: Severity;
|
||||
confidence: number;
|
||||
title: string;
|
||||
description: string;
|
||||
reasoning: string;
|
||||
suggestion: string | null;
|
||||
references: string[];
|
||||
prompt_version: string;
|
||||
}
|
||||
|
||||
export interface Conflict {
|
||||
id: string;
|
||||
finding_ids: string[];
|
||||
nature: ConflictNature;
|
||||
description: string;
|
||||
severity_weight: number;
|
||||
resolution: string | null;
|
||||
winning_finding_id: string | null;
|
||||
}
|
||||
|
||||
export interface ReviewDetail extends ReviewSummary {
|
||||
base_sha: string;
|
||||
head_sha: string;
|
||||
is_draft: boolean;
|
||||
verdict_reasoning: string | null;
|
||||
total_tokens: number | null;
|
||||
tokens_by_agent: Record<string, number> | null;
|
||||
cost_by_agent: Record<string, number> | null;
|
||||
started_at: string | null;
|
||||
error_message: string | null;
|
||||
findings: Finding[];
|
||||
conflicts: Conflict[];
|
||||
}
|
||||
|
||||
export interface DeliberationStep {
|
||||
id: string;
|
||||
step_type: StepType;
|
||||
timestamp: string;
|
||||
description: string;
|
||||
details: Record<string, unknown>;
|
||||
sequence: number;
|
||||
}
|
||||
|
||||
export interface ReviewListResponse {
|
||||
items: ReviewSummary[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export interface DeliberationLogResponse {
|
||||
review_id: string;
|
||||
steps: DeliberationStep[];
|
||||
}
|
||||
|
||||
export interface ReviewFilters {
|
||||
repository?: string;
|
||||
status?: ReviewStatus;
|
||||
verdict?: Verdict;
|
||||
author?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
|
||||
export interface MetricsResponse {
|
||||
total_reviews: number;
|
||||
completed_reviews: number;
|
||||
average_cost_usd: number;
|
||||
verdict_counts: Record<Verdict, number>;
|
||||
severity_counts: Record<Severity, number>;
|
||||
reviews_by_day: { date: string; count: number }[];
|
||||
cost_by_agent: Record<AgentName, number>;
|
||||
}
|
||||
Reference in New Issue
Block a user