From 604ab8710b1b72c54d7d3a392faabcaf22951004 Mon Sep 17 00:00:00 2001 From: lassecapel Date: Sun, 17 Nov 2024 20:11:51 +0100 Subject: [PATCH 1/9] feat: add custom unique filename when doanload as zip --- app/lib/stores/workbench.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/lib/stores/workbench.ts b/app/lib/stores/workbench.ts index 4db14e7..9f0401d 100644 --- a/app/lib/stores/workbench.ts +++ b/app/lib/stores/workbench.ts @@ -15,6 +15,7 @@ import { Octokit, type RestEndpointMethodTypes } from "@octokit/rest"; import * as nodePath from 'node:path'; import type { WebContainerProcess } from '@webcontainer/api'; import { extractRelativePath } from '~/utils/diff'; +import { description } from '../persistence'; export interface ArtifactState { id: string; @@ -171,6 +172,7 @@ export class WorkbenchStore { this.#editorStore.setSelectedFile(filePath); } + async saveFile(filePath: string) { const documents = this.#editorStore.documents.get(); const document = documents[filePath]; @@ -325,6 +327,15 @@ export class WorkbenchStore { async downloadZip() { const zip = new JSZip(); const files = this.files.get(); + // Get the project name (assuming it's stored in this.projectName) + const projectName = (description.value ?? 'project').toLocaleLowerCase().split(' ').join('_'); + + // Generate a simple 6-character hash based on the current timestamp + const timestampHash = Date.now().toString(36).slice(-6); + const uniqueProjectName = `${projectName}_${timestampHash}`; + + // Prompt the user for a file name, prefilled with the project name + const fileName = prompt('Enter the file name', `${uniqueProjectName}.zip`); for (const [filePath, dirent] of Object.entries(files)) { if (dirent?.type === 'file' && !dirent.isBinary) { @@ -348,8 +359,14 @@ export class WorkbenchStore { } } + + + + if (fileName) { + // Generate the zip file and save it const content = await zip.generateAsync({ type: 'blob' }); - saveAs(content, 'project.zip'); + saveAs(content, fileName); + } } async syncFiles(targetHandle: FileSystemDirectoryHandle) { From 399affd1e9e0cd113c45cabcdebb8db11a5434b3 Mon Sep 17 00:00:00 2001 From: lassecapel Date: Sun, 17 Nov 2024 20:24:33 +0100 Subject: [PATCH 2/9] update comment to reflect the the codeline --- app/lib/stores/workbench.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/stores/workbench.ts b/app/lib/stores/workbench.ts index 9f0401d..dbdd689 100644 --- a/app/lib/stores/workbench.ts +++ b/app/lib/stores/workbench.ts @@ -327,7 +327,7 @@ export class WorkbenchStore { async downloadZip() { const zip = new JSZip(); const files = this.files.get(); - // Get the project name (assuming it's stored in this.projectName) + // Get the project name from the description input, or use a default name const projectName = (description.value ?? 'project').toLocaleLowerCase().split(' ').join('_'); // Generate a simple 6-character hash based on the current timestamp From 8978ed0ff34c4d164ab67ad25f03951c8d5eaf5f Mon Sep 17 00:00:00 2001 From: Lasse Capel Date: Wed, 20 Nov 2024 09:54:31 +0100 Subject: [PATCH 3/9] use a descriptive anique filename when downloading the files to zip --- app/lib/stores/workbench.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/lib/stores/workbench.ts b/app/lib/stores/workbench.ts index dbdd689..7eed952 100644 --- a/app/lib/stores/workbench.ts +++ b/app/lib/stores/workbench.ts @@ -334,9 +334,6 @@ export class WorkbenchStore { const timestampHash = Date.now().toString(36).slice(-6); const uniqueProjectName = `${projectName}_${timestampHash}`; - // Prompt the user for a file name, prefilled with the project name - const fileName = prompt('Enter the file name', `${uniqueProjectName}.zip`); - for (const [filePath, dirent] of Object.entries(files)) { if (dirent?.type === 'file' && !dirent.isBinary) { const relativePath = extractRelativePath(filePath); @@ -358,15 +355,10 @@ export class WorkbenchStore { } } } - - - - - if (fileName) { // Generate the zip file and save it const content = await zip.generateAsync({ type: 'blob' }); - saveAs(content, fileName); - } + saveAs(content, `${uniqueProjectName}.zip`); + } async syncFiles(targetHandle: FileSystemDirectoryHandle) { From 43839b1318c26fb460bc45dcdebda2674611b0dc Mon Sep 17 00:00:00 2001 From: PuneetP16 Date: Tue, 26 Nov 2024 15:45:49 +0530 Subject: [PATCH 4/9] [fix]: artifact actionlist rendering in chat --- app/components/chat/Markdown.spec.ts | 48 ++++++++++++++++++++++++++++ app/components/chat/Markdown.tsx | 46 +++++++++++++++++++++++++- app/utils/logger.ts | 2 +- 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 app/components/chat/Markdown.spec.ts diff --git a/app/components/chat/Markdown.spec.ts b/app/components/chat/Markdown.spec.ts new file mode 100644 index 0000000..2381778 --- /dev/null +++ b/app/components/chat/Markdown.spec.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; +import { stripCodeFenceFromArtifact } from './Markdown'; + +describe('stripCodeFenceFromArtifact', () => { + it('should remove code fences around artifact element', () => { + const input = "```xml\n
\n```"; + const expected = "\n
\n"; + expect(stripCodeFenceFromArtifact(input)).toBe(expected); + }); + + it('should handle code fence with language specification', () => { + const input = "```typescript\n
\n```"; + const expected = "\n
\n"; + expect(stripCodeFenceFromArtifact(input)).toBe(expected); + }); + + it('should not modify content without artifacts', () => { + const input = '```\nregular code block\n```'; + expect(stripCodeFenceFromArtifact(input)).toBe(input); + }); + + it('should handle empty input', () => { + expect(stripCodeFenceFromArtifact('')).toBe(''); + }); + + it('should handle artifact without code fences', () => { + const input = "
"; + expect(stripCodeFenceFromArtifact(input)).toBe(input); + }); + + it('should handle multiple artifacts but only remove fences around them', () => { + const input = [ + 'Some text', + '```typescript', + "
", + '```', + '```', + 'regular code', + '```', + ].join('\n'); + + const expected = ['Some text', '', "
", '', '```', 'regular code', '```'].join( + '\n', + ); + + expect(stripCodeFenceFromArtifact(input)).toBe(expected); + }); +}); diff --git a/app/components/chat/Markdown.tsx b/app/components/chat/Markdown.tsx index a91df43..07b6a67 100644 --- a/app/components/chat/Markdown.tsx +++ b/app/components/chat/Markdown.tsx @@ -68,7 +68,51 @@ export const Markdown = memo(({ children, html = false, limitedMarkdown = false remarkPlugins={remarkPlugins(limitedMarkdown)} rehypePlugins={rehypePlugins(html)} > - {children} + {stripCodeFenceFromArtifact(children)} ); }); + +/** + * Removes code fence markers (```) surrounding an artifact element while preserving the artifact content. + * This is necessary because artifacts should not be wrapped in code blocks when rendered for rendering action list. + * + * @param content - The markdown content to process + * @returns The processed content with code fence markers removed around artifacts + * + * @example + * // Removes code fences around artifact + * const input = "```xml\n
\n```"; + * stripCodeFenceFromArtifact(input); + * // Returns: "\n
\n" + * + * @remarks + * - Only removes code fences that directly wrap an artifact (marked with __boltArtifact__ class) + * - Handles code fences with optional language specifications (e.g. ```xml, ```typescript) + * - Preserves original content if no artifact is found + * - Safely handles edge cases like empty input or artifacts at start/end of content + */ +export const stripCodeFenceFromArtifact = (content: string) => { + if (!content || !content.includes('__boltArtifact__')) { + return content; + } + + const lines = content.split('\n'); + const artifactLineIndex = lines.findIndex((line) => line.includes('__boltArtifact__')); + + // Return original content if artifact line not found + if (artifactLineIndex === -1) { + return content; + } + + // Check previous line for code fence + if (artifactLineIndex > 0 && lines[artifactLineIndex - 1]?.trim().match(/^```\w*$/)) { + lines[artifactLineIndex - 1] = ''; + } + + if (artifactLineIndex < lines.length - 1 && lines[artifactLineIndex + 1]?.trim().match(/^```$/)) { + lines[artifactLineIndex + 1] = ''; + } + + return lines.join('\n'); +}; diff --git a/app/utils/logger.ts b/app/utils/logger.ts index 9b2c31c..1a5c932 100644 --- a/app/utils/logger.ts +++ b/app/utils/logger.ts @@ -11,7 +11,7 @@ interface Logger { setLevel: (level: DebugLevel) => void; } -let currentLevel: DebugLevel = (import.meta.env.VITE_LOG_LEVEL ?? import.meta.env.DEV) ? 'debug' : 'info'; +let currentLevel: DebugLevel = import.meta.env.VITE_LOG_LEVEL ?? import.meta.env.DEV ? 'debug' : 'info'; const isWorker = 'HTMLRewriter' in globalThis; const supportsColor = !isWorker; From 651a4f87925c3060098f97e6165d0de8b33eb674 Mon Sep 17 00:00:00 2001 From: Dustin Loring Date: Sun, 1 Dec 2024 05:31:02 -0500 Subject: [PATCH 5/9] Updated README Headings and Ollama Section Updated README.md Headings Removed Ollama Section Added Ollama env's info --- README.md | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 0127dd0..c51aaa6 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ This fork of Bolt.new (oTToDev) allows you to choose the LLM that you use for each prompt! Currently, you can use OpenAI, Anthropic, Ollama, OpenRouter, Gemini, LMStudio, Mistral, xAI, HuggingFace, DeepSeek, or Groq models - and it is easily extended to use any other model supported by the Vercel AI SDK! See the instructions below for running this locally and extending it to include more models. -Join the community for oTToDev! +## Join the community for oTToDev! https://thinktank.ottomator.ai -# Requested Additions to this Fork - Feel Free to Contribute!! +## Requested Additions to this Fork - Feel Free to Contribute!! - ✅ OpenRouter Integration (@coleam00) - ✅ Gemini Integration (@jonathands) @@ -49,7 +49,7 @@ https://thinktank.ottomator.ai - ⬜ Upload documents for knowledge - UI design templates, a code base to reference coding style, etc. - ⬜ Voice prompting -# Bolt.new: AI-Powered Full-Stack Web Development in the Browser +## Bolt.new: AI-Powered Full-Stack Web Development in the Browser Bolt.new is an AI-powered web development agent that allows you to prompt, run, edit, and deploy full-stack applications directly from your browser—no local setup required. If you're here to build your own AI-powered web dev agent using the Bolt open source codebase, [click here to get started!](./CONTRIBUTING.md) @@ -124,6 +124,13 @@ Optionally, you can set the debug level: VITE_LOG_LEVEL=debug ``` +And if using Ollama set the DEFAULT_NUM_CTX, the example below uses 8K context and ollama running on localhost port 11434: + +``` +OLLAMA_API_BASE_URL=http://localhost:11434 +DEFAULT_NUM_CTX=8192 +``` + **Important**: Never commit your `.env.local` file to version control. It's already included in .gitignore. ## Run with Docker @@ -192,31 +199,6 @@ sudo npm install -g pnpm pnpm run dev ``` -## Super Important Note on Running Ollama Models - -Ollama models by default only have 2048 tokens for their context window. Even for large models that can easily handle way more. -This is not a large enough window to handle the Bolt.new/oTToDev prompt! You have to create a version of any model you want -to use where you specify a larger context window. Luckily it's super easy to do that. - -All you have to do is: - -- Create a file called "Modelfile" (no file extension) anywhere on your computer -- Put in the two lines: - -``` -FROM [Ollama model ID such as qwen2.5-coder:7b] -PARAMETER num_ctx 32768 -``` - -- Run the command: - -``` -ollama create -f Modelfile [your new model ID, can be whatever you want (example: qwen2.5-coder-extra-ctx:7b)] -``` - -Now you have a new Ollama model that isn't heavily limited in the context length like Ollama models are by default for some reason. -You'll see this new model in the list of Ollama models along with all the others you pulled! - ## Adding New LLMs: To make new LLMs available to use in this version of Bolt.new, head on over to `app/utils/constants.ts` and find the constant MODEL_LIST. Each element in this array is an object that has the model ID for the name (get this from the provider's API documentation), a label for the frontend model dropdown, and the provider. From 416f7f90976bcdd86211625d76aaac65151ec335 Mon Sep 17 00:00:00 2001 From: Dustin Loring Date: Sun, 1 Dec 2024 07:15:25 -0500 Subject: [PATCH 6/9] Update constants.ts --- app/utils/constants.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/utils/constants.ts b/app/utils/constants.ts index fcbb3f7..54b78c4 100644 --- a/app/utils/constants.ts +++ b/app/utils/constants.ts @@ -283,9 +283,11 @@ const getOllamaBaseUrl = () => { }; async function getOllamaModels(): Promise { - //if (typeof window === 'undefined') { - //return []; - //} + /* + * if (typeof window === 'undefined') { + * return []; + * } + */ try { const baseUrl = getOllamaBaseUrl(); From 9d826371a6cb2cb4e35391fda6be75c8e0907e62 Mon Sep 17 00:00:00 2001 From: Dustin Loring Date: Sun, 1 Dec 2024 07:17:34 -0500 Subject: [PATCH 7/9] Update docker-compose.yaml --- docker-compose.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 4a3cc0a..f2412fc 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,5 @@ services: - bolt-ai: + app-prod: image: bolt-ai:production build: context: . @@ -24,12 +24,12 @@ services: - DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768} - RUNNING_IN_DOCKER=true extra_hosts: - - "host.docker.internal:host-gateway" + - "host.docker.internal:host-gateway" command: pnpm run dockerstart profiles: - - production # This service only runs in the production profile + - production - bolt-ai-dev: + app-dev: image: bolt-ai:development build: target: bolt-ai-development @@ -39,7 +39,7 @@ services: - VITE_HMR_HOST=localhost - VITE_HMR_PORT=5173 - CHOKIDAR_USEPOLLING=true - - WATCHPACK_POLLING=true + - WATCHPACK_POLLING=true - PORT=5173 - GROQ_API_KEY=${GROQ_API_KEY} - HuggingFace_API_KEY=${HuggingFace_API_KEY} @@ -52,7 +52,7 @@ services: - DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768} - RUNNING_IN_DOCKER=true extra_hosts: - - "host.docker.internal:host-gateway" + - "host.docker.internal:host-gateway" volumes: - type: bind source: . @@ -60,6 +60,6 @@ services: consistency: cached - /app/node_modules ports: - - "5173:5173" # Same port, no conflict as only one runs at a time + - "5173:5173" command: pnpm run dev --host 0.0.0.0 - profiles: ["development", "default"] # Make development the default profile + profiles: ["development", "default"] From c4347cb10d9e23cffcc1cafc42f8f1811f2fd3df Mon Sep 17 00:00:00 2001 From: Dustin Loring Date: Sun, 1 Dec 2024 07:23:15 -0500 Subject: [PATCH 8/9] added collapsable chat area --- app/components/chat/BaseChat.tsx | 57 +++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index dc6aecc..be44ef7 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -47,7 +47,7 @@ const ModelSelector = ({ model, setModel, provider, setProvider, modelList, prov key={provider?.name} value={model} onChange={(e) => setModel(e.target.value)} - className="flex-1 p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all lg:max-w-[70%] " + className="flex-1 p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all lg:max-w-[70%]" > {[...modelList] .filter((e) => e.provider == provider?.name && e.name) @@ -116,6 +116,7 @@ export const BaseChat = React.forwardRef( const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200; const [apiKeys, setApiKeys] = useState>({}); const [modelList, setModelList] = useState(MODEL_LIST); + const [isModelSettingsCollapsed, setIsModelSettingsCollapsed] = useState(false); useEffect(() => { // Load API keys from cookies on component mount @@ -199,30 +200,48 @@ export const BaseChat = React.forwardRef(
- +
+
+ +
- {provider && ( - updateApiKey(provider.name, key)} - /> - )} +
+ + {provider && ( + updateApiKey(provider.name, key)} + /> + )} +
+
Date: Sun, 1 Dec 2024 07:30:17 -0500 Subject: [PATCH 9/9] Update ExamplePrompts.tsx --- app/components/chat/ExamplePrompts.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/chat/ExamplePrompts.tsx b/app/components/chat/ExamplePrompts.tsx index 60b7643..2274b0e 100644 --- a/app/components/chat/ExamplePrompts.tsx +++ b/app/components/chat/ExamplePrompts.tsx @@ -5,7 +5,7 @@ const EXAMPLE_PROMPTS = [ { text: 'Build a simple blog using Astro' }, { text: 'Create a cookie consent form using Material UI' }, { text: 'Make a space invaders game' }, - { text: 'How do I center a div?' }, + { text: 'Make a Tic Tac Toe game in html, css and js only' }, ]; export function ExamplePrompts(sendMessage?: { (event: React.UIEvent, messageInput?: string): void | undefined }) {