This commit is contained in:
seb
2026-10-16 16:03:31 +02:00
parent f5a5cb51e3
commit 99d95ee234
7 changed files with 377 additions and 103 deletions

View File

@@ -37,12 +37,17 @@ import LightModeRoundedIcon from '@mui/icons-material/LightModeRounded';
import CycloneRoundedIcon from '@mui/icons-material/CycloneRounded';
import { setting as settingApi } from '../../api';
import chatAutomat, { selectPendingAskUser } from '../../automats/chatAutomat';
import chatAutomat, {
selectPendingAskUser,
selectPendingDraftRead,
} from '../../automats/chatAutomat';
import { getActiveDraftContent } from '../../automats/draftSync';
import userSettingsAutomat from '../../automats/userSettingsAutomat';
import vfsAutomat from '../../automats/vfsAutomat';
import { VfsEditorPanel, VfsFileIcon } from '../vfs';
import ChatFlowList from './ChatFlowList';
import AskUserPrompt from './AskUserPrompt';
import DraftRequestPrompt from './DraftRequestPrompt';
import { ACCEPT_STRING, readFileAttachment } from './chatAttachments';
import { ChatStagedAttachmentsTray } from './ChatAttachmentItems';
import {
@@ -836,6 +841,33 @@ export default class ChatDrawer extends PureComponent {
};
getPendingAskUser = () => selectPendingAskUser(this.state);
getPendingDraftRead = () => selectPendingDraftRead(this.state);
handleProvideDraft = async ({ toolCallId, draftKey, empty = false }) => {
try {
let content;
if (empty) {
content = { error: 'Draft not open or empty' };
} else {
const manifest = getActiveDraftContent(draftKey);
content = {
manifest: manifest || {
kind: draftKey,
mode: 'create',
lines: [],
},
};
}
await chatAutomat.actions.sendToolResponse({
tool_call_id: toolCallId,
name: 'read_file',
content,
});
} catch (err) {
console.error('[ChatDrawer] Failed to provide draft response:', err);
}
};
renderHeader() {
const {
@@ -1332,6 +1364,7 @@ export default class ChatDrawer extends PureComponent {
}
const pendingAsk = this.getPendingAskUser();
const pendingDraftRead = this.getPendingDraftRead();
return (
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', minHeight: 0, overflow: 'hidden' }}>
@@ -1366,6 +1399,14 @@ export default class ChatDrawer extends PureComponent {
disabled={this.state.isSending}
/>
)}
{pendingDraftRead && (
<DraftRequestPrompt
draftData={pendingDraftRead}
onProvideDraft={this.handleProvideDraft}
disabled={this.state.isSending}
/>
)}
</Box>
);
}
@@ -1388,9 +1429,13 @@ export default class ChatDrawer extends PureComponent {
} = this.state;
const pendingAsk = this.getPendingAskUser();
const pendingDraftRead = this.getPendingDraftRead();
const isBusy = conversationState === 'busy';
const isWorking = isBusy || isSending;
const canChat = (Boolean(selectedConversationId) || draftConversation) && !pendingAsk;
const canChat =
(Boolean(selectedConversationId) || draftConversation) &&
!pendingAsk &&
!pendingDraftRead;
const canSend =
!isWorking &&
canChat &&
@@ -1762,9 +1807,11 @@ export default class ChatDrawer extends PureComponent {
placeholder={
pendingAsk
? 'Bitte Frage oben beantworten…'
: stagedAttachments.length > 0
? 'Nachricht hinzufügen oder direkt absenden…'
: undefined
: pendingDraftRead
? 'Bitte Entwurfsanfrage oben beantworten…'
: stagedAttachments.length > 0
? 'Nachricht hinzufügen oder direkt absenden…'
: undefined
}
sx={{
px: 0.5,

View File

@@ -0,0 +1,118 @@
import React from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
import SendRoundedIcon from '@mui/icons-material/SendRounded';
import BlockRoundedIcon from '@mui/icons-material/BlockRounded';
import { getActiveDraftContent } from '../../automats/draftSync';
import {
ASK_PROMPT_BG_PAIR,
ASK_PROMPT_BORDER_PAIR,
byMode,
} from '../../colors';
export default function DraftRequestPrompt({ draftData, onProvideDraft, disabled = false }) {
const { toolCallId, filename, draftKey } = draftData;
const manifest = getActiveDraftContent(draftKey);
const rawLines = manifest?.lines ?? manifest?.lineitems ?? [];
const linesCount = Array.isArray(rawLines) ? rawLines.length : 0;
const hasContent = Boolean(manifest && (linesCount > 0 || manifest.partyId || manifest.supplierId));
const handleSendDraft = () => {
if (disabled) return;
onProvideDraft({ toolCallId, draftKey, empty: false });
};
const handleSendEmpty = () => {
if (disabled) return;
onProvideDraft({ toolCallId, draftKey, empty: true });
};
const draftLabel =
draftKey === 'delivery'
? 'Lieferung'
: draftKey === 'quote'
? 'Angebot'
: draftKey === 'order'
? 'Bestellung'
: draftKey;
return (
<Box
sx={(theme) => ({
mx: 2,
my: 1,
p: 2,
borderRadius: 3,
bgcolor: byMode(theme, ASK_PROMPT_BG_PAIR),
border: `1px solid ${byMode(theme, ASK_PROMPT_BORDER_PAIR)}`,
display: 'flex',
flexDirection: 'column',
gap: 1.5,
})}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, color: 'primary.main' }}>
<DescriptionOutlinedIcon sx={{ fontSize: 20 }} />
<Typography variant="subtitle2" fontWeight={700} sx={{ color: 'text.primary' }}>
Entwurfsanfrage: {filename || `drafts/${draftKey}`}
</Typography>
</Box>
<Typography variant="body2" sx={{ color: 'text.secondary', fontSize: '0.82rem', lineHeight: 1.4 }}>
{hasContent ? (
<>
Das Modell möchte den aktuellen <strong>{draftLabel}-Entwurf</strong> ({linesCount} Position{linesCount === 1 ? '' : 'en'}) einlesen, um die Bearbeitung fortzusetzen.
</>
) : (
<>
Das Modell fordert <strong>{filename}</strong> an. Aktuell ist kein {draftLabel}-Entwurf geöffnet.
</>
)}
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mt: 0.5 }}>
<Button
variant="contained"
size="small"
disabled={disabled}
onClick={handleSendDraft}
startIcon={<SendRoundedIcon sx={{ fontSize: 16 }} />}
sx={{
borderRadius: 2,
px: 2,
py: 0.75,
textTransform: 'none',
fontSize: '0.82rem',
fontWeight: 600,
}}
>
{hasContent ? `Aktuellen ${draftLabel}-Entwurf senden` : 'Leeren Entwurf senden'}
</Button>
<Button
variant="outlined"
size="small"
disabled={disabled}
onClick={handleSendEmpty}
startIcon={<BlockRoundedIcon sx={{ fontSize: 16 }} />}
sx={{
borderRadius: 2,
px: 1.5,
py: 0.75,
textTransform: 'none',
fontSize: '0.82rem',
color: 'text.secondary',
borderColor: 'divider',
'&:hover': {
borderColor: 'error.main',
color: 'error.main',
},
}}
>
Nicht verfügbar melden
</Button>
</Box>
</Box>
);
}

View File

@@ -2,3 +2,4 @@ export { default as ChatFab } from './ChatFab';
export { default as ChatDrawer } from './ChatDrawer';
export { default as ChatFlowList } from './ChatFlowList';
export { default as NewConversationDialog } from './NewConversationDialog';
export { default as DraftRequestPrompt } from './DraftRequestPrompt';