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
This commit is contained in:
Anirban Kar 2025-02-27 13:34:57 +05:30 committed by GitHub
parent a33a1268c3
commit b98485d99f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 58 additions and 10 deletions

View File

@ -25,6 +25,7 @@ import { createSampler } from '~/utils/sampler';
import { getTemplates, selectStarterTemplate } from '~/utils/selectStarterTemplate'; import { getTemplates, selectStarterTemplate } from '~/utils/selectStarterTemplate';
import { logStore } from '~/lib/stores/logs'; import { logStore } from '~/lib/stores/logs';
import { streamingState } from '~/lib/stores/streaming'; import { streamingState } from '~/lib/stores/streaming';
import { filesToArtifacts } from '~/utils/fileUtils';
const toastAnimation = cssTransition({ const toastAnimation = cssTransition({
enter: 'animated fadeInRight', enter: 'animated fadeInRight',
@ -320,17 +321,17 @@ export const ChatImpl = memo(
const { assistantMessage, userMessage } = temResp; const { assistantMessage, userMessage } = temResp;
setMessages([ setMessages([
{ {
id: `${new Date().getTime()}`, id: `1-${new Date().getTime()}`,
role: 'user', role: 'user',
content: messageContent, content: messageContent,
}, },
{ {
id: `${new Date().getTime()}`, id: `2-${new Date().getTime()}`,
role: 'assistant', role: 'assistant',
content: assistantMessage, content: assistantMessage,
}, },
{ {
id: `${new Date().getTime()}`, id: `3-${new Date().getTime()}`,
role: 'user', role: 'user',
content: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${userMessage}`, content: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${userMessage}`,
annotations: ['hidden'], annotations: ['hidden'],
@ -371,17 +372,18 @@ export const ChatImpl = memo(
setMessages(messages.slice(0, -1)); setMessages(messages.slice(0, -1));
} }
const fileModifications = workbenchStore.getFileModifcations(); const modifiedFiles = workbenchStore.getModifiedFiles();
chatStore.setKey('aborted', false); chatStore.setKey('aborted', false);
if (fileModifications !== undefined) { if (modifiedFiles !== undefined) {
const userUpdateArtifact = filesToArtifacts(modifiedFiles, `${Date.now()}`);
append({ append({
role: 'user', role: 'user',
content: [ content: [
{ {
type: 'text', 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) => ({ ...imageDataList.map((imageData) => ({
type: 'image', type: 'image',

View File

@ -43,5 +43,6 @@ export function UserMessage({ content }: UserMessageProps) {
} }
function stripMetadata(content: string) { function stripMetadata(content: string) {
return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, ''); const artifactRegex = /<boltArtifact\s+[^>]*>[\s\S]*?<\/boltArtifact>/gm;
return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '').replace(artifactRegex, '');
} }

View File

@ -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() { export function useMessageParser() {
const [parsedMessages, setParsedMessages] = useState<{ [key: number]: string }>({}); const [parsedMessages, setParsedMessages] = useState<{ [key: number]: string }>({});
@ -55,9 +59,8 @@ export function useMessageParser() {
} }
for (const [index, message] of messages.entries()) { for (const [index, message] of messages.entries()) {
if (message.role === 'assistant') { if (message.role === 'assistant' || message.role === 'user') {
const newParsedContent = messageParser.parse(message.id, message.content); const newParsedContent = messageParser.parse(message.id, extractTextContent(message));
setParsedMessages((prevParsed) => ({ setParsedMessages((prevParsed) => ({
...prevParsed, ...prevParsed,
[index]: !reset ? (prevParsed[index] || '') + newParsedContent : newParsedContent, [index]: !reset ? (prevParsed[index] || '') + newParsedContent : newParsedContent,

View File

@ -75,6 +75,29 @@ export class FilesStore {
getFileModifications() { getFileModifications() {
return computeFileModifications(this.files.get(), this.#modifiedFiles); 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() { resetFileModifications() {
this.#modifiedFiles.clear(); this.#modifiedFiles.clear();

View File

@ -238,6 +238,9 @@ export class WorkbenchStore {
getFileModifcations() { getFileModifcations() {
return this.#filesStore.getFileModifications(); return this.#filesStore.getFileModifications();
} }
getModifiedFiles() {
return this.#filesStore.getModifiedFiles();
}
resetAllFileModifications() { resetAllFileModifications() {
this.#filesStore.resetFileModifications(); this.#filesStore.resetFileModifications();

View File

@ -103,3 +103,19 @@ export const detectProjectType = async (
return { type: '', setupCommand: '', followupMessage: '' }; return { type: '', setupCommand: '', followupMessage: '' };
}; };
export const filesToArtifacts = (files: { [path: string]: { content: string } }, id: string): string => {
return `
<boltArtifact id="${id}" title="User Updated Files">
${Object.keys(files)
.map(
(filePath) => `
<boltAction type="file" filePath="${filePath}">
${files[filePath].content}
</boltAction>
`,
)
.join('\n')}
</boltArtifact>
`;
};