From 621b8804d86f3345df98c6233de3b4e300f5df11 Mon Sep 17 00:00:00 2001 From: Dominic Elm Date: Wed, 17 Jul 2024 20:54:46 +0200 Subject: [PATCH] feat: add first version of workbench, increase token limit, improve system prompt --- eslint.config.mjs | 2 + .../bolt/app/components/chat/Artifact.tsx | 186 ++- .../bolt/app/components/chat/BaseChat.tsx | 19 +- .../bolt/app/components/chat/Chat.client.tsx | 6 +- .../bolt/app/components/chat/CodeBlock.tsx | 119 +- .../app/components/chat/Markdown.module.scss | 54 +- .../bolt/app/components/chat/Markdown.tsx | 16 +- .../app/components/chat/Messages.client.tsx | 6 +- .../editor/codemirror/BinaryContent.tsx | 7 + .../editor/codemirror/CodeMirrorEditor.tsx | 339 ++++++ .../components/editor/codemirror/cm-theme.ts | 165 +++ .../components/editor/codemirror/indent.ts | 68 ++ .../components/editor/codemirror/languages.ts | 91 ++ .../components/editor/codemirror/styles.css | 133 +++ .../editor/codemirror/themes/vscode-dark.ts | 76 ++ .../bolt/app/components/ui/IconButton.tsx | 2 +- .../app/components/workbench/EditorPanel.tsx | 21 + .../app/components/workbench/FileTree.tsx | 3 + .../components/workbench/FileTreePanel.tsx | 9 + .../bolt/app/components/workbench/Preview.tsx | 63 + .../components/workbench/Workbench.client.tsx | 69 ++ .../components/workspace/Workspace.client.tsx | 55 - .../bolt/app/lib/.server/llm/constants.ts | 2 + packages/bolt/app/lib/.server/llm/prompts.ts | 65 +- .../bolt/app/lib/.server/llm/stream-text.ts | 38 + .../bolt/app/lib/hooks/useMessageParser.ts | 27 +- .../bolt/app/lib/hooks/usePromptEnhancer.ts | 2 +- .../bolt/app/lib/runtime/action-runner.ts | 68 ++ .../app/lib/runtime/message-parser.spec.ts | 16 +- .../bolt/app/lib/runtime/message-parser.ts | 156 ++- packages/bolt/app/lib/stores/previews.ts | 42 + packages/bolt/app/lib/stores/theme.ts | 33 + packages/bolt/app/lib/stores/workbench.ts | 153 +++ packages/bolt/app/lib/stores/workspace.ts | 42 - packages/bolt/app/lib/webcontainer/index.ts | 3 +- packages/bolt/app/root.tsx | 25 +- packages/bolt/app/routes/_index.tsx | 8 +- packages/bolt/app/routes/api.chat.ts | 31 +- packages/bolt/app/routes/api.enhancer.ts | 37 +- packages/bolt/app/routes/login.tsx | 4 +- packages/bolt/app/styles/variables.scss | 27 +- packages/bolt/app/types/actions.ts | 18 + packages/bolt/app/types/artifact.ts | 4 + packages/bolt/app/types/theme.ts | 1 + packages/bolt/app/utils/debounce.ts | 17 + packages/bolt/app/utils/unreachable.ts | 3 + packages/bolt/package.json | 24 +- packages/bolt/uno.config.ts | 3 + packages/bolt/vite.config.ts | 6 + pnpm-lock.yaml | 1038 ++++++++++++++++- 50 files changed, 2979 insertions(+), 423 deletions(-) create mode 100644 packages/bolt/app/components/editor/codemirror/BinaryContent.tsx create mode 100644 packages/bolt/app/components/editor/codemirror/CodeMirrorEditor.tsx create mode 100644 packages/bolt/app/components/editor/codemirror/cm-theme.ts create mode 100644 packages/bolt/app/components/editor/codemirror/indent.ts create mode 100644 packages/bolt/app/components/editor/codemirror/languages.ts create mode 100644 packages/bolt/app/components/editor/codemirror/styles.css create mode 100644 packages/bolt/app/components/editor/codemirror/themes/vscode-dark.ts create mode 100644 packages/bolt/app/components/workbench/EditorPanel.tsx create mode 100644 packages/bolt/app/components/workbench/FileTree.tsx create mode 100644 packages/bolt/app/components/workbench/FileTreePanel.tsx create mode 100644 packages/bolt/app/components/workbench/Preview.tsx create mode 100644 packages/bolt/app/components/workbench/Workbench.client.tsx delete mode 100644 packages/bolt/app/components/workspace/Workspace.client.tsx create mode 100644 packages/bolt/app/lib/.server/llm/constants.ts create mode 100644 packages/bolt/app/lib/.server/llm/stream-text.ts create mode 100644 packages/bolt/app/lib/runtime/action-runner.ts create mode 100644 packages/bolt/app/lib/stores/previews.ts create mode 100644 packages/bolt/app/lib/stores/theme.ts create mode 100644 packages/bolt/app/lib/stores/workbench.ts delete mode 100644 packages/bolt/app/lib/stores/workspace.ts create mode 100644 packages/bolt/app/types/actions.ts create mode 100644 packages/bolt/app/types/artifact.ts create mode 100644 packages/bolt/app/types/theme.ts create mode 100644 packages/bolt/app/utils/debounce.ts create mode 100644 packages/bolt/app/utils/unreachable.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index c904562..8e27ad6 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,6 +9,8 @@ export default [ { rules: { '@blitz/catch-error-name': 'off', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/no-empty-object-type': 'off', }, }, { diff --git a/packages/bolt/app/components/chat/Artifact.tsx b/packages/bolt/app/components/chat/Artifact.tsx index 188e4db..781b1df 100644 --- a/packages/bolt/app/components/chat/Artifact.tsx +++ b/packages/bolt/app/components/chat/Artifact.tsx @@ -1,34 +1,176 @@ import { useStore } from '@nanostores/react'; -import { workspaceStore } from '~/lib/stores/workspace'; +import { AnimatePresence, motion } from 'framer-motion'; +import { computed } from 'nanostores'; +import { useState } from 'react'; +import { createHighlighter, type BundledLanguage, type BundledTheme, type HighlighterGeneric } from 'shiki'; +import { getArtifactKey, workbenchStore, type ActionState } from '../../lib/stores/workbench'; +import { classNames } from '../../utils/classNames'; +import { cubicEasingFn } from '../../utils/easings'; +import { IconButton } from '../ui/IconButton'; + +const highlighterOptions = { + langs: ['shell'], + themes: ['light-plus', 'dark-plus'], +}; + +const shellHighlighter: HighlighterGeneric = + import.meta.hot?.data.shellHighlighter ?? (await createHighlighter(highlighterOptions)); + +if (import.meta.hot) { + import.meta.hot.data.shellHighlighter = shellHighlighter; +} interface ArtifactProps { + artifactId: string; messageId: string; } -export function Artifact({ messageId }: ArtifactProps) { - const artifacts = useStore(workspaceStore.artifacts); +export function Artifact({ artifactId, messageId }: ArtifactProps) { + const [showActions, setShowActions] = useState(false); - const artifact = artifacts[messageId]; + const artifacts = useStore(workbenchStore.artifacts); + const artifact = artifacts[getArtifactKey(artifactId, messageId)]; + + const actions = useStore( + computed(artifact.actions, (actions) => { + return Object.values(actions); + }), + ); return ( - + + {actions.length && ( + setShowActions(!showActions)} + > +
+
+
+
+ )} +
+ + + {showActions && actions.length > 0 && ( + +
+ +

Actions

+
    + {actions.map((action, index) => { + const { status, type, content, abort } = action; + + return ( +
  • +
    +
    + {status === 'running' ? ( +
    + ) : status === 'pending' ? ( +
    + ) : status === 'complete' ? ( +
    + ) : status === 'failed' || status === 'aborted' ? ( +
    + ) : null} +
    + {type === 'file' ? ( +
    + Create {action.filePath} +
    + ) : type === 'shell' ? ( +
    + Run command + {abort !== undefined && status === 'running' && ( + abort()} /> + )} +
    + ) : null} +
    + {type === 'shell' && } +
  • + ); + })} +
+
+
+
)} - -
-
{artifact?.title}
- Click to open code -
- +
+ + ); +} + +function getTextColor(status: ActionState['status']) { + switch (status) { + case 'pending': { + return 'text-gray-500'; + } + case 'running': { + return 'text-gray-1000'; + } + case 'complete': { + return 'text-positive-600'; + } + case 'aborted': { + return 'text-gray-600'; + } + case 'failed': { + return 'text-negative-600'; + } + default: { + return undefined; + } + } +} + +interface ShellCodeBlockProps { + classsName?: string; + code: string; +} + +function ShellCodeBlock({ classsName, code }: ShellCodeBlockProps) { + return ( +
); } diff --git a/packages/bolt/app/components/chat/BaseChat.tsx b/packages/bolt/app/components/chat/BaseChat.tsx index 64b4712..e61770d 100644 --- a/packages/bolt/app/components/chat/BaseChat.tsx +++ b/packages/bolt/app/components/chat/BaseChat.tsx @@ -2,16 +2,14 @@ import type { Message } from 'ai'; import type { LegacyRef } from 'react'; import React from 'react'; import { ClientOnly } from 'remix-utils/client-only'; -import { IconButton } from '~/components/ui/IconButton'; -import { Workspace } from '~/components/workspace/Workspace.client'; -import { classNames } from '~/utils/classNames'; +import { classNames } from '../../utils/classNames'; +import { IconButton } from '../ui/IconButton'; +import { Workbench } from '../workbench/Workbench.client'; import { Messages } from './Messages.client'; import { SendButton } from './SendButton.client'; interface BaseChatProps { textareaRef?: LegacyRef | undefined; - messagesSlot?: React.ReactNode; - workspaceSlot?: React.ReactNode; chatStarted?: boolean; isStreaming?: boolean; messages?: Message[]; @@ -80,14 +78,17 @@ export const BaseChat = React.forwardRef(