Dominic Elm commited on
Commit
2cb3f09
·
unverified ·
1 Parent(s): a5ed695

feat: submit file changes to the llm (#11)

Browse files
packages/bolt/app/components/chat/BaseChat.tsx CHANGED
@@ -18,7 +18,7 @@ interface BaseChatProps {
18
  promptEnhanced?: boolean;
19
  input?: string;
20
  handleStop?: () => void;
21
- sendMessage?: () => void;
22
  handleInputChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
23
  enhancePrompt?: () => void;
24
  }
@@ -103,7 +103,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
103
 
104
  event.preventDefault();
105
 
106
- sendMessage?.();
107
  }
108
  }}
109
  value={input}
@@ -122,13 +122,13 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
122
  <SendButton
123
  show={input.length > 0 || isStreaming}
124
  isStreaming={isStreaming}
125
- onClick={() => {
126
  if (isStreaming) {
127
  handleStop?.();
128
  return;
129
  }
130
 
131
- sendMessage?.();
132
  }}
133
  />
134
  )}
 
18
  promptEnhanced?: boolean;
19
  input?: string;
20
  handleStop?: () => void;
21
+ sendMessage?: (event: React.UIEvent) => void;
22
  handleInputChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
23
  enhancePrompt?: () => void;
24
  }
 
103
 
104
  event.preventDefault();
105
 
106
+ sendMessage?.(event);
107
  }
108
  }}
109
  value={input}
 
122
  <SendButton
123
  show={input.length > 0 || isStreaming}
124
  isStreaming={isStreaming}
125
+ onClick={(event) => {
126
  if (isStreaming) {
127
  handleStop?.();
128
  return;
129
  }
130
 
131
+ sendMessage?.(event);
132
  }}
133
  />
134
  )}
packages/bolt/app/components/chat/Chat.client.tsx CHANGED
@@ -2,10 +2,11 @@ import type { Message } from 'ai';
2
  import { useChat } from 'ai/react';
3
  import { useAnimate } from 'framer-motion';
4
  import { useEffect, useRef, useState } from 'react';
5
- import { toast, ToastContainer, cssTransition } from 'react-toastify';
6
  import { useMessageParser, usePromptEnhancer, useSnapScroll } from '~/lib/hooks';
7
  import { chatStore } from '~/lib/stores/chat';
8
  import { workbenchStore } from '~/lib/stores/workbench';
 
9
  import { cubicEasingFn } from '~/utils/easings';
10
  import { createScopedLogger } from '~/utils/logger';
11
  import { BaseChat } from './BaseChat';
