From 62769b2fefca6ece45dae9d057a9affc53d2343e Mon Sep 17 00:00:00 2001 From: KevIsDev Date: Wed, 14 May 2025 11:54:51 +0100 Subject: [PATCH 1/2] fix: chat history snapshot logic to use the same ID as chat and update prompt instructions - Remove redundant `_chatId` parameter in `takeSnapshot` function - Update prompt instructions for shell commands and artifact handling --- app/lib/common/prompts/new-prompt.ts | 35 ++++----------------------- app/lib/persistence/useChatHistory.ts | 2 +- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/app/lib/common/prompts/new-prompt.ts b/app/lib/common/prompts/new-prompt.ts index 788dc188..227b7137 100644 --- a/app/lib/common/prompts/new-prompt.ts +++ b/app/lib/common/prompts/new-prompt.ts @@ -45,14 +45,6 @@ The year is 2025. - With each user request, you are provided with information about the shell command that is currently running. - - Example: - - - npm run dev - - CRITICAL: - NEVER mention or reference the XML tags or structure of this process list in your responses - DO NOT repeat or directly quote any part of the command information provided @@ -286,6 +278,7 @@ The year is 2025. - Follow the guidelines for shell commands. - Use the start action type over the shell type ONLY when the command is intended to start the project. + - IMPORTANT: Always execute the start command after executing a shell command. - file: For creating new files or updating existing files. Add \`filePath\` and \`contentType\` attributes: @@ -317,7 +310,7 @@ The year is 2025. 11. Prioritize installing required dependencies by updating \`package.json\` first. - If a \`package.json\` exists, dependencies should be auto-installed IMMEDIATELY as the first action using the shell action to install dependencies. - - If you need to update the \`package.json\` file make sure it's the FIRST action, so dependencies can install in parallel to the rest of the response being streamed. + - If you need to update the \`package.json\` file make sure it's the FIRST action, so dependencies can install in parallel to the rest of the response being streamed this should ALWAYS be done inside the artifact. - \`npm install\` will not automatically run every time \`package.json\` is updated, so you need to include a shell action to install dependencies. - Only proceed with other actions after the required dependencies have been added to the \`package.json\`. @@ -325,7 +318,7 @@ The year is 2025. 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! - 13. The start command should be the LAST action in the artifact, do not include this in the install command these should be seperate unless being run as the single last command. + 13. Always include a start command in the artifact, The start command should be the LAST action in the artifact. @@ -647,7 +640,8 @@ function App() { } export default App; -.container { + + { max-width: 800px; margin: 0 auto; padding: 20px; @@ -698,25 +692,6 @@ export default App; npm run dev - -I've created a demonstration of three different ways to center a div: - -1. **Using Flexbox** - This is the most recommended modern approach: - - Set the parent container to \`display: flex\` - - Use \`justify-content: center\` for horizontal centering - - Use \`align-items: center\` for vertical centering - -2. **Using CSS Grid** - Even simpler than flexbox in some cases: - - Set the parent container to \`display: grid\` - - Use \`place-items: center\` to center in both directions at once - -3. **Using Position Absolute** - The traditional method: - - Set the parent to \`position: relative\` - - Set the child to \`position: absolute\` - - Use \`top: 50%; left: 50%\` to position at the center - - Use \`transform: translate(-50%, -50%)\` to adjust for the element's size - -The flexbox method is generally the most versatile and recommended approach for most centering needs in modern web development. `; diff --git a/app/lib/persistence/useChatHistory.ts b/app/lib/persistence/useChatHistory.ts index 77540a6d..618659b5 100644 --- a/app/lib/persistence/useChatHistory.ts +++ b/app/lib/persistence/useChatHistory.ts @@ -199,7 +199,7 @@ ${value.content} const takeSnapshot = useCallback( async (chatIdx: string, files: FileMap, _chatId?: string | undefined, chatSummary?: string) => { - const id = _chatId || chatId.get(); + const id = chatId.get(); if (!id || !db) { return; From cfc2fc75d8ad2bd1d7ea0864e918df15262671a4 Mon Sep 17 00:00:00 2001 From: KevIsDev Date: Tue, 20 May 2025 00:57:52 +0100 Subject: [PATCH 2/2] refactor(files): simplify file event processing logic Remove redundant checks for deleted paths and streamline binary file handling. This fixes the browser using excessive memory and freezing. Improve DiffView to use a singleton instance of Shiki --- app/components/workbench/DiffView.tsx | 91 ++++++++++++++++++++------- app/lib/stores/files.ts | 57 ++++------------- 2 files changed, 78 insertions(+), 70 deletions(-) diff --git a/app/components/workbench/DiffView.tsx b/app/components/workbench/DiffView.tsx index 146076c5..7747c4a8 100644 --- a/app/components/workbench/DiffView.tsx +++ b/app/components/workbench/DiffView.tsx @@ -542,8 +542,53 @@ const FileInfo = memo( }, ); +// Create and manage a single highlighter instance at the module level +let highlighterInstance: any = null; +let highlighterPromise: Promise | null = null; + +const getSharedHighlighter = async () => { + if (highlighterInstance) { + return highlighterInstance; + } + + if (highlighterPromise) { + return highlighterPromise; + } + + highlighterPromise = getHighlighter({ + themes: ['github-dark', 'github-light'], + langs: [ + 'typescript', + 'javascript', + 'json', + 'html', + 'css', + 'jsx', + 'tsx', + 'python', + 'php', + 'java', + 'c', + 'cpp', + 'csharp', + 'go', + 'ruby', + 'rust', + 'plaintext', + ], + }); + + highlighterInstance = await highlighterPromise; + highlighterPromise = null; + + // Clear the promise once resolved + return highlighterInstance; +}; + const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }: CodeComparisonProps) => { const [isFullscreen, setIsFullscreen] = useState(false); + + // Use state to hold the shared highlighter instance const [highlighter, setHighlighter] = useState(null); const theme = useStore(themeStore); @@ -554,34 +599,32 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language } const { unifiedBlocks, hasChanges, isBinary, error } = useProcessChanges(beforeCode, afterCode); useEffect(() => { - getHighlighter({ - themes: ['github-dark', 'github-light'], - langs: [ - 'typescript', - 'javascript', - 'json', - 'html', - 'css', - 'jsx', - 'tsx', - 'python', - 'php', - 'java', - 'c', - 'cpp', - 'csharp', - 'go', - 'ruby', - 'rust', - 'plaintext', - ], - }).then(setHighlighter); - }, []); + // Fetch the shared highlighter instance + getSharedHighlighter().then(setHighlighter); + + /* + * No cleanup needed here for the highlighter instance itself, + * as it's managed globally. Shiki instances don't typically + * need disposal unless you are dynamically loading/unloading themes/languages. + * If you were dynamically loading, you might need a more complex + * shared instance manager with reference counting or similar. + * For static themes/langs, a single instance is sufficient. + */ + }, []); // Empty dependency array ensures this runs only once on mount if (isBinary || error) { return renderContentWarning(isBinary ? 'binary' : 'error'); } + // Render a loading state or null while highlighter is not ready + if (!highlighter) { + return ( +
+
Loading diff...
+
+ ); + } + return (
@@ -602,7 +645,7 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language } lineNumber={block.lineNumber} content={block.content} type={block.type} - highlighter={highlighter} + highlighter={highlighter} // Pass the shared instance language={language} block={block} theme={theme} diff --git a/app/lib/stores/files.ts b/app/lib/stores/files.ts index a976c3a9..e2d8e40f 100644 --- a/app/lib/stores/files.ts +++ b/app/lib/stores/files.ts @@ -692,27 +692,9 @@ export class FilesStore { #processEventBuffer(events: Array<[events: PathWatcherEvent[]]>) { const watchEvents = events.flat(2); - for (const { type, path: eventPath, buffer } of watchEvents) { + for (const { type, path, buffer } of watchEvents) { // remove any trailing slashes - const sanitizedPath = eventPath.replace(/\/+$/g, ''); - - // Skip processing if this file/folder was explicitly deleted - if (this.#deletedPaths.has(sanitizedPath)) { - continue; - } - - let isInDeletedFolder = false; - - for (const deletedPath of this.#deletedPaths) { - if (sanitizedPath.startsWith(deletedPath + '/')) { - isInDeletedFolder = true; - break; - } - } - - if (isInDeletedFolder) { - continue; - } + const sanitizedPath = path.replace(/\/+$/g, ''); switch (type) { case 'add_dir': { @@ -738,38 +720,21 @@ export class FilesStore { } let content = ''; + + /** + * @note This check is purely for the editor. The way we detect this is not + * bullet-proof and it's a best guess so there might be false-positives. + * The reason we do this is because we don't want to display binary files + * in the editor nor allow to edit them. + */ const isBinary = isBinaryFile(buffer); - if (isBinary && buffer) { - // For binary files, we need to preserve the content as base64 - content = Buffer.from(buffer).toString('base64'); - } else if (!isBinary) { + if (!isBinary) { content = this.#decodeFileContent(buffer); - - /* - * If the content is a single space and this is from our empty file workaround, - * convert it back to an actual empty string - */ - if (content === ' ' && type === 'add_file') { - content = ''; - } } - const existingFile = this.files.get()[sanitizedPath]; + this.files.setKey(sanitizedPath, { type: 'file', content, isBinary }); - if (existingFile?.type === 'file' && existingFile.isBinary && existingFile.content && !content) { - content = existingFile.content; - } - - // Preserve lock state if the file already exists - const isLocked = existingFile?.type === 'file' ? existingFile.isLocked : false; - - this.files.setKey(sanitizedPath, { - type: 'file', - content, - isBinary, - isLocked, - }); break; } case 'remove_file': {