From 4cc8c49cf193e790d08f6394d0e0eb5dee105775 Mon Sep 17 00:00:00 2001 From: seb Date: Wed, 7 Oct 2026 19:33:26 +0200 Subject: [PATCH] u --- src/automats/chatAutomat.js | 29 +++++++ src/components/AppDialog.jsx | 1 + .../article/ArticlePhotosDialog.jsx | 78 +++++++++++++------ src/components/chat/ChatDrawer.jsx | 63 +++++++++++++-- src/components/chat/ChatFab.jsx | 20 ++--- src/components/chat/ChatFlowList.jsx | 6 ++ src/devtools/AutomatInspector.jsx | 4 +- 7 files changed, 152 insertions(+), 49 deletions(-) diff --git a/src/automats/chatAutomat.js b/src/automats/chatAutomat.js index b47fa52..8ef56cf 100644 --- a/src/automats/chatAutomat.js +++ b/src/automats/chatAutomat.js @@ -77,6 +77,8 @@ const initialState = { selectedConversationId: null, /** First-open draft: no server conversation yet, created on first send. */ draftConversation: false, + /** Prefilled input text waiting to be consumed by the chat UI. */ + pendingInput: null, conversationState: 'idle', provider: 'openrouter', model: DEFAULT_MODEL, @@ -270,6 +272,33 @@ const actions = { 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 * created on the server until the first message is sent. diff --git a/src/components/AppDialog.jsx b/src/components/AppDialog.jsx index 41a7824..4a25166 100644 --- a/src/components/AppDialog.jsx +++ b/src/components/AppDialog.jsx @@ -95,6 +95,7 @@ class AppDialog extends Component { return ( void this.movePhoto(photo.fullHash, -1)} - sx={overlayIconSx} - > - - - = total - 1} - onClick={() => void this.movePhoto(photo.fullHash, 1)} - sx={overlayIconSx} - > - - - void this.deletePhoto(photo.fullHash)} + onClick={() => this.handleEditInChat(photo, index)} sx={overlayIconSx} > - + + + void this.movePhoto(photo.fullHash, -1)} + sx={overlayIconSx} + > + + + = total - 1} + onClick={() => void this.movePhoto(photo.fullHash, 1)} + sx={overlayIconSx} + > + + + void this.deletePhoto(photo.fullHash)} + sx={overlayIconSx} + > + + + diff --git a/src/components/chat/ChatDrawer.jsx b/src/components/chat/ChatDrawer.jsx index 3e5ba77..ffe20f5 100644 --- a/src/components/chat/ChatDrawer.jsx +++ b/src/components/chat/ChatDrawer.jsx @@ -38,6 +38,7 @@ import { ACCEPT_STRING, readFileAttachment } from './chatAttachments'; import { ChatStagedAttachmentsTray } from './ChatAttachmentItems'; import { BLUE_GLOW, + DIALOG_FOG, SHADOW_DRAWER, } from '../../colors'; @@ -69,7 +70,7 @@ export default class ChatDrawer extends PureComponent { sseStatus: s.sseStatus, sendError: s.sendError, activeTurns: s.activeTurns || [], - inputText: '', + inputText: s.pendingInput != null ? s.pendingInput : '', isInputFocused: false, stagedAttachments: [], stagedReferences: [], @@ -95,18 +96,39 @@ export default class ChatDrawer extends PureComponent { }, 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() { if (this.state.isOpen) { - this.focusInput(150); + if (chatAutomat.state.pendingInput != null) { + this.focusInputAtEnd(150); + } else { + this.focusInput(150); + } } void this.loadModelSettings(); + if (chatAutomat.state.pendingInput != null) { + queueMicrotask(() => { + if (chatAutomat.state.pendingInput != null) { + chatAutomat.setState({ pendingInput: null }); + } + }); + } this.unsub = chatAutomat.subscribe(this, (state) => { const wasOpen = this.state.isOpen; const willOpen = state.isOpen; - if (!wasOpen && willOpen) { - this.focusInput(150); - } - return { + const patch = { isOpen: state.isOpen, conversations: state.conversations, selectedConversationId: state.selectedConversationId, @@ -123,6 +145,20 @@ export default class ChatDrawer extends PureComponent { sendError: state.sendError, 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) => { @@ -1265,7 +1301,12 @@ export default class ChatDrawer extends PureComponent { {/* Unified Copilot-style input container with context reference pills */} 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={{ flex: 1, minWidth: 0, @@ -1463,8 +1504,14 @@ export default class ChatDrawer extends PureComponent { anchor="right" open={isOpen} onClose={this.handleClose} + ModalProps={{ + disableEnforceFocus: true, + }} sx={(theme) => ({ - zIndex: theme.zIndex.drawer + 2, + zIndex: theme.zIndex.modal + 10, + '& .MuiBackdrop-root': { + backgroundColor: DIALOG_FOG, + }, '& .MuiDrawer-paper': { width: { xs: '100vw', sm: `${totalWidth}px` }, maxWidth: '100vw', diff --git a/src/components/chat/ChatFab.jsx b/src/components/chat/ChatFab.jsx index bdcf206..4b2194f 100644 --- a/src/components/chat/ChatFab.jsx +++ b/src/components/chat/ChatFab.jsx @@ -61,7 +61,10 @@ export default class ChatFab extends PureComponent { let tooltipTitle = 'KI-Chat öffnen'; let ariaLabel = 'KI-Chat'; - if (hasPendingAsk) { + if (isOpen) { + tooltipTitle = 'KI-Chat schließen'; + ariaLabel = 'KI-Chat schließen'; + } else if (hasPendingAsk) { tooltipTitle = pendingAsk?.question ? `Antwort erforderlich: „${pendingAsk.question}“` : 'Antwort erforderlich: Das Modell wartet auf Ihre Eingabe'; @@ -76,12 +79,8 @@ export default class ChatFab extends PureComponent { sx={(theme) => ({ position: 'fixed', bottom: { xs: 20, sm: 28 }, - right: { xs: 20, sm: 28 }, + left: { xs: 20, sm: 164 }, 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', })} > @@ -144,7 +143,7 @@ export default class ChatFab extends PureComponent { /> )} - + theme.zIndex.drawer - 1, }} > - +