diff --git a/src/automats/chatAutomat.js b/src/automats/chatAutomat.js index d93fda5..e16ee44 100644 --- a/src/automats/chatAutomat.js +++ b/src/automats/chatAutomat.js @@ -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(); diff --git a/src/components/chat/ChatDrawer.jsx b/src/components/chat/ChatDrawer.jsx index a9ce16d..1e2f735 100644 --- a/src/components/chat/ChatDrawer.jsx +++ b/src/components/chat/ChatDrawer.jsx @@ -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) => ( { + 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', + }, }} > @@ -1921,6 +1971,71 @@ export default class ChatDrawer extends PureComponent { {this.renderFooter()} + + {/* Extraction Preview Dialog */} + + + + + + {this.state.previewExtraction?.label || 'parsed invoice'} + + + + + + + theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.02)' }}> + + {this.state.previewExtraction?.text || ''} + + + + + + + ); } diff --git a/src/components/manifest/Attachment.jsx b/src/components/manifest/Attachment.jsx index e86d310..180ef7b 100644 --- a/src/components/manifest/Attachment.jsx +++ b/src/components/manifest/Attachment.jsx @@ -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", }); }; diff --git a/src/routes/lieferung/neu.jsx b/src/routes/lieferung/neu.jsx index 4e23548..a7adc94 100644 --- a/src/routes/lieferung/neu.jsx +++ b/src/routes/lieferung/neu.jsx @@ -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", }); }