fix: remove monorepo

This commit is contained in:
Sam Denty 2024-09-25 19:54:09 +01:00
parent d364a6f774
commit 6fb59d2bc5
No known key found for this signature in database
GPG Key ID: 7B4EAF7B9E291B79
137 changed files with 194 additions and 1229 deletions

View File

@ -45,7 +45,6 @@ jobs:
with: with:
wranglerVersion: '* -w' wranglerVersion: '* -w'
packageManager: pnpm packageManager: pnpm
workingDirectory: 'packages/bolt'
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy command: pages deploy

View File

@ -1,55 +1,84 @@
# Bolt Monorepo # Bolt
Welcome to the Bolt monorepo! This repository contains the codebase for Bolt, an AI assistant developed by StackBlitz. Bolt is an AI assistant developed by StackBlitz. This package contains the UI interface for Bolt as well as the server components, built using [Remix Run](https://remix.run/).
## Repository Structure ## Prerequisites
Currently, this monorepo contains a single package: Before you begin, ensure you have the following installed:
- [`bolt`](packages/bolt): The main package containing the UI interface for Bolt as well as the server components.
As the project grows, additional packages may be added to this workspace.
## Getting Started
### Prerequisites
- Node.js (v20.15.1) - Node.js (v20.15.1)
- pnpm (v9.4.0) - pnpm (v9.4.0)
### Installation ## Setup
1. Clone the repository: 1. Clone the repository (if you haven't already):
```bash ```bash
git clone https://github.com/stackblitz/bolt.git git clone https://github.com/stackblitz/bolt.git
cd bolt
``` ```
2. Install dependencies: 2. Install dependencies:
```bash ```bash
pnpm i pnpm install
``` ```
3. Optionally, init git hooks: 3. Create a `.env.local` file in the root directory and add your Anthropic API key:
```bash ```
pnpmx husky ANTHROPIC_API_KEY=XXX
``` ```
### Development Optionally, you an set the debug level or disable authentication:
To start developing the Bolt UI: ```
VITE_LOG_LEVEL=debug
1. Navigate to the bolt package: VITE_DISABLE_AUTH=1
```bash
cd packages/bolt
``` ```
2. Start the development server: If you want to run authentication against a local StackBlitz instance, add:
```
VITE_CLIENT_ORIGIN=https://local.stackblitz.com:3000
```
**Important**: Never commit your `.env.local` file to version control. It's already included in .gitignore.
## Available Scripts
- `pnpm run dev`: Starts the development server.
- `pnpm run build`: Builds the project.
- `pnpm run start`: Runs the built application locally using Wrangler Pages. This script uses `bindings.sh` to set up necessary bindings so you don't have to duplicate environment variables.
- `pnpm run preview`: Builds the project and then starts it locally, useful for testing the production build. Note, HTTP streaming currently doesn't work as expected with `wrangler pages dev`.
- `pnpm test:` Runs the test suite using Vitest.
- `pnpm run typecheck`: Runs TypeScript type checking.
- `pnpm run typegen`: Generates TypeScript types using Wrangler.
- `pnpm run deploy`: Builds the project and deploys it to Cloudflare Pages.
## Development
To start the development server:
```bash ```bash
pnpm run dev pnpm run dev
``` ```
This will start the Remix Vite development server.
## Testing
Run the test suite with:
```bash
pnpm test
```
## Deployment
To deploy the application to Cloudflare Pages:
```bash
pnpm run deploy
```
Make sure you have the necessary permissions and Wrangler is correctly configured for your Cloudflare account.

View File

@ -4,7 +4,6 @@ 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 { AnalyticsAction, AnalyticsTrackEvent, sendAnalyticsEvent } from '~/lib/analytics';
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';
@ -195,18 +194,6 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
resetEnhancer(); resetEnhancer();
textareaRef.current?.blur(); textareaRef.current?.blur();
const event = messages.length === 0 ? AnalyticsTrackEvent.ChatCreated : AnalyticsTrackEvent.MessageSent;
sendAnalyticsEvent({
action: AnalyticsAction.Track,
payload: {
event,
properties: {
message: _input,
},
},
});
}; };
const [messageRef, scrollRef] = useSnapScroll(); const [messageRef, scrollRef] = useSnapScroll();

View File

@ -2,7 +2,6 @@ import { useStore } from '@nanostores/react';
import { chatStore } from '~/lib/stores/chat'; import { chatStore } from '~/lib/stores/chat';
import { workbenchStore } from '~/lib/stores/workbench'; import { workbenchStore } from '~/lib/stores/workbench';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
import { OpenStackBlitz } from './OpenStackBlitz.client';
interface HeaderActionButtonsProps {} interface HeaderActionButtonsProps {}
@ -40,9 +39,6 @@ export function HeaderActionButtons({}: HeaderActionButtonsProps) {
<div className="i-ph:code-bold" /> <div className="i-ph:code-bold" />
</Button> </Button>
</div> </div>
<div className="flex ml-2">
<OpenStackBlitz />
</div>
</div> </div>
); );
} }

38
app/lib/analytics.ts Normal file
View File

