This commit is contained in:
Dustin Loring 2025-06-25 19:41:15 +00:00 committed by GitHub
commit fc14fc739d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 185 additions and 11246 deletions

11
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View File

@ -1,2 +1,2 @@
nodejs 20.15.1 nodejs 22.14.0
pnpm 9.4.0 pnpm 10.12.3

View File

@ -1,17 +1,16 @@
import type { Message } from 'ai'; import type { Message } from 'ai';
import React, { type RefCallback } from 'react'; import React, { type RefCallback } from 'react';
import { ClientOnly } from 'remix-utils/client-only'; import { ClientOnly } from 'remix-utils/client-only';
import styles from './BaseChat.module.scss';
import { Messages } from './Messages.client';
import { SendButton } from './SendButton.client';
import { Menu } from '~/components/sidebar/Menu.client'; import { Menu } from '~/components/sidebar/Menu.client';
import { IconButton } from '~/components/ui/IconButton'; import { IconButton } from '~/components/ui/IconButton';
import { Workbench } from '~/components/workbench/Workbench.client'; import { Workbench } from '~/components/workbench/Workbench.client';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
import { Messages } from './Messages.client';
import { SendButton } from './SendButton.client';
import styles from './BaseChat.module.scss';
interface BaseChatProps { interface BaseChatProps {
textareaRef?: React.RefObject<HTMLTextAreaElement> | undefined; textareaRef?: React.RefObject<HTMLTextAreaElement | null> | undefined;
messageRef?: RefCallback<HTMLDivElement> | undefined; messageRef?: RefCallback<HTMLDivElement> | undefined;
scrollRef?: RefCallback<HTMLDivElement> | undefined; scrollRef?: RefCallback<HTMLDivElement> | undefined;
showChat?: boolean; showChat?: boolean;

View File

@ -4,6 +4,7 @@ import { useChat } from 'ai/react';
import { useAnimate } from 'framer-motion'; import { useAnimate } from 'framer-motion';
import { memo, useEffect, useRef, useState } from 'react'; import { memo, useEffect, useRef, useState } from 'react';
import { cssTransition, toast, ToastContainer } from 'react-toastify'; import { cssTransition, toast, ToastContainer } from 'react-toastify';
import { BaseChat } from './BaseChat';
import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from '~/lib/hooks'; import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from '~/lib/hooks';
import { useChatHistory } from '~/lib/persistence'; import { useChatHistory } from '~/lib/persistence';
import { chatStore } from '~/lib/stores/chat'; import { chatStore } from '~/lib/stores/chat';
@ -11,7 +12,6 @@ import { workbenchStore } from '~/lib/stores/workbench';
import { fileModificationsToHTML } from '~/utils/diff'; import { fileModificationsToHTML } from '~/utils/diff';
import { cubicEasingFn } from '~/utils/easings'; import { cubicEasingFn } from '~/utils/easings';
import { createScopedLogger, renderLogger } from '~/utils/logger'; import { createScopedLogger, renderLogger } from '~/utils/logger';
import { BaseChat } from './BaseChat';
const toastAnimation = cssTransition({ const toastAnimation = cssTransition({
enter: 'animated fadeInRight', enter: 'animated fadeInRight',

View File

@ -1,10 +1,9 @@
import { memo, useEffect, useState } from 'react'; import { memo, useEffect, useState } from 'react';
import { bundledLanguages, codeToHtml, isSpecialLang, type BundledLanguage, type SpecialLanguage } from 'shiki'; import { bundledLanguages, codeToHtml, isSpecialLang, type BundledLanguage, type SpecialLanguage } from 'shiki';
import styles from './CodeBlock.module.scss';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
import { createScopedLogger } from '~/utils/logger'; import { createScopedLogger } from '~/utils/logger';
import styles from './CodeBlock.module.scss';
const logger = createScopedLogger('CodeBlock'); const logger = createScopedLogger('CodeBlock');
interface CodeBlockProps { interface CodeBlockProps {

View File

@ -1,12 +1,12 @@
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import ReactMarkdown, { type Components } from 'react-markdown'; import ReactMarkdown, { type Components } from 'react-markdown';
import type { BundledLanguage } from 'shiki'; import type { BundledLanguage } from 'shiki';
import { createScopedLogger } from '~/utils/logger';
import { rehypePlugins, remarkPlugins, allowedHTMLElements } from '~/utils/markdown';
import { Artifact } from './Artifact'; import { Artifact } from './Artifact';
import { CodeBlock } from './CodeBlock'; import { CodeBlock } from './CodeBlock';
import styles from './Markdown.module.scss'; import styles from './Markdown.module.scss';
import { createScopedLogger } from '~/utils/logger';
import { rehypePlugins, remarkPlugins, allowedHTMLElements } from '~/utils/markdown';
const logger = createScopedLogger('MarkdownComponent'); const logger = createScopedLogger('MarkdownComponent');
@ -61,14 +61,15 @@ export const Markdown = memo(({ children, html = false, limitedMarkdown = false
}, []); }, []);
return ( return (
<div className={styles.MarkdownContent}>
<ReactMarkdown <ReactMarkdown
allowedElements={allowedHTMLElements} allowedElements={allowedHTMLElements}
className={styles.MarkdownContent}
components={components} components={components}
remarkPlugins={remarkPlugins(limitedMarkdown)} remarkPlugins={remarkPlugins(limitedMarkdown)}
rehypePlugins={rehypePlugins(html)} rehypePlugins={rehypePlugins(html)}
> >
{children} {children}
</ReactMarkdown> </ReactMarkdown>
</div>
); );
}); });

View File

@ -1,8 +1,8 @@
import type { Message } from 'ai'; import type { Message } from 'ai';
import React from 'react'; import React from 'react';
import { classNames } from '~/utils/classNames';
import { AssistantMessage } from './AssistantMessage'; import { AssistantMessage } from './AssistantMessage';
import { UserMessage } from './UserMessage'; import { UserMessage } from './UserMessage';
import { classNames } from '~/utils/classNames';
interface MessagesProps { interface MessagesProps {
id?: string; id?: string;

View File

@ -1,5 +1,5 @@
import { modificationsRegex } from '~/utils/diff';
import { Markdown } from './Markdown'; import { Markdown } from './Markdown';
import { modificationsRegex } from '~/utils/diff';
interface UserMessageProps { interface UserMessageProps {
content: string; content: string;

View File

@ -17,14 +17,14 @@ import {
type Tooltip, type Tooltip,
} from '@codemirror/view'; } from '@codemirror/view';
import { memo, useEffect, useRef, useState, type MutableRefObject } from 'react'; import { memo, useEffect, useRef, useState, type MutableRefObject } from 'react';
import type { Theme } from '~/types/theme';
import { classNames } from '~/utils/classNames';
import { debounce } from '~/utils/debounce';
import { createScopedLogger, renderLogger } from '~/utils/logger';
import { BinaryContent } from './BinaryContent'; import { BinaryContent } from './BinaryContent';
import { getTheme, reconfigureTheme } from './cm-theme'; import { getTheme, reconfigureTheme } from './cm-theme';
import { indentKeyBinding } from './indent'; import { indentKeyBinding } from './indent';
import { getLanguage } from './languages'; import { getLanguage } from './languages';
import type { Theme } from '~/types/theme';
import { classNames } from '~/utils/classNames';
import { debounce } from '~/utils/debounce';
import { createScopedLogger, renderLogger } from '~/utils/logger';
const logger = createScopedLogger('CodeMirrorEditor'); const logger = createScopedLogger('CodeMirrorEditor');
@ -135,10 +135,10 @@ export const CodeMirrorEditor = memo(
const [languageCompartment] = useState(new Compartment()); const [languageCompartment] = useState(new Compartment());
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
const viewRef = useRef<EditorView>(); const viewRef = useRef<EditorView | null>(null);
const themeRef = useRef<Theme>(); const themeRef = useRef<Theme | null>(null);
const docRef = useRef<EditorDocument>(); const docRef = useRef<EditorDocument | null>(null);
const editorStatesRef = useRef<EditorStates>(); const editorStatesRef = useRef<EditorStates | null>(null);
const onScrollRef = useRef(onScroll); const onScrollRef = useRef(onScroll);
const onChangeRef = useRef(onChange); const onChangeRef = useRef(onChange);
const onSaveRef = useRef(onSave); const onSaveRef = useRef(onSave);
@ -151,7 +151,7 @@ export const CodeMirrorEditor = memo(
onScrollRef.current = onScroll; onScrollRef.current = onScroll;
onChangeRef.current = onChange; onChangeRef.current = onChange;
onSaveRef.current = onSave; onSaveRef.current = onSave;
docRef.current = doc; docRef.current = doc ?? null;
themeRef.current = theme; themeRef.current = theme;
}); });
@ -188,7 +188,7 @@ export const CodeMirrorEditor = memo(
return () => { return () => {
viewRef.current?.destroy(); viewRef.current?.destroy();
viewRef.current = undefined; viewRef.current = null;
}; };
}, []); }, []);

View File

@ -1,8 +1,8 @@
import { Compartment, type Extension } from '@codemirror/state'; import { Compartment, type Extension } from '@codemirror/state';
import { EditorView } from '@codemirror/view'; import { EditorView } from '@codemirror/view';
import { vscodeDark, vscodeLight } from '@uiw/codemirror-theme-vscode'; import { vscodeDark, vscodeLight } from '@uiw/codemirror-theme-vscode';
import type { Theme } from '~/types/theme.js';
import type { EditorSettings } from './CodeMirrorEditor.js'; import type { EditorSettings } from './CodeMirrorEditor.js';
import type { Theme } from '~/types/theme.js';
export const darkTheme = EditorView.theme({}, { dark: true }); export const darkTheme = EditorView.theme({}, { dark: true });
export const themeSelection = new Compartment(); export const themeSelection = new Compartment();

View File

@ -1,9 +1,9 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { ClientOnly } from 'remix-utils/client-only'; import { ClientOnly } from 'remix-utils/client-only';
import { chatStore } from '~/lib/stores/chat';
import { classNames } from '~/utils/classNames';
import { HeaderActionButtons } from './HeaderActionButtons.client'; import { HeaderActionButtons } from './HeaderActionButtons.client';
import { ChatDescription } from '~/lib/persistence/ChatDescription.client'; import { ChatDescription } from '~/lib/persistence/ChatDescription.client';
import { chatStore } from '~/lib/stores/chat';
import { classNames } from '~/utils/classNames';
export function Header() { export function Header() {
const chat = useStore(chatStore); const chat = useStore(chatStore);

View File

@ -23,7 +23,7 @@ export function HeaderActionButtons({}: HeaderActionButtonsProps) {
} }
}} }}
> >
<div className="i-bolt:chat text-sm" /> <div className="i-ph:chat-text-bold" />
</Button> </Button>
<div className="w-[1px] bg-bolt-elements-borderColor" /> <div className="w-[1px] bg-bolt-elements-borderColor" />
<Button <Button

