Extract components and hooks from oversized page files (563/561/520 LOC) into colocated _components/ and _hooks/ subdirectories. All three page.tsx files are now thin orchestrators under 300 LOC each (dsfa: 216, audit-llm: 121, quality: 163). Zero behavior changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
91 lines
3.9 KiB
TypeScript
91 lines
3.9 KiB
TypeScript
'use client'
|
|
|
|
import { formatDate, formatNumber, formatDuration, type LLMLogEntry } from './types'
|
|
|
|
interface Props {
|
|
logEntries: LLMLogEntry[]
|
|
logFilter: { model: string; pii: string }
|
|
onFilterChange: (filter: { model: string; pii: string }) => void
|
|
}
|
|
|
|
export function LLMLogTab({ logEntries, logFilter, onFilterChange }: Props) {
|
|
return (
|
|
<div>
|
|
{/* Filters */}
|
|
<div className="flex gap-3 mb-4">
|
|
<input
|
|
type="text"
|
|
placeholder="Model filtern..."
|
|
value={logFilter.model}
|
|
onChange={e => onFilterChange({ ...logFilter, model: e.target.value })}
|
|
className="border border-gray-300 rounded-lg px-3 py-2 text-sm w-48"
|
|
/>
|
|
<select
|
|
value={logFilter.pii}
|
|
onChange={e => onFilterChange({ ...logFilter, pii: e.target.value })}
|
|
className="border border-gray-300 rounded-lg px-3 py-2 text-sm"
|
|
>
|
|
<option value="">Alle PII-Status</option>
|
|
<option value="true">PII erkannt</option>
|
|
<option value="false">Kein PII</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Table */}
|
|
<div className="overflow-x-auto bg-white rounded-xl border border-gray-200">
|
|
<table className="w-full text-sm">
|
|
<thead>
|
|
<tr className="border-b border-gray-200 bg-gray-50">
|
|
<th className="text-left px-4 py-3 font-medium text-gray-600">Zeitpunkt</th>
|
|
<th className="text-left px-4 py-3 font-medium text-gray-600">User</th>
|
|
<th className="text-left px-4 py-3 font-medium text-gray-600">Model</th>
|
|
<th className="text-right px-4 py-3 font-medium text-gray-600">Tokens</th>
|
|
<th className="text-center px-4 py-3 font-medium text-gray-600">PII</th>
|
|
<th className="text-right px-4 py-3 font-medium text-gray-600">Dauer</th>
|
|
<th className="text-center px-4 py-3 font-medium text-gray-600">Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{logEntries.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={7} className="text-center py-8 text-gray-400">
|
|
Keine Log-Eintraege im gewaehlten Zeitraum
|
|
</td>
|
|
</tr>
|
|
) : logEntries.map(entry => (
|
|
<tr key={entry.id} className="border-b border-gray-100 hover:bg-gray-50">
|
|
<td className="px-4 py-3 text-gray-500">{formatDate(entry.created_at)}</td>
|
|
<td className="px-4 py-3 font-mono text-xs">{entry.user_id?.slice(0, 8)}...</td>
|
|
<td className="px-4 py-3">
|
|
<span className="px-2 py-0.5 bg-blue-50 text-blue-700 rounded text-xs font-medium">
|
|
{entry.model}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-3 text-right font-mono">{formatNumber(entry.total_tokens)}</td>
|
|
<td className="px-4 py-3 text-center">
|
|
{entry.pii_detected ? (
|
|
<span className="px-2 py-0.5 bg-red-50 text-red-700 rounded text-xs font-medium">
|
|
{entry.redacted ? 'Redacted' : 'Erkannt'}
|
|
</span>
|
|
) : (
|
|
<span className="text-gray-400 text-xs">-</span>
|
|
)}
|
|
</td>
|
|
<td className="px-4 py-3 text-right text-gray-500">{formatDuration(entry.duration_ms)}</td>
|
|
<td className="px-4 py-3 text-center">
|
|
<span className={`px-2 py-0.5 rounded text-xs font-medium ${
|
|
entry.status === 'success' ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'
|
|
}`}>
|
|
{entry.status}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div className="mt-2 text-xs text-gray-400">{logEntries.length} Eintraege</div>
|
|
</div>
|
|
)
|
|
}
|