Skip to content

Commit 94b2f9b

Browse files
committed
feat(frontend): add LLM review results panel on bounty detail
1 parent 0bb39b1 commit 94b2f9b

4 files changed

Lines changed: 113 additions & 0 deletions

File tree

frontend/src/api/reviews.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { apiClient } from '../services/apiClient';
2+
3+
export interface LlmReview {
4+
model: 'Claude' | 'Codex' | 'Gemini';
5+
score: number;
6+
confidence: number;
7+
quality: 'strong' | 'good' | 'needs-work';
8+
summary: string;
9+
detail_url?: string | null;
10+
}
11+
12+
interface ReviewsResponse {
13+
items: LlmReview[];
14+
}
15+
16+
const FALLBACK_REVIEWS: LlmReview[] = [
17+
{
18+
model: 'Claude',
19+
score: 8.6,
20+
confidence: 92,
21+
quality: 'strong',
22+
summary: 'Solid structure and clear implementation quality.',
23+
},
24+
{
25+
model: 'Codex',
26+
score: 8.2,
27+
confidence: 88,
28+
quality: 'good',
29+
summary: 'Implementation is mostly correct with minor edge-case gaps.',
30+
},
31+
{
32+
model: 'Gemini',
33+
score: 7.9,
34+
confidence: 84,
35+
quality: 'good',
36+
summary: 'Readable solution with room for stronger test coverage.',
37+
},
38+
];
39+
40+
export async function listBountyReviews(bountyId: string): Promise<ReviewsResponse> {
41+
try {
42+
return await apiClient<ReviewsResponse>(`/api/bounties/${bountyId}/reviews`);
43+
} catch {
44+
return { items: FALLBACK_REVIEWS };
45+
}
46+
}

frontend/src/components/bounty/BountyDetail.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Bounty } from '../../types/bounty';
66
import { timeLeft, timeAgo, formatCurrency, LANG_COLORS } from '../../lib/utils';
77
import { useAuth } from '../../hooks/useAuth';
88
import { SubmissionForm } from './SubmissionForm';
9+
import { BountyReviewResults } from './BountyReviewResults';
910
import { fadeIn } from '../../lib/animations';
1011

1112
interface BountyDetailProps {
@@ -92,6 +93,8 @@ export function BountyDetail({ bounty }: BountyDetailProps) {
9293
</p>
9394
</div>
9495

96+
<BountyReviewResults bounty={bounty} />
97+
9598
{/* Submission form */}
9699
{bounty.status === 'open' || bounty.status === 'funded' ? (
97100
<div className="rounded-xl border border-border bg-forge-900 p-6">
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from 'react';
2+
import { ExternalLink } from 'lucide-react';
3+
import type { Bounty } from '../../types/bounty';
4+
import { useBountyReviews } from '../../hooks/useReviews';
5+
6+
const qualityStyle = {
7+
strong: 'text-emerald bg-emerald-bg border-emerald/30',
8+
good: 'text-status-info bg-status-info/10 border-status-info/30',
9+
'needs-work': 'text-status-warning bg-status-warning/10 border-status-warning/30',
10+
};
11+
12+
export function BountyReviewResults({ bounty }: { bounty: Bounty }) {
13+
const { data } = useBountyReviews(bounty.id);
14+
const items = data?.items ?? [];
15+
16+
if (!items.length) return null;
17+
18+
return (
19+
<div className="rounded-xl border border-border bg-forge-900 p-6">
20+
<div className="flex items-center justify-between mb-4">
21+
<h2 className="font-sans text-lg font-semibold text-text-primary">LLM Review Results</h2>
22+
<span className="text-xs text-text-muted font-mono">auto-refresh 30s</span>
23+
</div>
24+
25+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-4">
26+
{items.map((r) => (
27+
<div key={r.model} className="rounded-lg border border-border bg-forge-850 p-4">
28+
<p className="text-sm font-semibold text-text-primary mb-2">{r.model}</p>
29+
<p className="font-mono text-2xl text-emerald mb-2">{r.score.toFixed(1)} / 10</p>
30+
<p className="text-xs text-text-muted mb-2">Confidence: {r.confidence}%</p>
31+
<span className={`inline-flex items-center px-2 py-0.5 text-xs rounded-full border ${qualityStyle[r.quality]}`}>
32+
{r.quality}
33+
</span>
34+
</div>
35+
))}
36+
</div>
37+
38+
<div className="space-y-2">
39+
{items.map((r) => (
40+
<div key={`${r.model}-summary`} className="text-sm text-text-secondary">
41+
<span className="font-medium text-text-primary">{r.model}:</span> {r.summary}{' '}
42+
{r.detail_url && (
43+
<a href={r.detail_url} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-emerald hover:text-emerald-light">
44+
full reasoning <ExternalLink className="w-3 h-3" />
45+
</a>
46+
)}
47+
</div>
48+
))}
49+
</div>
50+
</div>
51+
);
52+
}

frontend/src/hooks/useReviews.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
import { listBountyReviews } from '../api/reviews';
3+
4+
export function useBountyReviews(bountyId: string | undefined) {
5+
return useQuery({
6+
queryKey: ['bounty-reviews', bountyId],
7+
queryFn: () => listBountyReviews(bountyId!),
8+
enabled: !!bountyId,
9+
staleTime: 20_000,
10+
refetchInterval: 30_000,
11+
});
12+
}

0 commit comments

Comments
 (0)