}>{progressAnnotation[0].message}
+ {(codeContext || chatSummary) && (
+
diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx
index 4bfc038c5..024f8b052 100644
--- a/app/components/chat/BaseChat.tsx
+++ b/app/components/chat/BaseChat.tsx
@@ -2,7 +2,7 @@
* @ts-nocheck
* Preventing TS checks with files presented in the video for a better presentation.
*/
-import type { Message } from 'ai';
+import type { JSONValue, Message } from 'ai';
import React, { type RefCallback, useEffect, useState } from 'react';
import { ClientOnly } from 'remix-utils/client-only';
import { Menu } from '~/components/sidebar/Menu.client';
@@ -32,6 +32,8 @@ import StarterTemplates from './StarterTemplates';
import type { ActionAlert } from '~/types/actions';
import ChatAlert from './ChatAlert';
import type { ModelInfo } from '~/lib/modules/llm/types';
+import ProgressCompilation from './ProgressCompilation';
+import type { ProgressAnnotation } from '~/types/context';
const TEXTAREA_MIN_HEIGHT = 76;
@@ -64,6 +66,7 @@ interface BaseChatProps {
setImageDataList?: (dataList: string[]) => void;
actionAlert?: ActionAlert;
clearAlert?: () => void;
+ data?: JSONValue[] | undefined;
}
export const BaseChat = React.forwardRef
(
@@ -97,6 +100,7 @@ export const BaseChat = React.forwardRef(
messages,
actionAlert,
clearAlert,
+ data,
},
ref,
) => {
@@ -108,7 +112,15 @@ export const BaseChat = React.forwardRef(
const [recognition, setRecognition] = useState(null);
const [transcript, setTranscript] = useState('');
const [isModelLoading, setIsModelLoading] = useState('all');
-
+ const [progressAnnotations, setProgressAnnotations] = useState([]);
+ useEffect(() => {
+ if (data) {
+ const progressList = data.filter(
+ (x) => typeof x === 'object' && (x as any).type === 'progress',
+ ) as ProgressAnnotation[];
+ setProgressAnnotations(progressList);
+ }
+ }, [data]);
useEffect(() => {
console.log(transcript);
}, [transcript]);
@@ -307,6 +319,7 @@ export const BaseChat = React.forwardRef(
className={classNames('pt-6 px-2 sm:px-6', {
'h-full flex flex-col': chatStarted,
})}
+ ref={scrollRef}
>
{() => {
@@ -337,6 +350,7 @@ export const BaseChat = React.forwardRef(
/>
)}
>({});
- const { messages, isLoading, input, handleInputChange, setInput, stop, append, setMessages, reload, error } =
- useChat({
- api: '/api/chat',
- body: {
- apiKeys,
- files,
- promptId,
- contextOptimization: contextOptimizationEnabled,
- },
- sendExtraMessageFields: true,
- onError: (e) => {
- logger.error('Request failed\n\n', e, error);
- toast.error(
- 'There was an error processing your request: ' + (e.message ? e.message : 'No details were returned'),
- );
- },
- onFinish: (message, response) => {
- const usage = response.usage;
-
- if (usage) {
- console.log('Token usage:', usage);
-
- // You can now use the usage data as needed
- }
+ const {
+ messages,
+ isLoading,
+ input,
+ handleInputChange,
+ setInput,
+ stop,
+ append,
+ setMessages,
+ reload,
+ error,
+ data: chatData,
+ setData,
+ } = useChat({
+ api: '/api/chat',
+ body: {
+ apiKeys,
+ files,
+ promptId,
+ contextOptimization: contextOptimizationEnabled,
+ },
+ sendExtraMessageFields: true,
+ onError: (e) => {
+ logger.error('Request failed\n\n', e, error);
+ toast.error(
+ 'There was an error processing your request: ' + (e.message ? e.message : 'No details were returned'),
+ );
+ },
+ onFinish: (message, response) => {
+ const usage = response.usage;
+ setData(undefined);
+
+ if (usage) {
+ console.log('Token usage:', usage);
+
+ // You can now use the usage data as needed
+ }
- logger.debug('Finished streaming');
- },
- initialMessages,
- initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '',
- });
+ logger.debug('Finished streaming');
+ },
+ initialMessages,
+ initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '',
+ });
useEffect(() => {
const prompt = searchParams.get('prompt');
@@ -535,6 +548,7 @@ export const ChatImpl = memo(
setImageDataList={setImageDataList}
actionAlert={actionAlert}
clearAlert={() => workbenchStore.clearAlert()}
+ data={chatData}
/>
);
},
diff --git a/app/components/chat/GitCloneButton.tsx b/app/components/chat/GitCloneButton.tsx
index 376d59d60..bc98924bd 100644
--- a/app/components/chat/GitCloneButton.tsx
+++ b/app/components/chat/GitCloneButton.tsx
@@ -1,11 +1,12 @@
import ignore from 'ignore';
import { useGit } from '~/lib/hooks/useGit';
import type { Message } from 'ai';
-import { detectProjectCommands, createCommandsMessage } from '~/utils/projectCommands';
+import { detectProjectCommands, createCommandsMessage, escapeBoltTags } from '~/utils/projectCommands';
import { generateId } from '~/utils/fileUtils';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
+import type { IChatMetadata } from '~/lib/persistence';
const IGNORE_PATTERNS = [
'node_modules/**',
@@ -35,7 +36,7 @@ const ig = ignore().add(IGNORE_PATTERNS);
interface GitCloneButtonProps {
className?: string;
- importChat?: (description: string, messages: Message[]) => Promise
;
+ importChat?: (description: string, messages: Message[], metadata?: IChatMetadata) => Promise;
}
export default function GitCloneButton({ importChat }: GitCloneButtonProps) {
@@ -83,7 +84,7 @@ ${fileContents
.map(
(file) =>
`
-${file.content}
+${escapeBoltTags(file.content)}
`,
)
.join('\n')}
@@ -98,7 +99,7 @@ ${file.content}
messages.push(commandsMessage);
}
- await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
+ await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages, { gitUrl: repoUrl });
}
} catch (error) {
console.error('Error during import:', error);
diff --git a/app/components/chat/Markdown.tsx b/app/components/chat/Markdown.tsx
index 46cffd4ed..90ba6b7f7 100644
--- a/app/components/chat/Markdown.tsx
+++ b/app/components/chat/Markdown.tsx
@@ -23,8 +23,6 @@ export const Markdown = memo(({ children, html = false, limitedMarkdown = false
const components = useMemo(() => {
return {
div: ({ className, children, node, ...props }) => {
- console.log(className, node);
-
if (className?.includes('__boltArtifact__')) {
const messageId = node?.properties.dataMessageId as string;
diff --git a/app/components/chat/ProgressCompilation.tsx b/app/components/chat/ProgressCompilation.tsx
new file mode 100644
index 000000000..270fac032
--- /dev/null
+++ b/app/components/chat/ProgressCompilation.tsx
@@ -0,0 +1,111 @@
+import { AnimatePresence, motion } from 'framer-motion';
+import React, { useState } from 'react';
+import type { ProgressAnnotation } from '~/types/context';
+import { classNames } from '~/utils/classNames';
+import { cubicEasingFn } from '~/utils/easings';
+
+export default function ProgressCompilation({ data }: { data?: ProgressAnnotation[] }) {
+ const [progressList, setProgressList] = React.useState([]);
+ const [expanded, setExpanded] = useState(false);
+ React.useEffect(() => {
+ if (!data || data.length == 0) {
+ setProgressList([]);
+ return;
+ }
+
+ const progressMap = new Map();
+ data.forEach((x) => {
+ const existingProgress = progressMap.get(x.label);
+
+ if (existingProgress && existingProgress.status === 'complete') {
+ return;
+ }
+
+ progressMap.set(x.label, x);
+ });
+
+ const newData = Array.from(progressMap.values());
+ newData.sort((a, b) => a.order - b.order);
+ setProgressList(newData);
+ }, [data]);
+
+ if (progressList.length === 0) {
+ return <>>;
+ }
+
+ return (
+
+
+
+
+
+ {expanded ? (
+
+ {progressList.map((x, i) => {
+ return ;
+ })}
+
+ ) : (
+
+ )}
+
+
+
setExpanded((v) => !v)}
+ >
+
+
+
+
+
+ );
+}
+
+const ProgressItem = ({ progress }: { progress: ProgressAnnotation }) => {
+ return (
+
+
+
+ {progress.status === 'in-progress' ? (
+
+ ) : progress.status === 'complete' ? (
+
+ ) : null}
+
+ {/* {x.label} */}
+
+ {progress.message}
+
+ );
+};
diff --git a/app/components/git/GitUrlImport.client.tsx b/app/components/git/GitUrlImport.client.tsx
index fe8b346bf..6053acdc3 100644
--- a/app/components/git/GitUrlImport.client.tsx
+++ b/app/components/git/GitUrlImport.client.tsx
@@ -7,7 +7,7 @@ import { BaseChat } from '~/components/chat/BaseChat';
import { Chat } from '~/components/chat/Chat.client';
import { useGit } from '~/lib/hooks/useGit';
import { useChatHistory } from '~/lib/persistence';
-import { createCommandsMessage, detectProjectCommands } from '~/utils/projectCommands';
+import { createCommandsMessage, detectProjectCommands, escapeBoltTags } from '~/utils/projectCommands';
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
import { toast } from 'react-toastify';
@@ -74,12 +74,12 @@ export function GitUrlImport() {
const filesMessage: Message = {
role: 'assistant',
content: `Cloning the repo ${repoUrl} into ${workdir}
-
+
${fileContents
.map(
(file) =>
`
-${file.content}
+${escapeBoltTags(file.content)}
`,
)
.join('\n')}
@@ -91,10 +91,15 @@ ${file.content}
const messages = [filesMessage];
if (commandsMessage) {
+ messages.push({
+ role: 'user',
+ id: generateId(),
+ content: 'Setup the codebase and Start the application',
+ });
messages.push(commandsMessage);
}
- await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
+ await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages, { gitUrl: repoUrl });
}
} catch (error) {
console.error('Error during import:', error);
diff --git a/app/components/ui/Popover.tsx b/app/components/ui/Popover.tsx
index d00bf976a..7aab92e83 100644
--- a/app/components/ui/Popover.tsx
+++ b/app/components/ui/Popover.tsx
@@ -1,15 +1,24 @@
import * as Popover from '@radix-ui/react-popover';
import type { PropsWithChildren, ReactNode } from 'react';
-export default ({ children, trigger }: PropsWithChildren<{ trigger: ReactNode }>) => (
+export default ({
+ children,
+ trigger,
+ side,
+ align,
+}: PropsWithChildren<{
+ trigger: ReactNode;
+ side: 'top' | 'right' | 'bottom' | 'left' | undefined;
+ align: 'center' | 'start' | 'end' | undefined;
+}>) => (
{trigger}
{children}
diff --git a/app/components/workbench/Workbench.client.tsx b/app/components/workbench/Workbench.client.tsx
index 0e34b5998..be74b7722 100644
--- a/app/components/workbench/Workbench.client.tsx
+++ b/app/components/workbench/Workbench.client.tsx
@@ -18,6 +18,7 @@ import { EditorPanel } from './EditorPanel';
import { Preview } from './Preview';
import useViewport from '~/lib/hooks';
import Cookies from 'js-cookie';
+import { chatMetadata, useChatHistory } from '~/lib/persistence';
interface WorkspaceProps {
chatStarted?: boolean;
@@ -66,6 +67,8 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
const unsavedFiles = useStore(workbenchStore.unsavedFiles);
const files = useStore(workbenchStore.files);
const selectedView = useStore(workbenchStore.currentView);
+ const metadata = useStore(chatMetadata);
+ const { updateChatMestaData } = useChatHistory();
const isSmallViewport = useViewport(1024);
@@ -171,18 +174,28 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
{
- const repoName = prompt(
- 'Please enter a name for your new GitHub repository:',
- 'bolt-generated-project',
- );
+ let repoName = metadata?.gitUrl?.split('/').slice(-1)[0]?.replace('.git', '') || null;
+ let repoConfirmed: boolean = true;
+
+ if (repoName) {
+ repoConfirmed = confirm(`Do you want to push to the repository ${repoName}?`);
+ }
+
+ if (!repoName || !repoConfirmed) {
+ repoName = prompt(
+ 'Please enter a name for your new GitHub repository:',
+ 'bolt-generated-project',
+ );
+ } else {
+ }
if (!repoName) {
alert('Repository name is required. Push to GitHub cancelled.');
return;
}
- const githubUsername = Cookies.get('githubUsername');
- const githubToken = Cookies.get('githubToken');
+ let githubUsername = Cookies.get('githubUsername');
+ let githubToken = Cookies.get('githubToken');
if (!githubUsername || !githubToken) {
const usernameInput = prompt('Please enter your GitHub username:');
@@ -193,9 +206,26 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
return;
}
- workbenchStore.pushToGitHub(repoName, usernameInput, tokenInput);
- } else {
- workbenchStore.pushToGitHub(repoName, githubUsername, githubToken);
+ githubUsername = usernameInput;
+ githubToken = tokenInput;
+
+ Cookies.set('githubUsername', usernameInput);
+ Cookies.set('githubToken', tokenInput);
+ Cookies.set(
+ 'git:github.com',
+ JSON.stringify({ username: tokenInput, password: 'x-oauth-basic' }),
+ );
+ }
+
+ const commitMessage =
+ prompt('Please enter a commit message:', 'Initial commit') || 'Initial commit';
+ workbenchStore.pushToGitHub(repoName, commitMessage, githubUsername, githubToken);
+
+ if (!metadata?.gitUrl) {
+ updateChatMestaData({
+ ...(metadata || {}),
+ gitUrl: `https://github.com/${githubUsername}/${repoName}.git`,
+ });
}
}}
>
diff --git a/app/lib/.server/llm/create-summary.ts b/app/lib/.server/llm/create-summary.ts
index 4d14a2791..e21abd81d 100644
--- a/app/lib/.server/llm/create-summary.ts
+++ b/app/lib/.server/llm/create-summary.ts
@@ -16,7 +16,7 @@ export async function createSummary(props: {
contextOptimization?: boolean;
onFinish?: (resp: GenerateTextResult>, never>) => void;
}) {
- const { messages, env: serverEnv, apiKeys, providerSettings, contextOptimization, onFinish } = props;
+ const { messages, env: serverEnv, apiKeys, providerSettings, onFinish } = props;
let currentModel = DEFAULT_MODEL;
let currentProvider = DEFAULT_PROVIDER.name;
const processedMessages = messages.map((message) => {
@@ -29,9 +29,9 @@ export async function createSummary(props: {
} else if (message.role == 'assistant') {
let content = message.content;
- if (contextOptimization) {
- content = simplifyBoltActions(content);
- }
+ content = simplifyBoltActions(content);
+ content = content.replace(/.*?<\/div>/s, '');
+ content = content.replace(/
.*?<\/think>/s, '');
return { ...message, content };
}
@@ -92,6 +92,8 @@ ${summary.summary}`;
}
}
+ logger.debug('Sliced Messages:', slicedMessages.length);
+
const extractTextContent = (message: Message) =>
Array.isArray(message.content)
? (message.content.find((item) => item.type === 'text')?.text as string) || ''
@@ -100,25 +102,82 @@ ${summary.summary}`;
// select files from the list of code file from the project that might be useful for the current request from the user
const resp = await generateText({
system: `
- You are a software engineer. You are working on a project. tou need to summarize the work till now and provide a summary of the chat till now.
+ You are a software engineer. You are working on a project. you need to summarize the work till now and provide a summary of the chat till now.
+
+ Please only use the following format to generate the summary:
+---
+# Project Overview
+- **Project**: {project_name} - {brief_description}
+- **Current Phase**: {phase}
+- **Tech Stack**: {languages}, {frameworks}, {key_dependencies}
+- **Environment**: {critical_env_details}
+
+# Conversation Context
+- **Last Topic**: {main_discussion_point}
+- **Key Decisions**: {important_decisions_made}
+- **User Context**:
+ - Technical Level: {expertise_level}
+ - Preferences: {coding_style_preferences}
+ - Communication: {preferred_explanation_style}
+
+# Implementation Status
+## Current State
+- **Active Feature**: {feature_in_development}
+- **Progress**: {what_works_and_what_doesn't}
+- **Blockers**: {current_challenges}
+
+## Code Evolution
+- **Recent Changes**: {latest_modifications}
+- **Working Patterns**: {successful_approaches}
+- **Failed Approaches**: {attempted_solutions_that_failed}
+
+# Requirements
+- **Implemented**: {completed_features}
+- **In Progress**: {current_focus}
+- **Pending**: {upcoming_features}
+- **Technical Constraints**: {critical_constraints}
+
+# Critical Memory
+- **Must Preserve**: {crucial_technical_context}
+- **User Requirements**: {specific_user_needs}
+- **Known Issues**: {documented_problems}
+
+# Next Actions
+- **Immediate**: {next_steps}
+- **Open Questions**: {unresolved_issues}
+
+---
+Note:
+4. Keep entries concise and focused on information needed for continuity
+
- ${summaryText}
+---
RULES:
- * Only provide the summary of the chat till now.
+ * Only provide the whole summary of the chat till now.
* Do not provide any new information.
+ * DO not need to think too much just start writing imidiately
+ * do not write any thing other that the summary with with the provided structure
`,
prompt: `
-please provide a summary of the chat till now.
-below is the latest chat:
+Here is the previous summary of the chat:
+
+${summaryText}
+
+
+Below is the chat after that:
---
+
${slicedMessages
.map((x) => {
return `---\n[${x.role}] ${extractTextContent(x)}\n---`;
})
.join('\n')}
+
---
+
+Please provide a summary of the chat till now including the hitorical summary of the chat.
`,
model: provider.getModelInstance({
model: currentModel,
diff --git a/app/lib/.server/llm/select-context.ts b/app/lib/.server/llm/select-context.ts
index 85780e42d..93e8be085 100644
--- a/app/lib/.server/llm/select-context.ts
+++ b/app/lib/.server/llm/select-context.ts
@@ -23,7 +23,7 @@ export async function selectContext(props: {
summary: string;
onFinish?: (resp: GenerateTextResult>, never>) => void;
}) {
- const { messages, env: serverEnv, apiKeys, files, providerSettings, contextOptimization, summary, onFinish } = props;
+ const { messages, env: serverEnv, apiKeys, files, providerSettings, summary, onFinish } = props;
let currentModel = DEFAULT_MODEL;
let currentProvider = DEFAULT_PROVIDER.name;
const processedMessages = messages.map((message) => {
@@ -36,9 +36,10 @@ export async function selectContext(props: {
} else if (message.role == 'assistant') {
let content = message.content;
- if (contextOptimization) {
- content = simplifyBoltActions(content);
- }
+ content = simplifyBoltActions(content);
+
+ content = content.replace(/.*?<\/div>/s, '');
+ content = content.replace(/
.*?<\/think>/s, '');
return { ...message, content };
}
diff --git a/app/lib/.server/llm/stream-text.ts b/app/lib/.server/llm/stream-text.ts
index 374610c71..29579c9ff 100644
--- a/app/lib/.server/llm/stream-text.ts
+++ b/app/lib/.server/llm/stream-text.ts
@@ -7,7 +7,7 @@ import { PromptLibrary } from '~/lib/common/prompt-library';
import { allowedHTMLElements } from '~/utils/markdown';
import { LLMManager } from '~/lib/modules/llm/manager';
import { createScopedLogger } from '~/utils/logger';
-import { createFilesContext, extractPropertiesFromMessage, simplifyBoltActions } from './utils';
+import { createFilesContext, extractPropertiesFromMessage } from './utils';
import { getFilePaths } from './select-context';
export type Messages = Message[];
@@ -27,6 +27,7 @@ export async function streamText(props: {
contextOptimization?: boolean;
contextFiles?: FileMap;
summary?: string;
+ messageSliceId?: number;
}) {
const {
messages,
@@ -51,10 +52,8 @@ export async function streamText(props: {
return { ...message, content };
} else if (message.role == 'assistant') {
let content = message.content;
-
- if (contextOptimization) {
- content = simplifyBoltActions(content);
- }
+ content = content.replace(/.*?<\/div>/s, '');
+ content = content.replace(/
.*?<\/think>/s, '');
return { ...message, content };
}
@@ -110,7 +109,7 @@ Below are all the files present in the project:
${filePaths.join('\n')}
---
-Below is the context loaded into context buffer for you to have knowledge of and might need changes to fullfill current user request.
+Below is the artifact containing the context loaded into context buffer for you to have knowledge of and might need changes to fullfill current user request.
CONTEXT BUFFER:
---
${codeContext}
@@ -126,10 +125,14 @@ ${props.summary}
---
`;
- const lastMessage = processedMessages.pop();
+ if (props.messageSliceId) {
+ processedMessages = processedMessages.slice(props.messageSliceId);
+ } else {
+ const lastMessage = processedMessages.pop();
- if (lastMessage) {
- processedMessages = [lastMessage];
+ if (lastMessage) {
+ processedMessages = [lastMessage];
+ }
}
}
}
diff --git a/app/lib/.server/llm/utils.ts b/app/lib/.server/llm/utils.ts
index 9aac0891f..e019a9299 100644
--- a/app/lib/.server/llm/utils.ts
+++ b/app/lib/.server/llm/utils.ts
@@ -82,10 +82,10 @@ export function createFilesContext(files: FileMap, useRelativePath?: boolean) {
filePath = path.replace('/home/project/', '');
}
- return `\n${codeWithLinesNumbers}\n`;
+ return `${codeWithLinesNumbers}`;
});
- return `${fileContexts.join('\n\n')}\n\n`;
+ return `\n${fileContexts.join('\n')}\n`;
}
export function extractCurrentContext(messages: Message[]) {
diff --git a/app/lib/common/prompts/optimized.ts b/app/lib/common/prompts/optimized.ts
index 1728ef11d..338720883 100644
--- a/app/lib/common/prompts/optimized.ts
+++ b/app/lib/common/prompts/optimized.ts
@@ -1,7 +1,7 @@
import type { PromptOptions } from '~/lib/common/prompt-library';
export default (options: PromptOptions) => {
- const { cwd, allowedHtmlElements, modificationTagName } = options;
+ const { cwd, allowedHtmlElements } = options;
return `
You are Bolt, an expert AI assistant and exceptional senior software developer with vast knowledge across multiple programming languages, frameworks, and best practices.
@@ -13,6 +13,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
- Use Vite for web servers
- Databases: prefer libsql, sqlite, or non-native solutions
- When for react dont forget to write vite config and index.html to the project
+ - WebContainer CANNOT execute diff or patch editing so always write your code in full no partial/diff update
Available shell commands: cat, cp, ls, mkdir, mv, rm, rmdir, touch, hostname, ps, pwd, uptime, env, node, python3, code, jq, curl, head, sort, tail, clear, which, export, chmod, scho, kill, ln, xxd, alias, getconf, loadenv, wasm, xdg-open, command, exit, source
@@ -25,12 +26,6 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
Available HTML elements: ${allowedHtmlElements.join(', ')}
-
- File modifications in \`<${modificationTagName}>\` section:
- - \`\`: GNU unified diff format
- - \`\`: Full new content
-
-
do not mention the phrase "chain of thought"
Before solutions, briefly outline implementation steps (2-4 lines max):
@@ -88,6 +83,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
24. Order actions logically - dependencies MUST be installed first
25. For Vite project must include vite config and index.html for entry point
26. Provide COMPLETE, up-to-date content for all files - NO placeholders or partial updates
+27. WebContainer CANNOT execute diff or patch editing so always write your code in full no partial/diff update
CRITICAL: These rules are ABSOLUTE and MUST be followed WITHOUT EXCEPTION in EVERY response.
diff --git a/app/lib/common/prompts/prompts.ts b/app/lib/common/prompts/prompts.ts
index 06f0b7277..1e9134ac0 100644
--- a/app/lib/common/prompts/prompts.ts
+++ b/app/lib/common/prompts/prompts.ts
@@ -1,4 +1,4 @@
-import { MODIFICATIONS_TAG_NAME, WORK_DIR } from '~/utils/constants';
+import { WORK_DIR } from '~/utils/constants';
import { allowedHTMLElements } from '~/utils/markdown';
import { stripIndents } from '~/utils/stripIndent';
@@ -25,6 +25,8 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
IMPORTANT: Git is NOT available.
+ IMPORTANT: WebContainer CANNOT execute diff or patch editing so always write your code in full no partial/diff update
+
IMPORTANT: Prefer writing Node.js scripts instead of shell scripts. The environment doesn't fully support shell scripts, so use Node.js for scripting tasks whenever possible!
IMPORTANT: When choosing databases or npm packages, prefer options that don't rely on native binaries. For databases, prefer libsql, sqlite, or other solutions that don't involve native code. WebContainer CANNOT execute arbitrary native binaries.
@@ -65,50 +67,6 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
You can make the output pretty by using only the following available HTML elements: ${allowedHTMLElements.map((tagName) => `<${tagName}>`).join(', ')}
-
- For user-made file modifications, a \`<${MODIFICATIONS_TAG_NAME}>\` section will appear at the start of the user message. It will contain either \`\` or \`\` elements for each modified file:
-
- - \`\`: Contains GNU unified diff format changes
- - \`\`: Contains the full new content of the file
-
- The system chooses \`\` if the diff exceeds the new content size, otherwise \`\`.
-
- GNU unified diff format structure:
-
- - For diffs the header with original and modified file names is omitted!
- - Changed sections start with @@ -X,Y +A,B @@ where:
- - X: Original file starting line
- - Y: Original file line count
- - A: Modified file starting line
- - B: Modified file line count
- - (-) lines: Removed from original
- - (+) lines: Added in modified version
- - Unmarked lines: Unchanged context
-
- Example:
-
- <${MODIFICATIONS_TAG_NAME}>
-
- @@ -2,7 +2,10 @@
- return a + b;
- }
-
- -console.log('Hello, World!');
- +console.log('Hello, Bolt!');
- +
- function greet() {
- - return 'Greetings!';
- + return 'Greetings!!';
- }
- +
- +console.log('The End');
-
-
- // full file content here
-
- ${MODIFICATIONS_TAG_NAME}>
-
-
Before providing a solution, BRIEFLY outline your implementation steps. This helps ensure systematic thinking and clear communication. Your planning should:
- List concrete steps you'll take
diff --git a/app/lib/hooks/useGit.ts b/app/lib/hooks/useGit.ts
index 2efc6e8cc..82c650c30 100644
--- a/app/lib/hooks/useGit.ts
+++ b/app/lib/hooks/useGit.ts
@@ -92,6 +92,7 @@ export function useGit() {
},
onAuthFailure: (url, _auth) => {
toast.error(`Error Authenticating with ${url.split('/')[2]}`);
+ throw `Error Authenticating with ${url.split('/')[2]}`;
},
onAuthSuccess: (url, auth) => {
saveGitAuth(url, auth);
@@ -107,6 +108,8 @@ export function useGit() {
return { workdir: webcontainer.workdir, data };
} catch (error) {
console.error('Git clone error:', error);
+
+ // toast.error(`Git clone error ${(error as any).message||""}`);
throw error;
}
},
diff --git a/app/lib/modules/llm/manager.ts b/app/lib/modules/llm/manager.ts
index 88ae28c91..aec919059 100644
--- a/app/lib/modules/llm/manager.ts
+++ b/app/lib/modules/llm/manager.ts
@@ -118,12 +118,14 @@ export class LLMManager {
return dynamicModels;
}),
);
+ const staticModels = Array.from(this._providers.values()).flatMap((p) => p.staticModels || []);
+ const dynamicModelsFlat = dynamicModels.flat();
+ const dynamicModelKeys = dynamicModelsFlat.map((d) => `${d.name}-${d.provider}`);
+ const filteredStaticModesl = staticModels.filter((m) => !dynamicModelKeys.includes(`${m.name}-${m.provider}`));
// Combine static and dynamic models
- const modelList = [
- ...dynamicModels.flat(),
- ...Array.from(this._providers.values()).flatMap((p) => p.staticModels || []),
- ];
+ const modelList = [...dynamicModelsFlat, ...filteredStaticModesl];
+ modelList.sort((a, b) => a.name.localeCompare(b.name));
this._modelList = modelList;
return modelList;
@@ -178,8 +180,12 @@ export class LLMManager {
logger.error(`Error getting dynamic models ${provider.name} :`, err);
return [];
});
+ const dynamicModelsName = dynamicModels.map((d) => d.name);
+ const filteredStaticList = staticModels.filter((m) => !dynamicModelsName.includes(m.name));
+ const modelList = [...dynamicModels, ...filteredStaticList];
+ modelList.sort((a, b) => a.name.localeCompare(b.name));
- return [...dynamicModels, ...staticModels];
+ return modelList;
}
getStaticModelListFromProvider(providerArg: BaseProvider) {
const provider = this._providers.get(providerArg.name);
diff --git a/app/lib/modules/llm/providers/google.ts b/app/lib/modules/llm/providers/google.ts
index edc8e1dd3..67043bad8 100644
--- a/app/lib/modules/llm/providers/google.ts
+++ b/app/lib/modules/llm/providers/google.ts
@@ -14,6 +14,12 @@ export default class GoogleProvider extends BaseProvider {
staticModels: ModelInfo[] = [
{ name: 'gemini-1.5-flash-latest', label: 'Gemini 1.5 Flash', provider: 'Google', maxTokenAllowed: 8192 },
+ {
+ name: 'gemini-2.0-flash-thinking-exp-01-21',
+ label: 'Gemini 2.0 Flash-thinking-exp-01-21',
+ provider: 'Google',
+ maxTokenAllowed: 65536,
+ },
{ name: 'gemini-2.0-flash-exp', label: 'Gemini 2.0 Flash', provider: 'Google', maxTokenAllowed: 8192 },
{ name: 'gemini-1.5-flash-002', label: 'Gemini 1.5 Flash-002', provider: 'Google', maxTokenAllowed: 8192 },
{ name: 'gemini-1.5-flash-8b', label: 'Gemini 1.5 Flash-8b', provider: 'Google', maxTokenAllowed: 8192 },
@@ -22,6 +28,41 @@ export default class GoogleProvider extends BaseProvider {
{ name: 'gemini-exp-1206', label: 'Gemini exp-1206', provider: 'Google', maxTokenAllowed: 8192 },
];
+ async getDynamicModels(
+ apiKeys?: Record,
+ settings?: IProviderSetting,
+ serverEnv?: Record,
+ ): Promise {
+ const { apiKey } = this.getProviderBaseUrlAndKey({
+ apiKeys,
+ providerSettings: settings,
+ serverEnv: serverEnv as any,
+ defaultBaseUrlKey: '',
+ defaultApiTokenKey: 'GOOGLE_GENERATIVE_AI_API_KEY',
+ });
+
+ if (!apiKey) {
+ throw `Missing Api Key configuration for ${this.name} provider`;
+ }
+
+ const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`, {
+ headers: {
+ ['Content-Type']: 'application/json',
+ },
+ });
+
+ const res = (await response.json()) as any;
+
+ const data = res.models.filter((model: any) => model.outputTokenLimit > 8000);
+
+ return data.map((m: any) => ({
+ name: m.name.replace('models/', ''),
+ label: `${m.displayName} - context ${Math.floor((m.inputTokenLimit + m.outputTokenLimit) / 1000) + 'k'}`,
+ provider: this.name,
+ maxTokenAllowed: m.inputTokenLimit + m.outputTokenLimit || 8000,
+ }));
+ }
+
getModelInstance(options: {
model: string;
serverEnv: any;
diff --git a/app/lib/modules/llm/providers/groq.ts b/app/lib/modules/llm/providers/groq.ts
index 034dab12e..e9d2b0bd8 100644
--- a/app/lib/modules/llm/providers/groq.ts
+++ b/app/lib/modules/llm/providers/groq.ts
@@ -19,8 +19,51 @@ export default class GroqProvider extends BaseProvider {
{ name: 'llama-3.2-3b-preview', label: 'Llama 3.2 3b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
{ name: 'llama-3.2-1b-preview', label: 'Llama 3.2 1b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
{ name: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
+ {
+ name: 'deepseek-r1-distill-llama-70b',
+ label: 'Deepseek R1 Distill Llama 70b (Groq)',
+ provider: 'Groq',
+ maxTokenAllowed: 131072,
+ },
];
+ async getDynamicModels(
+ apiKeys?: Record,
+ settings?: IProviderSetting,
+ serverEnv?: Record,
+ ): Promise {
+ const { apiKey } = this.getProviderBaseUrlAndKey({
+ apiKeys,
+ providerSettings: settings,
+ serverEnv: serverEnv as any,
+ defaultBaseUrlKey: '',
+ defaultApiTokenKey: 'GROQ_API_KEY',
+ });
+
+ if (!apiKey) {
+ throw `Missing Api Key configuration for ${this.name} provider`;
+ }
+
+ const response = await fetch(`https://api.groq.com/openai/v1/models`, {
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ },
+ });
+
+ const res = (await response.json()) as any;
+
+ const data = res.data.filter(
+ (model: any) => model.object === 'model' && model.active && model.context_window > 8000,
+ );
+
+ return data.map((m: any) => ({
+ name: m.id,
+ label: `${m.id} - context ${m.context_window ? Math.floor(m.context_window / 1000) + 'k' : 'N/A'} [ by ${m.owned_by}]`,
+ provider: this.name,
+ maxTokenAllowed: m.context_window || 8000,
+ }));
+ }
+
getModelInstance(options: {
model: string;
serverEnv: Env;
diff --git a/app/lib/persistence/db.ts b/app/lib/persistence/db.ts
index 64aea1cf7..2f346f60b 100644
--- a/app/lib/persistence/db.ts
+++ b/app/lib/persistence/db.ts
@@ -2,6 +2,11 @@ import type { Message } from 'ai';
import { createScopedLogger } from '~/utils/logger';
import type { ChatHistoryItem } from './useChatHistory';
+export interface IChatMetadata {
+ gitUrl: string;
+ gitBranch?: string;
+}
+
const logger = createScopedLogger('ChatHistory');
// this is used at the top level and never rejects
@@ -53,6 +58,7 @@ export async function setMessages(
urlId?: string,
description?: string,
timestamp?: string,
+ metadata?: IChatMetadata,
): Promise {
return new Promise((resolve, reject) => {
const transaction = db.transaction('chats', 'readwrite');
@@ -69,6 +75,7 @@ export async function setMessages(
urlId,
description,
timestamp: timestamp ?? new Date().toISOString(),
+ metadata,
});
request.onsuccess = () => resolve();
@@ -204,6 +211,7 @@ export async function createChatFromMessages(
db: IDBDatabase,
description: string,
messages: Message[],
+ metadata?: IChatMetadata,
): Promise {
const newId = await getNextId(db);
const newUrlId = await getUrlId(db, newId); // Get a new urlId for the duplicated chat
@@ -214,6 +222,8 @@ export async function createChatFromMessages(
messages,
newUrlId, // Use the new urlId
description,
+ undefined, // Use the current timestamp
+ metadata,
);
return newUrlId; // Return the urlId instead of id for navigation
@@ -230,5 +240,19 @@ export async function updateChatDescription(db: IDBDatabase, id: string, descrip
throw new Error('Description cannot be empty');
}
- await setMessages(db, id, chat.messages, chat.urlId, description, chat.timestamp);
+ await setMessages(db, id, chat.messages, chat.urlId, description, chat.timestamp, chat.metadata);
+}
+
+export async function updateChatMetadata(
+ db: IDBDatabase,
+ id: string,
+ metadata: IChatMetadata | undefined,
+): Promise {
+ const chat = await getMessages(db, id);
+
+ if (!chat) {
+ throw new Error('Chat not found');
+ }
+
+ await setMessages(db, id, chat.messages, chat.urlId, chat.description, chat.timestamp, metadata);
}
diff --git a/app/lib/persistence/useChatHistory.ts b/app/lib/persistence/useChatHistory.ts
index 0a8eeb58b..7baefa560 100644
--- a/app/lib/persistence/useChatHistory.ts
+++ b/app/lib/persistence/useChatHistory.ts
@@ -13,6 +13,7 @@ import {
setMessages,
duplicateChat,
createChatFromMessages,
+ type IChatMetadata,
} from './db';
export interface ChatHistoryItem {
@@ -21,6 +22,7 @@ export interface ChatHistoryItem {
description?: string;
messages: Message[];
timestamp: string;
+ metadata?: IChatMetadata;
}
const persistenceEnabled = !import.meta.env.VITE_DISABLE_PERSISTENCE;
@@ -29,7 +31,7 @@ export const db = persistenceEnabled ? await openDatabase() : undefined;
export const chatId = atom(undefined);
export const description = atom(undefined);
-
+export const chatMetadata = atom(undefined);
export function useChatHistory() {
const navigate = useNavigate();
const { id: mixedId } = useLoaderData<{ id?: string }>();
@@ -65,6 +67,7 @@ export function useChatHistory() {
setUrlId(storedMessages.urlId);
description.set(storedMessages.description);
chatId.set(storedMessages.id);
+ chatMetadata.set(storedMessages.metadata);
} else {
navigate('/', { replace: true });
}
@@ -81,6 +84,21 @@ export function useChatHistory() {
return {
ready: !mixedId || ready,
initialMessages,
+ updateChatMestaData: async (metadata: IChatMetadata) => {
+ const id = chatId.get();
+
+ if (!db || !id) {
+ return;
+ }
+
+ try {
+ await setMessages(db, id, initialMessages, urlId, description.get(), undefined, metadata);
+ chatMetadata.set(metadata);
+ } catch (error) {
+ toast.error('Failed to update chat metadata');
+ console.error(error);
+ }
+ },
storeMessageHistory: async (messages: Message[]) => {
if (!db || messages.length === 0) {
return;
@@ -109,7 +127,7 @@ export function useChatHistory() {
}
}
- await setMessages(db, chatId.get() as string, messages, urlId, description.get());
+ await setMessages(db, chatId.get() as string, messages, urlId, description.get(), undefined, chatMetadata.get());
},
duplicateCurrentChat: async (listItemId: string) => {
if (!db || (!mixedId && !listItemId)) {
@@ -125,13 +143,13 @@ export function useChatHistory() {
console.log(error);
}
},
- importChat: async (description: string, messages: Message[]) => {
+ importChat: async (description: string, messages: Message[], metadata?: IChatMetadata) => {
if (!db) {
return;
}
try {
- const newId = await createChatFromMessages(db, description, messages);
+ const newId = await createChatFromMessages(db, description, messages, metadata);
window.location.href = `/chat/${newId}`;
toast.success('Chat imported successfully');
} catch (error) {
diff --git a/app/lib/runtime/message-parser.ts b/app/lib/runtime/message-parser.ts
index 275717941..3b41b6d61 100644
--- a/app/lib/runtime/message-parser.ts
+++ b/app/lib/runtime/message-parser.ts
@@ -64,6 +64,10 @@ function cleanoutMarkdownSyntax(content: string) {
return content;
}
}
+
+function cleanEscapedTags(content: string) {
+ return content.replace(/</g, '<').replace(/>/g, '>');
+}
export class StreamingMessageParser {
#messages = new Map();
@@ -110,6 +114,7 @@ export class StreamingMessageParser {
// Remove markdown code block syntax if present and file is not markdown
if (!currentAction.filePath.endsWith('.md')) {
content = cleanoutMarkdownSyntax(content);
+ content = cleanEscapedTags(content);
}
content += '\n';
@@ -141,6 +146,7 @@ export class StreamingMessageParser {
if (!currentAction.filePath.endsWith('.md')) {
content = cleanoutMarkdownSyntax(content);
+ content = cleanEscapedTags(content);
}
this._options.callbacks?.onActionStream?.({
diff --git a/app/lib/stores/workbench.ts b/app/lib/stores/workbench.ts
index 92c3508cd..32d5b89f8 100644
--- a/app/lib/stores/workbench.ts
+++ b/app/lib/stores/workbench.ts
@@ -434,7 +434,7 @@ export class WorkbenchStore {
return syncedFiles;
}
- async pushToGitHub(repoName: string, githubUsername?: string, ghToken?: string) {
+ async pushToGitHub(repoName: string, commitMessage?: string, githubUsername?: string, ghToken?: string) {
try {
// Use cookies if username and token are not provided
const githubToken = ghToken || Cookies.get('githubToken');
@@ -523,7 +523,7 @@ export class WorkbenchStore {
const { data: newCommit } = await octokit.git.createCommit({
owner: repo.owner.login,
repo: repo.name,
- message: 'Initial commit from your app',
+ message: commitMessage || 'Initial commit from your app',
tree: newTree.sha,
parents: [latestCommitSha],
});
diff --git a/app/routes/api.chat.ts b/app/routes/api.chat.ts
index 8564bafdb..bbecdae59 100644
--- a/app/routes/api.chat.ts
+++ b/app/routes/api.chat.ts
@@ -10,6 +10,7 @@ import { getFilePaths, selectContext } from '~/lib/.server/llm/select-context';
import type { ContextAnnotation, ProgressAnnotation } from '~/types/context';
import { WORK_DIR } from '~/utils/constants';
import { createSummary } from '~/lib/.server/llm/create-summary';
+import { extractPropertiesFromMessage } from '~/lib/.server/llm/utils';
export async function action(args: ActionFunctionArgs) {
return chatAction(args);
@@ -70,15 +71,21 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
const filePaths = getFilePaths(files || {});
let filteredFiles: FileMap | undefined = undefined;
let summary: string | undefined = undefined;
+ let messageSliceId = 0;
+
+ if (messages.length > 3) {
+ messageSliceId = messages.length - 3;
+ }
if (filePaths.length > 0 && contextOptimization) {
- dataStream.writeData('HI ');
logger.debug('Generating Chat Summary');
- dataStream.writeMessageAnnotation({
+ dataStream.writeData({
type: 'progress',
- value: progressCounter++,
- message: 'Generating Chat Summary',
- } as ProgressAnnotation);
+ label: 'summary',
+ status: 'in-progress',
+ order: progressCounter++,
+ message: 'Analysing Request',
+ } satisfies ProgressAnnotation);
// Create a summary of the chat
console.log(`Messages count: ${messages.length}`);
@@ -99,6 +106,13 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
}
},
});
+ dataStream.writeData({
+ type: 'progress',
+ label: 'summary',
+ status: 'complete',
+ order: progressCounter++,
+ message: 'Analysis Complete',
+ } satisfies ProgressAnnotation);
dataStream.writeMessageAnnotation({
type: 'chatSummary',
@@ -108,11 +122,13 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
// Update context buffer
logger.debug('Updating Context Buffer');
- dataStream.writeMessageAnnotation({
+ dataStream.writeData({
type: 'progress',
- value: progressCounter++,
- message: 'Updating Context Buffer',
- } as ProgressAnnotation);
+ label: 'context',
+ status: 'in-progress',
+ order: progressCounter++,
+ message: 'Determining Files to Read',
+ } satisfies ProgressAnnotation);
// Select context files
console.log(`Messages count: ${messages.length}`);
@@ -152,12 +168,15 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
}),
} as ContextAnnotation);
- dataStream.writeMessageAnnotation({
+ dataStream.writeData({
type: 'progress',
- value: progressCounter++,
- message: 'Context Buffer Updated',
- } as ProgressAnnotation);
- logger.debug('Context Buffer Updated');
+ label: 'context',
+ status: 'complete',
+ order: progressCounter++,
+ message: 'Code Files Selected',
+ } satisfies ProgressAnnotation);
+
+ // logger.debug('Code Files Selected');
}
// Stream the text
@@ -181,6 +200,13 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
totalTokens: cumulativeUsage.totalTokens,
},
});
+ dataStream.writeData({
+ type: 'progress',
+ label: 'response',
+ status: 'complete',
+ order: progressCounter++,
+ message: 'Response Generated',
+ } satisfies ProgressAnnotation);
await new Promise((resolve) => setTimeout(resolve, 0));
// stream.close();
@@ -195,8 +221,14 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
logger.info(`Reached max token limit (${MAX_TOKENS}): Continuing message (${switchesLeft} switches left)`);
+ const lastUserMessage = messages.filter((x) => x.role == 'user').slice(-1)[0];
+ const { model, provider } = extractPropertiesFromMessage(lastUserMessage);
messages.push({ id: generateId(), role: 'assistant', content });
- messages.push({ id: generateId(), role: 'user', content: CONTINUE_PROMPT });
+ messages.push({
+ id: generateId(),
+ role: 'user',
+ content: `[Model: ${model}]\n\n[Provider: ${provider}]\n\n${CONTINUE_PROMPT}`,
+ });
const result = await streamText({
messages,
@@ -207,6 +239,9 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
providerSettings,
promptId,
contextOptimization,
+ contextFiles: filteredFiles,
+ summary,
+ messageSliceId,
});
result.mergeIntoDataStream(dataStream);
@@ -226,6 +261,14 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
},
};
+ dataStream.writeData({
+ type: 'progress',
+ label: 'response',
+ status: 'in-progress',
+ order: progressCounter++,
+ message: 'Generating Response',
+ } satisfies ProgressAnnotation);
+
const result = await streamText({
messages,
env: context.cloudflare?.env,
@@ -237,6 +280,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
contextOptimization,
contextFiles: filteredFiles,
summary,
+ messageSliceId,
});
(async () => {
diff --git a/app/routes/api.llmcall.ts b/app/routes/api.llmcall.ts
index 5dd4c098f..cf75e499a 100644
--- a/app/routes/api.llmcall.ts
+++ b/app/routes/api.llmcall.ts
@@ -7,6 +7,7 @@ import { MAX_TOKENS } from '~/lib/.server/llm/constants';
import { LLMManager } from '~/lib/modules/llm/manager';
import type { ModelInfo } from '~/lib/modules/llm/types';
import { getApiKeysFromCookie, getProviderSettingsFromCookie } from '~/lib/api/cookies';
+import { createScopedLogger } from '~/utils/logger';
export async function action(args: ActionFunctionArgs) {
return llmCallAction(args);
@@ -21,6 +22,8 @@ async function getModelList(options: {
return llmManager.updateModelList(options);
}
+const logger = createScopedLogger('api.llmcall');
+
async function llmCallAction({ context, request }: ActionFunctionArgs) {
const { system, message, model, provider, streamOutput } = await request.json<{
system: string;
@@ -106,6 +109,8 @@ async function llmCallAction({ context, request }: ActionFunctionArgs) {
throw new Error('Provider not found');
}
+ logger.info(`Generating response Provider: ${provider.name}, Model: ${modelDetails.name}`);
+
const result = await generateText({
system,
messages: [
@@ -123,6 +128,7 @@ async function llmCallAction({ context, request }: ActionFunctionArgs) {
maxTokens: dynamicMaxTokens,
toolChoice: 'none',
});
+ logger.info(`Generated response`);
return new Response(JSON.stringify(result), {
status: 200,
diff --git a/app/routes/api.models.ts b/app/routes/api.models.ts
index f75122263..5fad834d0 100644
--- a/app/routes/api.models.ts
+++ b/app/routes/api.models.ts
@@ -67,11 +67,11 @@ export async function loader({
const provider = llmManager.getProvider(params.provider);
if (provider) {
- const staticModels = provider.staticModels;
- const dynamicModels = provider.getDynamicModels
- ? await provider.getDynamicModels(apiKeys, providerSettings, context.cloudflare?.env)
- : [];
- modelList = [...staticModels, ...dynamicModels];
+ modelList = await llmManager.getModelListFromProvider(provider, {
+ apiKeys,
+ providerSettings,
+ serverEnv: context.cloudflare?.env,
+ });
}
} else {
// Update all models
diff --git a/app/types/context.ts b/app/types/context.ts
index 4d8ea02b7..75c21db7e 100644
--- a/app/types/context.ts
+++ b/app/types/context.ts
@@ -11,6 +11,8 @@ export type ContextAnnotation =
export type ProgressAnnotation = {
type: 'progress';
- value: number;
+ label: string;
+ status: 'in-progress' | 'complete';
+ order: number;
message: string;
};
diff --git a/app/utils/folderImport.ts b/app/utils/folderImport.ts
index 759df100b..11449cf32 100644
--- a/app/utils/folderImport.ts
+++ b/app/utils/folderImport.ts
@@ -1,6 +1,6 @@
import type { Message } from 'ai';
import { generateId } from './fileUtils';
-import { detectProjectCommands, createCommandsMessage } from './projectCommands';
+import { detectProjectCommands, createCommandsMessage, escapeBoltTags } from './projectCommands';
export const createChatFromFolder = async (
files: File[],
@@ -38,11 +38,11 @@ export const createChatFromFolder = async (
role: 'assistant',
content: `I've imported the contents of the "${folderName}" folder.${binaryFilesMessage}
-
+
${fileArtifacts
.map(
(file) => `
-${file.content}
+${escapeBoltTags(file.content)}
`,
)
.join('\n\n')}
@@ -61,6 +61,11 @@ ${file.content}
const messages = [userMessage, filesMessage];
if (commandsMessage) {
+ messages.push({
+ role: 'user',
+ id: generateId(),
+ content: 'Setup the codebase and Start the application',
+ });
messages.push(commandsMessage);
}
diff --git a/app/utils/markdown.ts b/app/utils/markdown.ts
index 262222bf5..579b6454f 100644
--- a/app/utils/markdown.ts
+++ b/app/utils/markdown.ts
@@ -54,8 +54,28 @@ export const allowedHTMLElements = [
'tr',
'ul',
'var',
+ 'think',
];
+// Add custom rehype plugin
+function remarkThinkRawContent() {
+ return (tree: any) => {
+ visit(tree, (node: any) => {
+ if (node.type === 'html' && node.value && node.value.startsWith('')) {
+ const cleanedContent = node.value.slice(7);
+ node.value = `${cleanedContent}`;
+
+ return;
+ }
+
+ if (node.type === 'html' && node.value && node.value.startsWith('')) {
+ const cleanedContent = node.value.slice(8);
+ node.value = `
${cleanedContent}`;
+ }
+ });
+ };
+}
+
const rehypeSanitizeOptions: RehypeSanitizeOptions = {
...defaultSchema,
tagNames: allowedHTMLElements,
@@ -79,6 +99,8 @@ export function remarkPlugins(limitedMarkdown: boolean) {
plugins.unshift(limitedMarkdownPlugin);
}
+ plugins.unshift(remarkThinkRawContent);
+
return plugins;
}
diff --git a/app/utils/projectCommands.ts b/app/utils/projectCommands.ts
index 050663aed..34abc0a0d 100644
--- a/app/utils/projectCommands.ts
+++ b/app/utils/projectCommands.ts
@@ -3,7 +3,8 @@ import { generateId } from './fileUtils';
export interface ProjectCommands {
type: string;
- setupCommand: string;
+ setupCommand?: string;
+ startCommand?: string;
followupMessage: string;
}
@@ -33,7 +34,8 @@ export async function detectProjectCommands(files: FileContent[]): Promise${commands.setupCommand}`;
+ }
+
+ if (commands.startCommand) {
+ commandString += `
+${commands.startCommand}
+`;
+ }
+
return {
role: 'assistant',
content: `
-
-${commands.setupCommand}
-
+${commandString}
${commands.followupMessage ? `\n\n${commands.followupMessage}` : ''}`,
id: generateId(),
createdAt: new Date(),
};
}
+
+export function escapeBoltArtifactTags(input: string) {
+ // Regular expression to match boltArtifact tags and their content
+ const regex = /(]*>)([\s\S]*?)(<\/boltArtifact>)/g;
+
+ return input.replace(regex, (match, openTag, content, closeTag) => {
+ // Escape the opening tag
+ const escapedOpenTag = openTag.replace(//g, '>');
+
+ // Escape the closing tag
+ const escapedCloseTag = closeTag.replace(//g, '>');
+
+ // Return the escaped version
+ return `${escapedOpenTag}${content}${escapedCloseTag}`;
+ });
+}
+
+export function escapeBoltAActionTags(input: string) {
+ // Regular expression to match boltArtifact tags and their content
+ const regex = /(]*>)([\s\S]*?)(<\/boltAction>)/g;
+
+ return input.replace(regex, (match, openTag, content, closeTag) => {
+ // Escape the opening tag
+ const escapedOpenTag = openTag.replace(//g, '>');
+
+ // Escape the closing tag
+ const escapedCloseTag = closeTag.replace(//g, '>');
+
+ // Return the escaped version
+ return `${escapedOpenTag}${content}${escapedCloseTag}`;
+ });
+}
+
+export function escapeBoltTags(input: string) {
+ return escapeBoltArtifactTags(escapeBoltAActionTags(input));
+}
diff --git a/app/utils/selectStarterTemplate.ts b/app/utils/selectStarterTemplate.ts
index 4a7536da9..0bbb18929 100644
--- a/app/utils/selectStarterTemplate.ts
+++ b/app/utils/selectStarterTemplate.ts
@@ -59,6 +59,7 @@ Instructions:
5. If no perfect match exists, recommend the closest option
Important: Provide only the selection tags in your response, no additional text.
+MOST IMPORTANT: YOU DONT HAVE TIME TO THINK JUST START RESPONDING BASED ON HUNCH
`;
const templates: Template[] = STARTER_TEMPLATES.filter((t) => !t.name.includes('shadcn'));