codacus commited on
Commit
b98485d
·
unverified ·
1 Parent(s): a33a126

feat: make user made changes persistent after reload (#1387)

Browse files

* 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 CHANGED
@@ -25,6 +25,7 @@ import { createSampler } from '~/utils/sampler';
25
  import { getTemplates, selectStarterTemplate } from '~/utils/selectStarterTemplate';
26
  import { logStore } from '~/lib/stores/logs';
27
  import { streamingState } from '~/lib/stores/streaming';
 
28
 
29
  const toastAnimation = cssTransition({
30
  enter: 'animated fadeInRight',
@@ -320,17 +321,17 @@ export const ChatImpl = memo(
320
  const { assistantMessage, userMessage } = temResp;
321
  setMessages([
322
  {
323
- id: `${new Date().getTime()}`,
324
  role: 'user',
325
  content: messageContent,
326
  },
327
  {
328
- id: `${new Date().getTime()}`,
329
  role: 'assistant',
330
  content: assistantMessage,
331
  },
332
  {
333
- id: `${new Date().getTime()}`,
334
  role: 'user',
335
  content: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${userMessage}`,
336
  annotations: ['hidden'],
@@ -371,17 +372,18 @@ export const ChatImpl = memo(
371
  setMessages(messages.slice(0, -1));
372
  }
373
 
374
- const fileModifications = workbenchStore.getFileModifcations();
375
 
376
  chatStore.setKey('aborted', false);
377
 
378
- if (fileModifications !== undefined) {
 
379
  append({
380
  role: 'user',
381
  content: [
382
  {
383
  type: 'text',
384
- text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${messageContent}`,
385
  },
386
  ...imageDataList.map((imageData) => ({
387
  type: 'image',
 
25
  import { getTemplates, selectStarterTemplate } from '~/utils/selectStarterTemplate';
26
  import { logStore } from '~/lib/stores/logs';
27
  import { streamingState } from '~/lib/stores/streaming';
28
+ import { filesToArtifacts } from '~/utils/fileUtils';
29
 
30
  const toastAnimation = cssTransition({
31
  enter: 'animated fadeInRight',
 
321
  const { assistantMessage, userMessage } = temResp;
322
  setMessages([
323
  {
324
+ id: `1-${new Date().getTime()}`,
325
  role: 'user',
326
  content: messageContent,
327
  },
328
  {
329
+ id: `2-${new Date().getTime()}`,
330
  role: 'assistant',
331
  content: assistantMessage,
332
  },
333
  {
334
+ id: `3-${new Date().getTime()}`,
335
  role: 'user',
336
  content: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${userMessage}`,
337
  annotations: ['hidden'],
 
372
  setMessages(messages.slice(0, -1));
373
  }
374
 
375
+ const modifiedFiles = workbenchStore.getModifiedFiles();
376
 
377
  chatStore.setKey('aborted', false);
378
 
379
+ if (modifiedFiles !== undefined) {
380
+ const userUpdateArtifact = filesToArtifacts(modifiedFiles, `${Date.now()}`);
381
  append({
382
  role: 'user',
383
  content: [
384
  {
385
  type: 'text',
386
+ text: `[Model: ${model}]\n\n[Provider: ${provider.name}]\n\n${userUpdateArtifact}${messageContent}`,
387
  },
388
  ...imageDataList.map((imageData) => ({
389
  type: 'image',
app/components/chat/UserMessage.tsx CHANGED
@@ -43,5 +43,6 @@ export function UserMessage({ content }: UserMessageProps) {
43
  }
44
 
45
  function stripMetadata(content: string) {
46
- return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '');
 
47
  }
 
43
  }
44
 
45
  function stripMetadata(content: string) {
46
+ const artifactRegex = /<boltArtifact\s+[^>]*>[\s\S]*?<\/boltArtifact>/gm;
47
+ return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '').replace(artifactRegex, '');
48
  }
app/lib/hooks/useMessageParser.ts CHANGED
@@ -42,6 +42,10 @@ const messageParser = new StreamingMessageParser({
42
  },
43
  },
44
  });
 
 
 
 
45
 
46
  export function useMessageParser() {
47
  const [parsedMessages, setParsedMessages] = useState<{ [key: number]: string }>({});
@@ -55,9 +59,8 @@ export function useMessageParser() {
55
  }
56
 
57
  for (const [index, message] of messages.entries()) {
58
- if (message.role === 'assistant') {
59
- const newParsedContent = messageParser.parse(message.id, message.content);
60
-
61
  setParsedMessages((prevParsed) => ({
62
  ...prevParsed,
63
  [index]: !reset ? (prevParsed[index] || '') + newParsedContent : newParsedContent,
 
42
  },
43
  },
44
  });
45
+ const extractTextContent = (message: Message) =>
46
+ Array.isArray(message.content)
47
+ ? (message.content.find((item) => item.type === 'text')?.text as string) || ''
48
+ : message.content;
49
 
50
  export function useMessageParser() {
51
  const [parsedMessages, setParsedMessages] = useState<{ [key: number]: string }>({});
 
59
  }
60
 
61
  for (const [index, message] of messages.entries()) {
62
+ if (message.role === 'assistant' || message.role === 'user') {
63
+ const newParsedContent = messageParser.parse(message.id, extractTextContent(message));
 
64
  setParsedMessages((prevParsed) => ({
65
  ...prevParsed,
66
  [index]: !reset ? (prevParsed[index] || '') + newParsedContent : newParsedContent,
app/lib/stores/files.ts CHANGED
@@ -75,6 +75,29 @@ export class FilesStore {
75
  getFileModifications() {
76
  return computeFileModifications(this.files.get(), this.#modifiedFiles);
77
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  resetFileModifications() {
80
  this.#modifiedFiles.clear();
 
75
  getFileModifications() {
76
  return computeFileModifications(this.files.get(), this.#modifiedFiles);
77
  }
78
+ getModifiedFiles() {
79
+ let modifiedFiles: { [path: string]: File } | undefined = undefined;
80
+
81
+ for (const [filePath, originalContent] of this.#modifiedFiles) {
82
+ const file = this.files.get()[filePath];
83
+
84
+ if (file?.type !== 'file') {
85
+ continue;
86
+ }
87
+
88
+ if (file.content === originalContent) {
89
+ continue;
90
+ }
91
+
92
+ if (!modifiedFiles) {
93
+ modifiedFiles = {};
94
+ }
95
+
96
+ modifiedFiles[filePath] = file;
97
+ }
98
+
99
+ return modifiedFiles;
100
+ }
101
 
102
  resetFileModifications() {
103
  this.#modifiedFiles.clear();
app/lib/stores/workbench.ts CHANGED
@@ -238,6 +238,9 @@ export class WorkbenchStore {
238
  getFileModifcations() {
239
  return this.#filesStore.getFileModifications();
240
  }
 
 
 
241
 
242
  resetAllFileModifications() {
243
  this.#filesStore.resetFileModifications();
 
238
  getFileModifcations() {
239
  return this.#filesStore.getFileModifications();
240
  }
241
+ getModifiedFiles() {
242
+ return this.#filesStore.getModifiedFiles();
243
+ }
244
 
245
  resetAllFileModifications() {
246
  this.#filesStore.resetFileModifications();
app/utils/fileUtils.ts CHANGED
@@ -103,3 +103,19 @@ export const detectProjectType = async (
103
 
104
  return { type: '', setupCommand: '', followupMessage: '' };
105
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
  return { type: '', setupCommand: '', followupMessage: '' };
105
  };
106
+
107
+ export const filesToArtifacts = (files: { [path: string]: { content: string } }, id: string): string => {
108
+ return `
109
+ <boltArtifact id="${id}" title="User Updated Files">
110
+ ${Object.keys(files)
111
+ .map(
112
+ (filePath) => `
113
+ <boltAction type="file" filePath="${filePath}">
114
+ ${files[filePath].content}
115
+ </boltAction>
116
+ `,
117
+ )
118
+ .join('\n')}
119
+ </boltArtifact>
120
+ `;
121
+ };