feat: F008 AI need builder — smart parse, criteria review, follow-up & weighting

Domain: ParsedNeedCriteria, FollowUpQuestion, ParseNeedResult, NeedBuilderStep, WEIGHTING_KEYS/LABELS. Services: weightingService (asset-type profiles), aiService extended with parseNeed (keyword extraction mock, 1.4s delay) and generateFollowUpQuestions. Components: NeedBuilderProgress, NeedInput, ConfidenceFieldBadge, ExtractedFieldRow, CriteriaReviewPanel, FollowUpQuestionCard, FollowUpPanel, WeightingEditor, NeedCardPreview, NeedBuilderErrorState. Page: AINeedBuilderPage replaces AISearch with 4-step flow (input → review → weighting → save).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-16 12:21:49 +02:00
parent cb18f3c381
commit 73a6a167e0
15 changed files with 1116 additions and 296 deletions
+66
View File
@@ -0,0 +1,66 @@
import { Box, Button, Card, Chip, TextField, Typography, Stack } from '@mui/material'
import { Sparkles } from 'lucide-react'
interface Props {
value: string
onChange: (v: string) => void
onSubmit: () => void
}
const EXAMPLES = [
'Büro Zürich 500800 m², ab September 2025, max. 45 CHF/m², gute ÖV-Anbindung',
'Logistik Basel 2000 m², Tiefgarage, sofort verfügbar',
'Retail Bern Innenstadt, hohe Passantenfrequenz, max. CHF 200/m²',
]
export function NeedInput({ value, onChange, onSubmit }: Props) {
return (
<Box sx={{ maxWidth: 720, mx: 'auto' }}>
<Card sx={{ p: 3, mb: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 2 }}>
Flächenbedarf beschreiben
</Typography>
<TextField
multiline
rows={5}
fullWidth
placeholder="Beispiel: Wir suchen 8001.000 m² Bürofläche in Zürich-West, verfügbar ab September 2025, Budget max. 45 CHF/m². Wichtig: gute ÖV-Anbindung, moderne Infrastruktur."
value={value}
onChange={e => onChange(e.target.value)}
slotProps={{ htmlInput: { maxLength: 2000 } }}
sx={{ mb: 1 }}
/>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', textAlign: 'right', mb: 2 }}>
{value.length}/2000
</Typography>
<Button
variant="contained"
fullWidth
disabled={value.length < 20}
onClick={onSubmit}
endIcon={<Sparkles size={16} />}
sx={{ bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#162d4a' } }}
>
KI-Analyse starten
</Button>
</Card>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1 }}>
Beispiele:
</Typography>
<Stack direction="row" spacing={1} sx={{ flexWrap: 'wrap', gap: 1 }}>
{EXAMPLES.map(q => (
<Chip
key={q}
label={q}
size="small"
variant="outlined"
clickable
onClick={() => onChange(q)}
sx={{ maxWidth: 300, overflow: 'hidden', textOverflow: 'ellipsis' }}
/>
))}
</Stack>
</Box>
)
}