u
This commit is contained in:
@@ -77,6 +77,8 @@ const initialState = {
|
|||||||
selectedConversationId: null,
|
selectedConversationId: null,
|
||||||
/** First-open draft: no server conversation yet, created on first send. */
|
/** First-open draft: no server conversation yet, created on first send. */
|
||||||
draftConversation: false,
|
draftConversation: false,
|
||||||
|
/** Prefilled input text waiting to be consumed by the chat UI. */
|
||||||
|
pendingInput: null,
|
||||||
conversationState: 'idle',
|
conversationState: 'idle',
|
||||||
provider: 'openrouter',
|
provider: 'openrouter',
|
||||||
model: DEFAULT_MODEL,
|
model: DEFAULT_MODEL,
|
||||||
@@ -270,6 +272,33 @@ const actions = {
|
|||||||
void chatAutomat.actions.loadConversations();
|
void chatAutomat.actions.loadConversations();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the chat drawer with a fresh draft conversation and prefilled prompt.
|
||||||
|
* @param {string} [prompt='']
|
||||||
|
*/
|
||||||
|
openWithPrompt(prompt = '') {
|
||||||
|
chatOpenedThisSession = true;
|
||||||
|
closeConversationStream();
|
||||||
|
chatAutomat.setState({
|
||||||
|
isOpen: true,
|
||||||
|
sseEstablished: true,
|
||||||
|
draftConversation: true,
|
||||||
|
selectedConversationId: null,
|
||||||
|
provider: 'openrouter',
|
||||||
|
model: DEFAULT_MODEL,
|
||||||
|
messages: [],
|
||||||
|
conversationState: 'idle',
|
||||||
|
streamPhase: 'idle',
|
||||||
|
streamingText: '',
|
||||||
|
lastUsage: null,
|
||||||
|
activeTurns: [],
|
||||||
|
sendError: null,
|
||||||
|
pendingInput: prompt,
|
||||||
|
});
|
||||||
|
ensureCompanyEventsConnected();
|
||||||
|
void chatAutomat.actions.loadConversations();
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start an implicit new conversation with the first preset model. Nothing is
|
* Start an implicit new conversation with the first preset model. Nothing is
|
||||||
* created on the server until the first message is sent.
|
* created on the server until the first message is sent.
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ class AppDialog extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
|
disableEnforceFocus
|
||||||
{...rest}
|
{...rest}
|
||||||
slots={restSlots}
|
slots={restSlots}
|
||||||
slotProps={slotProps}
|
slotProps={slotProps}
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
|||||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||||
import ImageSearchIcon from '@mui/icons-material/ImageSearch';
|
import ImageSearchIcon from '@mui/icons-material/ImageSearch';
|
||||||
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
|
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
|
||||||
|
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||||
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 { enqueueSnackbar } from 'notistack';
|
import { enqueueSnackbar } from 'notistack';
|
||||||
import { article as articleApi } from '../../api';
|
import { article as articleApi } from '../../api';
|
||||||
import artikelAutomat from '../../automats/artikelAutomat';
|
import artikelAutomat from '../../automats/artikelAutomat';
|
||||||
|
import chatAutomat from '../../automats/chatAutomat';
|
||||||
import { requireArray, requireObject } from '../../utils/apiShape';
|
import { requireArray, requireObject } from '../../utils/apiShape';
|
||||||
import {
|
import {
|
||||||
articleWithPhotoOrder,
|
articleWithPhotoOrder,
|
||||||
@@ -65,6 +67,7 @@ class ArticlePhotosDialog extends Component {
|
|||||||
this.handleSearchClose = this.handleSearchClose.bind(this);
|
this.handleSearchClose = this.handleSearchClose.bind(this);
|
||||||
this.handleFileChange = this.handleFileChange.bind(this);
|
this.handleFileChange = this.handleFileChange.bind(this);
|
||||||
this.uploadRawImage = this.uploadRawImage.bind(this);
|
this.uploadRawImage = this.uploadRawImage.bind(this);
|
||||||
|
this.handleEditInChat = this.handleEditInChat.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClose() {
|
handleClose() {
|
||||||
@@ -72,6 +75,14 @@ class ArticlePhotosDialog extends Component {
|
|||||||
this.props.onClose();
|
this.props.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleEditInChat(photo, index) {
|
||||||
|
const { article: articleData } = this.props;
|
||||||
|
const sku = articleData?.sku ?? '';
|
||||||
|
const imageNr = index + 1;
|
||||||
|
const prompt = `Artikelbild Nr ${imageNr} von Artikel SKU ${sku} folgende Änderung: `;
|
||||||
|
chatAutomat.actions.openWithPrompt(prompt);
|
||||||
|
}
|
||||||
|
|
||||||
async uploadRawImage(rawBlob) {
|
async uploadRawImage(rawBlob) {
|
||||||
const { article: articleData, onArticleUpdated } = this.props;
|
const { article: articleData, onArticleUpdated } = this.props;
|
||||||
this.setState({ busy: true });
|
this.setState({ busy: true });
|
||||||
@@ -223,10 +234,26 @@ class ArticlePhotosDialog extends Component {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-end',
|
alignItems: 'center',
|
||||||
gap: 0.25,
|
justifyContent: 'space-between',
|
||||||
pointerEvents: 'auto',
|
pointerEvents: 'auto',
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
aria-label="Bearbeiten"
|
||||||
|
disabled={busy}
|
||||||
|
onClick={() => this.handleEditInChat(photo, index)}
|
||||||
|
sx={overlayIconSx}
|
||||||
|
>
|
||||||
|
<EditOutlinedIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 0.25,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
@@ -261,6 +288,7 @@ class ArticlePhotosDialog extends Component {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import { ACCEPT_STRING, readFileAttachment } from './chatAttachments';
|
|||||||
import { ChatStagedAttachmentsTray } from './ChatAttachmentItems';
|
import { ChatStagedAttachmentsTray } from './ChatAttachmentItems';
|
||||||
import {
|
import {
|
||||||
BLUE_GLOW,
|
BLUE_GLOW,
|
||||||
|
DIALOG_FOG,
|
||||||
SHADOW_DRAWER,
|
SHADOW_DRAWER,
|
||||||
} from '../../colors';
|
} from '../../colors';
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
sseStatus: s.sseStatus,
|
sseStatus: s.sseStatus,
|
||||||
sendError: s.sendError,
|
sendError: s.sendError,
|
||||||
activeTurns: s.activeTurns || [],
|
activeTurns: s.activeTurns || [],
|
||||||
inputText: '',
|
inputText: s.pendingInput != null ? s.pendingInput : '',
|
||||||
isInputFocused: false,
|
isInputFocused: false,
|
||||||
stagedAttachments: [],
|
stagedAttachments: [],
|
||||||
stagedReferences: [],
|
stagedReferences: [],
|
||||||
@@ -95,18 +96,39 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
}, delay);
|
}, delay);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
focusInputAtEnd = (delay = 50) => {
|
||||||
|
if (this.focusTimeout) clearTimeout(this.focusTimeout);
|
||||||
|
this.focusTimeout = setTimeout(() => {
|
||||||
|
const el = this.inputRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
el.focus?.();
|
||||||
|
if (typeof el.setSelectionRange === 'function' && el.value) {
|
||||||
|
const len = el.value.length;
|
||||||
|
el.setSelectionRange(len, len);
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.state.isOpen) {
|
if (this.state.isOpen) {
|
||||||
|
if (chatAutomat.state.pendingInput != null) {
|
||||||
|
this.focusInputAtEnd(150);
|
||||||
|
} else {
|
||||||
this.focusInput(150);
|
this.focusInput(150);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void this.loadModelSettings();
|
void this.loadModelSettings();
|
||||||
|
if (chatAutomat.state.pendingInput != null) {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
if (chatAutomat.state.pendingInput != null) {
|
||||||
|
chatAutomat.setState({ pendingInput: 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;
|
||||||
if (!wasOpen && willOpen) {
|
const patch = {
|
||||||
this.focusInput(150);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
isOpen: state.isOpen,
|
isOpen: state.isOpen,
|
||||||
conversations: state.conversations,
|
conversations: state.conversations,
|
||||||
selectedConversationId: state.selectedConversationId,
|
selectedConversationId: state.selectedConversationId,
|
||||||
@@ -123,6 +145,20 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
sendError: state.sendError,
|
sendError: state.sendError,
|
||||||
activeTurns: state.activeTurns || [],
|
activeTurns: state.activeTurns || [],
|
||||||
};
|
};
|
||||||
|
if (state.pendingInput != null) {
|
||||||
|
patch.inputText = state.pendingInput;
|
||||||
|
patch.stagedAttachments = [];
|
||||||
|
patch.stagedReferences = [];
|
||||||
|
queueMicrotask(() => {
|
||||||
|
if (chatAutomat.state.pendingInput != null) {
|
||||||
|
chatAutomat.setState({ pendingInput: null });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.focusInputAtEnd(150);
|
||||||
|
} else if (!wasOpen && willOpen) {
|
||||||
|
this.focusInput(150);
|
||||||
|
}
|
||||||
|
return patch;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.unsubSettings = userSettingsAutomat.subscribe(this, (state) => {
|
this.unsubSettings = userSettingsAutomat.subscribe(this, (state) => {
|
||||||
@@ -1265,7 +1301,12 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
|
|
||||||
{/* Unified Copilot-style input container with context reference pills */}
|
{/* Unified Copilot-style input container with context reference pills */}
|
||||||
<Box
|
<Box
|
||||||
onClick={() => this.focusInput(0)}
|
onClick={(e) => {
|
||||||
|
if (this.inputRef.current && (e.target === this.inputRef.current || this.inputRef.current.contains(e.target))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.focusInput(0);
|
||||||
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
@@ -1463,8 +1504,14 @@ export default class ChatDrawer extends PureComponent {
|
|||||||
anchor="right"
|
anchor="right"
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
onClose={this.handleClose}
|
onClose={this.handleClose}
|
||||||
|
ModalProps={{
|
||||||
|
disableEnforceFocus: true,
|
||||||
|
}}
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
zIndex: theme.zIndex.drawer + 2,
|
zIndex: theme.zIndex.modal + 10,
|
||||||
|
'& .MuiBackdrop-root': {
|
||||||
|
backgroundColor: DIALOG_FOG,
|
||||||
|
},
|
||||||
'& .MuiDrawer-paper': {
|
'& .MuiDrawer-paper': {
|
||||||
width: { xs: '100vw', sm: `${totalWidth}px` },
|
width: { xs: '100vw', sm: `${totalWidth}px` },
|
||||||
maxWidth: '100vw',
|
maxWidth: '100vw',
|
||||||
|
|||||||
@@ -61,7 +61,10 @@ export default class ChatFab extends PureComponent {
|
|||||||
|
|
||||||
let tooltipTitle = 'KI-Chat öffnen';
|
let tooltipTitle = 'KI-Chat öffnen';
|
||||||
let ariaLabel = 'KI-Chat';
|
let ariaLabel = 'KI-Chat';
|
||||||
if (hasPendingAsk) {
|
if (isOpen) {
|
||||||
|
tooltipTitle = 'KI-Chat schließen';
|
||||||
|
ariaLabel = 'KI-Chat schließen';
|
||||||
|
} else if (hasPendingAsk) {
|
||||||
tooltipTitle = pendingAsk?.question
|
tooltipTitle = pendingAsk?.question
|
||||||
? `Antwort erforderlich: „${pendingAsk.question}“`
|
? `Antwort erforderlich: „${pendingAsk.question}“`
|
||||||
: 'Antwort erforderlich: Das Modell wartet auf Ihre Eingabe';
|
: 'Antwort erforderlich: Das Modell wartet auf Ihre Eingabe';
|
||||||
@@ -76,12 +79,8 @@ export default class ChatFab extends PureComponent {
|
|||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
bottom: { xs: 20, sm: 28 },
|
bottom: { xs: 20, sm: 28 },
|
||||||
right: { xs: 20, sm: 28 },
|
left: { xs: 20, sm: 164 },
|
||||||
zIndex: theme.zIndex.speedDial || 1050,
|
zIndex: theme.zIndex.speedDial || 1050,
|
||||||
opacity: isOpen ? 0 : 1,
|
|
||||||
transform: isOpen ? 'scale(0)' : 'scale(1)',
|
|
||||||
pointerEvents: isOpen ? 'none' : 'auto',
|
|
||||||
transition: 'transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s linear',
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Box sx={{ position: 'relative' }}>
|
<Box sx={{ position: 'relative' }}>
|
||||||
@@ -144,7 +143,7 @@ export default class ChatFab extends PureComponent {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Tooltip title={tooltipTitle} placement="left" arrow disableHoverListener={isOpen}>
|
<Tooltip title={tooltipTitle} placement="right" arrow>
|
||||||
<Badge
|
<Badge
|
||||||
badgeContent={hasPendingAsk ? '?' : undefined}
|
badgeContent={hasPendingAsk ? '?' : undefined}
|
||||||
color={hasPendingAsk ? 'error' : isBusy ? 'warning' : 'default'}
|
color={hasPendingAsk ? 'error' : isBusy ? 'warning' : 'default'}
|
||||||
@@ -184,13 +183,6 @@ export default class ChatFab extends PureComponent {
|
|||||||
: isBusy
|
: isBusy
|
||||||
? CHAT_FAB_SHADOW_BUSY
|
? CHAT_FAB_SHADOW_BUSY
|
||||||
: CHAT_FAB_SHADOW_IDLE,
|
: CHAT_FAB_SHADOW_IDLE,
|
||||||
transition: 'transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.2s',
|
|
||||||
'&:hover': {
|
|
||||||
transform: 'scale(1.06)',
|
|
||||||
},
|
|
||||||
'&:active': {
|
|
||||||
transform: 'scale(0.96)',
|
|
||||||
},
|
|
||||||
background: hasPendingAsk
|
background: hasPendingAsk
|
||||||
? CHAT_FAB_PENDING
|
? CHAT_FAB_PENDING
|
||||||
: isBusy
|
: isBusy
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ import WebIcon from '@mui/icons-material/Web';
|
|||||||
import EscalatorWarningIcon from '@mui/icons-material/EscalatorWarning';
|
import EscalatorWarningIcon from '@mui/icons-material/EscalatorWarning';
|
||||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||||
import ManageSearchIcon from '@mui/icons-material/ManageSearch';
|
import ManageSearchIcon from '@mui/icons-material/ManageSearch';
|
||||||
|
import LensBlurIcon from '@mui/icons-material/LensBlur';
|
||||||
|
import CalculateIcon from '@mui/icons-material/Calculate';
|
||||||
|
import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner';
|
||||||
import HealingMarkdown from './HealingMarkdown';
|
import HealingMarkdown from './HealingMarkdown';
|
||||||
import { VfsAttachmentLink, parseAttachmentsFromText } from './ChatAttachmentItems';
|
import { VfsAttachmentLink, parseAttachmentsFromText } from './ChatAttachmentItems';
|
||||||
import {
|
import {
|
||||||
@@ -80,6 +83,9 @@ const TOOL_ICON_MAP = {
|
|||||||
sub_agent: EscalatorWarningIcon,
|
sub_agent: EscalatorWarningIcon,
|
||||||
pdf_read: PictureAsPdfIcon,
|
pdf_read: PictureAsPdfIcon,
|
||||||
search_articles: ManageSearchIcon,
|
search_articles: ManageSearchIcon,
|
||||||
|
edit_image: LensBlurIcon,
|
||||||
|
net_gross_calc: CalculateIcon,
|
||||||
|
ean_checksum: QrCodeScannerIcon,
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatMessageTime(isoString) {
|
function formatMessageTime(isoString) {
|
||||||
|
|||||||
@@ -430,12 +430,12 @@ class AutomatInspector extends PureComponent {
|
|||||||
invisible={names.length === 0}
|
invisible={names.length === 0}
|
||||||
sx={{
|
sx={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
right: { xs: 27, sm: 35 },
|
left: { xs: 27, sm: 171 },
|
||||||
bottom: { xs: 84, sm: 94 },
|
bottom: { xs: 84, sm: 94 },
|
||||||
zIndex: (theme) => theme.zIndex.drawer - 1,
|
zIndex: (theme) => theme.zIndex.drawer - 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip title="Automat-Inspector (Alt+Shift+A)">
|
<Tooltip title="Automat-Inspector (Alt+Shift+A)" placement="right">
|
||||||
<Fab
|
<Fab
|
||||||
size="small"
|
size="small"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
Reference in New Issue
Block a user