diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index 1c6eca2f..fb6d61ce 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -531,16 +531,18 @@ export const ChatImpl = memo( description={description} importChat={importChat} exportChat={exportChat} - messages={messages.map((message, i) => { - if (message.role === 'user') { - return message; - } + messages={messages + .filter((message, i) => (message.role === 'assistant' ? parsedMessages[i] : true)) + .map((message, i) => { + if (message.role === 'user') { + return message; + } - return { - ...message, - content: parsedMessages[i] || '', - }; - })} + return { + ...message, + content: parsedMessages[i] || '', + }; + })} enhancePrompt={() => { enhancePrompt( input, diff --git a/app/lib/common/prompts/new-prompt.ts b/app/lib/common/prompts/new-prompt.ts index 227b7137..3d275d20 100644 --- a/app/lib/common/prompts/new-prompt.ts +++ b/app/lib/common/prompts/new-prompt.ts @@ -1,6 +1,5 @@ import { WORK_DIR } from '~/utils/constants'; import { allowedHTMLElements } from '~/utils/markdown'; -import { stripIndents } from '~/utils/stripIndent'; export const getFineTunedPrompt = ( cwd: string = WORK_DIR, @@ -695,7 +694,12 @@ npm run dev `; -export const CONTINUE_PROMPT = stripIndents` - Continue your prior response. IMPORTANT: Immediately begin from where you left off without any interruptions. - Do not repeat any content, including artifact and action tags. -`; +export const CONTINUE_PROMPT = `Continue your prior response. +Important: continue your last message without any interruptions, even if you're in the middle of a thought. You are continuing a document that will be re-assembled later. Never repeat any text that has already been sent. +Example: + Previous message: + + Good: Continues from where the previous message left off: + ="en">`; diff --git a/app/lib/common/prompts/prompts.ts b/app/lib/common/prompts/prompts.ts index 193c28b7..03e1800d 100644 --- a/app/lib/common/prompts/prompts.ts +++ b/app/lib/common/prompts/prompts.ts @@ -698,7 +698,12 @@ Here are some examples of correct usage of artifacts: `; -export const CONTINUE_PROMPT = stripIndents` - Continue your prior response. IMPORTANT: Immediately begin from where you left off without any interruptions. - Do not repeat any content, including artifact and action tags. -`; +export const CONTINUE_PROMPT = `Continue your prior response. +Important: continue your last message without any interruptions, even if you're in the middle of a thought. You are continuing a document that will be re-assembled later. Never repeat any text that has already been sent. +Example: + Previous message: + + Good: Continues from where the previous message left off: + ="en">`; diff --git a/app/lib/hooks/useMessageParser.ts b/app/lib/hooks/useMessageParser.ts index e08e77e9..2fec50c2 100644 --- a/app/lib/hooks/useMessageParser.ts +++ b/app/lib/hooks/useMessageParser.ts @@ -1,7 +1,8 @@ -import type { Message } from 'ai'; +import type { JSONValue, Message } from 'ai'; import { useCallback, useState } from 'react'; import { StreamingMessageParser } from '~/lib/runtime/message-parser'; import { workbenchStore } from '~/lib/stores/workbench'; +import type { SegmentsGroupAnnotation } from '~/types/context'; import { createScopedLogger } from '~/utils/logger'; const logger = createScopedLogger('useMessageParser'); @@ -47,6 +48,14 @@ const extractTextContent = (message: Message) => ? (message.content.find((item) => item.type === 'text')?.text as string) || '' : message.content; +const segmentsGroupIdFromAnnotation = (annotation: JSONValue): string | null => { + if (annotation && typeof annotation === 'object' && 'type' in annotation && annotation.type === 'segmentsGroup') { + return (annotation as SegmentsGroupAnnotation).segmentsGroupId; + } + + return null; +}; + export function useMessageParser() { const [parsedMessages, setParsedMessages] = useState<{ [key: number]: string }>({}); @@ -58,13 +67,44 @@ export function useMessageParser() { messageParser.reset(); } + const messageContents: Record = {}; + const segmentGroups: Record = {}; + + for (const [index, message] of messages.entries()) { + if (message.role === 'user') { + messageContents[index] = extractTextContent(message); + } else if (message.role === 'assistant') { + const segmentsGroupId = message.annotations?.reduce( + (groupId: string | null, a) => groupId ?? segmentsGroupIdFromAnnotation(a), + null, + ); + + if (!segmentsGroupId) { + messageContents[index] = extractTextContent(message); + } else { + const firstIndex = segmentGroups[segmentsGroupId]?.firstGroupIndex; + + if (firstIndex === undefined) { + segmentGroups[segmentsGroupId] = { firstGroupIndex: index }; + messageContents[index] = extractTextContent(message); + } else { + messageContents[firstIndex] += extractTextContent(message); + } + } + } + } + for (const [index, message] of messages.entries()) { if (message.role === 'assistant' || message.role === 'user') { - const newParsedContent = messageParser.parse(message.id, extractTextContent(message)); - setParsedMessages((prevParsed) => ({ - ...prevParsed, - [index]: !reset ? (prevParsed[index] || '') + newParsedContent : newParsedContent, - })); + const content = messageContents[index]; + + if (content !== undefined) { + const newParsedContent = messageParser.parse(message.id, content); + setParsedMessages((prevParsed) => ({ + ...prevParsed, + [index]: !reset ? (prevParsed[index] || '') + newParsedContent : newParsedContent, + })); + } } } }, []); diff --git a/app/routes/api.chat.ts b/app/routes/api.chat.ts index 5917dfc4..c3671077 100644 --- a/app/routes/api.chat.ts +++ b/app/routes/api.chat.ts @@ -7,7 +7,7 @@ import SwitchableStream from '~/lib/.server/llm/switchable-stream'; import type { IProviderSetting } from '~/types/model'; import { createScopedLogger } from '~/utils/logger'; import { getFilePaths, selectContext } from '~/lib/.server/llm/select-context'; -import type { ContextAnnotation, ProgressAnnotation } from '~/types/context'; +import type { ContextAnnotation, ProgressAnnotation, SegmentsGroupAnnotation } from '~/types/context'; import { WORK_DIR } from '~/utils/constants'; import { createSummary } from '~/lib/.server/llm/create-summary'; import { extractPropertiesFromMessage } from '~/lib/.server/llm/utils'; @@ -59,6 +59,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) { ); const stream = new SwitchableStream(); + const segmentsGroupId = generateId(); const cumulativeUsage = { completionTokens: 0, @@ -238,6 +239,11 @@ async function chatAction({ context, request }: ActionFunctionArgs) { content: `[Model: ${model}]\n\n[Provider: ${provider}]\n\n${CONTINUE_PROMPT}`, }); + dataStream.writeMessageAnnotation({ + type: 'segmentsGroup', + segmentsGroupId, + } satisfies SegmentsGroupAnnotation); + const result = await streamText({ messages, env: context.cloudflare?.env, diff --git a/app/types/context.ts b/app/types/context.ts index 75c21db7..320b0a0b 100644 --- a/app/types/context.ts +++ b/app/types/context.ts @@ -16,3 +16,8 @@ export type ProgressAnnotation = { order: number; message: string; }; + +export type SegmentsGroupAnnotation = { + type: 'segmentsGroup'; + segmentsGroupId: string; +};