@@ -41,7 +42,7 @@ export function ChatImpl({ initialMessages, storeMessageHistory }: ChatProps) {
41
 
42
  const [animationScope, animate] = useAnimate();
43
 
44
- const { messages, isLoading, input, handleInputChange, setInput, handleSubmit, stop } = useChat({
45
  api: '/api/chat',
46
  onError: (error) => {
47
  logger.error(error);
@@ -100,15 +101,49 @@ export function ChatImpl({ initialMessages, storeMessageHistory }: ChatProps) {
100
  setChatStarted(true);
101
  };
102
 
103
- const sendMessage = () => {
104
- if (input.length === 0) {
105
  return;
106
  }
107
 
 
 
 
 
 
 
 
 
 
 
 
108
  chatStore.setKey('aborted', false);
109
 
110
  runAnimation();
111
- handleSubmit();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  resetEnhancer();
113
 
114
  textareaRef.current?.blur();
 
2
  import { useChat } from 'ai/react';
3
  import { useAnimate } from 'framer-motion';
4
  import { useEffect, useRef, useState } from 'react';
5
+ import { cssTransition, toast, ToastContainer } from 'react-toastify';
6
  import { useMessageParser, usePromptEnhancer, useSnapScroll } from '~/lib/hooks';
7
  import { chatStore } from '~/lib/stores/chat';
8
  import { workbenchStore } from '~/lib/stores/workbench';
9
+ import { fileModificationsToHTML } from '~/utils/diff';
10
  import { cubicEasingFn } from '~/utils/easings';
11
  import { createScopedLogger } from '~/utils/logger';
12
  import { BaseChat } from './BaseChat';
 
42
 
43
  const [animationScope, animate] = useAnimate();
44
 
45
+ const { messages, isLoading, input, handleInputChange, setInput, handleSubmit, stop, append } = useChat({
46
  api: '/api/chat',
47
  onError: (error) => {
48
  logger.error(error);
 
101
  setChatStarted(true);
102
  };
103
 
104
+ const sendMessage = async (event: React.UIEvent) => {
105
+ if (input.length === 0 || isLoading) {
106
  return;
107
  }
108
 
109
+ /**
110
+ * @note (delm) Usually saving files shouldn't take long but it may take longer if there
111
+ * many unsaved files. In that case we need to block user input and show an indicator
112
+ * of some kind so the user is aware that something is happening. But I consider the
113
+ * happy case to be no unsaved files and I would expect users to save their changes
114
+ * before they send another message.
115
+ */
116
+ await workbenchStore.saveAllFiles();
117
+
118
+ const fileModifications = workbenchStore.getFileModifcations();
119
+
120
  chatStore.setKey('aborted', false);
121
 
122
  runAnimation();
123
+
124
+ if (fileModifications !== undefined) {
125
+ const diff = fileModificationsToHTML(fileModifications);
126
+
127
+ /**
128
+ * If we have file modifications we append a new user message manually since we have to prefix
129
+ * the user input with the file modifications and we don't want the new user input to appear
130
+ * in the prompt. Using `append` is almost the same as `handleSubmit` except that we have to
131
+ * manually reset the input and we'd have to manually pass in file attachments. However, those
132
+ * aren't relevant here.
133
+ */
134
+ append({ role: 'user', content: `${diff}\n\n${input}` });
135
+
136
+ setInput('');
137
+
138
+ /**
139
+ * After sending a new message we reset all modifications since the model
140
+ * should now be aware of all the changes.
141
+ */
142
+ workbenchStore.resetAllFileModifications();
143
+ } else {
144
+ handleSubmit(event);
145
+ }
146
+
147
  resetEnhancer();
148
 
149
  textareaRef.current?.blur();
packages/bolt/app/components/chat/SendButton.client.tsx CHANGED
@@ -3,7 +3,7 @@ import { AnimatePresence, cubicBezier, motion } from 'framer-motion';
3
  interface SendButtonProps {
4
  show: boolean;
5
  isStreaming?: boolean;
6
- onClick?: VoidFunction;
7
  }
8
 
9
  const customEasingFn = cubicBezier(0.4, 0, 0.2, 1);
@@ -20,7 +20,7 @@ export function SendButton({ show, isStreaming, onClick }: SendButtonProps) {
20
  exit={{ opacity: 0, y: 10 }}
21
  onClick={(event) => {
22
  event.preventDefault();
23
- onClick?.();
24
  }}
25
  >
26
  <div className="text-lg">
 
3
  interface SendButtonProps {
4
  show: boolean;
5
  isStreaming?: boolean;
6
+ onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
7
  }
8
 
9
  const customEasingFn = cubicBezier(0.4, 0, 0.2, 1);
 
20
  exit={{ opacity: 0, y: 10 }}
21
  onClick={(event) => {
22
  event.preventDefault();
23
+ onClick?.(event);
24
  }}
25
  >
26
  <div className="text-lg">
packages/bolt/app/components/chat/UserMessage.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import { Markdown } from './Markdown';
2
 
3
  interface UserMessageProps {
@@ -7,7 +8,11 @@ interface UserMessageProps {
7
  export function UserMessage({ content }: UserMessageProps) {
8
  return (
9
  <div className="overflow-hidden">
10
- <Markdown>{content}</Markdown>
11
  </div>
12
  );
13
  }
 
 
 
 
 
1
+ import { modificationsRegex } from '~/utils/diff';
2
  import { Markdown } from './Markdown';
3
 
4
  interface UserMessageProps {
 
8
  export function UserMessage({ content }: UserMessageProps) {
9
  return (
10
  <div className="overflow-hidden">
11
+ <Markdown>{sanitizeUserMessage(content)}</Markdown>
12
  </div>
13
  );
14
  }
15
+
16
+ function sanitizeUserMessage(content: string) {
17
+ return content.replace(modificationsRegex, '').trim();
18
+ }
packages/bolt/app/components/editor/codemirror/CodeMirrorEditor.tsx CHANGED
@@ -26,7 +26,8 @@ import { getLanguage } from './languages';
26
  const logger = createScopedLogger('CodeMirrorEditor');
27
 
28
  export interface EditorDocument {
29
- value: string | Uint8Array;
 
30
  filePath: string;
31
  scroll?: ScrollPosition;
32
  }
@@ -116,8 +117,6 @@ export const CodeMirrorEditor = memo(
116
  const onChangeRef = useRef(onChange);
117
  const onSaveRef = useRef(onSave);
118
 
119
- const isBinaryFile = doc?.value instanceof Uint8Array;
120
-
121
  /**
122
  * This effect is used to avoid side effects directly in the render function
123
  * and instead the refs are updated after each render.
@@ -198,7 +197,7 @@ export const CodeMirrorEditor = memo(
198
  return;
199
  }
200
 
201
- if (doc.value instanceof Uint8Array) {
202
  return;
203
  }
204
 
@@ -230,7 +229,7 @@ export const CodeMirrorEditor = memo(
230
 
231
  return (
232
  <div className={classNames('relative h-full', className)}>
233
- {isBinaryFile && <BinaryContent />}
234
  <div className="h-full overflow-hidden" ref={containerRef} />
235
  </div>
236
  );
@@ -343,7 +342,7 @@ function setEditorDocument(
343
  }
344
 
345
  view.dispatch({
346
- effects: [editableStateEffect.of(editable)],
347
  });
348
 
349
  getLanguage(doc.filePath).then((languageSupport) => {
 
26
  const logger = createScopedLogger('CodeMirrorEditor');
27
 
28
  export interface EditorDocument {
29
+ value: string;
30
+ isBinary: boolean;
31
  filePath: string;
32
  scroll?: ScrollPosition;
33
  }
 
117
  const onChangeRef = useRef(onChange);
118
  const onSaveRef = useRef(onSave);
119
 
 
 
120
  /**
121
  * This effect is used to avoid side effects directly in the render function
122
  * and instead the refs are updated after each render.
 
197
  return;
198
  }
199
 
200
+ if (doc.isBinary) {
201
  return;
202
  }
203
 
 
229
 
230
  return (
231
  <div className={classNames('relative h-full', className)}>
232
+ {doc?.isBinary && <BinaryContent />}
233
  <div className="h-full overflow-hidden" ref={containerRef} />
234
  </div>
235
  );
 
342
  }
343
 
344
  view.dispatch({
345
+ effects: [editableStateEffect.of(editable && !doc.isBinary)],
346
  });
347
 
348
  getLanguage(doc.filePath).then((languageSupport) => {
packages/bolt/app/components/ui/PanelHeaderButton.tsx CHANGED
@@ -14,7 +14,7 @@ export const PanelHeaderButton = memo(
14
  return (
15
  <button
16
  className={classNames(
17
- 'flex items-center gap-1.5 px-1.5 rounded-lg py-0.5 bg-transparent hover:bg-white disabled:cursor-not-allowed',
18
  {
19
  [classNames('opacity-30', disabledClassName)]: disabled,
20
  },
 
14
  return (
15
  <button
16
  className={classNames(
17
+ 'flex items-center gap-1.5 px-1.5 rounded-md py-0.5 bg-transparent hover:bg-white disabled:cursor-not-allowed',
18
  {
19
  [classNames('opacity-30', disabledClassName)]: disabled,
20
  },
packages/bolt/app/lib/.server/llm/prompts.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { WORK_DIR } from '~/utils/constants';
2
  import { stripIndents } from '~/utils/stripIndent';
3
 
4
  export const getSystemPrompt = (cwd: string = WORK_DIR) => `
@@ -20,6 +20,50 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
20
  Use 2 spaces for code indentation
21
  </code_formatting_info>
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  <artifact_info>
24
  Bolt creates a SINGLE, comprehensive artifact for each project. The artifact contains all necessary steps and components, including:
25
 
@@ -28,19 +72,21 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
28
  - Folders to create if necessary
29
 
30
  <artifact_instructions>
31
- 1. Think BEFORE creating an artifact
 
 
32
 
33
- 2. The current working directory is \`${cwd}\`.
34
 
35
- 3. Wrap the content in opening and closing \`<boltArtifact>\` tags. These tags contain more specific \`<boltAction>\` elements.
36
 
37
- 4. Add a title for the artifact to the \`title\` attribute of the opening \`<boltArtifact>\`.
38
 
39
- 5. Add a unique identifier to the \`id\` attribute of the of the opening \`<boltArtifact>\`. For updates, reuse the prior identifier. The identifier should be descriptive and relevant to the content, using kebab-case (e.g., "example-code-snippet"). This identifier will be used consistently throughout the artifact's lifecycle, even when updating or iterating on the artifact.
40
 
41
- 6. Use \`<boltAction>\` tags to define specific actions to perform.
42
 
43
- 7. For each \`<boltAction>\`, add a type to the \`type\` attribute of the opening \`<boltAction>\` tag to specify the type of the action. Assign one of the following values to the \`type\` attribute:
44
 
45
  - shell: For running shell commands.
46
 
@@ -50,19 +96,19 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
50
 
51
  - file: For writing new files or updating existing files. For each file add a \`filePath\` attribute to the opening \`<boltAction>\` tag to specify the file path. The content of the file artifact is the file contents. All file paths MUST BE relative to the current working directory.
52
 
53
- 8. The order of the actions is VERY IMPORTANT. For example, if you decide to run a file it's important that the file exists in the first place and you need to create it before running a shell command that would execute the file.
54
 
55
- 9. ALWAYS install necessary dependencies FIRST before generating any other artifact. If that requires a \`package.json\` then you should create that first!
56
 
57
  IMPORTANT: Add all required dependencies to the \`package.json\` already and try to avoid \`npm i <pkg>\` if possible!
58
 
59
- 10. Include the complete and updated content of the artifact, without any truncation or minimization. Don't use "// rest of the code remains the same...".
60
 
61
- 11. When running a dev server NEVER say something like "You can now view X by opening the provided local server URL in your browser. The preview will be opened automatically or by the user manually!
62
 
63
- 12. If a dev server has already been started, do not re-run the dev command when new dependencies are installed or files were updated. Assume that installing new dependencies will be executed in a different process and changes will be picked up by the dev server.
64
 
65
- 13. ULTRA IMPORTANT: Use coding best practices and split functionality into smaller modules instead of putting everything in a single gigantic file. Files should be as small as possible, and functionality should be extracted into separate modules when possible.
66
 
67
  - Ensure code is clean, readable, and maintainable.
68
  - Adhere to proper naming conventions and consistent formatting.
 
1
+ import { MODIFICATIONS_TAG_NAME, WORK_DIR } from '~/utils/constants';
2
  import { stripIndents } from '~/utils/stripIndent';
3
 
4
  export const getSystemPrompt = (cwd: string = WORK_DIR) => `
 
20
  Use 2 spaces for code indentation
21
  </code_formatting_info>
22
 
23
+ <diff_spec>
24
+ For user-made file modifications, a \`<${MODIFICATIONS_TAG_NAME}>\` section will appear at the start of the user message. It will contain either \`<diff>\` or \`<file>\` elements for each modified file:
25
+
26
+ - \`<diff path="/some/file/path.ext">\`: Contains GNU unified diff format changes
27
+ - \`<file path="/some/file/path.ext">\`: Contains the full new content of the file
28
+
29
+ The system chooses \`<file>\` if the diff exceeds the new content size, otherwise \`<diff>\`.
30
+
31
+ GNU unified diff format structure:
32
+
33
+ - For diffs the header with original and modified file names is omitted!
34
+ - Changed sections start with @@ -X,Y +A,B @@ where:
35
+ - X: Original file starting line
36
+ - Y: Original file line count
37
+ - A: Modified file starting line
38
+ - B: Modified file line count
39
+ - (-) lines: Removed from original
40
+ - (+) lines: Added in modified version
41
+ - Unmarked lines: Unchanged context
42
+
43
+ Example:
44
+
45
+ <${MODIFICATIONS_TAG_NAME}>
46
+ <diff path="/home/project/src/main.js">
47
+ @@ -2,7 +2,10 @@
48
+ return a + b;
49
+ }
50
+
51
+ -console.log('Hello, World!');
52
+ +console.log('Hello, Bolt!');
53
+ +
54
+ function greet() {
55
+ - return 'Greetings!';
56
+ + return 'Greetings!!';
57
+ }
58
+ +
59
+ +console.log('The End');
60
+ </diff>
61
+ <file path="/home/project/package.json">
62
+ // full file content here
63
+ </file>
64
+ </${MODIFICATIONS_TAG_NAME}>
65
+ </diff_spec>
66
+
67
  <artifact_info>
68
  Bolt creates a SINGLE, comprehensive artifact for each project. The artifact contains all necessary steps and components, including:
69
 
 
72
  - Folders to create if necessary
73
 
74
  <artifact_instructions>
75
+ 1. Think BEFORE creating an artifact.
76
+
77
+ 2. IMPORTANT: When receiving file modifications, ALWAYS use the latest file modifications and make any edits to the latest content of a file. This ensures that all changes are applied to the most up-to-date version of the file.
78
 
79
+ 3. The current working directory is \`${cwd}\`.
80
 
81
+ 4. Wrap the content in opening and closing \`<boltArtifact>\` tags. These tags contain more specific \`<boltAction>\` elements.
82
 
83
+ 5. Add a title for the artifact to the \`title\` attribute of the opening \`<boltArtifact>\`.
84
 
85
+ 6. Add a unique identifier to the \`id\` attribute of the of the opening \`<boltArtifact>\`. For updates, reuse the prior identifier. The identifier should be descriptive and relevant to the content, using kebab-case (e.g., "example-code-snippet"). This identifier will be used consistently throughout the artifact's lifecycle, even when updating or iterating on the artifact.
86
 
87
+ 7. Use \`<boltAction>\` tags to define specific actions to perform.
88
 
89
+ 8. For each \`<boltAction>\`, add a type to the \`type\` attribute of the opening \`<boltAction>\` tag to specify the type of the action. Assign one of the following values to the \`type\` attribute:
90
 
91
  - shell: For running shell commands.
92
 
 
96
 
97
  - file: For writing new files or updating existing files. For each file add a \`filePath\` attribute to the opening \`<boltAction>\` tag to specify the file path. The content of the file artifact is the file contents. All file paths MUST BE relative to the current working directory.
98
 
99
+ 9. The order of the actions is VERY IMPORTANT. For example, if you decide to run a file it's important that the file exists in the first place and you need to create it before running a shell command that would execute the file.
100
 
101
+ 10. ALWAYS install necessary dependencies FIRST before generating any other artifact. If that requires a \`package.json\` then you should create that first!
102
 
103
  IMPORTANT: Add all required dependencies to the \`package.json\` already and try to avoid \`npm i <pkg>\` if possible!
104
 
105
+ 11. Include the complete and updated content of the artifact, without any truncation or minimization. Don't use "// rest of the code remains the same...".
106
 
107
+ 12. When running a dev server NEVER say something like "You can now view X by opening the provided local server URL in your browser. The preview will be opened automatically or by the user manually!
108
 
109
+ 13. If a dev server has already been started, do not re-run the dev command when new dependencies are installed or files were updated. Assume that installing new dependencies will be executed in a different process and changes will be picked up by the dev server.
110
 
111
+ 14. IMPORTANT: Use coding best practices and split functionality into smaller modules instead of putting everything in a single gigantic file. Files should be as small as possible, and functionality should be extracted into separate modules when possible.
112
 
113
  - Ensure code is clean, readable, and maintainable.
114
  - Adhere to proper naming conventions and consistent formatting.
packages/bolt/app/lib/runtime/action-runner.ts CHANGED
@@ -37,20 +37,17 @@ export class ActionRunner {
37
  #webcontainer: Promise<WebContainer>;
38
  #currentExecutionPromise: Promise<void> = Promise.resolve();
39
 
40
- actions: ActionsMap = import.meta.hot?.data.actions ?? map({});
41
 
42
  constructor(webcontainerPromise: Promise<WebContainer>) {
43
  this.#webcontainer = webcontainerPromise;
44
-
45
- if (import.meta.hot) {
46
- import.meta.hot.data.actions = this.actions;
47
- }
48
  }
49
 
50
  addAction(data: ActionCallbackData) {
51
  const { actionId } = data;
52
 
53
- const action = this.actions.get()[actionId];
 
54
 
55
  if (action) {
56
  // action already added
 
37
  #webcontainer: Promise<WebContainer>;
38
  #currentExecutionPromise: Promise<void> = Promise.resolve();
39
 
40
+ actions: ActionsMap = map({});
41
 
42
  constructor(webcontainerPromise: Promise<WebContainer>) {
43
  this.#webcontainer = webcontainerPromise;
 
 
 
 
44
  }
45
 
46
  addAction(data: ActionCallbackData) {
47
  const { actionId } = data;
48
 
49
+ const actions = this.actions.get();
50
+ const action = actions[actionId];
51
 
52
  if (action) {
53
  // action already added
packages/bolt/app/lib/stores/editor.ts CHANGED
@@ -10,7 +10,7 @@ export class EditorStore {
10
  #filesStore: FilesStore;
11
 
12
  selectedFile: SelectedFile = import.meta.hot?.data.selectedFile ?? atom<string | undefined>();
13
- documents: MapStore<EditorDocuments> = import.meta.hot?.data.documents ?? map<EditorDocuments>({});
14
 
15
  currentDocument = computed([this.documents, this.selectedFile], (documents, selectedFile) => {
16
  if (!selectedFile) {
@@ -74,7 +74,7 @@ export class EditorStore {
74
  });
75
  }
76
 
77
- updateFile(filePath: string, newContent: string | Uint8Array) {
78
  const documents = this.documents.get();
79
  const documentState = documents[filePath];
80
 
 
10
  #filesStore: FilesStore;
11
 
12
  selectedFile: SelectedFile = import.meta.hot?.data.selectedFile ?? atom<string | undefined>();
13
+ documents: MapStore<EditorDocuments> = import.meta.hot?.data.documents ?? map({});
14
 
15
  currentDocument = computed([this.documents, this.selectedFile], (documents, selectedFile) => {
16
  if (!selectedFile) {
 
74
  });
75
  }
76
 
77
+ updateFile(filePath: string, newContent: string) {
78
  const documents = this.documents.get();
79
  const documentState = documents[filePath];
80
 
packages/bolt/app/lib/stores/files.ts CHANGED
@@ -1,17 +1,22 @@
1
  import type { PathWatcherEvent, WebContainer } from '@webcontainer/api';
 
2
  import { map, type MapStore } from 'nanostores';
 
3
  import * as nodePath from 'node:path';
4
  import { bufferWatchEvents } from '~/utils/buffer';
5
  import { WORK_DIR } from '~/utils/constants';
 
6
  import { createScopedLogger } from '~/utils/logger';
 
7
 
8
  const logger = createScopedLogger('FilesStore');
9
 
10
- const textDecoder = new TextDecoder('utf8', { fatal: true });
11
 
12
  export interface File {
13
  type: 'file';
14
- content: string | Uint8Array;
 
15
  }
16
 
17
  export interface Folder {
@@ -30,6 +35,16 @@ export class FilesStore {
30
  */
31
  #size = 0;
32
 
 
 
 
 
 
 
 
 
 
 
33
  files: MapStore<FileMap> = import.meta.hot?.data.files ?? map({});
34
 
35
  get filesCount() {
@@ -41,6 +56,7 @@ export class FilesStore {
41
 
42
  if (import.meta.hot) {
43
  import.meta.hot.data.files = this.files;
 
44
  }
45
 
46
  this.#init();
@@ -56,7 +72,15 @@ export class FilesStore {
56
  return dirent;
57
  }
58
 
59
- async saveFile(filePath: string, content: string | Uint8Array) {
 
 
 
 
 
 
 
 
60
  const webcontainer = await this.#webcontainer;
61
 
62
  try {
@@ -66,9 +90,20 @@ export class FilesStore {
66
  throw new Error(`EINVAL: invalid file path, write '${relativePath}'`);
67
  }
68
 
 
 
 
 
 
 
69
  await webcontainer.fs.writeFile(relativePath, content);
70
 
71
- this.files.setKey(filePath, { type: 'file', content });
 
 
 
 
 
72
 
73
  logger.info('File updated');
74
  } catch (error) {
@@ -117,7 +152,21 @@ export class FilesStore {
117
  this.#size++;
118
  }
119
 
120
- this.files.setKey(sanitizedPath, { type: 'file', content: this.#decodeFileContent(buffer) });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
  break;
123
  }
@@ -140,10 +189,32 @@ export class FilesStore {
140
  }
141
 
142
  try {
143
- return textDecoder.decode(buffer);
144
  } catch (error) {
145
  console.log(error);
146
  return '';
147
  }
148
  }
149
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import type { PathWatcherEvent, WebContainer } from '@webcontainer/api';
2
+ import { getEncoding } from 'istextorbinary';
3
  import { map, type MapStore } from 'nanostores';
4
+ import { Buffer } from 'node:buffer';
5
  import * as nodePath from 'node:path';
6
  import { bufferWatchEvents } from '~/utils/buffer';
7
  import { WORK_DIR } from '~/utils/constants';
8
+ import { computeFileModifications } from '~/utils/diff';
9
  import { createScopedLogger } from '~/utils/logger';
10
+ import { unreachable } from '~/utils/unreachable';
11
 
12
  const logger = createScopedLogger('FilesStore');
13
 
14
+ const utf8TextDecoder = new TextDecoder('utf8', { fatal: true });
15
 
16
  export interface File {
17
  type: 'file';
18
+ content: string;
19
+ isBinary: boolean;
20
  }
21
 
22
  export interface Folder {
 
35
  */
36
  #size = 0;
37
 
38
+ /**
39
+ * @note Keeps track all modified files with their original content since the last user message.
40
+ * Needs to be reset when the user sends another message and all changes have to be submitted
41
+ * for the model to be aware of the changes.
42
+ */
43
+ #modifiedFiles: Map<string, string> = import.meta.hot?.data.modifiedFiles ?? new Map();
44
+
45
+ /**
46
+ * Map of files that matches the state of WebContainer.
47
+ */
48
  files: MapStore<FileMap> = import.meta.hot?.data.files ?? map({});
49
 
50
  get filesCount() {
 
56
 
57
  if (import.meta.hot) {
58
  import.meta.hot.data.files = this.files;
59
+ import.meta.hot.data.modifiedFiles = this.#modifiedFiles;
60
  }
61
 
62
  this.#init();
 
72
  return dirent;
73
  }
74
 
75
+ getFileModifications() {
76
+ return computeFileModifications(this.files.get(), this.#modifiedFiles);
77
+ }
78
+
79
+ resetFileModifications() {
80
+ this.#modifiedFiles.clear();
81
+ }
82
+
83
+ async saveFile(filePath: string, content: string) {
84
  const webcontainer = await this.#webcontainer;
85
 
86
  try {
 
90
  throw new Error(`EINVAL: invalid file path, write '${relativePath}'`);
91
  }
92
 
93
+ const oldContent = this.getFile(filePath)?.content;
94
+
95
+ if (!oldContent) {
96
+ unreachable('Expected content to be defined');
97
+ }
98
+
99
  await webcontainer.fs.writeFile(relativePath, content);
100
 
101
+ if (!this.#modifiedFiles.has(filePath)) {
102
+ this.#modifiedFiles.set(filePath, oldContent);
103
+ }
104
+
105
+ // we immediately update the file and don't rely on the `change` event coming from the watcher
106
+ this.files.setKey(filePath, { type: 'file', content, isBinary: false });
107
 
108
  logger.info('File updated');
109
  } catch (error) {
 
152
  this.#size++;
153
  }
154
 
155
+ let content = '';
156
+
157
+ /**
158
+ * @note This check is purely for the editor. The way we detect this is not
159
+ * bullet-proof and it's a best guess so there might be false-positives.
160
+ * The reason we do this is because we don't want to display binary files
161
+ * in the editor nor allow to edit them.
162
+ */
163
+ const isBinary = isBinaryFile(buffer);
164
+
165
+ if (!isBinary) {
166
+ content = this.#decodeFileContent(buffer);
167
+ }
168
+
169
+ this.files.setKey(sanitizedPath, { type: 'file', content, isBinary });
170
 
171
  break;
172
  }
 
189
  }
190
 
191
  try {
192
+ return utf8TextDecoder.decode(buffer);
193
  } catch (error) {
194
  console.log(error);
195
  return '';
196
  }
197
  }
198
  }
199
+
200
+ function isBinaryFile(buffer: Uint8Array | undefined) {
201
+ if (buffer === undefined) {
202
+ return false;
203
+ }
204
+
205
+ return getEncoding(convertToBuffer(buffer), { chunkLength: 100 }) === 'binary';
206
+ }
207
+
208
+ /**
209
+ * Converts a `Uint8Array` into a Node.js `Buffer` by copying the prototype.
210
+ * The goal is to avoid expensive copies. It does create a new typed array
211
+ * but that's generally cheap as long as it uses the same underlying
212
+ * array buffer.
213
+ */
214
+ function convertToBuffer(view: Uint8Array): Buffer {
215
+ const buffer = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
216
+
217
+ Object.setPrototypeOf(buffer, Buffer.prototype);
218
+
219
+ return buffer as Buffer;
220
+ }
packages/bolt/app/lib/stores/workbench.ts CHANGED
@@ -70,7 +70,7 @@ export class WorkbenchStore {
70
  this.showWorkbench.set(show);
71
  }
72
 
73
- setCurrentDocumentContent(newContent: string | Uint8Array) {
74
  const filePath = this.currentDocument.get()?.filePath;
75
 
76
  if (!filePath) {
@@ -119,16 +119,15 @@ export class WorkbenchStore {
119
  this.#editorStore.setSelectedFile(filePath);
120
  }
121
 
122
- async saveCurrentDocument() {
123
- const currentDocument = this.currentDocument.get();
 
124
 
125
- if (currentDocument === undefined) {
126
  return;
127
  }
128
 
129
- const { filePath } = currentDocument;
130
-
131
- await this.#filesStore.saveFile(filePath, currentDocument.value);
132
 
133
  const newUnsavedFiles = new Set(this.unsavedFiles.get());
134
  newUnsavedFiles.delete(filePath);
@@ -136,6 +135,16 @@ export class WorkbenchStore {
136
  this.unsavedFiles.set(newUnsavedFiles);
137
  }
138
 
 
 
 
 
 
 
 
 
 
 
139
  resetCurrentDocument() {
140
  const currentDocument = this.currentDocument.get();
141
 
@@ -153,6 +162,20 @@ export class WorkbenchStore {
153
  this.setCurrentDocumentContent(file.content);
154
  }
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  abortAllActions() {
157
  // TODO: what do we wanna do and how do we wanna recover from this?
158
  }
 
70
  this.showWorkbench.set(show);
71
  }
72
 
73
+ setCurrentDocumentContent(newContent: string) {
74
  const filePath = this.currentDocument.get()?.filePath;
75
 
76
  if (!filePath) {
 
119
  this.#editorStore.setSelectedFile(filePath);
120
  }
121
 
122
+ async saveFile(filePath: string) {
123
+ const documents = this.#editorStore.documents.get();
124
+ const document = documents[filePath];
125
 
126
+ if (document === undefined) {
127
  return;
128
  }
129
 
130
+ await this.#filesStore.saveFile(filePath, document.value);
 
 
131
 
132
  const newUnsavedFiles = new Set(this.unsavedFiles.get());
133
  newUnsavedFiles.delete(filePath);
 
135
  this.unsavedFiles.set(newUnsavedFiles);
136
  }
137
 
138
+ async saveCurrentDocument() {
139
+ const currentDocument = this.currentDocument.get();
140
+
141
+ if (currentDocument === undefined) {
142
+ return;
143
+ }
144
+
145
+ await this.saveFile(currentDocument.filePath);
146
+ }
147
+
148
  resetCurrentDocument() {
149
  const currentDocument = this.currentDocument.get();
150
 
 
162
  this.setCurrentDocumentContent(file.content);
163
  }
164
 
165
+ async saveAllFiles() {
166
+ for (const filePath of this.unsavedFiles.get()) {
167
+ await this.saveFile(filePath);
168
+ }
169
+ }
170
+
171
+ getFileModifcations() {
172
+ return this.#filesStore.getFileModifications();
173
+ }
174
+
175
+ resetAllFileModifications() {
176
+ this.#filesStore.resetFileModifications();
177
+ }
178
+
179
  abortAllActions() {
180
  // TODO: what do we wanna do and how do we wanna recover from this?
181
  }
packages/bolt/app/routes/api.chat.ts CHANGED
@@ -7,6 +7,7 @@ import SwitchableStream from '~/lib/.server/llm/switchable-stream';
7
 
8
  export async function action({ context, request }: ActionFunctionArgs) {
9
  const { messages } = await request.json<{ messages: Messages }>();
 
10
  const stream = new SwitchableStream();
11
 
12
  try {
 
7
 
8
  export async function action({ context, request }: ActionFunctionArgs) {
9
  const { messages } = await request.json<{ messages: Messages }>();
10
+
11
  const stream = new SwitchableStream();
12
 
13
  try {
packages/bolt/app/utils/constants.ts CHANGED
@@ -1,2 +1,3 @@
1
  export const WORK_DIR_NAME = 'project';
2
  export const WORK_DIR = `/home/${WORK_DIR_NAME}`;
 
 
1
  export const WORK_DIR_NAME = 'project';
2
  export const WORK_DIR = `/home/${WORK_DIR_NAME}`;
3
+ export const MODIFICATIONS_TAG_NAME = 'bolt_file_modifications';
packages/bolt/app/utils/diff.ts ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createTwoFilesPatch } from 'diff';
2
+ import type { FileMap } from '~/lib/stores/files';
3
+ import { MODIFICATIONS_TAG_NAME } from './constants';
4
+
5
+ export const modificationsRegex = new RegExp(
6
+ `^<${MODIFICATIONS_TAG_NAME}>[\\s\\S]*?<\\/${MODIFICATIONS_TAG_NAME}>\\s+`,
7
+ 'g',
8
+ );
9
+
10
+ interface ModifiedFile {
11
+ type: 'diff' | 'file';
12
+ content: string;
13
+ }
14
+
15
+ type FileModifications = Record<string, ModifiedFile>;
16
+
17
+ export function computeFileModifications(files: FileMap, modifiedFiles: Map<string, string>) {
18
+ const modifications: FileModifications = {};
19
+
20
+ let hasModifiedFiles = false;
21
+
22
+ for (const [filePath, originalContent] of modifiedFiles) {
23
+ const file = files[filePath];
24
+
25
+ if (file?.type !== 'file') {
26
+ continue;
27
+ }
28
+
29
+ const unifiedDiff = diffFiles(filePath, originalContent, file.content);
30
+
31
+ if (!unifiedDiff) {
32
+ // files are identical
33
+ continue;
34
+ }
35
+
36
+ hasModifiedFiles = true;
37
+
38
+ if (unifiedDiff.length > file.content.length) {
39
+ // if there are lots of changes we simply grab the current file content since it's smaller than the diff
40
+ modifications[filePath] = { type: 'file', content: file.content };
41
+ } else {
42
+ // otherwise we use the diff since it's smaller
43
+ modifications[filePath] = { type: 'diff', content: unifiedDiff };
44
+ }
45
+ }
46
+
47
+ if (!hasModifiedFiles) {
48
+ return undefined;
49
+ }
50
+
51
+ return modifications;
52
+ }
53
+
54
+ /**
55
+ * Computes a diff in the unified format. The only difference is that the header is omitted
56
+ * because it will always assume that you're comparing two versions of the same file and
57
+ * it allows us to avoid the extra characters we send back to the llm.
58
+ *
59
+ * @see https://www.gnu.org/software/diffutils/manual/html_node/Unified-Format.html
60
+ */
61
+ export function diffFiles(fileName: string, oldFileContent: string, newFileContent: string) {
62
+ let unifiedDiff = createTwoFilesPatch(fileName, fileName, oldFileContent, newFileContent);
63
+
64
+ const patchHeaderEnd = `--- ${fileName}\n+++ ${fileName}\n`;
65
+ const headerEndIndex = unifiedDiff.indexOf(patchHeaderEnd);
66
+
67
+ if (headerEndIndex >= 0) {
68
+ unifiedDiff = unifiedDiff.slice(headerEndIndex + patchHeaderEnd.length);
69
+ }
70
+
71
+ if (unifiedDiff === '') {
72
+ return undefined;
73
+ }
74
+
75
+ return unifiedDiff;
76
+ }
77
+
78
+ /**
79
+ * Converts the unified diff to HTML.
80
+ *
81
+ * Example:
82
+ *
83
+ * ```html
84
+ * <bolt_file_modifications>
85
+ * <diff path="/home/project/index.js">
86
+ * - console.log('Hello, World!');
87
+ * + console.log('Hello, Bolt!');
88
+ * </diff>
89
+ * </bolt_file_modifications>
90
+ * ```
91
+ */
92
+ export function fileModificationsToHTML(modifications: FileModifications) {
93
+ const entries = Object.entries(modifications);
94
+
95
+ if (entries.length === 0) {
96
+ return undefined;
97
+ }
98
+
99
+ const result: string[] = [`<${MODIFICATIONS_TAG_NAME}>`];
100
+
101
+ for (const [filePath, { type, content }] of entries) {
102
+ result.push(`<${type} path=${JSON.stringify(filePath)}>`, content, `</${type}>`);
103
+ }
104
+
105
+ result.push(`</${MODIFICATIONS_TAG_NAME}>`);
106
+
107
+ return result.join('\n');
108
+ }
packages/bolt/package.json CHANGED
@@ -43,8 +43,10 @@
43
  "@xterm/addon-web-links": "^0.11.0",
44
  "@xterm/xterm": "^5.5.0",
45
  "ai": "^3.2.27",
 
46
  "framer-motion": "^11.2.12",
47
  "isbot": "^4.1.0",
 
48
  "nanostores": "^0.10.3",
49
  "react": "^18.2.0",
50
  "react-dom": "^18.2.0",
@@ -59,6 +61,7 @@
59
  "devDependencies": {
60
  "@cloudflare/workers-types": "^4.20240620.0",
61
  "@remix-run/dev": "^2.10.0",
 
62
  "@types/react": "^18.2.20",
63
  "@types/react-dom": "^18.2.7",
64
  "fast-glob": "^3.3.2",
 
43
  "@xterm/addon-web-links": "^0.11.0",
44
  "@xterm/xterm": "^5.5.0",
45
  "ai": "^3.2.27",
46
+ "diff": "^5.2.0",
47
  "framer-motion": "^11.2.12",
48
  "isbot": "^4.1.0",
49
+ "istextorbinary": "^9.5.0",
50
  "nanostores": "^0.10.3",
51
  "react": "^18.2.0",
52
  "react-dom": "^18.2.0",
 
61
  "devDependencies": {
62
  "@cloudflare/workers-types": "^4.20240620.0",
63
  "@remix-run/dev": "^2.10.0",
64
+ "@types/diff": "^5.2.1",
65
  "@types/react": "^18.2.20",
66
  "@types/react-dom": "^18.2.7",
67
  "fast-glob": "^3.3.2",
packages/bolt/types/istextorbinary.d.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @note For some reason the types aren't picked up from node_modules so I declared the module here
3
+ * with only the function that we use.
4
+ */
5
+ declare module 'istextorbinary' {
6
+ export interface EncodingOpts {
7
+ /** Defaults to 24 */
8
+ chunkLength?: number;
9
+
10
+ /** If not provided, will check the start, beginning, and end */
11
+ chunkBegin?: number;
12
+ }
13
+
14
+ export function getEncoding(buffer: Buffer | null, opts?: EncodingOpts): 'utf8' | 'binary' | null;
15
+ }
packages/bolt/vite.config.ts CHANGED
@@ -12,7 +12,7 @@ export default defineConfig((config) => {
12
  },
13
  plugins: [
14
  nodePolyfills({
15
- include: ['path'],
16
  }),
17
  config.mode !== 'test' && remixCloudflareDevProxy(),
18
  remixVitePlugin({
 
12
  },
13
  plugins: [
14
  nodePolyfills({
15
+ include: ['path', 'buffer'],
16
  }),
17
  config.mode !== 'test' && remixCloudflareDevProxy(),
18
  remixVitePlugin({
pnpm-lock.yaml CHANGED
@@ -116,12 +116,18 @@ importers:
116
  ai:
117
  specifier: ^3.2.27
118
 
 
 
119
  framer-motion:
120
  specifier: ^11.2.12
121
122
  isbot:
123
  specifier: ^4.1.0
124
  version: 4.4.0
 
 
 
125
  nanostores:
126
  specifier: ^0.10.3
127
  version: 0.10.3
@@ -159,6 +165,9 @@ importers:
159
  '@remix-run/dev':
160
  specifier: ^2.10.0
161
 
 
 
162
  '@types/react':
163
  specifier: ^18.2.20
164
  version: 18.3.3
@@ -1445,6 +1454,9 @@ packages:
1445
  '@types/[email protected]':
1446
  resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
1447
 
 
 
 
1448
  '@types/[email protected]':
1449
  resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==}
1450
 
@@ -1865,6 +1877,10 @@ packages:
1865
  resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
1866
  engines: {node: '>=8'}
1867
 
 
 
 
 
1868
1869
  resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
1870
 
@@ -2328,6 +2344,10 @@ packages:
2328
2329
  resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
2330
 
 
 
 
 
2331
2332
  resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
2333
 
@@ -3040,6 +3060,10 @@ packages:
3040
  resolution: {integrity: sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==}
3041
  engines: {node: '>=10'}
3042
 
 
 
 
 
3043
3044
  resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==}
3045
  engines: {node: '>=14'}
@@ -4522,6 +4546,10 @@ packages:
4522
4523
  resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
4524
 
 
 
 
 
4525
4526
  resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
4527
 
@@ -4766,6 +4794,10 @@ packages:
4766
  resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
4767
  engines: {node: '>= 0.8'}
4768
 
 
 
 
 
4769
4770
  resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==}
4771
 
@@ -6344,6 +6376,8 @@ snapshots:
6344
 
6345
  '@types/[email protected]': {}
6346
 
 
 
6347
  '@types/[email protected]':
6348
  dependencies:
6349
  '@types/estree': 1.0.5
@@ -6933,6 +6967,10 @@ snapshots:
6933
 
6934
6935
 
 
 
 
 
6936
6937
  dependencies:
6938
  buffer: 5.7.1
@@ -7435,6 +7473,10 @@ snapshots:
7435
 
7436
7437
 
 
 
 
 
7438
7439
 
7440
@@ -8284,6 +8326,12 @@ snapshots:
8284
 
8285
8286
 
 
 
 
 
 
 
8287
8288
  dependencies:
8289
  '@isaacs/cliui': 8.0.2
@@ -10228,6 +10276,10 @@ snapshots:
10228
 
10229
10230
 
 
 
 
 
10231
10232
  dependencies:
10233
  readable-stream: 2.3.8
@@ -10502,6 +10554,8 @@ snapshots:
10502
 
10503
10504
 
 
 
10505
10506
  dependencies:
10507
  '@types/unist': 3.0.2
 
116
  ai:
117
  specifier: ^3.2.27
118
119
+ diff:
120
+ specifier: ^5.2.0
121
+ version: 5.2.0
122
  framer-motion:
123
  specifier: ^11.2.12
124
125
  isbot:
126
  specifier: ^4.1.0
127
  version: 4.4.0
128
+ istextorbinary:
129
+ specifier: ^9.5.0
130
+ version: 9.5.0
131
  nanostores:
132
  specifier: ^0.10.3
133
  version: 0.10.3
 
165
  '@remix-run/dev':
166
  specifier: ^2.10.0
167
168
+ '@types/diff':
169
+ specifier: ^5.2.1
170
+ version: 5.2.1
171
  '@types/react':
172
  specifier: ^18.2.20
173
  version: 18.3.3
 
1454
  '@types/[email protected]':
1455
  resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
1456
 
1457
+ '@types/[email protected]':
1458
+ resolution: {integrity: sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==}
1459
+
1460
  '@types/[email protected]':
1461
  resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==}
1462
 
 
1877
  resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
1878
  engines: {node: '>=8'}
1879
 
1880
1881
+ resolution: {integrity: sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==}
1882
+ engines: {node: '>=4'}
1883
+
1884
1885
  resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
1886
 
 
2344
2345
  resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
2346
 
2347
2348
+ resolution: {integrity: sha512-ofkXJtn7z0urokN62DI3SBo/5xAtF0rR7tn+S/bSYV79Ka8pTajIIl+fFQ1q88DQEImymmo97M4azY3WX/nUdg==}
2349
+ engines: {node: '>=4'}
2350
+
2351
2352
  resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
2353
 
 
3060
  resolution: {integrity: sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==}
3061
  engines: {node: '>=10'}
3062
 
3063
3064
+ resolution: {integrity: sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==}
3065
+ engines: {node: '>=4'}
3066
+
3067
3068
  resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==}
3069
  engines: {node: '>=14'}
 
4546
4547
  resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
4548
 
4549
4550
+ resolution: {integrity: sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==}
4551
+ engines: {node: '>=4'}
4552
+
4553
4554
  resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
4555
 
 
4794
  resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
4795
  engines: {node: '>= 0.8'}
4796
 
4797
4798
+ resolution: {integrity: sha512-gjb0ARm9qlcBAonU4zPwkl9ecKkas+tC2CGwFfptTCWWIVTWY1YUbT2zZKsOAF1jR/tNxxyLwwG0cb42XlYcTg==}
4799
+ engines: {node: '>=4'}
4800
+
4801
4802
  resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==}
4803
 
 
6376
 
6377
  '@types/[email protected]': {}
6378
 
6379
+ '@types/[email protected]': {}
6380
+
6381
  '@types/[email protected]':
6382
  dependencies:
6383
  '@types/estree': 1.0.5
 
6967
 
6968
6969
 
6970
6971
+ dependencies:
6972
+ editions: 6.21.0
6973
+
6974
6975
  dependencies:
6976
  buffer: 5.7.1
 
7473
 
7474
7475
 
7476
7477
+ dependencies:
7478
+ version-range: 4.14.0
7479
+
7480
7481
 
7482
 
8326
 
8327
8328
 
8329
8330
+ dependencies:
8331
+ binaryextensions: 6.11.0
8332
+ editions: 6.21.0
8333
+ textextensions: 6.11.0
8334
+
8335
8336
  dependencies:
8337
  '@isaacs/cliui': 8.0.2
 
10276
 
10277
10278
 
10279
10280
+ dependencies:
10281
+ editions: 6.21.0
10282
+
10283
10284
  dependencies:
10285
  readable-stream: 2.3.8
 
10554
 
10555
10556
 
10557
10558
+
10559
10560
  dependencies:
10561
  '@types/unist': 3.0.2