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.
|
* 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();
|
const trimmed = String(text || '').trim();
|
||||||
if (!trimmed) return;
|
if (!trimmed) return;
|
||||||
chatOpenedThisSession = true;
|
chatOpenedThisSession = true;
|
||||||
@@ -343,6 +347,7 @@ const actions = {
|
|||||||
pendingExtraction: {
|
pendingExtraction: {
|
||||||
text: trimmed,
|
text: trimmed,
|
||||||
label: label || 'parsed invoice',
|
label: label || 'parsed invoice',
|
||||||
|
instruction: instruction || "use 'Process Incoming Product and Invoices' SKILL to fill drafts/delivery vfs file",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
ensureCompanyEventsConnected();
|
ensureCompanyEventsConnected();
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
|
|||||||
import Tooltip from '@mui/material/Tooltip';
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
import Badge from '@mui/material/Badge';
|
import Badge from '@mui/material/Badge';
|
||||||
import Divider from '@mui/material/Divider';
|
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 AttachFileRoundedIcon from '@mui/icons-material/AttachFileRounded';
|
||||||
|
import ContentCopyRoundedIcon from '@mui/icons-material/ContentCopyRounded';
|
||||||
import TuneRoundedIcon from '@mui/icons-material/TuneRounded';
|
import TuneRoundedIcon from '@mui/icons-material/TuneRounded';
|
||||||
import ElderlyRoundedIcon from '@mui/icons-material/ElderlyRounded';
|
import ElderlyRoundedIcon from '@mui/icons-material/ElderlyRounded';
|
||||||
import DirectionsWalkRoundedIcon from '@mui/icons-material/DirectionsWalkRounded';
|
import DirectionsWalkRoundedIcon from '@mui/icons-material/DirectionsWalkRounded';
|
||||||
@@ -47,6 +52,7 @@ import {
|
|||||||
} from '../../colors';
|
} from '../../colors';
|
||||||
|
|
||||||
const DEFAULT_DRAWER_WIDTH = 460;
|
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. */
|
/** Sort by updated_at desc; subagents (started_by set) directly below their parent. */
|
||||||
function sortConversations(list) {
|
function sortConversations(list) {
|
||||||
@@ -122,9 +128,11 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
id: `ext-${Date.now()}`,
|
id: `ext-${Date.now()}`,
|
||||||
text: s.pendingExtraction.text,
|
text: s.pendingExtraction.text,
|
||||||
label: s.pendingExtraction.label || 'parsed invoice',
|
label: s.pendingExtraction.label || 'parsed invoice',
|
||||||
|
instruction: s.pendingExtraction.instruction || EXTRACTION_MODEL_INSTRUCTION,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
|
previewExtraction: null,
|
||||||
isProcessingFiles: false,
|
isProcessingFiles: false,
|
||||||
isDraggingFiles: false,
|
isDraggingFiles: false,
|
||||||
attachmentError: null,
|
attachmentError: null,
|
||||||
@@ -234,6 +242,7 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
id: `ext-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
id: `ext-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||||
text: ext.text,
|
text: ext.text,
|
||||||
label: ext.label || 'parsed invoice',
|
label: ext.label || 'parsed invoice',
|
||||||
|
instruction: ext.instruction || EXTRACTION_MODEL_INSTRUCTION,
|
||||||
};
|
};
|
||||||
patch.stagedExtractions = [...(this.state.stagedExtractions || []), newExt];
|
patch.stagedExtractions = [...(this.state.stagedExtractions || []), newExt];
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
@@ -348,6 +357,22 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
this.focusInput(50);
|
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) => {
|
handleVfsResizerStart = (e) => {
|
||||||
if (e.button !== undefined && e.button !== 0) return;
|
if (e.button !== undefined && e.button !== 0) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -534,10 +559,22 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
|
|
||||||
let text = rawText;
|
let text = rawText;
|
||||||
if (extractions.length > 0) {
|
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
|
const extMarkdown = extractions
|
||||||
.map((ext) => `\`\`\`\n${ext.text.trim()}\n\`\`\``)
|
.map((ext) => `\`\`\`\n${ext.text.trim()}\n\`\`\``)
|
||||||
.join('\n\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) {
|
if (references.length > 0) {
|
||||||
@@ -1601,6 +1638,10 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
{stagedExtractions && stagedExtractions.map((ext) => (
|
{stagedExtractions && stagedExtractions.map((ext) => (
|
||||||
<Box
|
<Box
|
||||||
key={ext.id}
|
key={ext.id}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.handlePreviewExtraction(ext);
|
||||||
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
display: 'inline-flex',
|
display: 'inline-flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -1622,6 +1663,15 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
lineHeight: 1.2,
|
lineHeight: 1.2,
|
||||||
userSelect: 'none',
|
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' }} />
|
<DescriptionOutlinedIcon sx={{ fontSize: 14, color: 'primary.main' }} />
|
||||||
@@ -1921,6 +1971,71 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
{this.renderFooter()}
|
{this.renderFooter()}
|
||||||
</Box>
|
</Box>
|
||||||
</Drawer>
|
</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) => {
|
handleMentionInChat = async (e) => {
|
||||||
e?.stopPropagation?.();
|
e?.stopPropagation?.();
|
||||||
const { attachment, onMentionInChat } = this.props;
|
const { attachment, onMentionInChat } = this.props;
|
||||||
|
console.log('[Attachment] Mention in chat clicked', attachment);
|
||||||
if (!attachment) return;
|
if (!attachment) return;
|
||||||
|
|
||||||
if (onMentionInChat) {
|
if (onMentionInChat) {
|
||||||
@@ -105,11 +106,15 @@ export class Attachment extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trimmed = (textContent || '').trim();
|
const trimmed = (textContent || '').trim();
|
||||||
if (!trimmed) return;
|
if (!trimmed) {
|
||||||
|
console.warn('[Attachment] No text found to mention in chat', attachment);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
chatAutomat.actions.insertExtractionSnippet({
|
chatAutomat.actions.insertExtractionSnippet({
|
||||||
text: trimmed,
|
text: trimmed,
|
||||||
label: 'parsed invoice',
|
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) {
|
async handleMentionInChat(attachment) {
|
||||||
|
console.log('[neu.jsx] handleMentionInChat called', attachment);
|
||||||
if (!attachment) return;
|
if (!attachment) return;
|
||||||
let text = attachment.text;
|
let text = attachment.text;
|
||||||
if (!text && (!attachment.file || attachment.file.size === 0)) {
|
if (!text && (!attachment.file || attachment.file.size === 0)) {
|
||||||
@@ -708,11 +709,16 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trimmed = (text || '').trim();
|
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({
|
chatAutomat.actions.insertExtractionSnippet({
|
||||||
text: trimmed,
|
text: trimmed,
|
||||||
label: 'parsed invoice',
|
label: 'parsed invoice',
|
||||||
|
instruction: "use 'Process Incoming Product and Invoices' SKILL to fill drafts/delivery vfs file",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user