feat: F024 global AI assistant — contextual decision intelligence drawer
Wires the existing "AI Assistent" button in the TopBar and adds a 420px slide-in drawer with context-aware suggestions, message thread, loading animation, and action cards requiring manual confirmation. Template-based mock service returns German-language answers keyed by route + question keywords — no real LLM calls, no invented facts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { Box, Chip, Typography } from '@mui/material'
|
||||
import type { AssistantContext } from '../../domain/assistant'
|
||||
|
||||
const PAGE_LABELS: Record<string, string> = {
|
||||
'/supply/dashboard': 'Übersicht',
|
||||
'/supply/properties': 'Meine Objekte',
|
||||
'/supply/match-center': 'Eingehende Bedarfe',
|
||||
'/supply/data-quality': 'Datenpflege',
|
||||
'/supply/future-availability':'Marktchancen',
|
||||
'/demand/ai-search': 'Flächensuche',
|
||||
'/demand/results': 'Ergebnisse',
|
||||
'/demand/compare': 'Vergleich',
|
||||
'/demand/shortlists': 'Shortlists',
|
||||
'/ops/review-queue': 'Review Queue',
|
||||
'/ops/ai-monitoring': 'AI Monitoring',
|
||||
'/ops/governance': 'Governance',
|
||||
}
|
||||
|
||||
function resolvePageLabel(route: string): string {
|
||||
for (const [path, label] of Object.entries(PAGE_LABELS)) {
|
||||
if (route.startsWith(path)) return label
|
||||
}
|
||||
return route.split('/').filter(Boolean).pop()?.replace(/-/g, ' ') ?? 'Seite'
|
||||
}
|
||||
|
||||
const ENTITY_LABELS: Record<string, string> = {
|
||||
PROPERTY: 'Objekt', NEED: 'Gesuch', MATCH: 'Match',
|
||||
SIGNAL: 'Signal', AI_OUTPUT: 'AI-Output',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
context: AssistantContext
|
||||
}
|
||||
|
||||
export function AssistantContextSummary({ context }: Props) {
|
||||
const pageLabel = resolvePageLabel(context.currentRoute)
|
||||
|
||||
return (
|
||||
<Box sx={{ px: 2, py: 1, bgcolor: '#f8fafc', borderBottom: '1px solid #e2e8f0' }}>
|
||||
<Box sx={{ display: 'flex', gap: 0.75, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<Chip
|
||||
label={pageLabel}
|
||||
size="small"
|
||||
sx={{ bgcolor: '#e0f2fe', color: '#075985', fontWeight: 600, fontSize: '0.7rem', height: 20 }}
|
||||
/>
|
||||
{context.selectedEntityType && context.selectedEntityId && (
|
||||
<Chip
|
||||
label={`${ENTITY_LABELS[context.selectedEntityType] ?? context.selectedEntityType}: ${context.selectedEntityId}`}
|
||||
size="small"
|
||||
sx={{ bgcolor: '#f1f5f9', color: '#475569', fontWeight: 500, fontSize: '0.65rem', height: 20, fontFamily: 'monospace' }}
|
||||
/>
|
||||
)}
|
||||
{context.visibleScores?.quality !== undefined && (
|
||||
<Chip
|
||||
label={`Qualität: ${context.visibleScores.quality}%`}
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: context.visibleScores.quality >= 70 ? '#dcfce7' : '#fef3c7',
|
||||
color: context.visibleScores.quality >= 70 ? '#166534' : '#92400e',
|
||||
fontWeight: 600, fontSize: '0.65rem', height: 20,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{context.visibleScores?.matchScore !== undefined && (
|
||||
<Chip
|
||||
label={`Match: ${context.visibleScores.matchScore}%`}
|
||||
size="small"
|
||||
sx={{ bgcolor: '#ede9fe', color: '#5b21b6', fontWeight: 600, fontSize: '0.65rem', height: 20 }}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{context.visibleMissingData && context.visibleMissingData.length > 0 && (
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5, fontSize: '0.65rem' }}>
|
||||
Fehlende Daten: {context.visibleMissingData.slice(0, 3).join(', ')}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user