@ -0,0 +1,38 @@
import { CLIENT_ORIGIN } from '~/lib/constants';
import { request as doRequest } from '~/lib/fetch';
export interface Identity {
userId?: string | null;
guestId?: string | null;
segmentWriteKey?: string | null;
avatar?: string;
}
const MESSAGE_PREFIX = 'Bolt';
export enum AnalyticsTrackEvent {
MessageSent = `${MESSAGE_PREFIX} Message Sent`,
MessageComplete = `${MESSAGE_PREFIX} Message Complete`,
ChatCreated = `${MESSAGE_PREFIX} Chat Created`,
}
export async function identifyUser(access: string): Promise<Identity | undefined> {
const response = await doRequest(`${CLIENT_ORIGIN}/api/identify`, {
method: 'GET',
headers: { authorization: `Bearer ${access}` },
});
const body = await response.json();
if (!response.ok) {
return undefined;
}
// convert numerical identity values to strings
const stringified = Object.entries(body).map(([key, value]) => [
key,
typeof value === 'number' ? value.toString() : value,
]);
return Object.fromEntries(stringified) as Identity;
}

View File

@ -3,7 +3,6 @@ import { useState, useEffect } from 'react';
import { atom } from 'nanostores'; import { atom } from 'nanostores';
import type { Message } from 'ai'; import type { Message } from 'ai';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { AnalyticsAction, sendAnalyticsEvent } from '~/lib/analytics';
import { workbenchStore } from '~/lib/stores/workbench'; import { workbenchStore } from '~/lib/stores/workbench';
import { getMessages, getNextId, getUrlId, openDatabase, setMessages } from './db'; import { getMessages, getNextId, getUrlId, openDatabase, setMessages } from './db';
@ -107,14 +106,4 @@ function navigateChat(nextId: string) {
url.pathname = `/chat/${nextId}`; url.pathname = `/chat/${nextId}`;
window.history.replaceState({}, '', url); window.history.replaceState({}, '', url);
// since the `replaceState` call doesn't trigger a page reload, we need to manually log this event
sendAnalyticsEvent({
action: AnalyticsAction.Page,
payload: {
properties: {
url: url.href,
},
},
});
} }

View File

@ -1,9 +1,7 @@
import { useStore } from '@nanostores/react'; 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, useLocation } 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 { useEffect } from 'react';
import { sendAnalyticsEvent, AnalyticsAction } from './lib/analytics';
import { themeStore } from './lib/stores/theme'; import { themeStore } from './lib/stores/theme';
import { stripIndents } from './utils/stripIndent'; import { stripIndents } from './utils/stripIndent';
@ -55,20 +53,6 @@ const inlineThemeCode = stripIndents`
export function Layout({ children }: { children: React.ReactNode }) { export function Layout({ children }: { children: React.ReactNode }) {
const theme = useStore(themeStore); const theme = useStore(themeStore);
const { pathname } = useLocation();
// log page events when the window location changes
useEffect(() => {
sendAnalyticsEvent({
action: AnalyticsAction.Page,
payload: {
properties: {
url: window.location.href,
},
},
});
}, [pathname]);
return ( return (
<html lang="en" data-theme={theme}> <html lang="en" data-theme={theme}>
<head> <head>

View File

@ -4,14 +4,12 @@ import { MAX_RESPONSE_SEGMENTS, MAX_TOKENS } from '~/lib/.server/llm/constants';
import { CONTINUE_PROMPT } from '~/lib/.server/llm/prompts'; import { CONTINUE_PROMPT } from '~/lib/.server/llm/prompts';
import { streamText, type Messages, type StreamingOptions } from '~/lib/.server/llm/stream-text'; import { streamText, type Messages, type StreamingOptions } from '~/lib/.server/llm/stream-text';
import SwitchableStream from '~/lib/.server/llm/switchable-stream'; import SwitchableStream from '~/lib/.server/llm/switchable-stream';
import type { Session } from '~/lib/.server/sessions';
import { AnalyticsAction, AnalyticsTrackEvent, sendEventInternal } from '~/lib/analytics';
export async function action(args: ActionFunctionArgs) { export async function action(args: ActionFunctionArgs) {
return actionWithAuth(args, chatAction); return actionWithAuth(args, chatAction);
} }
async function chatAction({ context, request }: ActionFunctionArgs, session: Session) { async function chatAction({ context, request }: ActionFunctionArgs) {
const { messages } = await request.json<{ messages: Messages }>(); const { messages } = await request.json<{ messages: Messages }>();
const stream = new SwitchableStream(); const stream = new SwitchableStream();
@ -19,19 +17,8 @@ async function chatAction({ context, request }: ActionFunctionArgs, session: Ses
try { try {
const options: StreamingOptions = { const options: StreamingOptions = {
toolChoice: 'none', toolChoice: 'none',
onFinish: async ({ text: content, finishReason, usage }) => { onFinish: async ({ text: content, finishReason }) => {
if (finishReason !== 'length') { if (finishReason !== 'length') {
await sendEventInternal(session, {
action: AnalyticsAction.Track,
payload: {
event: AnalyticsTrackEvent.MessageComplete,
properties: {
usage,
finishReason,
},
},
});
return stream.close(); return stream.close();
} }

Some files were not shown because too many files have changed in this diff Show More