fix: build speed, observer AI details, round tracker empty state
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled

- Disable typedRoutes and skip TS in build (run tsc separately) — build
  drops from ~9min to ~36s
- Expand optimizePackageImports for sonner, date-fns, recharts, motion, zod
- Docker: mount .next/cache as build cache for faster rebuilds
- Observer filtering panel: fix AI reasoning extraction (nested under rule ID)
  and show confidence, quality score, spam risk, override reason
- Round User Tracker: show empty state message instead of disappearing when
  selected round has no passed projects yet

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 17:30:11 +01:00
parent 0d94ee1fe8
commit 22731e7978
4 changed files with 91 additions and 24 deletions

View File

@@ -19,6 +19,29 @@ import {
import { Filter, ChevronDown, ChevronUp, ChevronLeft, ChevronRight } from 'lucide-react'
import { cn } from '@/lib/utils'
type AIScreeningData = {
meetsCriteria?: boolean
confidence?: number
reasoning?: string
qualityScore?: number
spamRisk?: boolean
}
function parseAIData(json: unknown): AIScreeningData | null {
if (!json || typeof json !== 'object') return null
const obj = json as Record<string, unknown>
// aiScreeningJson is nested under rule ID: { [ruleId]: { outcome, confidence, ... } }
if (!('outcome' in obj) && !('reasoning' in obj)) {
const keys = Object.keys(obj)
if (keys.length > 0) {
const inner = obj[keys[0]]
if (inner && typeof inner === 'object') return inner as AIScreeningData
}
return null
}
return obj as unknown as AIScreeningData
}
export function FilteringPanel({ roundId }: { roundId: string }) {
const [outcomeFilter, setOutcomeFilter] = useState<string>('ALL')
const [page, setPage] = useState(1)
@@ -199,17 +222,39 @@ export function FilteringPanel({ roundId }: { roundId: string }) {
</div>
</div>
</button>
{expandedId === r.id && (
<div className="px-4 pb-3 pt-0">
<div className="rounded bg-muted/50 p-3 text-xs leading-relaxed text-muted-foreground">
{(() => {
const screening = r.aiScreeningJson as Record<string, unknown> | null
const reasoning = (screening?.reasoning ?? screening?.explanation ?? r.overrideReason ?? 'No details available') as string
return reasoning
})()}
{expandedId === r.id && (() => {
const ai = parseAIData(r.aiScreeningJson)
return (
<div className="px-4 pb-3 pt-0 space-y-2">
{ai?.confidence != null && (
<div className="flex items-center gap-3 text-xs">
{ai.confidence != null && (
<span className="text-muted-foreground">
Confidence: <strong>{Math.round(ai.confidence * 100)}%</strong>
</span>
)}
{ai.qualityScore != null && (
<span className="text-muted-foreground">
Quality: <strong>{ai.qualityScore}/10</strong>
</span>
)}
{ai.spamRisk && (
<Badge variant="destructive" className="text-[10px] px-1.5 py-0">Spam Risk</Badge>
)}
</div>
)}
<div className="rounded bg-muted/50 border p-3 text-xs leading-relaxed text-muted-foreground whitespace-pre-wrap">
{ai?.reasoning || 'No AI reasoning available'}
</div>
{r.overrideReason && (
<div className="rounded bg-amber-50 border border-amber-200 p-3 text-xs">
<span className="font-medium text-amber-800">Override: </span>
{r.overrideReason}
</div>
)}
</div>
</div>
)}
)
})()}
</div>
))}
</div>