u
This commit is contained in:
@@ -331,9 +331,13 @@ const actions = {
|
||||
|
||||
/**
|
||||
* Stage an extracted text snippet (e.g. from an invoice or delivery document) as a pill.
|
||||
* @param {{ text: string, label?: string }} snippet
|
||||
* @param {{ text: string, label?: string, instruction?: string }} snippet
|
||||
*/
|
||||
insertExtractionSnippet({ text = '', label = 'parsed invoice' } = {}) {
|
||||
insertExtractionSnippet({
|
||||
text = '',
|
||||
label = 'parsed invoice',
|
||||
instruction = "use 'Process Incoming Product and Invoices' SKILL to fill drafts/delivery vfs file",
|
||||
} = {}) {
|
||||
const trimmed = String(text || '').trim();
|
||||
if (!trimmed) return;
|
||||
chatOpenedThisSession = true;
|
||||
@@ -343,6 +347,7 @@ const actions = {
|
||||
pendingExtraction: {
|
||||
text: trimmed,
|
||||
label: label || 'parsed invoice',
|
||||
instruction: instruction || "use 'Process Incoming Product and Invoices' SKILL to fill drafts/delivery vfs file",
|
||||
},
|
||||
});
|
||||
ensureCompanyEventsConnected();
|
||||
|
||||
@@ -20,7 +20,12 @@ import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Badge from '@mui/material/Badge';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import AttachFileRoundedIcon from '@mui/icons-material/AttachFileRounded';
|
||||
import ContentCopyRoundedIcon from '@mui/icons-material/ContentCopyRounded';
|
||||
import TuneRoundedIcon from '@mui/icons-material/TuneRounded';
|
||||
import ElderlyRoundedIcon from '@mui/icons-material/ElderlyRounded';
|
||||
import DirectionsWalkRoundedIcon from '@mui/icons-material/DirectionsWalkRounded';
|
||||
@@ -47,6 +52,7 @@ import {
|
||||
} from '../../colors';
|
||||
|
||||
const DEFAULT_DRAWER_WIDTH = 460;
|
||||
const EXTRACTION_MODEL_INSTRUCTION = "use 'Process Incoming Product and Invoices' SKILL to fill drafts/delivery vfs file";
|
||||
|
||||
/** Sort by updated_at desc; subagents (started_by set) directly below their parent. */
|
||||
function sortConversations(list) {
|
||||
@@ -122,9 +128,11 @@ export default class ChatDrawer extends PureComponent {
|
||||
id: `ext-${Date.now()}`,
|
||||
text: s.pendingExtraction.text,
|
||||
label: s.pendingExtraction.label || 'parsed invoice',
|
||||
instruction: s.pendingExtraction.instruction || EXTRACTION_MODEL_INSTRUCTION,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
previewExtraction: null,
|
||||
isProcessingFiles: false,
|
||||
isDraggingFiles: false,
|
||||
attachmentError: null,
|
||||
@@ -234,6 +242,7 @@ export default class ChatDrawer extends PureComponent {
|
||||
id: `ext-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
text: ext.text,
|
||||
label: ext.label || 'parsed invoice',
|
||||
instruction: ext.instruction || EXTRACTION_MODEL_INSTRUCTION,
|
||||
};
|
||||
patch.stagedExtractions = [...(this.state.stagedExtractions || []), newExt];
|
||||
queueMicrotask(() => {
|
||||
@@ -348,6 +357,22 @@ export default class ChatDrawer extends PureComponent {
|
||||
this.focusInput(50);
|
||||
};
|
||||
|
||||
handlePreviewExtraction = (ext) => {
|
||||
console.log('[ChatDrawer] Clicked extraction pill:', ext);
|
||||
this.setState({ previewExtraction: ext });
|
||||
};
|
||||
|
||||
handleCloseExtractionPreview = () => {
|
||||
this.setState({ previewExtraction: null });
|
||||
};
|
||||
|
||||
handleCopyExtractionText = () => {
|
||||
const text = this.state.previewExtraction?.text;
|
||||
if (text && navigator?.clipboard?.writeText) {
|
||||
navigator.clipboard.writeText(text);
|
||||
}
|
||||
};
|
||||
|
||||
handleVfsResizerStart = (e) => {
|
||||
if (e.button !== undefined && e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
@@ -534,10 +559,22 @@ export default class ChatDrawer extends PureComponent {
|
||||
|
||||
let text = rawText;
|
||||
if (extractions.length > 0) {
|
||||
const instructions = extractions
|
||||
.map((e) => e.instruction)
|
||||
.filter(Boolean);
|
||||
const uniqueInstructions = Array.from(new Set(instructions));
|
||||
const instructionText =
|
||||
uniqueInstructions.join('\n\n') || EXTRACTION_MODEL_INSTRUCTION;
|
||||
|
||||
const extMarkdown = extractions
|
||||
.map((ext) => `\`\`\`\n${ext.text.trim()}\n\`\`\``)
|
||||
.join('\n\n');
|
||||
text = text ? `${extMarkdown}\n\n${text}` : extMarkdown;
|
||||
|
||||
if (text) {
|
||||
text = `${instructionText}\n\n${text}\n\n${extMarkdown}`;
|
||||
} else {
|
||||
text = `${instructionText}\n\n${extMarkdown}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (references.length > 0) {
|
||||
@@ -1601,6 +1638,10 @@ export default class ChatDrawer extends PureComponent {
|
||||
{stagedExtractions && stagedExtractions.map((ext) => (
|
||||
<Box
|
||||
key={ext.id}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.handlePreviewExtraction(ext);
|
||||
}}
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
@@ -1622,6 +1663,15 @@ export default class ChatDrawer extends PureComponent {
|
||||
fontSize: '0.75rem',
|
||||
lineHeight: 1.2,
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.15s ease',
|
||||
'&:hover': {
|
||||
bgcolor: (theme) =>
|
||||
theme.palette.mode === 'dark'
|
||||
? 'rgba(255, 255, 255, 0.14)'
|
||||
: 'rgba(0, 0, 0, 0.09)',
|
||||
borderColor: 'primary.light',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DescriptionOutlinedIcon sx={{ fontSize: 14, color: 'primary.main' }} />
|
||||
@@ -1921,6 +1971,71 @@ export default class ChatDrawer extends PureComponent {
|
||||
{this.renderFooter()}
|
||||
</Box>
|
||||
</Drawer>
|
||||
|
||||
{/* Extraction Preview Dialog */}
|
||||
<Dialog
|
||||
open={Boolean(this.state.previewExtraction)}
|
||||
onClose={this.handleCloseExtractionPreview}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
borderRadius: 2.5,
|
||||
bgcolor: 'background.paper',
|
||||
backgroundImage: 'none',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', pb: 1 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<DescriptionOutlinedIcon sx={{ color: 'primary.main', fontSize: 22 }} />
|
||||
<Typography variant="h6" component="span" sx={{ fontSize: '1.05rem', fontWeight: 600 }}>
|
||||
{this.state.previewExtraction?.label || 'parsed invoice'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton size="small" onClick={this.handleCloseExtractionPreview} aria-label="Schließen">
|
||||
<CloseRoundedIcon sx={{ fontSize: 20 }} />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent dividers sx={{ p: 2, bgcolor: (theme) => theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.02)' }}>
|
||||
<Box
|
||||
component="pre"
|
||||
sx={{
|
||||
m: 0,
|
||||
p: 1.5,
|
||||
borderRadius: 1.5,
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.5,
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word',
|
||||
overflow: 'auto',
|
||||
maxHeight: '60vh',
|
||||
bgcolor: 'background.paper',
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
{this.state.previewExtraction?.text || ''}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 2.5, py: 1.5 }}>
|
||||
<Button
|
||||
startIcon={<ContentCopyRoundedIcon sx={{ fontSize: 18 }} />}
|
||||
onClick={this.handleCopyExtractionText}
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
>
|
||||
Kopieren
|
||||
</Button>
|
||||
<Button onClick={this.handleCloseExtractionPreview} color="inherit" size="small">
|
||||
Schließen
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ export class Attachment extends PureComponent {
|
||||
handleMentionInChat = async (e) => {
|
||||
e?.stopPropagation?.();
|
||||
const { attachment, onMentionInChat } = this.props;
|
||||
console.log('[Attachment] Mention in chat clicked', attachment);
|
||||
if (!attachment) return;
|
||||
|
||||
if (onMentionInChat) {
|
||||
@@ -105,11 +106,15 @@ export class Attachment extends PureComponent {
|
||||
}
|
||||
|
||||
const trimmed = (textContent || '').trim();
|
||||
if (!trimmed) return;
|
||||
if (!trimmed) {
|
||||
console.warn('[Attachment] No text found to mention in chat', attachment);
|
||||
return;
|
||||
}
|
||||
|
||||
chatAutomat.actions.insertExtractionSnippet({
|
||||
text: trimmed,
|
||||
label: 'parsed invoice',
|
||||
instruction: "use 'Process Incoming Product and Invoices' SKILL to fill drafts/delivery vfs file",
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -688,6 +688,7 @@ class LieferungNeuPage extends PureComponent {
|
||||
}
|
||||
|
||||
async handleMentionInChat(attachment) {
|
||||
console.log('[neu.jsx] handleMentionInChat called', attachment);
|
||||
if (!attachment) return;
|
||||
let text = attachment.text;
|
||||
if (!text && (!attachment.file || attachment.file.size === 0)) {
|
||||
@@ -708,11 +709,16 @@ class LieferungNeuPage extends PureComponent {
|
||||
}
|
||||
|
||||
const trimmed = (text || '').trim();
|
||||
if (!trimmed) return;
|
||||
if (!trimmed) {
|
||||
console.warn('[neu.jsx] handleMentionInChat: text is empty for attachment', attachment);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[neu.jsx] Inserting extraction snippet to chat, length:', trimmed.length);
|
||||
chatAutomat.actions.insertExtractionSnippet({
|
||||
text: trimmed,
|
||||
label: 'parsed invoice',
|
||||
instruction: "use 'Process Incoming Product and Invoices' SKILL to fill drafts/delivery vfs file",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user