diff --git a/src/apps/chat/AppChat.tsx b/src/apps/chat/AppChat.tsx index e3a12d349..956009212 100644 --- a/src/apps/chat/AppChat.tsx +++ b/src/apps/chat/AppChat.tsx @@ -6,6 +6,7 @@ import { Box, useTheme } from '@mui/joy'; import type { DiagramConfig } from '~/modules/aifn/digrams/DiagramsModal'; import type { TradeConfig } from '~/modules/trade/TradeModal'; +import { autoConversationTitle } from '~/modules/aifn/autotitle/autoTitle'; import { downloadSingleChat, importConversationsFromFilesAtRest, openConversationsAtRestPicker } from '~/modules/trade/trade.client'; import { imaginePromptFromTextOrThrow } from '~/modules/aifn/imagine/imaginePromptFromText'; import { useAreBeamsOpen } from '~/modules/beam/store-beam.hooks'; @@ -53,6 +54,7 @@ import { usePanesManager } from './components/panes/store-panes-manager'; import type { ChatExecuteMode } from './execute-mode/execute-mode.types'; import { _handleExecute } from './editors/_handleExecute'; +import { getChatAutoAI } from './store-app-chat'; // what to say when a chat is new and has no title @@ -315,10 +317,11 @@ export function AppChat() { // replace the prompt in history const lastMessage = inputHistory[inputHistory.length - 1]; + const _autoTitle = () => getChatAutoAI().autoTitleChat && void autoConversationTitle(focusedPaneConversationId, false); if (lastMessage.role === 'assistant') - cHandler.beamInvoke(inputHistory.slice(0, -1), [lastMessage], lastMessage.id); + cHandler.beamInvoke(inputHistory.slice(0, -1), [lastMessage], lastMessage.id, _autoTitle); else if (lastMessage.role === 'user') - cHandler.beamInvoke(inputHistory, [], null); + cHandler.beamInvoke(inputHistory, [], null, _autoTitle); }, [focusedPaneConversationId]); const handleTextDiagram = React.useCallback((diagramConfig: DiagramConfig | null) => setDiagramConfig(diagramConfig), []); diff --git a/src/apps/chat/components/ChatMessageList.tsx b/src/apps/chat/components/ChatMessageList.tsx index e23a13871..a8b7fa46b 100644 --- a/src/apps/chat/components/ChatMessageList.tsx +++ b/src/apps/chat/components/ChatMessageList.tsx @@ -7,6 +7,7 @@ import { Box, List } from '@mui/joy'; import type { SystemPurposeExample } from '../../../data'; import type { DiagramConfig } from '~/modules/aifn/digrams/DiagramsModal'; +import { autoConversationTitle } from '~/modules/aifn/autotitle/autoTitle'; import { speakText } from '~/modules/speex/speex.client'; import type { ConversationHandler } from '~/common/chat-overlay/ConversationHandler'; @@ -28,7 +29,7 @@ import { ChatMessage, ChatMessageMemo } from './message/ChatMessage'; import { CleanerMessage, MessagesSelectionHeader } from './message/CleanerMessage'; import { Ephemerals } from './Ephemerals'; import { PersonaSelector } from './persona-selector/PersonaSelector'; -import { useChatAutoSuggestHTMLUI, useChatShowSystemMessages } from '../store-app-chat'; +import { getChatAutoAI, useChatAutoSuggestHTMLUI, useChatShowSystemMessages } from '../store-app-chat'; const stableNoMessages: DMessage[] = []; @@ -201,17 +202,20 @@ export function ChatMessageList(props: { const lastTruncatedMessage = truncatedHistory[truncatedHistory.length - 1]; if (!lastTruncatedMessage) return; + // auto-title callback (injected to avoid common/ -> apps/ dependency) + const _autoTitle = conversationId ? () => getChatAutoAI().autoTitleChat && void autoConversationTitle(conversationId, false) : undefined; + // assistant: do an in-place beam if (lastTruncatedMessage.role === 'assistant') { if (truncatedHistory.length >= 2) - conversationHandler.beamInvoke(truncatedHistory.slice(0, -1), [lastTruncatedMessage], lastTruncatedMessage.id); + conversationHandler.beamInvoke(truncatedHistory.slice(0, -1), [lastTruncatedMessage], lastTruncatedMessage.id, _autoTitle); } else if (lastTruncatedMessage.role === 'user') { // user: truncate and append (but if the next message is an assistant message, import it) const possibleNextMessage = inputHistory[truncatedHistory.length]; if (possibleNextMessage?.role === 'assistant') - conversationHandler.beamInvoke(truncatedHistory, [possibleNextMessage], null); + conversationHandler.beamInvoke(truncatedHistory, [possibleNextMessage], null, _autoTitle); else - conversationHandler.beamInvoke(truncatedHistory, [], null); + conversationHandler.beamInvoke(truncatedHistory, [], null, _autoTitle); } }, [conversationHandler, conversationId]); diff --git a/src/apps/chat/editors/_handleExecute.ts b/src/apps/chat/editors/_handleExecute.ts index e9c6a313b..c42352001 100644 --- a/src/apps/chat/editors/_handleExecute.ts +++ b/src/apps/chat/editors/_handleExecute.ts @@ -1,3 +1,5 @@ +import { autoConversationTitle } from '~/modules/aifn/autotitle/autoTitle'; + import { getChatLLMId } from '~/common/stores/llms/store-llms'; import type { DConversationId } from '~/common/stores/chat/chat.conversation'; @@ -8,6 +10,7 @@ import { createTextContentFragment, isContentOrAttachmentFragment, isImageRefPar import { getConversationSystemPurposeId } from '~/common/stores/chat/store-chats'; import type { ChatExecuteMode } from '../execute-mode/execute-mode.types'; +import { getChatAutoAI } from '../store-app-chat'; import { textToDrawCommand } from '../commands/CommandsDraw'; import { _handleExecuteCommand, RET_NO_CMD } from './_handleExecuteCommand'; @@ -73,7 +76,7 @@ export async function _handleExecute(chatExecuteMode: ChatExecuteMode, conversat case 'beam-content': const updatedInputHistory = cHandler.historyViewHeadOrThrow('chat-beam-execute'); - cHandler.beamInvoke(updatedInputHistory, [], null); + cHandler.beamInvoke(updatedInputHistory, [], null, () => getChatAutoAI().autoTitleChat && void autoConversationTitle(conversationId, false)); return true; case 'append-user': diff --git a/src/common/chat-overlay/ConversationHandler.ts b/src/common/chat-overlay/ConversationHandler.ts index 909107c36..2d4d6008c 100644 --- a/src/common/chat-overlay/ConversationHandler.ts +++ b/src/common/chat-overlay/ConversationHandler.ts @@ -248,7 +248,7 @@ export class ConversationHandler { * @param importMessages If set, any message to import into the beam as pre-set rays * @param destReplaceMessageId If set, the output will replace the message with this id, otherwise it will append to the history */ - beamInvoke(viewHistory: Readonly, importMessages: DMessage[], destReplaceMessageId: DMessage['id'] | null): void { + beamInvoke(viewHistory: Readonly, importMessages: DMessage[], destReplaceMessageId: DMessage['id'] | null, onSuccess?: () => void): void { const { open: beamOpen, importRays: beamImportRays, terminateKeepingSettings } = this.beamStore.getState(); const onBeamSuccess = (messageUpdate: Pick) => { @@ -275,6 +275,9 @@ export class ConversationHandler { // close beam terminateKeepingSettings(); + + // caller-injected post-success hook (e.g. auto-titling) + onSuccess?.(); }; beamOpen(viewHistory, getChatLLMId(), !!destReplaceMessageId, onBeamSuccess);