u
This commit is contained in:
@@ -7,7 +7,7 @@ description: >-
|
|||||||
|
|
||||||
# Dash API Routes Overview (All)
|
# Dash API Routes Overview (All)
|
||||||
|
|
||||||
Overview of all 248 API endpoints across all modules.
|
Overview of all 250 API endpoints across all modules.
|
||||||
|
|
||||||
### Notation
|
### Notation
|
||||||
|
|
||||||
@@ -919,6 +919,9 @@ _none_
|
|||||||
#### List virtual filesystem files — `GET /vfs`
|
#### List virtual filesystem files — `GET /vfs`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { files: { name: str, size: int, mime: str, updated_at: str, created_at: str, isText: bool, isImage: bool, isPdf: bool }[] }`
|
- Returns: `200 { files: { name: str, size: int, mime: str, updated_at: str, created_at: str, isText: bool, isImage: bool, isPdf: bool }[] }`
|
||||||
|
#### Restore system default applets — `POST /vfs/applets/restore`
|
||||||
|
_none_
|
||||||
|
- Returns: `200 { success: bool, restored: str[], count: int }`
|
||||||
#### Respond to a frontend draft request — `POST /vfs/drafts/response`
|
#### Respond to a frontend draft request — `POST /vfs/drafts/response`
|
||||||
- Params: `body: { requestId: str, content?: any, error?: str, files?: any[] }`
|
- Params: `body: { requestId: str, content?: any, error?: str, files?: any[] }`
|
||||||
- Returns: `200 { success: bool }`
|
- Returns: `200 { success: bool }`
|
||||||
@@ -928,6 +931,9 @@ _none_
|
|||||||
#### Rename or move a virtual filesystem file or directory — `POST /vfs/rename`
|
#### Rename or move a virtual filesystem file or directory — `POST /vfs/rename`
|
||||||
- Params: `body: { oldPath: str, newPath: str }`
|
- Params: `body: { oldPath: str, newPath: str }`
|
||||||
- Returns: `200 { success: bool, oldPath: str, newPath: str }`
|
- Returns: `200 { success: bool, oldPath: str, newPath: str }`
|
||||||
|
#### Restore system default skills — `POST /vfs/skills/restore`
|
||||||
|
_none_
|
||||||
|
- Returns: `200 { success: bool, restored: str[], count: int }`
|
||||||
#### Upload files to virtual filesystem — `POST /vfs/upload`
|
#### Upload files to virtual filesystem — `POST /vfs/upload`
|
||||||
- Params: `form: { files: str[], targetDir?: str }`
|
- Params: `form: { files: str[], targetDir?: str }`
|
||||||
- Returns: `200 { success: bool, uploaded: { name: str, size: int, mime: str }[] }`
|
- Returns: `200 { success: bool, uploaded: { name: str, size: int, mime: str }[] }`
|
||||||
|
|||||||
@@ -181,4 +181,32 @@ export const vfs = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/vfs/skills/restore
|
||||||
|
* Restores all system default .SKILL files into the company's VFS (overwrites existing files).
|
||||||
|
* Requires no request body. Emits vfs SSE event.
|
||||||
|
* → 200 { success: true, restored: string[], count: number }
|
||||||
|
*/
|
||||||
|
// #region POST /api/vfs/skills/restore — restoreSkills
|
||||||
|
restoreSkills() {
|
||||||
|
return http('/api/vfs/skills/restore', {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/vfs/applets/restore
|
||||||
|
* Restores all system default .applet.js example applets into the company's VFS (overwrites existing files).
|
||||||
|
* Requires no request body. Emits vfs SSE event.
|
||||||
|
* → 200 { success: true, restored: string[], count: number }
|
||||||
|
*/
|
||||||
|
// #region POST /api/vfs/applets/restore — restoreApplets
|
||||||
|
restoreApplets() {
|
||||||
|
return http('/api/vfs/applets/restore', {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -331,20 +331,18 @@ 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, size?: number, label?: string }} snippet
|
* @param {{ text: string, label?: string }} snippet
|
||||||
*/
|
*/
|
||||||
insertExtractionSnippet({ text = '', size, label } = {}) {
|
insertExtractionSnippet({ text = '', label = 'parsed invoice' } = {}) {
|
||||||
const trimmed = String(text || '').trim();
|
const trimmed = String(text || '').trim();
|
||||||
if (!trimmed) return;
|
if (!trimmed) return;
|
||||||
const finalSize = Number(size) || new Blob([trimmed]).size;
|
|
||||||
chatOpenedThisSession = true;
|
chatOpenedThisSession = true;
|
||||||
chatAutomat.setState({
|
chatAutomat.setState({
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
sseEstablished: true,
|
sseEstablished: true,
|
||||||
pendingExtraction: {
|
pendingExtraction: {
|
||||||
text: trimmed,
|
text: trimmed,
|
||||||
size: finalSize,
|
label: label || 'parsed invoice',
|
||||||
label,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
ensureCompanyEventsConnected();
|
ensureCompanyEventsConnected();
|
||||||
@@ -356,26 +354,17 @@ const actions = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the chat drawer and append text / prompt to the chat composer.
|
* Open the chat drawer and append text / prompt to the chat composer.
|
||||||
* If the prompt is an extracted text snippet (e.g. `Text aus „...“:`),
|
|
||||||
* automatically converts it into a staged extraction pill.
|
|
||||||
* @param {string} prompt
|
* @param {string} prompt
|
||||||
*/
|
*/
|
||||||
insertPrompt(prompt = '') {
|
insertPrompt(prompt = '') {
|
||||||
chatOpenedThisSession = true;
|
const text = String(prompt || '').trim();
|
||||||
const trimmedPrompt = String(prompt || '').trim();
|
if (!text) return;
|
||||||
const extractionMatch = trimmedPrompt.match(/^(?:Text for Data Extraction|Text aus „[^“]+“):\s*```(?:\w+)?\n([\s\S]*?)```$/);
|
|
||||||
if (extractionMatch) {
|
|
||||||
const content = (extractionMatch[1] || '').trim();
|
|
||||||
if (content) {
|
|
||||||
chatAutomat.actions.insertExtractionSnippet({ text: content });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
chatOpenedThisSession = true;
|
||||||
chatAutomat.setState((prev) => ({
|
chatAutomat.setState((prev) => ({
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
sseEstablished: true,
|
sseEstablished: true,
|
||||||
pendingInput: prev.pendingInput ? `${prev.pendingInput}\n\n${prompt}` : prompt,
|
pendingInput: prev.pendingInput ? `${prev.pendingInput}\n\n${text}` : text,
|
||||||
}));
|
}));
|
||||||
ensureCompanyEventsConnected();
|
ensureCompanyEventsConnected();
|
||||||
if (!chatAutomat.state.selectedConversationId && !chatAutomat.state.draftConversation) {
|
if (!chatAutomat.state.selectedConversationId && !chatAutomat.state.draftConversation) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import SendRoundedIcon from '@mui/icons-material/SendRounded';
|
|||||||
import AddRoundedIcon from '@mui/icons-material/AddRounded';
|
import AddRoundedIcon from '@mui/icons-material/AddRounded';
|
||||||
import AutoAwesomeRoundedIcon from '@mui/icons-material/AutoAwesomeRounded';
|
import AutoAwesomeRoundedIcon from '@mui/icons-material/AutoAwesomeRounded';
|
||||||
import DeleteOutlineRoundedIcon from '@mui/icons-material/DeleteOutlineRounded';
|
import DeleteOutlineRoundedIcon from '@mui/icons-material/DeleteOutlineRounded';
|
||||||
|
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';
|
||||||
@@ -115,6 +116,15 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
isInputFocused: false,
|
isInputFocused: false,
|
||||||
stagedAttachments: [],
|
stagedAttachments: [],
|
||||||
stagedReferences: [],
|
stagedReferences: [],
|
||||||
|
stagedExtractions: s.pendingExtraction
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: `ext-${Date.now()}`,
|
||||||
|
text: s.pendingExtraction.text,
|
||||||
|
label: s.pendingExtraction.label || 'parsed invoice',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
isProcessingFiles: false,
|
isProcessingFiles: false,
|
||||||
isDraggingFiles: false,
|
isDraggingFiles: false,
|
||||||
attachmentError: null,
|
attachmentError: null,
|
||||||
@@ -178,6 +188,13 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (chatAutomat.state.pendingExtraction != null) {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
if (chatAutomat.state.pendingExtraction != null) {
|
||||||
|
chatAutomat.setState({ pendingExtraction: null });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
this.unsub = chatAutomat.subscribe(this, (state) => {
|
this.unsub = chatAutomat.subscribe(this, (state) => {
|
||||||
const wasOpen = this.state.isOpen;
|
const wasOpen = this.state.isOpen;
|
||||||
const willOpen = state.isOpen;
|
const willOpen = state.isOpen;
|
||||||
@@ -211,6 +228,21 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
} else if (!wasOpen && willOpen) {
|
} else if (!wasOpen && willOpen) {
|
||||||
this.focusInput(0);
|
this.focusInput(0);
|
||||||
}
|
}
|
||||||
|
if (state.pendingExtraction != null) {
|
||||||
|
const ext = state.pendingExtraction;
|
||||||
|
const newExt = {
|
||||||
|
id: `ext-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||||
|
text: ext.text,
|
||||||
|
label: ext.label || 'parsed invoice',
|
||||||
|
};
|
||||||
|
patch.stagedExtractions = [...(this.state.stagedExtractions || []), newExt];
|
||||||
|
queueMicrotask(() => {
|
||||||
|
if (chatAutomat.state.pendingExtraction != null) {
|
||||||
|
chatAutomat.setState({ pendingExtraction: null });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.focusInput(0);
|
||||||
|
}
|
||||||
return patch;
|
return patch;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -309,6 +341,13 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
this.focusInput(50);
|
this.focusInput(50);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleRemoveExtraction = (id) => {
|
||||||
|
this.setState((prev) => ({
|
||||||
|
stagedExtractions: (prev.stagedExtractions || []).filter((e) => e.id !== id),
|
||||||
|
}));
|
||||||
|
this.focusInput(50);
|
||||||
|
};
|
||||||
|
|
||||||
handleVfsResizerStart = (e) => {
|
handleVfsResizerStart = (e) => {
|
||||||
if (e.button !== undefined && e.button !== 0) return;
|
if (e.button !== undefined && e.button !== 0) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -483,9 +522,10 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
const rawText = this.state.inputText.trim();
|
const rawText = this.state.inputText.trim();
|
||||||
const attachments = this.state.stagedAttachments || [];
|
const attachments = this.state.stagedAttachments || [];
|
||||||
const references = this.state.stagedReferences || [];
|
const references = this.state.stagedReferences || [];
|
||||||
|
const extractions = this.state.stagedExtractions || [];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(!rawText && attachments.length === 0 && references.length === 0) ||
|
(!rawText && attachments.length === 0 && references.length === 0 && extractions.length === 0) ||
|
||||||
this.state.conversationState === 'busy' ||
|
this.state.conversationState === 'busy' ||
|
||||||
this.state.isSending
|
this.state.isSending
|
||||||
) {
|
) {
|
||||||
@@ -493,6 +533,13 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let text = rawText;
|
let text = rawText;
|
||||||
|
if (extractions.length > 0) {
|
||||||
|
const extMarkdown = extractions
|
||||||
|
.map((ext) => `\`\`\`\n${ext.text.trim()}\n\`\`\``)
|
||||||
|
.join('\n\n');
|
||||||
|
text = text ? `${extMarkdown}\n\n${text}` : extMarkdown;
|
||||||
|
}
|
||||||
|
|
||||||
if (references.length > 0) {
|
if (references.length > 0) {
|
||||||
const refMarkdown = references
|
const refMarkdown = references
|
||||||
.map((ref) => {
|
.map((ref) => {
|
||||||
@@ -522,6 +569,7 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
inputText: '',
|
inputText: '',
|
||||||
stagedAttachments: [],
|
stagedAttachments: [],
|
||||||
stagedReferences: [],
|
stagedReferences: [],
|
||||||
|
stagedExtractions: [],
|
||||||
attachmentError: null,
|
attachmentError: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1265,6 +1313,7 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
sendError,
|
sendError,
|
||||||
stagedAttachments,
|
stagedAttachments,
|
||||||
stagedReferences,
|
stagedReferences,
|
||||||
|
stagedExtractions = [],
|
||||||
isProcessingFiles,
|
isProcessingFiles,
|
||||||
isDraggingFiles,
|
isDraggingFiles,
|
||||||
attachmentError,
|
attachmentError,
|
||||||
@@ -1277,7 +1326,10 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
const canSend =
|
const canSend =
|
||||||
!isWorking &&
|
!isWorking &&
|
||||||
canChat &&
|
canChat &&
|
||||||
(Boolean(inputText.trim()) || stagedAttachments.length > 0 || stagedReferences.length > 0);
|
(Boolean(inputText.trim()) ||
|
||||||
|
stagedAttachments.length > 0 ||
|
||||||
|
stagedReferences.length > 0 ||
|
||||||
|
stagedExtractions.length > 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -1449,7 +1501,7 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{stagedReferences.length > 0 && (
|
{(stagedReferences.length > 0 || (stagedExtractions && stagedExtractions.length > 0)) && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -1546,6 +1598,64 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
{stagedExtractions && stagedExtractions.map((ext) => (
|
||||||
|
<Box
|
||||||
|
key={ext.id}
|
||||||
|
sx={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 0.6,
|
||||||
|
py: 0.2,
|
||||||
|
pl: 0.7,
|
||||||
|
pr: 0.4,
|
||||||
|
borderRadius: 1.5,
|
||||||
|
bgcolor: (theme) =>
|
||||||
|
theme.palette.mode === 'dark'
|
||||||
|
? 'rgba(255, 255, 255, 0.08)'
|
||||||
|
: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: (theme) =>
|
||||||
|
theme.palette.mode === 'dark'
|
||||||
|
? 'rgba(255, 255, 255, 0.12)'
|
||||||
|
: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
color: 'text.primary',
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
lineHeight: 1.2,
|
||||||
|
userSelect: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DescriptionOutlinedIcon sx={{ fontSize: 14, color: 'primary.main' }} />
|
||||||
|
<Typography
|
||||||
|
component="span"
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.74rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
letterSpacing: '-0.2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ext.label || 'parsed invoice'}
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.handleRemoveExtraction(ext.id);
|
||||||
|
}}
|
||||||
|
aria-label="Entfernen"
|
||||||
|
sx={{
|
||||||
|
p: 0.2,
|
||||||
|
ml: 0.1,
|
||||||
|
color: 'text.secondary',
|
||||||
|
'&:hover': {
|
||||||
|
color: 'error.main',
|
||||||
|
bgcolor: 'action.hover',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseRoundedIcon sx={{ fontSize: 13 }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { PureComponent } from 'react';
|
import { PureComponent } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import TextSnippetOutlinedIcon from '@mui/icons-material/TextSnippetOutlined';
|
import TextSnippetOutlinedIcon from '@mui/icons-material/TextSnippetOutlined';
|
||||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||||
@@ -9,7 +8,6 @@ import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineR
|
|||||||
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
|
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
|
||||||
import { tileBaseSx, TILE_SIZE } from 'dashgui/tiles';
|
import { tileBaseSx, TILE_SIZE } from 'dashgui/tiles';
|
||||||
import SkeleHashImage from '../settings/SkeleHashImage';
|
import SkeleHashImage from '../settings/SkeleHashImage';
|
||||||
import { enqueueSnackbar } from 'notistack';
|
|
||||||
import { generateTextThumbnail } from '../../utils/textThumbnail';
|
import { generateTextThumbnail } from '../../utils/textThumbnail';
|
||||||
import chatAutomat from '../../automats/chatAutomat';
|
import chatAutomat from '../../automats/chatAutomat';
|
||||||
import {
|
import {
|
||||||
@@ -107,17 +105,12 @@ export class Attachment extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trimmed = (textContent || '').trim();
|
const trimmed = (textContent || '').trim();
|
||||||
if (!trimmed) {
|
if (!trimmed) return;
|
||||||
enqueueSnackbar('Datei enthält keinen Text zum Erwähnen im Chat.', { variant: 'warning' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = attachment.size || new Blob([trimmed]).size;
|
|
||||||
chatAutomat.actions.insertExtractionSnippet({
|
chatAutomat.actions.insertExtractionSnippet({
|
||||||
text: trimmed,
|
text: trimmed,
|
||||||
size,
|
label: 'parsed invoice',
|
||||||
});
|
});
|
||||||
enqueueSnackbar('Text im Chat erwähnt', { variant: 'success' });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleDelete = (e) => {
|
handleDelete = (e) => {
|
||||||
@@ -290,7 +283,6 @@ export class Attachment extends PureComponent {
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={this.handleRescan}
|
onClick={this.handleRescan}
|
||||||
aria-label="Zu Text"
|
aria-label="Zu Text"
|
||||||
title="Zu Text"
|
|
||||||
sx={{
|
sx={{
|
||||||
bgcolor: SCRIM_ACTION,
|
bgcolor: SCRIM_ACTION,
|
||||||
color: WHITE,
|
color: WHITE,
|
||||||
@@ -311,7 +303,6 @@ export class Attachment extends PureComponent {
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={this.handleMentionInChat}
|
onClick={this.handleMentionInChat}
|
||||||
aria-label="Im Chat erwähnen"
|
aria-label="Im Chat erwähnen"
|
||||||
title="Im Chat erwähnen"
|
|
||||||
sx={{
|
sx={{
|
||||||
bgcolor: SCRIM_ACTION,
|
bgcolor: SCRIM_ACTION,
|
||||||
color: WHITE,
|
color: WHITE,
|
||||||
@@ -331,7 +322,6 @@ export class Attachment extends PureComponent {
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={this.handleDelete}
|
onClick={this.handleDelete}
|
||||||
aria-label="Löschen"
|
aria-label="Löschen"
|
||||||
title="Löschen"
|
|
||||||
sx={{
|
sx={{
|
||||||
bgcolor: SCRIM_ACTION,
|
bgcolor: SCRIM_ACTION,
|
||||||
color: WHITE,
|
color: WHITE,
|
||||||
|
|||||||
@@ -21,19 +21,28 @@ import TableRow from '@mui/material/TableRow';
|
|||||||
import Tabs from '@mui/material/Tabs';
|
import Tabs from '@mui/material/Tabs';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
import BackupOutlinedIcon from '@mui/icons-material/BackupOutlined';
|
import BackupOutlinedIcon from '@mui/icons-material/BackupOutlined';
|
||||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutlined';
|
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutlined';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import CloudQueueOutlinedIcon from '@mui/icons-material/CloudQueueOutlined';
|
import CloudQueueOutlinedIcon from '@mui/icons-material/CloudQueueOutlined';
|
||||||
import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined';
|
import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined';
|
||||||
import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined';
|
import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined';
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||||
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||||
|
import PsychologyOutlinedIcon from '@mui/icons-material/PsychologyOutlined';
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
||||||
import StorageIcon from '@mui/icons-material/Storage';
|
import StorageIcon from '@mui/icons-material/Storage';
|
||||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
||||||
|
import WidgetsOutlinedIcon from '@mui/icons-material/WidgetsOutlined';
|
||||||
import { enqueueSnackbar } from 'notistack';
|
import { enqueueSnackbar } from 'notistack';
|
||||||
|
import { vfs } from '../../api';
|
||||||
import backupAutomat from '../../automats/backupAutomat';
|
import backupAutomat from '../../automats/backupAutomat';
|
||||||
import { notifyError } from '../../utils/notify';
|
import { notifyError } from '../../utils/notify';
|
||||||
import {
|
import {
|
||||||
@@ -92,11 +101,18 @@ export default class BackupSettingsPanel extends Component {
|
|||||||
vfsLoadingByBackup: vfsLoadingByBackup || {},
|
vfsLoadingByBackup: vfsLoadingByBackup || {},
|
||||||
tierTab: 'hourly', // 'hourly' | 'daily' | 'monthly'
|
tierTab: 'hourly', // 'hourly' | 'daily' | 'monthly'
|
||||||
expandedItemName: null,
|
expandedItemName: null,
|
||||||
|
restoringSkills: false,
|
||||||
|
restoringApplets: false,
|
||||||
|
confirmRestore: null, // null | 'skills' | 'applets'
|
||||||
|
lastRestoreResult: null, // null | { type: 'skills' | 'applets', count: number, restored: string[] }
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handleRefresh = this.handleRefresh.bind(this);
|
this.handleRefresh = this.handleRefresh.bind(this);
|
||||||
this.handleTierChange = this.handleTierChange.bind(this);
|
this.handleTierChange = this.handleTierChange.bind(this);
|
||||||
this.handleToggleExpand = this.handleToggleExpand.bind(this);
|
this.handleToggleExpand = this.handleToggleExpand.bind(this);
|
||||||
|
this.handleOpenConfirm = this.handleOpenConfirm.bind(this);
|
||||||
|
this.handleCloseConfirm = this.handleCloseConfirm.bind(this);
|
||||||
|
this.handleExecuteRestore = this.handleExecuteRestore.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -151,6 +167,65 @@ export default class BackupSettingsPanel extends Component {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleOpenConfirm(type) {
|
||||||
|
this.setState({ confirmRestore: type });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCloseConfirm() {
|
||||||
|
if (this.state.restoringSkills || this.state.restoringApplets) return;
|
||||||
|
this.setState({ confirmRestore: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region handleExecuteRestore
|
||||||
|
// MARK: calls POST /api/vfs/skills/restore or /api/vfs/applets/restore
|
||||||
|
async handleExecuteRestore() {
|
||||||
|
const { confirmRestore } = this.state;
|
||||||
|
if (!confirmRestore) return;
|
||||||
|
|
||||||
|
if (confirmRestore === 'skills') {
|
||||||
|
this.setState({ restoringSkills: true });
|
||||||
|
try {
|
||||||
|
const res = await vfs.restoreSkills();
|
||||||
|
enqueueSnackbar(`${res.count} Standard-Skills (.SKILL) erfolgreich wiederhergestellt.`, {
|
||||||
|
variant: 'success',
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
confirmRestore: null,
|
||||||
|
restoringSkills: false,
|
||||||
|
lastRestoreResult: {
|
||||||
|
type: 'skills',
|
||||||
|
count: res.count,
|
||||||
|
restored: res.restored || [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({ restoringSkills: false });
|
||||||
|
notifyError(err);
|
||||||
|
}
|
||||||
|
} else if (confirmRestore === 'applets') {
|
||||||
|
this.setState({ restoringApplets: true });
|
||||||
|
try {
|
||||||
|
const res = await vfs.restoreApplets();
|
||||||
|
enqueueSnackbar(`${res.count} Beispiel-Applets (.applet.js) erfolgreich wiederhergestellt.`, {
|
||||||
|
variant: 'success',
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
confirmRestore: null,
|
||||||
|
restoringApplets: false,
|
||||||
|
lastRestoreResult: {
|
||||||
|
type: 'applets',
|
||||||
|
count: res.count,
|
||||||
|
restored: res.restored || [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({ restoringApplets: false });
|
||||||
|
notifyError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
@@ -161,6 +236,10 @@ export default class BackupSettingsPanel extends Component {
|
|||||||
vfsLoadingByBackup,
|
vfsLoadingByBackup,
|
||||||
tierTab,
|
tierTab,
|
||||||
expandedItemName,
|
expandedItemName,
|
||||||
|
restoringSkills,
|
||||||
|
restoringApplets,
|
||||||
|
confirmRestore,
|
||||||
|
lastRestoreResult,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
if (status === 'loading' && !backupStatus) {
|
if (status === 'loading' && !backupStatus) {
|
||||||
@@ -224,7 +303,7 @@ export default class BackupSettingsPanel extends Component {
|
|||||||
size="small"
|
size="small"
|
||||||
startIcon={<RefreshIcon />}
|
startIcon={<RefreshIcon />}
|
||||||
onClick={this.handleRefresh}
|
onClick={this.handleRefresh}
|
||||||
disabled={isRunning}
|
disabled={status === 'loading'}
|
||||||
>
|
>
|
||||||
Aktualisieren
|
Aktualisieren
|
||||||
</Button>
|
</Button>
|
||||||
@@ -246,19 +325,11 @@ export default class BackupSettingsPanel extends Component {
|
|||||||
>
|
>
|
||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
icon={backupStatus?.enabled ? <CheckCircleOutlineIcon /> : <WarningAmberIcon />}
|
icon={backupStatus?.enabled && isRunning ? <CheckCircleOutlineIcon /> : <WarningAmberIcon />}
|
||||||
label={backupStatus?.enabled ? 'Dienst: Aktiv' : 'Dienst: Deaktiviert'}
|
label={backupStatus?.enabled && isRunning ? 'Dienst: Aktiv' : 'Dienst: Deaktiviert'}
|
||||||
color={backupStatus?.enabled ? 'success' : 'default'}
|
color={backupStatus?.enabled && isRunning ? 'success' : 'default'}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
{isRunning && (
|
|
||||||
<Chip
|
|
||||||
size="small"
|
|
||||||
icon={<CircularProgress size={12} color="inherit" />}
|
|
||||||
label="Backup wird ausgeführt..."
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
icon={<StorageIcon />}
|
icon={<StorageIcon />}
|
||||||
@@ -431,6 +502,231 @@ export default class BackupSettingsPanel extends Component {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* System Defaults Restore Section (VFS Skills & Applets) */}
|
||||||
|
<Box sx={{ border: `1px solid ${BORDER}`, borderRadius: 2, bgcolor: WHITE, overflow: 'hidden' }}>
|
||||||
|
<Box sx={{ borderBottom: `1px solid ${BORDER}`, bgcolor: SURFACE_RAISED, px: 2, py: 1.5 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<SettingsBackupRestoreIcon sx={{ color: PRIMARY, fontSize: 22 }} />
|
||||||
|
<Typography variant="subtitle2" fontWeight={700} sx={{ color: DARK_4 }}>
|
||||||
|
System-Standarddateien wiederherstellen (VFS)
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, display: 'block' }}>
|
||||||
|
Setzt systemweite Vorlagen im virtuellen Dateisystem (VFS) des Unternehmens auf den Auslieferungszustand zurück. Bereits vorhandene gleichnamige Dateien werden dabei überschrieben.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
|
||||||
|
gap: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Skills Restore Card */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
borderRadius: 2,
|
||||||
|
border: `1px solid ${BORDER}`,
|
||||||
|
bgcolor: SURFACE_BASE,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: 1.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 1.5,
|
||||||
|
bgcolor: 'rgba(15, 118, 110, 0.1)',
|
||||||
|
color: PRIMARY,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PsychologyOutlinedIcon fontSize="small" />
|
||||||
|
</Box>
|
||||||
|
<Typography variant="subtitle2" fontWeight={700} sx={{ color: DARK_4 }}>
|
||||||
|
Standard-Skills
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Chip size="small" label=".SKILL" variant="outlined" sx={{ height: 20, fontSize: '0.75rem', fontWeight: 600 }} />
|
||||||
|
</Box>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ fontSize: '0.825rem', lineHeight: 1.4 }}>
|
||||||
|
Stellt alle systemweiten Standard-.SKILL-Dateien für KI-Assistenten und Workflows im VFS wieder her.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', pt: 1, borderTop: `1px solid ${BORDER}` }}>
|
||||||
|
<Typography variant="caption" color="text.secondary" sx={{ fontStyle: 'italic', fontSize: '0.75rem' }}>
|
||||||
|
Überschreibt existierende Dateien
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
startIcon={restoringSkills ? <CircularProgress size={14} color="inherit" /> : <SettingsBackupRestoreIcon fontSize="small" />}
|
||||||
|
onClick={() => this.handleOpenConfirm('skills')}
|
||||||
|
disabled={restoringSkills}
|
||||||
|
sx={{ textTransform: 'none', minHeight: 30, fontSize: '0.8rem', fontWeight: 600 }}
|
||||||
|
>
|
||||||
|
Skills wiederherstellen
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Applets Restore Card */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
borderRadius: 2,
|
||||||
|
border: `1px solid ${BORDER}`,
|
||||||
|
bgcolor: SURFACE_BASE,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: 1.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 1.5,
|
||||||
|
bgcolor: 'rgba(15, 118, 110, 0.1)',
|
||||||
|
color: PRIMARY,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<WidgetsOutlinedIcon fontSize="small" />
|
||||||
|
</Box>
|
||||||
|
<Typography variant="subtitle2" fontWeight={700} sx={{ color: DARK_4 }}>
|
||||||
|
Beispiel-Applets
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Chip size="small" label=".applet.js" variant="outlined" sx={{ height: 20, fontSize: '0.75rem', fontWeight: 600 }} />
|
||||||
|
</Box>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ fontSize: '0.825rem', lineHeight: 1.4 }}>
|
||||||
|
Stellt alle systemweiten Standard-.applet.js-Beispiel-Applets für Workflows und Auswertungen im VFS wieder her.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', pt: 1, borderTop: `1px solid ${BORDER}` }}>
|
||||||
|
<Typography variant="caption" color="text.secondary" sx={{ fontStyle: 'italic', fontSize: '0.75rem' }}>
|
||||||
|
Überschreibt existierende Dateien
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
startIcon={restoringApplets ? <CircularProgress size={14} color="inherit" /> : <SettingsBackupRestoreIcon fontSize="small" />}
|
||||||
|
onClick={() => this.handleOpenConfirm('applets')}
|
||||||
|
disabled={restoringApplets}
|
||||||
|
sx={{ textTransform: 'none', minHeight: 30, fontSize: '0.8rem', fontWeight: 600 }}
|
||||||
|
>
|
||||||
|
Applets wiederherstellen
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Last restore results notification / chip display */}
|
||||||
|
{lastRestoreResult && (
|
||||||
|
<Box sx={{ mt: 2, p: 1.5, borderRadius: 1.5, bgcolor: 'rgba(15, 118, 110, 0.08)', border: '1px solid rgba(15, 118, 110, 0.2)' }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
||||||
|
<Typography variant="caption" fontWeight={600} sx={{ color: PRIMARY }}>
|
||||||
|
Erfolgreich wiederhergestellt ({lastRestoreResult.count} {lastRestoreResult.type === 'skills' ? 'Skills' : 'Applets'}):
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => this.setState({ lastRestoreResult: null })}
|
||||||
|
sx={{ p: 0.25, color: 'text.secondary' }}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" sx={{ fontSize: 16 }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75 }}>
|
||||||
|
{lastRestoreResult.restored.map((name) => (
|
||||||
|
<Chip
|
||||||
|
key={name}
|
||||||
|
size="small"
|
||||||
|
label={name}
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
height: 22,
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
bgcolor: WHITE,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Confirmation Dialog for System Defaults Restore */}
|
||||||
|
<Dialog
|
||||||
|
open={Boolean(confirmRestore)}
|
||||||
|
onClose={this.handleCloseConfirm}
|
||||||
|
maxWidth="xs"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<WarningAmberIcon color="warning" />
|
||||||
|
{confirmRestore === 'skills'
|
||||||
|
? 'Standard-Skills wiederherstellen?'
|
||||||
|
: 'Beispiel-Applets wiederherstellen?'}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText sx={{ color: DARK_2 }}>
|
||||||
|
{confirmRestore === 'skills'
|
||||||
|
? 'Möchten Sie alle systemweiten Standard-.SKILL-Dateien im VFS des aktuellen Unternehmens wiederherstellen? Bestehende .SKILL-Dateien werden dabei überschrieben.'
|
||||||
|
: 'Möchten Sie alle systemweiten Standard-.applet.js-Beispiel-Applets im VFS des aktuellen Unternehmens wiederherstellen? Bestehende .applet.js-Dateien werden dabei überschrieben.'}
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
|
<Button
|
||||||
|
onClick={this.handleCloseConfirm}
|
||||||
|
disabled={restoringSkills || restoringApplets}
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ textTransform: 'none' }}
|
||||||
|
>
|
||||||
|
Abbrechen
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={this.handleExecuteRestore}
|
||||||
|
disabled={restoringSkills || restoringApplets}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={
|
||||||
|
(confirmRestore === 'skills' ? restoringSkills : restoringApplets) ? (
|
||||||
|
<CircularProgress size={14} color="inherit" />
|
||||||
|
) : (
|
||||||
|
<SettingsBackupRestoreIcon />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sx={{ textTransform: 'none' }}
|
||||||
|
>
|
||||||
|
{(confirmRestore === 'skills' ? restoringSkills : restoringApplets)
|
||||||
|
? 'Wiederherstellen...'
|
||||||
|
: 'Jetzt wiederherstellen'}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -659,20 +659,17 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
const existingTxt = this.state.attachments?.find(
|
const existingTxt = this.state.attachments?.find(
|
||||||
(a) => a.parentPdfId === attachment.id || a.name === txtName,
|
(a) => a.parentPdfId === attachment.id || a.name === txtName,
|
||||||
);
|
);
|
||||||
let targetTxt = existingTxt;
|
|
||||||
if (existingTxt) {
|
if (existingTxt) {
|
||||||
const updated = {
|
this.automat.actions.updateAttachment({
|
||||||
id: existingTxt.id,
|
id: existingTxt.id,
|
||||||
file: txtFile,
|
file: txtFile,
|
||||||
size: txtFile.size,
|
size: txtFile.size,
|
||||||
text,
|
text,
|
||||||
thumbnail: txtThumbnail || existingTxt.thumbnail,
|
thumbnail: txtThumbnail || existingTxt.thumbnail,
|
||||||
isDirty: true,
|
isDirty: true,
|
||||||
};
|
});
|
||||||
this.automat.actions.updateAttachment(updated);
|
|
||||||
targetTxt = { ...existingTxt, ...updated };
|
|
||||||
} else {
|
} else {
|
||||||
targetTxt = {
|
this.automat.actions.addAttachment({
|
||||||
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||||
file: txtFile,
|
file: txtFile,
|
||||||
name: txtFile.name,
|
name: txtFile.name,
|
||||||
@@ -682,14 +679,10 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
text,
|
text,
|
||||||
parentPdfId: attachment.id,
|
parentPdfId: attachment.id,
|
||||||
uploadedAt: new Date().toISOString(),
|
uploadedAt: new Date().toISOString(),
|
||||||
};
|
});
|
||||||
this.automat.actions.addAttachment(targetTxt);
|
|
||||||
}
|
}
|
||||||
this.handleAttachmentClick(targetTxt);
|
|
||||||
enqueueSnackbar('Text aus PDF extrahiert und geöffnet', { variant: 'success' });
|
|
||||||
} catch (txtErr) {
|
} catch (txtErr) {
|
||||||
console.error('Fehler beim Aktualisieren der Textdatei:', txtErr);
|
console.error('Fehler beim Aktualisieren der Textdatei:', txtErr);
|
||||||
enqueueSnackbar('Fehler beim Extrahieren von Text aus der PDF', { variant: 'error' });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -715,17 +708,12 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trimmed = (text || '').trim();
|
const trimmed = (text || '').trim();
|
||||||
if (!trimmed) {
|
if (!trimmed) return;
|
||||||
enqueueSnackbar('Datei enthält keinen Text zum Erwähnen im Chat.', { variant: 'warning' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = attachment.size || new Blob([trimmed]).size;
|
|
||||||
chatAutomat.actions.insertExtractionSnippet({
|
chatAutomat.actions.insertExtractionSnippet({
|
||||||
text: trimmed,
|
text: trimmed,
|
||||||
size,
|
label: 'parsed invoice',
|
||||||
});
|
});
|
||||||
enqueueSnackbar('Text im Chat erwähnt', { variant: 'success' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePdfDelete(attachment) {
|
handlePdfDelete(attachment) {
|
||||||
|
|||||||
@@ -505,6 +505,12 @@ const server = http.createServer(async (req, res) => {
|
|||||||
if (p === '/api/vfs/upload' && method === 'POST') {
|
if (p === '/api/vfs/upload' && method === 'POST') {
|
||||||
return json(res, 200, { files: [] });
|
return json(res, 200, { files: [] });
|
||||||
}
|
}
|
||||||
|
if (p === '/api/vfs/skills/restore' && method === 'POST') {
|
||||||
|
return json(res, 200, { success: true, restored: ['system.SKILL'], count: 1 });
|
||||||
|
}
|
||||||
|
if (p === '/api/vfs/applets/restore' && method === 'POST') {
|
||||||
|
return json(res, 200, { success: true, restored: ['example.applet.js'], count: 1 });
|
||||||
|
}
|
||||||
if (p.startsWith('/api/vfs/') && method === 'PUT') {
|
if (p.startsWith('/api/vfs/') && method === 'PUT') {
|
||||||
return json(res, 200, { ok: true });
|
return json(res, 200, { ok: true });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user