From b98485d99f05fb067f5df027f2859409c6ce8be6 Mon Sep 17 00:00:00 2001 From: Anirban Kar Date: Thu, 27 Feb 2025 13:34:57 +0530 Subject: [PATCH] feat: make user made changes persistent after reload (#1387) * feat: save user made changes persistent * fix: remove artifact from user message on the UI * fix: message Id generation fix --- app/components/chat/Chat.client.tsx | 14 ++++++++------ app/components/chat/UserMessage.tsx | 3 ++- app/lib/hooks/useMessageParser.ts | 9 ++++++--- app/lib/stores/files.ts | 23 +++++++++++++++++++++++ app/lib/stores/workbench.ts | 3 +++ app/utils/fileUtils.ts | 16 ++++++++++++++++ 6 files changed, 58 insertions(+), 10 deletions(-) diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index 8c3346bd..c08dd6d8 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -25,6 +25,7 @@ import { createSampler } from '~/utils/sampler'; import { getTemplates, selectStarterTemplate } from '~/utils/selectStarterTemplate'; import { logStore } from '~/lib/stores/logs'; import { streamingState } from '~/lib/stores/streaming'; +import { filesToArtifacts } from '~/utils/fileUtils'; const toastAnimation = cssTransition({ enter: 'animated fadeInRight', @@ -320,17 +321,17 @@ export const ChatImpl = memo( const { assistantMessage, userMessage } = temResp; setMessages([ { - id: `${new Date().getTime()}`, + id: `1-${new Date().getTime()}`, role: 'user', content: messageContent, }, { - id: `${new Date().getTime()}`, + id: `2-${new Date().getTime()}`, role: 'assistant', content: assistantMessage, }, { - id: `${new Date().getTime()}`, + id: `3-${new Date().getTime()}`, role: 'user', content: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${userMessage}`, annotations: ['hidden'], @@ -371,17 +372,18 @@ export const ChatImpl = memo( setMessages(messages.slice(0, -1)); } - const fileModifications = workbenchStore.getFileModifcations(); + const modifiedFiles = workbenchStore.getModifiedFiles(); chatStore.setKey('aborted', false); - if (fileModifications !== undefined) { + if (modifiedFiles !== undefined) { + const userUpdateArtifact = filesToArtifacts(modifiedFiles, `${Date.now()}`); append({ role: 'user', content: [ { type: 'text', - text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${messageContent}`, + text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${userUpdateArtifact}${messageContent}`, }, ...imageDataList.map((imageData) => ({ type: 'image', diff --git a/app/components/chat/UserMessage.tsx b/app/components/chat/UserMessage.tsx index 6fd6fc0d..e7ef54a6 100644 --- a/app/components/chat/UserMessage.tsx +++ b/app/components/chat/UserMessage.tsx @@ -43,5 +43,6 @@ export function UserMessage({ content }: UserMessageProps) { } function stripMetadata(content: string) { - return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, ''); + const artifactRegex = /]*>[\s\S]*?<\/boltArtifact>/gm; + return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '').replace(artifactRegex, ''); } diff --git a/app/lib/hooks/useMessageParser.ts b/app/lib/hooks/useMessageParser.ts index 3510d00c..e08e77e9 100644 --- a/app/lib/hooks/useMessageParser.ts +++ b/app/lib/hooks/useMessageParser.ts @@ -42,6 +42,10 @@ const messageParser = new StreamingMessageParser({ }, }, }); +const extractTextContent = (message: Message) => + Array.isArray(message.content) + ? (message.content.find((item) => item.type === 'text')?.text as string) || '' + : message.content; export function useMessageParser() { const [parsedMessages, setParsedMessages] = useState<{ [key: number]: string }>({}); @@ -55,9 +59,8 @@ export function useMessageParser() { } for (const [index, message] of messages.entries()) { - if (message.role === 'assistant') { - const newParsedContent = messageParser.parse(message.id, message.content); - + if (message.role === 'assistant' || message.role === 'user') { + const newParsedContent = messageParser.parse(message.id, extractTextContent(message)); setParsedMessages((prevParsed) => ({ ...prevParsed, [index]: !reset ? (prevParsed[index] || '') + newParsedContent : newParsedContent, diff --git a/app/lib/stores/files.ts b/app/lib/stores/files.ts index e7c0f60a..687b8037 100644 --- a/app/lib/stores/files.ts +++ b/app/lib/stores/files.ts @@ -75,6 +75,29 @@ export class FilesStore { getFileModifications() { return computeFileModifications(this.files.get(), this.#modifiedFiles); } + getModifiedFiles() { + let modifiedFiles: { [path: string]: File } | undefined = undefined; + + for (const [filePath, originalContent] of this.#modifiedFiles) { + const file = this.files.get()[filePath]; + + if (file?.type !== 'file') { + continue; + } + + if (file.content === originalContent) { + continue; + } + + if (!modifiedFiles) { + modifiedFiles = {}; + } + + modifiedFiles[filePath] = file; + } + + return modifiedFiles; + } resetFileModifications() { this.#modifiedFiles.clear(); diff --git a/app/lib/stores/workbench.ts b/app/lib/stores/workbench.ts index 80111079..4914ebc5 100644 --- a/app/lib/stores/workbench.ts +++ b/app/lib/stores/workbench.ts @@ -238,6 +238,9 @@ export class WorkbenchStore { getFileModifcations() { return this.#filesStore.getFileModifications(); } + getModifiedFiles() { + return this.#filesStore.getModifiedFiles(); + } resetAllFileModifications() { this.#filesStore.resetFileModifications(); diff --git a/app/utils/fileUtils.ts b/app/utils/fileUtils.ts index fcf2a017..2a1c8756 100644 --- a/app/utils/fileUtils.ts +++ b/app/utils/fileUtils.ts @@ -103,3 +103,19 @@ export const detectProjectType = async ( return { type: '', setupCommand: '', followupMessage: '' }; }; + +export const filesToArtifacts = (files: { [path: string]: { content: string } }, id: string): string => { + return ` + +${Object.keys(files) + .map( + (filePath) => ` + +${files[filePath].content} + +`, + ) + .join('\n')} + + `; +};