View File

@ -1,14 +1,13 @@
import { motion, type Variants } from 'framer-motion'; import { motion, type Variants } from 'framer-motion';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { HistoryItem } from './HistoryItem';
import { binDates } from './date-binning';
import { Dialog, DialogButton, DialogDescription, DialogRoot, DialogTitle } from '~/components/ui/Dialog'; import { Dialog, DialogButton, DialogDescription, DialogRoot, DialogTitle } from '~/components/ui/Dialog';
import { IconButton } from '~/components/ui/IconButton';
import { ThemeSwitch } from '~/components/ui/ThemeSwitch'; import { ThemeSwitch } from '~/components/ui/ThemeSwitch';
import { db, deleteById, getAll, chatId, type ChatHistoryItem } from '~/lib/persistence'; import { db, deleteById, getAll, chatId, type ChatHistoryItem } from '~/lib/persistence';
import { cubicEasingFn } from '~/utils/easings'; import { cubicEasingFn } from '~/utils/easings';
import { logger } from '~/utils/logger'; import { logger } from '~/utils/logger';
import { HistoryItem } from './HistoryItem';
import { binDates } from './date-binning';
const menuVariants = { const menuVariants = {
closed: { closed: {

View File

@ -1,9 +1,9 @@
import * as RadixDialog from '@radix-ui/react-dialog'; import * as RadixDialog from '@radix-ui/react-dialog';
import { motion, type Variants } from 'framer-motion'; import { motion, type Variants } from 'framer-motion';
import React, { memo, type ReactNode } from 'react'; import React, { memo, type ReactNode } from 'react';
import { IconButton } from './IconButton';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
import { cubicEasingFn } from '~/utils/easings'; import { cubicEasingFn } from '~/utils/easings';
import { IconButton } from './IconButton';
export { Close as DialogClose, Root as DialogRoot } from '@radix-ui/react-dialog'; export { Close as DialogClose, Root as DialogRoot } from '@radix-ui/react-dialog';

View File

@ -1,3 +1,4 @@
import * as React from 'react';
import { memo } from 'react'; import { memo } from 'react';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
@ -20,7 +21,7 @@ type IconButtonWithoutChildrenProps = {
type IconButtonWithChildrenProps = { type IconButtonWithChildrenProps = {
icon?: undefined; icon?: undefined;
children: string | JSX.Element | JSX.Element[]; children: string | React.ReactElement | React.ReactElement[];
} & BaseIconButtonProps; } & BaseIconButtonProps;
type IconButtonProps = IconButtonWithoutChildrenProps | IconButtonWithChildrenProps; type IconButtonProps = IconButtonWithoutChildrenProps | IconButtonWithChildrenProps;

View File

@ -1,3 +1,4 @@
import * as React from 'react';
import { memo } from 'react'; import { memo } from 'react';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
@ -5,7 +6,7 @@ interface PanelHeaderButtonProps {
className?: string; className?: string;
disabledClassName?: string; disabledClassName?: string;
disabled?: boolean; disabled?: boolean;
children: string | JSX.Element | Array<JSX.Element | string>; children: string | React.ReactElement | Array<React.ReactElement | string>;
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void; onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
} }

View File

@ -1,4 +1,5 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import * as React from 'react';
import { memo } from 'react'; import { memo } from 'react';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
import { cubicEasingFn } from '~/utils/easings'; import { cubicEasingFn } from '~/utils/easings';
@ -37,7 +38,7 @@ export const Slider = genericMemo(<T,>({ selected, options, setSelected }: Slide
interface SliderButtonProps { interface SliderButtonProps {
selected: boolean; selected: boolean;
children: string | JSX.Element | Array<JSX.Element | string>; children: string | React.ReactElement | Array<React.ReactElement | string>;
setSelected: () => void; setSelected: () => void;
} }

View File

@ -1,7 +1,7 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { memo, useEffect, useState } from 'react'; import { memo, useEffect, useState } from 'react';
import { themeStore, toggleTheme } from '~/lib/stores/theme';
import { IconButton } from './IconButton'; import { IconButton } from './IconButton';
import { themeStore, toggleTheme } from '~/lib/stores/theme';
interface ThemeSwitchProps { interface ThemeSwitchProps {
className?: string; className?: string;

View File

@ -1,6 +1,9 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { memo, useEffect, useMemo, useRef, useState } from 'react'; import { memo, useEffect, useMemo, useRef, useState } from 'react';
import { Panel, PanelGroup, PanelResizeHandle, type ImperativePanelHandle } from 'react-resizable-panels'; import { Panel, PanelGroup, PanelResizeHandle, type ImperativePanelHandle } from 'react-resizable-panels';
import { FileBreadcrumb } from './FileBreadcrumb';
import { FileTree } from './FileTree';
import { Terminal, type TerminalRef } from './terminal/Terminal';
import { import {
CodeMirrorEditor, CodeMirrorEditor,
type EditorDocument, type EditorDocument,
@ -20,9 +23,6 @@ import { classNames } from '~/utils/classNames';
import { WORK_DIR } from '~/utils/constants'; import { WORK_DIR } from '~/utils/constants';
import { renderLogger } from '~/utils/logger'; import { renderLogger } from '~/utils/logger';
import { isMobile } from '~/utils/mobile'; import { isMobile } from '~/utils/mobile';
import { FileBreadcrumb } from './FileBreadcrumb';
import { FileTree } from './FileTree';
import { Terminal, type TerminalRef } from './terminal/Terminal';
interface EditorPanelProps { interface EditorPanelProps {
files?: FileMap; files?: FileMap;

View File

@ -1,12 +1,12 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { AnimatePresence, motion, type Variants } from 'framer-motion'; import { AnimatePresence, motion, type Variants } from 'framer-motion';
import { memo, useEffect, useRef, useState } from 'react'; import { memo, useEffect, useRef, useState } from 'react';
import FileTree from './FileTree';
import type { FileMap } from '~/lib/stores/files'; import type { FileMap } from '~/lib/stores/files';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
import { WORK_DIR } from '~/utils/constants'; import { WORK_DIR } from '~/utils/constants';
import { cubicEasingFn } from '~/utils/easings'; import { cubicEasingFn } from '~/utils/easings';
import { renderLogger } from '~/utils/logger'; import { renderLogger } from '~/utils/logger';
import FileTree from './FileTree';
const WORK_DIR_REGEX = new RegExp(`^${WORK_DIR.split('/').slice(0, -1).join('/').replaceAll('/', '\\/')}/`); const WORK_DIR_REGEX = new RegExp(`^${WORK_DIR.split('/').slice(0, -1).join('/').replaceAll('/', '\\/')}/`);
@ -87,7 +87,9 @@ export const FileBreadcrumb = memo<FileBreadcrumbProps>(({ files, pathSegments =
<DropdownMenu.Root open={isActive} modal={false}> <DropdownMenu.Root open={isActive} modal={false}>
<DropdownMenu.Trigger asChild> <DropdownMenu.Trigger asChild>
<span <span
ref={(ref) => (segmentRefs.current[index] = ref)} ref={(ref) => {
segmentRefs.current[index] = ref;
}}
className={classNames('flex items-center gap-1.5 cursor-pointer shrink-0', { className={classNames('flex items-center gap-1.5 cursor-pointer shrink-0', {
'text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary': !isActive, 'text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary': !isActive,
'text-bolt-elements-textPrimary underline': isActive, 'text-bolt-elements-textPrimary underline': isActive,

View File

@ -1,8 +1,8 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { PortDropdown } from './PortDropdown';
import { IconButton } from '~/components/ui/IconButton'; import { IconButton } from '~/components/ui/IconButton';
import { workbenchStore } from '~/lib/stores/workbench'; import { workbenchStore } from '~/lib/stores/workbench';
import { PortDropdown } from './PortDropdown';
export const Preview = memo(() => { export const Preview = memo(() => {
const iframeRef = useRef<HTMLIFrameElement>(null); const iframeRef = useRef<HTMLIFrameElement>(null);

View File

@ -1,8 +1,11 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { motion, type HTMLMotionProps, type Variants } from 'framer-motion'; import { motion, type HTMLMotionProps, type Variants } from 'framer-motion';
import { computed } from 'nanostores'; import { computed } from 'nanostores';
import * as React from 'react';
import { memo, useCallback, useEffect } from 'react'; import { memo, useCallback, useEffect } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { EditorPanel } from './EditorPanel';
import { Preview } from './Preview';
import { import {
type OnChangeCallback as OnEditorChange, type OnChangeCallback as OnEditorChange,
type OnScrollCallback as OnEditorScroll, type OnScrollCallback as OnEditorScroll,
@ -14,8 +17,6 @@ import { workbenchStore, type WorkbenchViewType } from '~/lib/stores/workbench';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
import { cubicEasingFn } from '~/utils/easings'; import { cubicEasingFn } from '~/utils/easings';
import { renderLogger } from '~/utils/logger'; import { renderLogger } from '~/utils/logger';
import { EditorPanel } from './EditorPanel';
import { Preview } from './Preview';
interface WorkspaceProps { interface WorkspaceProps {
chatStarted?: boolean; chatStarted?: boolean;
@ -175,7 +176,7 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
}); });
interface ViewProps extends HTMLMotionProps<'div'> { interface ViewProps extends HTMLMotionProps<'div'> {
children: JSX.Element; children: React.ReactElement;
} }
const View = memo(({ children, ...props }: ViewProps) => { const View = memo(({ children, ...props }: ViewProps) => {

View File

@ -2,9 +2,9 @@ import { FitAddon } from '@xterm/addon-fit';
import { WebLinksAddon } from '@xterm/addon-web-links'; import { WebLinksAddon } from '@xterm/addon-web-links';
import { Terminal as XTerm } from '@xterm/xterm'; import { Terminal as XTerm } from '@xterm/xterm';
import { forwardRef, memo, useEffect, useImperativeHandle, useRef } from 'react'; import { forwardRef, memo, useEffect, useImperativeHandle, useRef } from 'react';
import { getTerminalTheme } from './theme';
import type { Theme } from '~/lib/stores/theme'; import type { Theme } from '~/lib/stores/theme';
import { createScopedLogger } from '~/utils/logger'; import { createScopedLogger } from '~/utils/logger';
import { getTerminalTheme } from './theme';
const logger = createScopedLogger('Terminal'); const logger = createScopedLogger('Terminal');
@ -23,7 +23,7 @@ export interface TerminalProps {
export const Terminal = memo( export const Terminal = memo(
forwardRef<TerminalRef, TerminalProps>(({ className, theme, readonly, onTerminalReady, onTerminalResize }, ref) => { forwardRef<TerminalRef, TerminalProps>(({ className, theme, readonly, onTerminalReady, onTerminalResize }, ref) => {
const terminalElementRef = useRef<HTMLDivElement>(null); const terminalElementRef = useRef<HTMLDivElement>(null);
const terminalRef = useRef<XTerm>(); const terminalRef = useRef<XTerm | null>(null);
useEffect(() => { useEffect(() => {
const element = terminalElementRef.current!; const element = terminalElementRef.current!;

View File

@ -5,5 +5,5 @@ export function getAnthropicModel(apiKey: string) {
apiKey, apiKey,
}); });
return anthropic('claude-3-5-sonnet-20240620'); return anthropic('claude-3-5-sonnet-20241022');
} }

View File

@ -1,10 +1,11 @@
import { streamText as _streamText, convertToCoreMessages } from 'ai'; import { streamText as _streamText, convertToCoreMessages } from 'ai';
import { getAPIKey } from '~/lib/.server/llm/api-key';
import { getAnthropicModel } from '~/lib/.server/llm/model';
import { MAX_TOKENS } from './constants'; import { MAX_TOKENS } from './constants';
import { getSystemPrompt } from './prompts'; import { getSystemPrompt } from './prompts';
import { getAPIKey } from '~/lib/.server/llm/api-key';
import { getAnthropicModel } from '~/lib/.server/llm/model';
interface ToolResult<Name extends string, Args, Result> { interface ToolResult<Name extends string, Args, Result> {
state: 'result';
toolCallId: string; toolCallId: string;
toolName: Name; toolName: Name;
args: Args; args: Args;

View File

@ -2,9 +2,9 @@ import { useRef, useCallback } from 'react';
export function useSnapScroll() { export function useSnapScroll() {
const autoScrollRef = useRef(true); const autoScrollRef = useRef(true);
const scrollNodeRef = useRef<HTMLDivElement>(); const scrollNodeRef = useRef<HTMLDivElement | null>(null);
const onScrollRef = useRef<() => void>(); const onScrollRef = useRef<(() => void) | null>(null);
const observerRef = useRef<ResizeObserver>(); const observerRef = useRef<ResizeObserver | null>(null);
const messageRef = useCallback((node: HTMLDivElement | null) => { const messageRef = useCallback((node: HTMLDivElement | null) => {
if (node) { if (node) {
@ -22,7 +22,7 @@ export function useSnapScroll() {
observer.observe(node); observer.observe(node);
} else { } else {
observerRef.current?.disconnect(); observerRef.current?.disconnect();
observerRef.current = undefined; observerRef.current = null;
} }
}, []); }, []);
@ -43,8 +43,8 @@ export function useSnapScroll() {
scrollNodeRef.current?.removeEventListener('scroll', onScrollRef.current); scrollNodeRef.current?.removeEventListener('scroll', onScrollRef.current);
} }
scrollNodeRef.current = undefined; scrollNodeRef.current = null;
onScrollRef.current = undefined; onScrollRef.current = null;
} }
}, []); }, []);

View File

@ -1,6 +1,6 @@
import type { Message } from 'ai'; import type { Message } from 'ai';
import { createScopedLogger } from '~/utils/logger';
import type { ChatHistoryItem } from './useChatHistory'; import type { ChatHistoryItem } from './useChatHistory';
import { createScopedLogger } from '~/utils/logger';
const logger = createScopedLogger('ChatHistory'); const logger = createScopedLogger('ChatHistory');

View File

@ -1,10 +1,10 @@
import { useLoaderData, useNavigate } from '@remix-run/react'; import { useLoaderData, useNavigate } from '@remix-run/react';
import { useState, useEffect } from 'react';
import { atom } from 'nanostores';
import type { Message } from 'ai'; import type { Message } from 'ai';
import { atom } from 'nanostores';
import { useState, useEffect } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { workbenchStore } from '~/lib/stores/workbench';
import { getMessages, getNextId, getUrlId, openDatabase, setMessages } from './db'; import { getMessages, getNextId, getUrlId, openDatabase, setMessages } from './db';
import { workbenchStore } from '~/lib/stores/workbench';
export interface ChatHistoryItem { export interface ChatHistoryItem {
id: string; id: string;

View File

@ -1,10 +1,10 @@
import * as nodePath from 'node:path';
import { WebContainer } from '@webcontainer/api'; import { WebContainer } from '@webcontainer/api';
import { map, type MapStore } from 'nanostores'; import { map, type MapStore } from 'nanostores';
import * as nodePath from 'node:path'; import type { ActionCallbackData } from './message-parser';
import type { BoltAction } from '~/types/actions'; import type { BoltAction } from '~/types/actions';
import { createScopedLogger } from '~/utils/logger'; import { createScopedLogger } from '~/utils/logger';
import { unreachable } from '~/utils/unreachable'; import { unreachable } from '~/utils/unreachable';
import type { ActionCallbackData } from './message-parser';
const logger = createScopedLogger('ActionRunner'); const logger = createScopedLogger('ActionRunner');

View File

@ -1,6 +1,6 @@
import { atom, computed, map, type MapStore, type WritableAtom } from 'nanostores'; import { atom, computed, map, type MapStore, type WritableAtom } from 'nanostores';
import type { EditorDocument, ScrollPosition } from '~/components/editor/codemirror/CodeMirrorEditor';
import type { FileMap, FilesStore } from './files'; import type { FileMap, FilesStore } from './files';
import type { EditorDocument, ScrollPosition } from '~/components/editor/codemirror/CodeMirrorEditor';
export type EditorDocuments = Record<string, EditorDocument>; export type EditorDocuments = Record<string, EditorDocument>;

View File

@ -1,8 +1,8 @@
import { Buffer } from 'node:buffer';
import * as nodePath from 'node:path';
import type { PathWatcherEvent, WebContainer } from '@webcontainer/api'; import type { PathWatcherEvent, WebContainer } from '@webcontainer/api';
import { getEncoding } from 'istextorbinary'; import { getEncoding } from 'istextorbinary';
import { map, type MapStore } from 'nanostores'; import { map, type MapStore } from 'nanostores';
import { Buffer } from 'node:buffer';
import * as nodePath from 'node:path';
import { bufferWatchEvents } from '~/utils/buffer'; import { bufferWatchEvents } from '~/utils/buffer';
import { WORK_DIR } from '~/utils/constants'; import { WORK_DIR } from '~/utils/constants';
import { computeFileModifications } from '~/utils/diff'; import { computeFileModifications } from '~/utils/diff';

View File

@ -1,14 +1,14 @@
import { atom, map, type MapStore, type ReadableAtom, type WritableAtom } from 'nanostores'; import { atom, map, type MapStore, type ReadableAtom, type WritableAtom } from 'nanostores';
import { EditorStore } from './editor';
import { FilesStore, type FileMap } from './files';
import { PreviewsStore } from './previews';
import { TerminalStore } from './terminal';
import type { EditorDocument, ScrollPosition } from '~/components/editor/codemirror/CodeMirrorEditor'; import type { EditorDocument, ScrollPosition } from '~/components/editor/codemirror/CodeMirrorEditor';
import { ActionRunner } from '~/lib/runtime/action-runner'; import { ActionRunner } from '~/lib/runtime/action-runner';
import type { ActionCallbackData, ArtifactCallbackData } from '~/lib/runtime/message-parser'; import type { ActionCallbackData, ArtifactCallbackData } from '~/lib/runtime/message-parser';
import { webcontainer } from '~/lib/webcontainer'; import { webcontainer } from '~/lib/webcontainer';
import type { ITerminal } from '~/types/terminal'; import type { ITerminal } from '~/types/terminal';
import { unreachable } from '~/utils/unreachable'; import { unreachable } from '~/utils/unreachable';
import { EditorStore } from './editor';
import { FilesStore, type FileMap } from './files';
import { PreviewsStore } from './previews';
import { TerminalStore } from './terminal';
export interface ArtifactState { export interface ArtifactState {
id: string; id: string;

View File

@ -2,14 +2,13 @@ import { useStore } from '@nanostores/react';
import type { LinksFunction } from '@remix-run/cloudflare'; import type { LinksFunction } from '@remix-run/cloudflare';
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react'; import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react';
import tailwindReset from '@unocss/reset/tailwind-compat.css?url'; import tailwindReset from '@unocss/reset/tailwind-compat.css?url';
import { themeStore } from './lib/stores/theme';
import { stripIndents } from './utils/stripIndent';
import { createHead } from 'remix-island';
import { useEffect } from 'react';
import reactToastifyStyles from 'react-toastify/dist/ReactToastify.css?url';
import globalStyles from './styles/index.scss?url';
import xtermStyles from '@xterm/xterm/css/xterm.css?url'; import xtermStyles from '@xterm/xterm/css/xterm.css?url';
import { useEffect } from 'react';
import reactToastifyStyles from 'react-toastify/dist/ReactToastify.css?url';
import { createHead } from 'remix-island';
import { themeStore } from './lib/stores/theme';
import globalStyles from './styles/index.scss?url';
import { stripIndents } from './utils/stripIndent';
import 'virtual:uno.css'; import 'virtual:uno.css';

View File

@ -34,13 +34,23 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
const result = await streamText(messages, context.cloudflare.env, options); const result = await streamText(messages, context.cloudflare.env, options);
return stream.switchSource(result.toAIStream()); return (
/**
* WARNING: toAIStream has been removed from streamText.
* See migration guide at https://sdk.vercel.ai/docs/migrations.
*/
stream.switchSource(result.toDataStream())
);
}, },
}; };
const result = await streamText(messages, context.cloudflare.env, options); const result = await streamText(messages, context.cloudflare.env, options);
stream.switchSource(result.toAIStream()); /**
* WARNING: toAIStream has been removed from streamText.
* See migration guide at https://sdk.vercel.ai/docs/migrations.
*/
stream.switchSource(result.toDataStream());
return new Response(stream.readable, { return new Response(stream.readable, {
status: 200, status: 200,

View File

@ -1,11 +1,7 @@
import { type ActionFunctionArgs } from '@remix-run/cloudflare'; import { type ActionFunctionArgs } from '@remix-run/cloudflare';
import { StreamingTextResponse, parseStreamPart } from 'ai';
import { streamText } from '~/lib/.server/llm/stream-text'; import { streamText } from '~/lib/.server/llm/stream-text';
import { stripIndents } from '~/utils/stripIndent'; import { stripIndents } from '~/utils/stripIndent';
const encoder = new TextEncoder();
const decoder = new TextDecoder();
export async function action(args: ActionFunctionArgs) { export async function action(args: ActionFunctionArgs) {
return enhancerAction(args); return enhancerAction(args);
} }
@ -32,23 +28,13 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
context.cloudflare.env, context.cloudflare.env,
); );
const transformStream = new TransformStream({ /**
transform(chunk, controller) { * WARNING: toAIStream has been removed from streamText
const processedChunk = decoder * See migration guide at https://sdk.vercel.ai/docs/migrations.
.decode(chunk) */
.split('\n') // result.toDataStream().pipeThrough(transformStream);
.filter((line) => line !== '')
.map(parseStreamPart)
.map((part) => part.value)
.join('');
controller.enqueue(encoder.encode(processedChunk)); return result.toDataStreamResponse();
},
});
const transformedStream = result.toAIStream().pipeThrough(transformStream);
return new StreamingTextResponse(transformedStream);
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@ -1,3 +1,6 @@
@use '../variables.scss' as *;
@use '../z-index.scss' as *;
[data-resize-handle] { [data-resize-handle] {
position: relative; position: relative;

View File

@ -1,11 +1,11 @@
@import './variables.scss'; @use './variables.scss' as *;
@import './z-index.scss'; @use './z-index.scss' as *;
@import './animations.scss'; @use './animations.scss' as *;
@import './components/terminal.scss'; @use './components/terminal.scss' as *;
@import './components/resize-handle.scss'; @use './components/resize-handle.scss' as *;
@import './components/code.scss'; @use './components/code.scss' as *;
@import './components/editor.scss'; @use './components/editor.scss' as *;
@import './components/toast.scss'; @use './components/toast.scss' as *;
html, html,
body { body {

View File

@ -1,6 +1,6 @@
import { createTwoFilesPatch } from 'diff'; import { createTwoFilesPatch } from 'diff';
import type { FileMap } from '~/lib/stores/files';
import { MODIFICATIONS_TAG_NAME } from './constants'; import { MODIFICATIONS_TAG_NAME } from './constants';
import type { FileMap } from '~/lib/stores/files';
export const modificationsRegex = new RegExp( export const modificationsRegex = new RegExp(
`^<${MODIFICATIONS_TAG_NAME}>[\\s\\S]*?<\\/${MODIFICATIONS_TAG_NAME}>\\s+`, `^<${MODIFICATIONS_TAG_NAME}>[\\s\\S]*?<\\/${MODIFICATIONS_TAG_NAME}>\\s+`,

View File

@ -11,7 +11,7 @@ interface Logger {
setLevel: (level: DebugLevel) => void; 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 isWorker = 'HTMLRewriter' in globalThis;
const supportsColor = !isWorker; const supportsColor = !isWorker;

View File

@ -1,9 +1,9 @@
import type { UnistNode, UnistParent } from 'node_modules/unist-util-visit/lib';
import rehypeRaw from 'rehype-raw'; import rehypeRaw from 'rehype-raw';
import rehypeSanitize, { defaultSchema, type Options as RehypeSanitizeOptions } from 'rehype-sanitize';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import type { PluggableList, Plugin } from 'unified'; import type { PluggableList, Plugin } from 'unified';
import rehypeSanitize, { defaultSchema, type Options as RehypeSanitizeOptions } from 'rehype-sanitize';
import { SKIP, visit } from 'unist-util-visit'; import { SKIP, visit } from 'unist-util-visit';
import type { UnistNode, UnistParent } from 'node_modules/unist-util-visit/lib';
export const allowedHTMLElements = [ export const allowedHTMLElements = [
'a', 'a',

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import { memo } from 'react'; import { memo } from 'react';
export const genericMemo: <T extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>>( export const genericMemo: <T extends keyof React.JSX.IntrinsicElements | React.JSXElementConstructor<any>>(
component: T, component: T,
propsAreEqual?: (prevProps: React.ComponentProps<T>, nextProps: React.ComponentProps<T>) => boolean, propsAreEqual?: (prevProps: React.ComponentProps<T>, nextProps: React.ComponentProps<T>) => boolean,
) => T & { displayName?: string } = memo; ) => T & { displayName?: string } = memo;

View File

@ -1,6 +1,6 @@
import type { WebContainer } from '@webcontainer/api'; import type { WebContainer } from '@webcontainer/api';
import type { ITerminal } from '~/types/terminal';
import { withResolvers } from './promises'; import { withResolvers } from './promises';
import type { ITerminal } from '~/types/terminal';
export async function newShellProcess(webcontainer: WebContainer, terminal: ITerminal) { export async function newShellProcess(webcontainer: WebContainer, terminal: ITerminal) {
const args: string[] = []; const args: string[] = [];

View File

@ -12,6 +12,7 @@ export default [
'@blitz/catch-error-name': 'off', '@blitz/catch-error-name': 'off',
'@typescript-eslint/no-this-alias': 'off', '@typescript-eslint/no-this-alias': 'off',
'@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-empty-object-type': 'off',
'@blitz/lines-around-comment': 'off',
}, },
}, },
{ {

View File

@ -3,7 +3,7 @@
"description": "StackBlitz AI Agent", "description": "StackBlitz AI Agent",
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"packageManager": "pnpm@9.4.0", "packageManager": "pnpm@10.12.3",
"sideEffects": false, "sideEffects": false,
"type": "module", "type": "module",
"scripts": { "scripts": {
@ -20,82 +20,83 @@
"preview": "pnpm run build && pnpm run start" "preview": "pnpm run build && pnpm run start"
}, },
"engines": { "engines": {
"node": ">=18.18.0" "node": ">=22.14.0"
}, },
"dependencies": { "dependencies": {
"@ai-sdk/anthropic": "^0.0.39", "@ai-sdk/anthropic": "^1.2.12",
"@codemirror/autocomplete": "^6.17.0", "@codemirror/autocomplete": "^6.18.6",
"@codemirror/commands": "^6.6.0", "@codemirror/commands": "^6.8.1",
"@codemirror/lang-cpp": "^6.0.2", "@codemirror/lang-cpp": "^6.0.3",
"@codemirror/lang-css": "^6.2.1", "@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.9", "@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-json": "^6.0.1", "@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-markdown": "^6.2.5", "@codemirror/lang-markdown": "^6.3.3",
"@codemirror/lang-python": "^6.1.6", "@codemirror/lang-python": "^6.2.1",
"@codemirror/lang-sass": "^6.0.2", "@codemirror/lang-sass": "^6.0.2",
"@codemirror/lang-wast": "^6.0.2", "@codemirror/lang-wast": "^6.0.2",
"@codemirror/language": "^6.10.2", "@codemirror/language": "^6.11.1",
"@codemirror/search": "^6.5.6", "@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.28.4", "@codemirror/view": "^6.37.2",
"@iconify-json/ph": "^1.1.13", "@iconify-json/ph": "^1.1.13",
"@iconify-json/svg-spinners": "^1.1.2", "@iconify-json/svg-spinners": "^1.1.2",
"@lezer/highlight": "^1.2.0", "@lezer/highlight": "^1.2.0",
"@nanostores/react": "^0.7.2", "@nanostores/react": "^1.0.0",
"@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1",
"@remix-run/cloudflare": "^2.10.2", "@remix-run/cloudflare": "^2.10.2",
"@remix-run/cloudflare-pages": "^2.10.2", "@remix-run/cloudflare-pages": "^2.10.2",
"@remix-run/react": "^2.10.2", "@remix-run/react": "^2.10.2",
"@uiw/codemirror-theme-vscode": "^4.23.0", "@uiw/codemirror-theme-vscode": "^4.23.0",
"@unocss/reset": "^0.61.0", "@unocss/reset": "^66.3.1",
"@webcontainer/api": "1.3.0-internal.10", "@webcontainer/api": "1.6.4-internal.2",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0", "@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"ai": "^3.3.4", "ai": "^4.3.16",
"date-fns": "^3.6.0", "date-fns": "^4.1.0",
"diff": "^5.2.0", "diff": "^8.0.2",
"framer-motion": "^11.2.12", "framer-motion": "^12.19.1",
"isbot": "^4.1.0", "isbot": "^5.1.28",
"istextorbinary": "^9.5.0", "istextorbinary": "^9.5.0",
"jose": "^5.6.3", "jose": "^6.0.11",
"nanostores": "^0.10.3", "nanostores": "^1.0.1",
"react": "^18.2.0", "react": "^19.1.0",
"react-dom": "^18.2.0", "react-dom": "^19.1.0",
"react-hotkeys-hook": "^4.5.0", "react-hotkeys-hook": "^5.1.0",
"react-markdown": "^9.0.1", "react-markdown": "^10.1.0",
"react-resizable-panels": "^2.0.20", "react-resizable-panels": "^3.0.3",
"react-toastify": "^10.0.5", "react-toastify": "^11.0.5",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0", "rehype-sanitize": "^6.0.0",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.0",
"remix-island": "^0.2.0", "remix-island": "^0.2.0",
"remix-utils": "^7.6.0", "remix-utils": "^8.7.0",
"shiki": "^1.9.1", "shiki": "^3.7.0",
"unist-util-visit": "^5.0.0" "unist-util-visit": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@blitz/eslint-plugin": "0.1.0", "@blitz/eslint-plugin": "0.1.4",
"@cloudflare/workers-types": "^4.20240620.0", "@cloudflare/workers-types": "^4.20241127.0",
"@remix-run/dev": "^2.10.0", "@remix-run/dev": "^2.10.0",
"@types/diff": "^5.2.1", "@types/diff": "^7.0.2",
"@types/react": "^18.2.20", "@types/react": "^19.1.8",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^19.1.6",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-ci": "^3.0.1", "is-ci": "^4.1.0",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"prettier": "^3.3.2", "prettier": "^3.3.2",
"sass-embedded": "^1.89.2",
"typescript": "^5.5.2", "typescript": "^5.5.2",
"unified": "^11.0.5", "unified": "^11.0.5",
"unocss": "^0.61.3", "unocss": "^66.3.1",
"vite": "^5.3.1", "vite": "^7.0.0",
"vite-plugin-node-polyfills": "^0.22.0", "vite-plugin-node-polyfills": "^0.23.0",
"vite-plugin-optimize-css-modules": "^1.1.0", "vite-plugin-optimize-css-modules": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^5.1.4",
"vitest": "^2.0.1", "vitest": "^3.2.4",
"wrangler": "^3.63.2", "wrangler": "^4.21.2",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"resolutions": { "resolutions": {

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
import { globSync } from 'fast-glob';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import { basename } from 'node:path'; import { basename } from 'node:path';
import { globSync } from 'fast-glob';
import { defineConfig, presetIcons, presetUno, transformerDirectives } from 'unocss'; import { defineConfig, presetIcons, presetUno, transformerDirectives } from 'unocss';
const iconPaths = globSync('./icons/*.svg'); const iconPaths = globSync('./icons/*.svg');

View File

@ -20,6 +20,7 @@ export default defineConfig((config) => {
v3_fetcherPersist: true, v3_fetcherPersist: true,
v3_relativeSplatPath: true, v3_relativeSplatPath: true,
v3_throwAbortReason: true, v3_throwAbortReason: true,
v3_lazyRouteDiscovery: true,
}, },
}), }),
UnoCSS(), UnoCSS(),