diff --git a/src/apps/chat/components/message/useSelHighlighterMemo.ts b/src/apps/chat/components/message/useSelHighlighterMemo.ts index d1d7690a0..c4e830d9f 100644 --- a/src/apps/chat/components/message/useSelHighlighterMemo.ts +++ b/src/apps/chat/components/message/useSelHighlighterMemo.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import type { DMessageId } from '~/common/stores/chat/chat.message'; -import { createTextContentFragment, DMessageFragment, DMessageFragmentId, isContentFragment, isTextPart } from '~/common/stores/chat/chat.fragments'; +import { createTextContentFragment, DMessageFragment, DMessageFragmentId, isTextContentFragment } from '~/common/stores/chat/chat.fragments'; import { wrapWithMarkdownSyntax } from '~/modules/blocks/markdown/markdown.wrapper'; import { BUBBLE_MIN_TEXT_LENGTH } from './ChatMessage'; @@ -41,7 +41,7 @@ export function useSelHighlighterMemo( // Create the highlighter function, if there's 1 and only 1 occurrence of the selection const highlightFunction = fragments.reduce((acc: false /* not found */ | ((tool: HighlightTool) => void) | true /* more than one */, fragment) => { - if (!acc && isContentFragment(fragment) && isTextPart(fragment.part)) { + if (!acc && isTextContentFragment(fragment)) { const fragmentText = fragment.part.text; let index = fragmentText.indexOf(selText); diff --git a/src/apps/chat/editors/_handleExecute.ts b/src/apps/chat/editors/_handleExecute.ts index 96738d105..a63aca7a6 100644 --- a/src/apps/chat/editors/_handleExecute.ts +++ b/src/apps/chat/editors/_handleExecute.ts @@ -4,7 +4,7 @@ import type { DConversationId } from '~/common/stores/chat/chat.conversation'; import type { DMessage } from '~/common/stores/chat/chat.message'; import { ConversationHandler } from '~/common/chat-overlay/ConversationHandler'; import { ConversationsManager } from '~/common/chat-overlay/ConversationsManager'; -import { createTextContentFragment, isContentFragment, isTextPart } from '~/common/stores/chat/chat.fragments'; +import { createTextContentFragment, isTextContentFragment } from '~/common/stores/chat/chat.fragments'; import { getConversationSystemPurposeId } from '~/common/stores/chat/store-chats'; import type { ChatExecuteMode } from '../execute-mode/execute-mode.types'; @@ -80,7 +80,7 @@ export async function _handleExecute(chatExecuteMode: ChatExecuteMode, conversat case 'generate-image': // verify we were called with a single DMessageTextContent - if (!isContentFragment(firstFragment) || !isTextPart(firstFragment.part)) + if (!isTextContentFragment(firstFragment)) return false; const imagePrompt = firstFragment.part.text; cHandler.messageFragmentReplace(lastMessage.id, firstFragment.fId, createTextContentFragment(textToDrawCommand(imagePrompt)), true); @@ -88,7 +88,7 @@ export async function _handleExecute(chatExecuteMode: ChatExecuteMode, conversat case 'react-content': // verify we were called with a single DMessageTextContent - if (!isContentFragment(firstFragment) || !isTextPart(firstFragment.part)) + if (!isTextContentFragment(firstFragment)) return false; const reactPrompt = firstFragment.part.text; cHandler.messageFragmentReplace(lastMessage.id, firstFragment.fId, createTextContentFragment(textToDrawCommand(reactPrompt)), true); diff --git a/src/apps/chat/editors/_handleExecuteCommand.ts b/src/apps/chat/editors/_handleExecuteCommand.ts index 604020234..6f54c7a29 100644 --- a/src/apps/chat/editors/_handleExecuteCommand.ts +++ b/src/apps/chat/editors/_handleExecuteCommand.ts @@ -1,7 +1,7 @@ import type { DLLMId } from '~/common/stores/llms/llms.types'; import type { DMessage, DMessageId } from '~/common/stores/chat/chat.message'; import { ConversationHandler } from '~/common/chat-overlay/ConversationHandler'; -import { createTextContentFragment, DMessageFragment, isContentFragment, isTextPart } from '~/common/stores/chat/chat.fragments'; +import { createTextContentFragment, DMessageFragment, isTextContentFragment } from '~/common/stores/chat/chat.fragments'; import { extractChatCommand, helpPrettyChatCommands } from '../commands/commands.registry'; import { runBrowseGetPageUpdatingState } from './browse-load'; @@ -15,7 +15,7 @@ export const RET_NO_CMD = 'no-cmd'; export async function _handleExecuteCommand(lastMessageId: DMessageId, lastMessageFirstFragment: DMessageFragment, lastMessage: Readonly, cHandler: ConversationHandler, chatLLMId: DLLMId) { // commands must have a first Content DMessageTextPart - if (!isContentFragment(lastMessageFirstFragment) || !isTextPart(lastMessageFirstFragment.part)) + if (!isTextContentFragment(lastMessageFirstFragment)) return RET_NO_CMD; // check if we have a command diff --git a/src/apps/chat/editors/persona/PersonaChatMessageSpeak.ts b/src/apps/chat/editors/persona/PersonaChatMessageSpeak.ts index 3e1acf5e6..a016af1da 100644 --- a/src/apps/chat/editors/persona/PersonaChatMessageSpeak.ts +++ b/src/apps/chat/editors/persona/PersonaChatMessageSpeak.ts @@ -1,6 +1,6 @@ import { speakText } from '~/modules/elevenlabs/elevenlabs.client'; -import { isContentFragment, isTextPart } from '~/common/stores/chat/chat.fragments'; +import { isTextContentFragment } from '~/common/stores/chat/chat.fragments'; import type { AixChatGenerateContent_DMessage } from '~/modules/aix/client/aix.client'; @@ -20,7 +20,7 @@ export class PersonaChatMessageSpeak implements PersonaProcessorInterface { if (this.autoSpeakType === 'off' || this.spokenLine) return; // Require a Content.Text first fragment - if (!accumulatedMessage.fragments?.length || !isContentFragment(accumulatedMessage.fragments[0]) || !isTextPart(accumulatedMessage.fragments[0].part)) + if (!accumulatedMessage.fragments?.length || !isTextContentFragment(accumulatedMessage.fragments[0])) return; const text = accumulatedMessage.fragments[0].part.text; diff --git a/src/common/stores/chat/chat.fragments.ts b/src/common/stores/chat/chat.fragments.ts index 3576aef62..46f895214 100644 --- a/src/common/stores/chat/chat.fragments.ts +++ b/src/common/stores/chat/chat.fragments.ts @@ -176,6 +176,10 @@ export function isContentFragment(fragment: DMessageFragment) { return fragment.ft === 'content'; } +export function isTextContentFragment(fragment: DMessageFragment): fragment is DMessageContentFragment & { part: DMessageTextPart } { + return fragment.ft === 'content' && fragment.part.pt === 'text'; +} + export function isAttachmentFragment(fragment: DMessageFragment) { return fragment.ft === 'attachment'; } @@ -205,7 +209,7 @@ export function isErrorPart(part: DMessageContentFragment['part']) { return part.pt === 'error'; } -export function isToolResponseFunctionCallPart(part: DMessageContentFragment['part']) { +export function isToolResponseFunctionCallPart(part: DMessageContentFragment['part']): part is DMessageToolResponsePart & { response: { type: 'function_call' } } { return part.pt === 'tool_response' && part.response.type === 'function_call'; } @@ -443,14 +447,14 @@ function _duplicate_DataReference(ref: DMessageDataRef): DMessageDataRef { export function editTextPartsInline(fragments: DMessageFragment[], editText: (text: string, idx: number) => string): void { fragments.forEach((fragment, idx) => { - if (isContentFragment(fragment) && isTextPart(fragment.part)) + if (isTextContentFragment(fragment)) fragment.part.text = editText(fragment.part.text, idx); }); } export function prependTextPartsInline(fragments: DMessageFragment[], textPrefix: string): void { for (const fragment of fragments) { - if (!isContentFragment(fragment) || !isTextPart(fragment.part)) + if (!isTextContentFragment(fragment)) continue; fragment.part.text = textPrefix + ' ' + fragment.part.text; return; diff --git a/src/common/stores/chat/chat.message.ts b/src/common/stores/chat/chat.message.ts index cbce6ac9b..bb867c8bf 100644 --- a/src/common/stores/chat/chat.message.ts +++ b/src/common/stores/chat/chat.message.ts @@ -237,31 +237,34 @@ export function messageFragmentsReduceText(fragments: DMessageFragment[], fragme return fragments .map(fragment => { - if (isContentFragment(fragment)) { - switch (fragment.part.pt) { - case 'text': - return fragment.part.text; - case 'error': - return fragment.part.error; - case 'image_ref': - return ''; - case 'tool_invocation': - case 'tool_response': - // Ignore tools for the text reduction - return ''; - } - } else if (isAttachmentFragment(fragment)) { - switch (fragment.part.pt) { - case 'doc': - return fragment.part.data.text; - case 'image_ref': - return ''; - } - } else if (isVoidFragment(fragment)) { - // all void fragments are ignored by definition when doing a text reduction - return ''; + switch (true) { + case isContentFragment(fragment): + switch (fragment.part.pt) { + case 'text': + return fragment.part.text; + case 'error': + return fragment.part.error; + case 'image_ref': + return ''; + case 'tool_invocation': + case 'tool_response': + // Ignore tools for the text reduction + return ''; + } + break; + case isAttachmentFragment(fragment): + switch (fragment.part.pt) { + case 'doc': + return fragment.part.data.text; + case 'image_ref': + return ''; + } + break; + case isVoidFragment(fragment): + // all void fragments are ignored by definition when doing a text reduction + return ''; } - console.warn(`DEV: messageFragmentsReduceText: unexpected '${fragment.ft}' fragment with '${(fragment as any)?.part?.pt}' part`); + console.warn(`[DEV] messageFragmentsReduceText: unexpected '${fragment.ft}' fragment with '${(fragment as any)?.part?.pt}' part`); return ''; }) .filter(text => !!text) diff --git a/src/common/stores/chat/chats.converters.ts b/src/common/stores/chat/chats.converters.ts index 9100933c3..3981f6c82 100644 --- a/src/common/stores/chat/chats.converters.ts +++ b/src/common/stores/chat/chats.converters.ts @@ -8,7 +8,7 @@ import type { DModelsService } from '~/common/stores/llms/modelsservice.types'; import { createDConversation, DConversation, type DConversationId } from './chat.conversation'; import { createDMessageTextContent, DMessage, MESSAGE_FLAG_NOTIFY_COMPLETE, messageSetUserFlag } from './chat.message'; -import { createErrorContentFragment, isAttachmentFragment, isContentFragment, isContentOrAttachmentFragment, isDocPart, isPlaceholderPart, isTextPart, isVoidFragment } from './chat.fragments'; +import { createErrorContentFragment, isAttachmentFragment, isContentFragment, isContentOrAttachmentFragment, isDocPart, isPlaceholderPart, isTextContentFragment, isTextPart, isVoidFragment } from './chat.fragments'; // configuration @@ -63,7 +63,8 @@ export namespace V4ToHeadConverters { // [Emergency] validate part types, can mess up in development if (EMERGENCY_CLEANUP_PARTS) { // If a text part has 'object' in place of 'string' for pText: remove the part altogether - if (isContentFragment(fragment) && isTextPart(fragment.part)) { + if (isTextContentFragment(fragment)) { + // noinspection SuspiciousTypeOfGuard if (typeof fragment.part.text !== 'string') { // Remove this fragment m.fragments.splice(i, 1); diff --git a/src/modules/aix/client/aix.client.chatGenerateRequest.ts b/src/modules/aix/client/aix.client.chatGenerateRequest.ts index d66edf7fa..cd92598b3 100644 --- a/src/modules/aix/client/aix.client.chatGenerateRequest.ts +++ b/src/modules/aix/client/aix.client.chatGenerateRequest.ts @@ -1,7 +1,7 @@ import { getImageAsset } from '~/modules/dblobs/dblobs.images'; import { DMessage, DMessageRole, DMetaReferenceItem, MESSAGE_FLAG_AIX_SKIP, MESSAGE_FLAG_VND_ANT_CACHE_AUTO, MESSAGE_FLAG_VND_ANT_CACHE_USER, messageHasUserFlag } from '~/common/stores/chat/chat.message'; -import { DMessageFragment, DMessageImageRefPart, isContentFragment, isContentOrAttachmentFragment, isTextPart, isToolResponseFunctionCallPart } from '~/common/stores/chat/chat.fragments'; +import { DMessageFragment, DMessageImageRefPart, isContentOrAttachmentFragment, isTextContentFragment, isToolResponseFunctionCallPart } from '~/common/stores/chat/chat.fragments'; import { Is } from '~/common/util/pwaUtils'; import { LLMImageResizeMode, resizeBase64ImageIfNeeded } from '~/common/util/imageUtils'; @@ -85,7 +85,7 @@ export async function aixCGR_FromDMessagesOrThrow( }; } for (const systemFragment of m.fragments) { - if (isContentFragment(systemFragment) && isTextPart(systemFragment.part)) { + if (isTextContentFragment(systemFragment)) { acc.systemMessage.parts.push(systemFragment.part); } else { console.warn('aixCGR_FromDMessages: unexpected system fragment', systemFragment);