feat: F019 AI monitoring layer — output transparency & governance workspace
Full 2-panel workspace at /ops/ai-monitoring: metrics strip (total, failed, pending, approval rate, top prompt version, active model), filterable output table by type/status/error, and detail panel with output preview, prompt versioning, error details, and role-aware review actions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { Box, Typography } from '@mui/material'
|
||||
import type { AIOutput } from '../../domain/aiOutput'
|
||||
|
||||
interface Props {
|
||||
outputs: AIOutput[]
|
||||
}
|
||||
|
||||
function MetricCell({ label, value, color }: { label: string; value: string | number; color?: string }) {
|
||||
return (
|
||||
<Box sx={{ px: 2, py: 1.25, borderRight: '1px solid #e2e8f0', '&:last-child': { borderRight: 'none' }, minWidth: 0, flex: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', whiteSpace: 'nowrap', mb: 0.25 }}>
|
||||
{label}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{ fontWeight: 700, fontSize: '0.9375rem', color: color ?? '#1e293b', lineHeight: 1.2 }}
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function mostCommon(arr: string[]): string {
|
||||
if (!arr.length) return '–'
|
||||
const freq = arr.reduce<Record<string, number>>((acc, v) => ({ ...acc, [v]: (acc[v] ?? 0) + 1 }), {})
|
||||
return Object.entries(freq).sort((a, b) => b[1] - a[1])[0][0]
|
||||
}
|
||||
|
||||
export function AIMonitoringMetrics({ outputs }: Props) {
|
||||
const total = outputs.length
|
||||
const failed = outputs.filter(o => !!o.error).length
|
||||
const needsReview = outputs.filter(o => o.reviewStatus === 'UNREVIEWED' || o.reviewStatus === 'FLAGGED').length
|
||||
const approved = outputs.filter(o => o.reviewStatus === 'APPROVED').length
|
||||
const approvalRate = total > 0 ? Math.round((approved / total) * 100) : 0
|
||||
const topPrompt = mostCommon(outputs.map(o => o.promptVersion))
|
||||
const latestModel = outputs.length > 0
|
||||
? outputs.sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0].model
|
||||
: '–'
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', bgcolor: 'white', borderBottom: '1px solid #e2e8f0', flexShrink: 0 }}>
|
||||
<MetricCell label="AI-Outputs total" value={total} />
|
||||
<MetricCell label="Fehlgeschlagen" value={failed} color={failed > 0 ? '#c0392b' : undefined} />
|
||||
<MetricCell label="Prüfung ausstehend" value={needsReview} color={needsReview > 0 ? '#d97706' : undefined} />
|
||||
<MetricCell label="Genehmigungsrate" value={`${approvalRate}%`} color={approvalRate >= 70 ? '#1a7a4a' : '#d97706'} />
|
||||
<MetricCell label="Häufigste Version" value={topPrompt} />
|
||||
<MetricCell label="Aktives Modell" value={latestModel.replace('claude-3-5-sonnet-20241022', 'sonnet-3.5').replace('claude-3-haiku-20240307', 'haiku-3').replace('claude-3-opus-20240229', 'opus-3')} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user