This commit is contained in:
seb
2026-10-16 14:17:22 +02:00
parent 764d6f850a
commit a592cc30f6
8 changed files with 477 additions and 64 deletions

View File

@@ -15,6 +15,7 @@ import SendRoundedIcon from '@mui/icons-material/SendRounded';
import AddRoundedIcon from '@mui/icons-material/AddRounded';
import AutoAwesomeRoundedIcon from '@mui/icons-material/AutoAwesomeRounded';
import DeleteOutlineRoundedIcon from '@mui/icons-material/DeleteOutlineRounded';
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
import Tooltip from '@mui/material/Tooltip';
import Badge from '@mui/material/Badge';
@@ -115,6 +116,15 @@ export default class ChatDrawer extends PureComponent {
isInputFocused: false,
stagedAttachments: [],
stagedReferences: [],
stagedExtractions: s.pendingExtraction
? [
{
id: `ext-${Date.now()}`,
text: s.pendingExtraction.text,
label: s.pendingExtraction.label || 'parsed invoice',
},
]
: [],
isProcessingFiles: false,
isDraggingFiles: false,
attachmentError: null,
@@ -178,6 +188,13 @@ export default class ChatDrawer extends PureComponent {
}
});
}
if (chatAutomat.state.pendingExtraction != null) {
queueMicrotask(() => {
if (chatAutomat.state.pendingExtraction != null) {
chatAutomat.setState({ pendingExtraction: null });
}
});
}
this.unsub = chatAutomat.subscribe(this, (state) => {
const wasOpen = this.state.isOpen;
const willOpen = state.isOpen;
@@ -211,6 +228,21 @@ export default class ChatDrawer extends PureComponent {
} else if (!wasOpen && willOpen) {
this.focusInput(0);
}
if (state.pendingExtraction != null) {
const ext = state.pendingExtraction;
const newExt = {
id: `ext-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
text: ext.text,
label: ext.label || 'parsed invoice',
};
patch.stagedExtractions = [...(this.state.stagedExtractions || []), newExt];
queueMicrotask(() => {
if (chatAutomat.state.pendingExtraction != null) {
chatAutomat.setState({ pendingExtraction: null });
}
});
this.focusInput(0);
}
return patch;
});
@@ -309,6 +341,13 @@ export default class ChatDrawer extends PureComponent {
this.focusInput(50);
};
handleRemoveExtraction = (id) => {
this.setState((prev) => ({
stagedExtractions: (prev.stagedExtractions || []).filter((e) => e.id !== id),
}));
this.focusInput(50);
};
handleVfsResizerStart = (e) => {
if (e.button !== undefined && e.button !== 0) return;
e.preventDefault();
@@ -483,9 +522,10 @@ export default class ChatDrawer extends PureComponent {
const rawText = this.state.inputText.trim();
const attachments = this.state.stagedAttachments || [];
const references = this.state.stagedReferences || [];
const extractions = this.state.stagedExtractions || [];
if (
(!rawText && attachments.length === 0 && references.length === 0) ||
(!rawText && attachments.length === 0 && references.length === 0 && extractions.length === 0) ||
this.state.conversationState === 'busy' ||
this.state.isSending
) {
@@ -493,6 +533,13 @@ export default class ChatDrawer extends PureComponent {
}
let text = rawText;
if (extractions.length > 0) {
const extMarkdown = extractions
.map((ext) => `\`\`\`\n${ext.text.trim()}\n\`\`\``)
.join('\n\n');
text = text ? `${extMarkdown}\n\n${text}` : extMarkdown;
}
if (references.length > 0) {
const refMarkdown = references
.map((ref) => {
@@ -522,6 +569,7 @@ export default class ChatDrawer extends PureComponent {
inputText: '',
stagedAttachments: [],
stagedReferences: [],
stagedExtractions: [],
attachmentError: null,
});
@@ -1265,6 +1313,7 @@ export default class ChatDrawer extends PureComponent {
sendError,
stagedAttachments,
stagedReferences,
stagedExtractions = [],
isProcessingFiles,
isDraggingFiles,
attachmentError,
@@ -1277,7 +1326,10 @@ export default class ChatDrawer extends PureComponent {
const canSend =
!isWorking &&
canChat &&
(Boolean(inputText.trim()) || stagedAttachments.length > 0 || stagedReferences.length > 0);
(Boolean(inputText.trim()) ||
stagedAttachments.length > 0 ||
stagedReferences.length > 0 ||
stagedExtractions.length > 0);
return (
<Box
@@ -1449,7 +1501,7 @@ export default class ChatDrawer extends PureComponent {
boxSizing: 'border-box',
}}
>
{stagedReferences.length > 0 && (
{(stagedReferences.length > 0 || (stagedExtractions && stagedExtractions.length > 0)) && (
<Box
sx={{
display: 'flex',
@@ -1546,6 +1598,64 @@ export default class ChatDrawer extends PureComponent {
</IconButton>
</Box>
))}
{stagedExtractions && stagedExtractions.map((ext) => (
<Box
key={ext.id}
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.6,
py: 0.2,
pl: 0.7,
pr: 0.4,
borderRadius: 1.5,
bgcolor: (theme) =>
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, 0.08)'
: 'rgba(0, 0, 0, 0.05)',
border: '1px solid',
borderColor: (theme) =>
theme.palette.mode === 'dark'
? 'rgba(255, 255, 255, 0.12)'
: 'rgba(0, 0, 0, 0.1)',
color: 'text.primary',
fontSize: '0.75rem',
lineHeight: 1.2,
userSelect: 'none',
}}
>
<DescriptionOutlinedIcon sx={{ fontSize: 14, color: 'primary.main' }} />
<Typography
component="span"
sx={{
fontSize: '0.74rem',
fontWeight: 600,
letterSpacing: '-0.2px',
}}
>
{ext.label || 'parsed invoice'}
</Typography>
<IconButton
size="small"
onClick={(e) => {
e.stopPropagation();
this.handleRemoveExtraction(ext.id);
}}
aria-label="Entfernen"
sx={{
p: 0.2,
ml: 0.1,
color: 'text.secondary',
'&:hover': {
color: 'error.main',
bgcolor: 'action.hover',
},
}}
>
<CloseRoundedIcon sx={{ fontSize: 13 }} />
</IconButton>
</Box>
))}
</Box>
)}