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:
Benjamin Sutter
2026-05-17 13:27:33 +02:00
parent 71f4b1eeb6
commit 30e840489d
13 changed files with 1222 additions and 0 deletions
@@ -0,0 +1,87 @@
import { Box, Button, Typography } from '@mui/material'
import { ArrowRight } from 'lucide-react'
import { useNavigate } from 'react-router'
import type { AssistantAction } from '../../domain/assistant'
interface Props {
actions: AssistantAction[]
onExecute?: (action: AssistantAction) => void
}
const ACTION_COLORS: Record<string, string> = {
NAVIGATE: '#1e3a5f',
OPEN_REVIEW: '#7c3aed',
ADD_TO_SHORTLIST: '#1a7a4a',
REQUEST_DATA: '#d97706',
SEND_TO_REVIEW: '#ea580c',
}
export function AssistantActionCards({ actions, onExecute }: Props) {
const navigate = useNavigate()
const handleExecute = (action: AssistantAction) => {
if (action.actionType === 'NAVIGATE' && action.payload?.path) {
navigate(action.payload.path as string)
} else if (action.actionType === 'OPEN_REVIEW') {
navigate('/ops/review-queue')
}
onExecute?.(action)
}
if (!actions.length) return null
return (
<Box sx={{ px: 2, pt: 0.75, pb: 0.5 }}>
<Typography variant="caption" sx={{ fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.5, color: '#94a3b8', display: 'block', mb: 0.625, fontSize: '0.6rem' }}>
Vorgeschlagene Aktionen
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
{actions.map(action => {
const color = ACTION_COLORS[action.actionType] ?? '#64748b'
return (
<Box
key={action.id}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
px: 1.25,
py: 0.875,
borderRadius: 1.5,
border: `1px solid ${color}30`,
bgcolor: `${color}08`,
}}
>
<Box>
<Typography variant="caption" sx={{ fontWeight: 700, color, fontSize: '0.75rem', display: 'block' }}>
{action.label}
</Typography>
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
{action.description}
</Typography>
</Box>
<Button
size="small"
variant="outlined"
endIcon={<ArrowRight size={12} />}
onClick={() => handleExecute(action)}
sx={{
textTransform: 'none',
fontSize: '0.7rem',
color,
borderColor: `${color}60`,
flexShrink: 0,
ml: 1,
py: 0.4,
'&:hover': { borderColor: color, bgcolor: `${color}12` },
}}
>
Ausführen
</Button>
</Box>
)
})}
</Box>
</Box>
)
}