From d929438df93da032794b03d62605e2753e8b1e19 Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Wed, 24 Apr 2024 19:38:40 -0700 Subject: [PATCH] ReplyTo: extract --- src/apps/chat/AppChat.tsx | 20 +++++------------ .../chat/components/composer/Composer.tsx | 13 +++-------- src/apps/chat/editors/_handleExecute.tsx | 5 +++++ src/modules/aifn/replyto/replyTo.ts | 22 +++++++++++++++++++ 4 files changed, 36 insertions(+), 24 deletions(-) create mode 100644 src/modules/aifn/replyto/replyTo.ts diff --git a/src/apps/chat/AppChat.tsx b/src/apps/chat/AppChat.tsx index 4000137fd..841a7d941 100644 --- a/src/apps/chat/AppChat.tsx +++ b/src/apps/chat/AppChat.tsx @@ -23,7 +23,7 @@ import { PreferencesTab, useOptimaLayout, usePluggableOptimaLayout } from '~/com import { ScrollToBottom } from '~/common/scroll-to-bottom/ScrollToBottom'; import { ScrollToBottomButton } from '~/common/scroll-to-bottom/ScrollToBottomButton'; import { addSnackbar, removeSnackbar } from '~/common/components/useSnackbarsStore'; -import { createDMessage, DConversationId, DMessage, getConversation, getConversationSystemPurposeId, useConversation } from '~/common/state/store-chats'; +import { createDMessage, DConversationId, DMessage, DMessageMetadata, getConversation, getConversationSystemPurposeId, useConversation } from '~/common/state/store-chats'; import { themeBgAppChatComposer } from '~/common/app.theme'; import { useFolderStore } from '~/common/state/store-folders'; import { useIsMobile } from '~/common/components/useMatchMedia'; @@ -39,7 +39,7 @@ import { ChatBeamWrapper } from './components/ChatBeamWrapper'; import { ChatDrawerMemo } from './components/ChatDrawer'; import { ChatMessageList } from './components/ChatMessageList'; import { ChatPageMenuItems } from './components/ChatPageMenuItems'; -import { Composer, ComposerActionMetadata } from './components/composer/Composer'; +import { Composer } from './components/composer/Composer'; import { usePanesManager } from './components/panes/usePanesManager'; import { _handleExecute } from './editors/_handleExecute'; @@ -210,7 +210,7 @@ export function AppChat() { return outcome === true; }, [openModelsSetup, openPreferencesTab]); - const handleComposerAction = React.useCallback((conversationId: DConversationId, chatModeId: ChatModeId, multiPartMessage: ComposerOutputMultiPart, metadata: ComposerActionMetadata): boolean => { + const handleComposerAction = React.useCallback((conversationId: DConversationId, chatModeId: ChatModeId, multiPartMessage: ComposerOutputMultiPart, metadata?: DMessageMetadata): boolean => { // validate inputs if (multiPartMessage.length !== 1 || multiPartMessage[0].type !== 'text-block') { addSnackbar({ @@ -236,19 +236,11 @@ export function AppChat() { const history = getConversation(_cId)?.messages; if (!history) continue; - const newHistory = [ - ...history, - createDMessage('user', userText), - ]; - - // FIXME: HACK - this is a temporary solution to pass the metadata to the execution - // Only works with OpenAI right now - shall be passed as higher level metadata, so that - // each LLM vendor can encode this the way they like - if (metadata.inReplyTo) - newHistory.push(createDMessage('system', `The user is referring to this in particular:\n${metadata.inReplyTo}`)); + const newUserMessage = createDMessage('user', userText); + if (metadata) newUserMessage.metadata = metadata; // fire/forget - void handleExecuteAndOutcome(chatModeId, _cId, newHistory); + void handleExecuteAndOutcome(chatModeId, _cId, [...history, newUserMessage]); enqueuedAny = true; } return enqueuedAny; diff --git a/src/apps/chat/components/composer/Composer.tsx b/src/apps/chat/components/composer/Composer.tsx index a45d42ec5..fa519c137 100644 --- a/src/apps/chat/components/composer/Composer.tsx +++ b/src/apps/chat/components/composer/Composer.tsx @@ -27,7 +27,7 @@ import { ConversationsManager } from '~/common/chats/ConversationsManager'; import { PreferencesTab, useOptimaLayout } from '~/common/layout/optima/useOptimaLayout'; import { SpeechResult, useSpeechRecognition } from '~/common/components/useSpeechRecognition'; import { animationEnterBelow } from '~/common/util/animUtils'; -import { conversationTitle, DConversationId, getConversation, useChatStore } from '~/common/state/store-chats'; +import { conversationTitle, DConversationId, DMessageMetadata, getConversation, useChatStore } from '~/common/state/store-chats'; import { countModelTokens } from '~/common/util/token-counter'; import { isMacUser } from '~/common/util/pwaUtils'; import { launchAppCall } from '~/common/app.routes'; @@ -90,11 +90,6 @@ const dropppedCardDraggingSx: SxProps = { } as const; -export interface ComposerActionMetadata { - inReplyTo: string | null; -} - - /** * A React component for composing messages, with attachments and different modes. */ @@ -106,7 +101,7 @@ export function Composer(props: { capabilityHasT2I: boolean; isMulticast: boolean | null; isDeveloperMode: boolean; - onAction: (conversationId: DConversationId, chatModeId: ChatModeId, multiPartMessage: ComposerOutputMultiPart, metadata: ComposerActionMetadata) => boolean; + onAction: (conversationId: DConversationId, chatModeId: ChatModeId, multiPartMessage: ComposerOutputMultiPart, metadata?: DMessageMetadata) => boolean; onTextImagine: (conversationId: DConversationId, text: string) => void; setIsMulticast: (on: boolean) => void; sx?: SxProps; @@ -217,9 +212,7 @@ export function Composer(props: { return false; // metadata - const metadata: ComposerActionMetadata = { - inReplyTo: replyToGenerateText || null, - }; + const metadata = replyToGenerateText ? { inReplyToText: replyToGenerateText } : undefined; // send the message const enqueued = onAction(conversationId, _chatModeId, multiPartMessage, metadata); diff --git a/src/apps/chat/editors/_handleExecute.tsx b/src/apps/chat/editors/_handleExecute.tsx index 95af1b6af..d7e74dc45 100644 --- a/src/apps/chat/editors/_handleExecute.tsx +++ b/src/apps/chat/editors/_handleExecute.tsx @@ -1,4 +1,5 @@ import { getChatLLMId } from '~/modules/llms/store-llms'; +import { updateHistoryForReplyTo } from '~/modules/aifn/replyto/replyTo'; import { ConversationsManager } from '~/common/chats/ConversationsManager'; import { createDMessage, DConversationId, DMessage, getConversationSystemPurposeId } from '~/common/state/store-chats'; @@ -30,6 +31,10 @@ export async function _handleExecute(chatModeId: ChatModeId, conversationId: DCo const cHandler = ConversationsManager.getHandler(conversationId); cHandler.inlineUpdatePurposeInHistory(history, chatLLMId || undefined); + // FIXME: shouldn't do this for all the code paths. The advantage for having it here (vs Composer output only) is re-executing history + // TODO: move this to the server side after transferring metadata? + updateHistoryForReplyTo(history); + // Handle unconfigured if (!chatLLMId || !chatModeId) { // set the history (e.g. the updated system prompt and the user prompt) at least, see #523 diff --git a/src/modules/aifn/replyto/replyTo.ts b/src/modules/aifn/replyto/replyTo.ts new file mode 100644 index 000000000..a28e4588b --- /dev/null +++ b/src/modules/aifn/replyto/replyTo.ts @@ -0,0 +1,22 @@ +import { createDMessage, DMessage } from '~/common/state/store-chats'; + + +const replyToSystemPrompt = `The user is referring to this in particular: +{{ReplyToText}}`; + +/** + * Adds a system message to the history, explaining the context of the reply + * + * FIXME: HACK - this is a temporary solution to pass the metadata to the execution + * + * Only works with OpenAI and a couple more right now. Fix it by making it vendor-agnostic + */ +export function updateHistoryForReplyTo(history: DMessage[]) { + if (history?.length < 1) + return; + + const lastMessage = history[history.length - 1]; + + if (lastMessage.role === 'user' && lastMessage.metadata?.inReplyToText) + history.push(createDMessage('system', replyToSystemPrompt.replace('{{ReplyToText}}', lastMessage.metadata.inReplyToText))); +}