feat: document language checker on round overview
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m15s

- New roundLanguageSummary query in file router aggregates per-round document
  language data from existing detectedLang/langConfidence fields
- Document Languages card on round overview tab shows analysis status and
  flags non-English documents grouped by project with confidence scores
- Green border when all documents are English, amber when issues detected
- Project names link to project detail page for easy navigation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 11:53:43 +01:00
parent d4c946470a
commit 3180bfa946
2 changed files with 135 additions and 0 deletions

View File

@@ -77,6 +77,8 @@ import {
ArrowRight,
RotateCcw,
ListChecks,
FileText,
Languages,
} from 'lucide-react'
import {
Tooltip,
@@ -1471,6 +1473,9 @@ export default function RoundDetailPage() {
</Card>
</AnimatedCard>
</div>
{/* Document Language Summary */}
<DocumentLanguageSummary roundId={roundId as string} />
</TabsContent>
{/* ═══════════ PROJECTS TAB ═══════════ */}
@@ -2482,3 +2487,75 @@ export default function RoundDetailPage() {
</div>
)
}
// =============================================================================
// Document Language Summary — flags non-English docs
// =============================================================================
const LANG_NAMES: Record<string, string> = {
eng: 'English', fra: 'French', deu: 'German', spa: 'Spanish', ita: 'Italian',
por: 'Portuguese', nld: 'Dutch', rus: 'Russian', ara: 'Arabic', zho: 'Chinese',
jpn: 'Japanese', kor: 'Korean', tur: 'Turkish', pol: 'Polish', ron: 'Romanian',
ces: 'Czech', ell: 'Greek', hun: 'Hungarian', swe: 'Swedish', dan: 'Danish',
fin: 'Finnish', nor: 'Norwegian', und: 'Unknown',
}
function DocumentLanguageSummary({ roundId }: { roundId: string }) {
const { data, isLoading } = trpc.file.roundLanguageSummary.useQuery(
{ roundId },
{ refetchInterval: 60_000 }
)
if (isLoading || !data) return null
if (data.totalFiles === 0) return null
const allGood = data.nonEnglishCount === 0 && data.unanalyzedCount === 0
return (
<Card className={allGood ? 'border-green-200 bg-green-50/30' : data.nonEnglishCount > 0 ? 'border-amber-300 bg-amber-50/30' : ''}>
<CardHeader className="pb-2">
<CardTitle className="text-sm flex items-center gap-2">
<Languages className={cn('h-4 w-4', allGood ? 'text-green-600' : data.nonEnglishCount > 0 ? 'text-amber-600' : 'text-muted-foreground')} />
Document Languages
{data.nonEnglishCount > 0 && (
<Badge variant="destructive" className="text-[10px] px-1.5 py-0">
{data.nonEnglishCount} flagged
</Badge>
)}
</CardTitle>
<CardDescription className="text-xs">
{data.analyzedCount} of {data.totalFiles} documents analyzed
{data.unanalyzedCount > 0 && `${data.unanalyzedCount} pending`}
</CardDescription>
</CardHeader>
{data.nonEnglishCount > 0 && (
<CardContent className="space-y-2">
{data.flaggedProjects.map((project) => (
<div key={project.projectId} className="rounded-md border bg-white p-3">
<Link
href={`/admin/projects/${project.projectId}` as Route}
className="text-sm font-medium text-primary hover:underline"
>
{project.projectTitle}
</Link>
<div className="mt-1.5 space-y-1">
{project.files.map((file) => (
<div key={file.id} className="flex items-center justify-between text-xs">
<div className="flex items-center gap-1.5 min-w-0">
<FileText className="h-3 w-3 text-muted-foreground shrink-0" />
<span className="truncate">{file.fileName}</span>
</div>
<Badge variant="outline" className="text-[10px] px-1.5 py-0 shrink-0 ml-2 border-amber-300 text-amber-700">
{LANG_NAMES[file.detectedLang ?? ''] || file.detectedLang}
{file.langConfidence != null && ` (${Math.round(file.langConfidence * 100)}%)`}
</Badge>
</div>
))}
</div>
</div>
))}
</CardContent>
)}
</Card>
)
}