feat(lieferung/neu): PDF upload with SSE streaming and result dialog
- Add PDF-Upload button to ManifestHeader (delivery forms only) - New ManifestPdfDialog: live SSE event log, structured basedata, and line-items table streamed from POST /api/delivery/parsePDF - ApiClient.delivery.parsePdfSse: multipart/form-data upload, async generator yielding typed SSE events (status, llm_call, tool_call, usage, progress, result, error) - ManifestForm.handlePdfUpload: drives SSE generator, updates dialog state in real-time; dialog opens immediately on upload - Fix MUI icon imports (CheckCircle, ErrorOutlined)
This commit is contained in:
@@ -12,6 +12,7 @@ import { notifyError } from '../../utils/notify';
|
||||
import withNavigate from '../withNavigate';
|
||||
import ManifestHeader from './ManifestHeader';
|
||||
import ManifestLines from './ManifestLines';
|
||||
import ManifestPdfDialog from './ManifestPdfDialog';
|
||||
import {
|
||||
buildManifestBody,
|
||||
dateInputValue,
|
||||
@@ -38,6 +39,10 @@ class ManifestForm extends PureComponent {
|
||||
externalNumber: '',
|
||||
lines: [emptyLine()],
|
||||
saving: false,
|
||||
pdfUploading: false,
|
||||
pdfDialogOpen: false,
|
||||
pdfDialogData: null,
|
||||
pdfFileName: '',
|
||||
};
|
||||
this._cancelled = false;
|
||||
this.handleRetry = this.handleRetry.bind(this);
|
||||
@@ -50,6 +55,8 @@ class ManifestForm extends PureComponent {
|
||||
this.handleAddLine = this.handleAddLine.bind(this);
|
||||
this.handleRemoveLine = this.handleRemoveLine.bind(this);
|
||||
this.handleSave = this.handleSave.bind(this);
|
||||
this.handlePdfUpload = this.handlePdfUpload.bind(this);
|
||||
this.handleClosePdfDialog = this.handleClosePdfDialog.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -185,6 +192,83 @@ class ManifestForm extends PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
async handlePdfUpload(file) {
|
||||
if (!file || this.state.pdfUploading) return;
|
||||
this.setState({
|
||||
pdfUploading: true,
|
||||
pdfDialogOpen: true,
|
||||
pdfFileName: file.name,
|
||||
pdfDialogData: { streaming: true, messages: [], result: null, error: null },
|
||||
});
|
||||
|
||||
const appendMessage = (msg) => {
|
||||
if (this._cancelled) return;
|
||||
this.setState((prev) => ({
|
||||
pdfDialogData: {
|
||||
...prev.pdfDialogData,
|
||||
messages: [...(prev.pdfDialogData?.messages ?? []), msg],
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
try {
|
||||
const gen = this.context?.delivery?.parsePdfSse;
|
||||
if (!gen) throw new Error('parsePdfSse nicht verfügbar.');
|
||||
|
||||
for await (const { event, data } of this.context.delivery.parsePdfSse(file)) {
|
||||
if (this._cancelled) return;
|
||||
switch (event) {
|
||||
case 'status':
|
||||
appendMessage({ type: 'status', text: data.message ?? String(data) });
|
||||
break;
|
||||
case 'llm_call':
|
||||
appendMessage({ type: 'llm_call', text: `LLM-Aufruf: ${data.model} (${data.promptSize} tokens, ${data.messages} msg)` });
|
||||
break;
|
||||
case 'tool_call':
|
||||
appendMessage({ type: 'tool_call', text: `Tool-Aufruf: ${data.name}` });
|
||||
break;
|
||||
case 'usage':
|
||||
appendMessage({ type: 'usage', text: `Token-Verbrauch: ${data.input} in / ${data.cached} cached / ${data.output} out — $${(data.cost ?? 0).toFixed(4)}` });
|
||||
break;
|
||||
case 'progress':
|
||||
this.setState((prev) => ({
|
||||
pdfDialogData: { ...prev.pdfDialogData, result: data },
|
||||
}));
|
||||
break;
|
||||
case 'result':
|
||||
this.setState((prev) => ({
|
||||
pdfDialogData: { ...prev.pdfDialogData, result: data, streaming: false },
|
||||
}));
|
||||
break;
|
||||
case 'error':
|
||||
this.setState((prev) => ({
|
||||
pdfDialogData: { ...prev.pdfDialogData, error: data.message ?? String(data), streaming: false },
|
||||
}));
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (this._cancelled) return;
|
||||
notifyError(err);
|
||||
this.setState((prev) => ({
|
||||
pdfDialogData: {
|
||||
...(prev.pdfDialogData ?? {}),
|
||||
error: err.message || String(err),
|
||||
streaming: false,
|
||||
},
|
||||
}));
|
||||
} finally {
|
||||
if (!this._cancelled) this.setState({ pdfUploading: false });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
handleClosePdfDialog() {
|
||||
this.setState({ pdfDialogOpen: false });
|
||||
}
|
||||
|
||||
async handleSave() {
|
||||
const { mode, docId } = this.props;
|
||||
const cfg = this.cfg;
|
||||
@@ -298,6 +382,10 @@ class ManifestForm extends PureComponent {
|
||||
number,
|
||||
externalNumber,
|
||||
saving,
|
||||
pdfUploading,
|
||||
pdfDialogOpen,
|
||||
pdfDialogData,
|
||||
pdfFileName,
|
||||
} = this.state;
|
||||
|
||||
if (status === 'loading') {
|
||||
@@ -313,26 +401,36 @@ class ManifestForm extends PureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<PanelTileGrid>
|
||||
<ManifestHeader
|
||||
kind={kind}
|
||||
headerColor={cfg.headerColor}
|
||||
dateLabel={cfg.dateLabel}
|
||||
partyLabel={cfg.partyLabel}
|
||||
parties={parties}
|
||||
partiesLoading={partiesLoading || status === 'loadingParties'}
|
||||
partyId={partyId}
|
||||
date={date}
|
||||
number={number}
|
||||
externalNumber={externalNumber}
|
||||
saving={saving}
|
||||
onPartyChange={this.handlePartyChange}
|
||||
onDateChange={this.handleDateChange}
|
||||
onNumberChange={this.handleNumberChange}
|
||||
onExternalNumberChange={this.handleExternalNumberChange}
|
||||
<>
|
||||
<PanelTileGrid>
|
||||
<ManifestHeader
|
||||
kind={kind}
|
||||
headerColor={cfg.headerColor}
|
||||
dateLabel={cfg.dateLabel}
|
||||
partyLabel={cfg.partyLabel}
|
||||
parties={parties}
|
||||
partiesLoading={partiesLoading || status === 'loadingParties'}
|
||||
partyId={partyId}
|
||||
date={date}
|
||||
number={number}
|
||||
externalNumber={externalNumber}
|
||||
saving={saving}
|
||||
pdfUploading={pdfUploading}
|
||||
onPdfUpload={kind === 'delivery' ? this.handlePdfUpload : undefined}
|
||||
onPartyChange={this.handlePartyChange}
|
||||
onDateChange={this.handleDateChange}
|
||||
onNumberChange={this.handleNumberChange}
|
||||
onExternalNumberChange={this.handleExternalNumberChange}
|
||||
/>
|
||||
{status === 'ready' ? this.renderArticles() : null}
|
||||
</PanelTileGrid>
|
||||
<ManifestPdfDialog
|
||||
open={pdfDialogOpen}
|
||||
filename={pdfFileName}
|
||||
data={pdfDialogData}
|
||||
onClose={this.handleClosePdfDialog}
|
||||
/>
|
||||
{status === 'ready' ? this.renderArticles() : null}
|
||||
</PanelTileGrid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user