Compare commits

...

1 Commits

Author SHA1 Message Date
claude[bot] 48cee6766a fix: auto-title conversations after Beam success
Add optional onSuccess callback to beamInvoke() so callers can inject
post-success behavior (auto-titling) without introducing a dependency
from common/ to modules/aifn/. All chat call sites now pass the
auto-title hook, matching the existing chat-persona flow.

Closes #1078

Co-authored-by: Enrico Ros <enricoros@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 04:22:56 +00:00
4 changed files with 21 additions and 8 deletions
+5 -2
View File
@@ -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), []);
+8 -4
View File
@@ -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]);
+4 -1
View File
@@ -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':
@@ -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<DMessage[]>, importMessages: DMessage[], destReplaceMessageId: DMessage['id'] | null): void {
beamInvoke(viewHistory: Readonly<DMessage[]>, importMessages: DMessage[], destReplaceMessageId: DMessage['id'] | null, onSuccess?: () => void): void {
const { open: beamOpen, importRays: beamImportRays, terminateKeepingSettings } = this.beamStore.getState();
const onBeamSuccess = (messageUpdate: Pick<DMessage, 'fragments' | 'generator'>) => {
@@ -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);