├── .env.example
├── .gitignore
├── LICENSE
├── README.md
├── app
├── (auth)
│ ├── chat
│ │ └── [id]
│ │ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── api
│ ├── auth
│ │ └── [...nextauth]
│ │ │ └── route.ts
│ ├── chat
│ │ └── route.ts
│ ├── chats
│ │ ├── [id]
│ │ │ ├── delete
│ │ │ │ └── route.ts
│ │ │ ├── route.ts
│ │ │ └── update
│ │ │ │ └── route.ts
│ │ ├── export
│ │ │ └── [id]
│ │ │ │ └── route.ts
│ │ └── route.ts
│ ├── deploy-to-machine
│ │ └── route.ts
│ ├── deploy
│ │ └── route.ts
│ ├── enhancer
│ │ └── route.ts
│ ├── git-proxy
│ │ └── [...path]
│ │ │ └── route.ts
│ ├── git
│ │ └── getGitHubRepoContent
│ │ │ └── route.ts
│ ├── health
│ │ └── route.ts
│ ├── llmcall
│ │ └── route.ts
│ ├── models
│ │ └── route.ts
│ └── usage
│ │ └── get-credits
│ │ └── route.ts
├── background
│ └── page.tsx
├── favicon.ico
├── globals.css
├── layout.tsx
├── login
│ ├── login-git-button.tsx
│ ├── login-google-button.tsx
│ ├── login-notion-button.tsx
│ └── page.tsx
├── privacy-policy
│ ├── markdown.css
│ └── page.tsx
└── terms
│ ├── markdown.css
│ └── page.tsx
├── auth.ts
├── components
├── @settings
│ ├── core
│ │ ├── AvatarDropdown.tsx
│ │ ├── ControlPanel.tsx
│ │ ├── constants.ts
│ │ └── types.ts
│ ├── index.ts
│ ├── shared
│ │ └── components
│ │ │ ├── DraggableTabList.tsx
│ │ │ ├── TabManagement.tsx
│ │ │ └── TabTile.tsx
│ ├── tabs
│ │ ├── connections
│ │ │ ├── ConnectionsTab.tsx
│ │ │ ├── components
│ │ │ │ ├── ConnectionForm.tsx
│ │ │ │ ├── CreateBranchDialog.tsx
│ │ │ │ ├── PushToGitHubDialog.tsx
│ │ │ │ └── RepositorySelectionDialog.tsx
│ │ │ └── types
│ │ │ │ └── GitHub.ts
│ │ ├── data
│ │ │ └── DataTab.tsx
│ │ ├── debug
│ │ │ └── DebugTab.tsx
│ │ ├── event-logs
│ │ │ └── EventLogsTab.tsx
│ │ ├── features
│ │ │ └── FeaturesTab.tsx
│ │ ├── notifications
│ │ │ └── NotificationsTab.tsx
│ │ ├── profile
│ │ │ └── ProfileTab.tsx
│ │ ├── providers
│ │ │ ├── cloud
│ │ │ │ └── CloudProvidersTab.tsx
│ │ │ ├── local
│ │ │ │ ├── LocalProvidersTab.tsx
│ │ │ │ └── OllamaModelInstaller.tsx
│ │ │ ├── service-status
│ │ │ │ ├── ServiceStatusTab.tsx
│ │ │ │ ├── base-provider.ts
│ │ │ │ ├── provider-factory.ts
│ │ │ │ ├── providers
│ │ │ │ │ ├── amazon-bedrock.ts
│ │ │ │ │ ├── anthropic.ts
│ │ │ │ │ ├── cohere.ts
│ │ │ │ │ ├── deepseek.ts
│ │ │ │ │ ├── google.ts
│ │ │ │ │ ├── groq.ts
│ │ │ │ │ ├── huggingface.ts
│ │ │ │ │ ├── hyperbolic.ts
│ │ │ │ │ ├── mistral.ts
│ │ │ │ │ ├── openai.ts
│ │ │ │ │ ├── openrouter.ts
│ │ │ │ │ ├── perplexity.ts
│ │ │ │ │ ├── together.ts
│ │ │ │ │ └── xai.ts
│ │ │ │ └── types.ts
│ │ │ └── status
│ │ │ │ └── ServiceStatusTab.tsx
│ │ ├── settings
│ │ │ └── SettingsTab.tsx
│ │ ├── task-manager
│ │ │ └── TaskManagerTab.tsx
│ │ └── update
│ │ │ └── UpdateTab.tsx
│ └── utils
│ │ ├── animations.ts
│ │ └── tab-helpers.ts
├── ClientOnly.tsx
├── auth
│ └── LoginModal.tsx
├── chat
│ ├── APIKeyManager.tsx
│ ├── Artifact.tsx
│ ├── AssistantMessage.tsx
│ ├── BaseChat.module.scss
│ ├── BaseChat.tsx
│ ├── Chat.client.tsx
│ ├── ChatAlert.tsx
│ ├── CodeBlock.module.scss
│ ├── CodeBlock.tsx
│ ├── ExamplePrompts.tsx
│ ├── FilePreview.tsx
│ ├── GitCloneButton.tsx
│ ├── IdeaShortcut.tsx
│ ├── ImportFolderButton.tsx
│ ├── Markdown.module.scss
│ ├── Markdown.tsx
│ ├── Messages.client.tsx
│ ├── ModelSelector.tsx
│ ├── ProgressCompilation.tsx
│ ├── ScreenshotStateManager.tsx
│ ├── SendButton.client.tsx
│ ├── SpeechRecognition.tsx
│ ├── StarterTemplates.tsx
│ ├── ThoughtBox.tsx
│ ├── UserMessage.tsx
│ └── chatExportAndImport
│ │ ├── ExportChatButton.tsx
│ │ └── ImportButtons.tsx
├── editor
│ └── codemirror
│ │ ├── BinaryContent.tsx
│ │ ├── CodeMirrorEditor.tsx
│ │ ├── _CodeMirrorEditor.tsx
│ │ ├── cm-theme.ts
│ │ ├── indent.ts
│ │ └── languages.ts
├── git
│ └── GitUrlImport.client.tsx
├── header
│ ├── Header.tsx
│ └── HeaderActionButtons.client.tsx
├── sidebar
│ ├── HistoryItem.tsx
│ ├── Menu.client.tsx
│ ├── date-binning.ts
│ └── left.tsx
├── ui
│ ├── BackgroundMeteor
│ │ ├── Background.tsx
│ │ ├── MeteorShower.tsx
│ │ ├── NatureScene.tsx
│ │ ├── sceneElements.ts
│ │ └── throttle.ts
│ ├── BackgroundRays
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── Badge.tsx
│ ├── Button.tsx
│ ├── Card.tsx
│ ├── Collapsible.tsx
│ ├── Dialog.tsx
│ ├── Dropdown.tsx
│ ├── IconButton.tsx
│ ├── Input.tsx
│ ├── Label.tsx
│ ├── LoadingDots.tsx
│ ├── LoadingOverlay.tsx
│ ├── PanelHeader.tsx
│ ├── PanelHeaderButton.tsx
│ ├── Popover.tsx
│ ├── Progress-ui.tsx
│ ├── Progress.tsx
│ ├── ScrollArea.tsx
│ ├── Separator.tsx
│ ├── SettingsButton.tsx
│ ├── Slider.tsx
│ ├── Switch.tsx
│ ├── Tabs.tsx
│ ├── ThemeSwitch.tsx
│ ├── Tooltip.tsx
│ └── use-toast.ts
└── workbench
│ ├── EditorPanel.tsx
│ ├── FileBreadcrumb.tsx
│ ├── FileTree.tsx
│ ├── PortDropdown.tsx
│ ├── Preview.tsx
│ ├── ScreenshotSelector.tsx
│ ├── Workbench.client.tsx
│ └── terminal
│ ├── Terminal.tsx
│ ├── TerminalTabs.tsx
│ └── theme.ts
├── db
├── edge-db.ts
├── index.ts
└── schema.ts
├── drizzle.config.ts
├── eslint.config.mjs
├── icons
├── angular.svg
├── astro.svg
├── chat.svg
├── logo-text.svg
├── logo.svg
├── nativescript.svg
├── nextjs.svg
├── nuxt.svg
├── qwik.svg
├── react.svg
├── remix.svg
├── remotion.svg
├── slidev.svg
├── stars.svg
├── svelte.svg
├── typescript.svg
├── vite.svg
└── vue.svg
├── lib
├── .server
│ └── llm
│ │ ├── constants.ts
│ │ ├── create-summary.ts
│ │ ├── prompts.ts
│ │ ├── select-context.ts
│ │ ├── stream-text.ts
│ │ ├── switchable-stream.ts
│ │ └── utils.ts
├── api
│ ├── connection.ts
│ ├── cookies.ts
│ ├── debug.ts
│ ├── features.ts
│ ├── notifications.ts
│ └── updates.ts
├── common
│ ├── prompt-library.ts
│ └── prompts
│ │ ├── optimized.ts
│ │ └── prompts.ts
├── crypto.ts
├── fetch.ts
├── hooks
│ ├── index.ts
│ ├── useConnectionStatus.ts
│ ├── useDebugStatus.ts
│ ├── useEditChatDescription.ts
│ ├── useFeatures.ts
│ ├── useGit.ts
│ ├── useLocalProviders.ts
│ ├── useMessageParser.ts
│ ├── useNotifications.ts
│ ├── usePromptEnhancer.ts
│ ├── useSearchFilter.ts
│ ├── useSettings.ts
│ ├── useShortcuts.ts
│ ├── useSnapScroll.ts
│ ├── useUpdateCheck.ts
│ └── useViewport.ts
├── modules
│ └── llm
│ │ ├── base-provider.ts
│ │ ├── manager.ts
│ │ ├── providers
│ │ ├── amazon-bedrock.ts
│ │ ├── anthropic.ts
│ │ ├── cohere.ts
│ │ ├── deepseek.ts
│ │ ├── github.ts
│ │ ├── google.ts
│ │ ├── groq.ts
│ │ ├── huggingface.ts
│ │ ├── hyperbolic.ts
│ │ ├── lmstudio.ts
│ │ ├── mistral.ts
│ │ ├── ollama.ts
│ │ ├── open-router.ts
│ │ ├── openai-like.ts
│ │ ├── openai.ts
│ │ ├── perplexity.ts
│ │ ├── together.ts
│ │ └── xai.ts
│ │ ├── registry.ts
│ │ └── types.ts
├── persistence
│ ├── ChatDescription.client.tsx
│ ├── db.ts
│ ├── index.ts
│ ├── localStorage.ts
│ ├── types.ts
│ └── useChatHistory.ts
├── runtime
│ ├── __snapshots__
│ │ └── message-parser.spec.ts.snap
│ ├── action-runner.ts
│ ├── message-parser.spec.ts
│ └── message-parser.ts
├── stores
│ ├── chat.ts
│ ├── editor.ts
│ ├── files.ts
│ ├── logs.ts
│ ├── previews.ts
│ ├── profile.ts
│ ├── settings.ts
│ ├── tabConfigurationStore.ts
│ ├── terminal.ts
│ ├── theme.ts
│ ├── user.ts
│ └── workbench.ts
└── webcontainer
│ ├── auth.client.ts
│ └── index.ts
├── middleware.ts
├── migrations
├── 0000_watery_wind_dancer.sql
├── 0001_wet_moondragon.sql
├── 0002_far_meltdown.sql
├── 0003_brave_golden_guardian.sql
├── 0004_woozy_silverclaw.sql
├── 0005_broken_quicksilver.sql
├── 0006_mature_kat_farrell.sql
├── 0007_cloudy_luckman.sql
├── 0008_fast_makkari.sql
├── 0009_ambiguous_turbo.sql
└── meta
│ ├── 0000_snapshot.json
│ ├── 0001_snapshot.json
│ ├── 0002_snapshot.json
│ ├── 0003_snapshot.json
│ ├── 0004_snapshot.json
│ ├── 0005_snapshot.json
│ ├── 0006_snapshot.json
│ ├── 0007_snapshot.json
│ ├── 0008_snapshot.json
│ ├── 0009_snapshot.json
│ └── _journal.json
├── next.config.ts
├── package.json
├── pnpm-lock.yaml
├── postcss.config.mjs
├── public
├── _favicon.ico
├── _favicon.svg
├── apple-touch-icon-precomposed.png
├── apple-touch-icon.png
├── favicon.svg
├── file.svg
├── globe.svg
├── icons
│ ├── AmazonBedrock.svg
│ ├── Anthropic.svg
│ ├── Cohere.svg
│ ├── Deepseek.svg
│ ├── Default.svg
│ ├── Google.svg
│ ├── Groq.svg
│ ├── HuggingFace.svg
│ ├── Hyperbolic.svg
│ ├── LMStudio.svg
│ ├── Mistral.svg
│ ├── Ollama.svg
│ ├── OpenAI.svg
│ ├── OpenAILike.svg
│ ├── OpenRouter.svg
│ ├── Perplexity.svg
│ ├── Together.svg
│ └── xAI.svg
├── logo-01.png
├── logo-02.png
├── logo-03.png
├── logo-04.png
├── logo-200.png
├── logo-200.svg
├── logo-dark-styled.png
├── logo-dark.png
├── logo-light-styled.png
├── logo-light.png
├── logo.svg
├── next.svg
├── social_preview_index.jpg
├── vercel.svg
└── window.svg
├── styles
├── animations.scss
├── components
│ ├── code.scss
│ ├── editor.scss
│ ├── resize-handle.scss
│ ├── terminal.scss
│ └── toast.scss
├── index.scss
├── variables.scss
└── z-index.scss
├── tsconfig.json
├── types
├── GitHub.ts
├── actions.ts
├── artifact.ts
├── context.ts
├── global.d.ts
├── model.ts
├── template.ts
├── terminal.ts
└── theme.ts
├── uno.config.ts
└── utils
├── buffer.ts
├── classNames.ts
├── cloudflare.ts
├── constants.ts
├── debounce.ts
├── diff.spec.ts
├── diff.ts
├── easings.ts
├── fileUtils.ts
├── folderImport.ts
├── formatSize.ts
├── logger.ts
├── machines.ts
├── markdown.ts
├── mobile.ts
├── os.ts
├── path.ts
├── projectCommands.ts
├── promises.ts
├── react.ts
├── sampler.ts
├── selectStarterTemplate.ts
├── shell.ts
├── stacktrace.ts
├── stripIndent.ts
├── terminal.ts
├── throttle.ts
├── types.ts
└── unreachable.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env
35 | .env.local
36 |
37 | # vercel
38 | .vercel
39 |
40 | # typescript
41 | *.tsbuildinfo
42 | next-env.d.ts
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # genfly.dev
2 |
3 |
4 | [](https://genfly.dev)
5 |
6 | Welcome to genfly.dev, an open-source AI-powered code generation tool that provides an isolated sandbox environment preview for each generated application
7 |
8 | -----
9 |
10 | ## Video Demo
11 |
12 | Here's a feature demonstration video of genfly.dev:
13 |
14 |
15 |
16 |
17 |
18 | [](https://github.com/user-attachments/assets/812571f9-4377-4da1-9f31-3b1f2d385338)
19 |
20 | |
21 |
22 |
23 | [](https://github.com/user-attachments/assets/426b01bf-2b20-4dfe-a63c-6150d1308fc9)
24 |
25 | |
26 |
27 |
28 |
29 | ## Features
30 |
31 | - AI-powered full-stack web development for NodeJS based applications directly in your browser.
32 | - Attach images to prompts for better contextual understanding.
33 | - Download projects as ZIP for easy portability Sync to a folder on the host.
34 | - Isolated sandbox environment for running code.
35 | - Self-hosting support with Next.js
36 |
37 |
38 | ## Local Development
39 |
40 | 1. **Install Package Manager (pnpm)**:
41 |
42 | ```bash
43 | npm install -g pnpm
44 | ```
45 |
46 | 2. **Install Project Dependencies**:
47 |
48 | ```bash
49 | pnpm install
50 | ```
51 |
52 | 3. **Start the Application**:
53 |
54 | ```bash
55 | pnpm run dev
56 | ```
57 |
58 |
59 | ## Community & contact
60 | - [GitHub Issues](https://github.com/sparrow-js/an-codeAI/issues):The bug must be mentioned in the issues.
61 | - Wechat:sparrow777-js,Open for collaboration and exchange.
62 | - email: genflyai@gmail.com
63 |
64 |
--------------------------------------------------------------------------------
/app/(auth)/chat/[id]/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useEffect } from 'react';
4 | import dynamic from 'next/dynamic';
5 | import { Header } from '@/components/header/Header';
6 | import { BaseChat } from '@/components/chat/BaseChat';
7 | import { Chat } from '@/components/chat/Chat.client';
8 | import { Suspense } from 'react';
9 |
10 |
11 | // Client-side only components
12 | // const Chat = dynamic(() => import('@/components/chat/Chat.client').then(mod => mod.Chat), {
13 | // ssr: false,
14 | // });
15 |
16 | /**
17 | * Landing page component for genfly
18 | * Note: Settings functionality should ONLY be accessed through the sidebar menu.
19 | * Do not add settings button/panel to this landing page as it was intentionally removed
20 | * to keep the UI clean and consistent with the design system.
21 | */
22 | export default function Home() {
23 | return (
24 |
25 | }>
26 |
27 |
28 |
29 | );
30 | }
--------------------------------------------------------------------------------
/app/(auth)/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import type { ReactNode } from "react";
3 | import { auth } from "auth"
4 | import { SessionProvider } from "next-auth/react"
5 | import { Header } from '@/components/header/Header';
6 | import { ToastContainer } from 'react-toastify';
7 | import 'react-toastify/dist/ReactToastify.css';
8 | import Background from '@/components/ui/BackgroundMeteor/Background';
9 |
10 | export const metadata: Metadata = {
11 | title: "Genfly | Fast-track your idea to reality",
12 | };
13 |
14 | export default async function AuthLayout({ children }: { children: ReactNode }) {
15 | const session = await auth();
16 | return (
17 |
18 |
19 |
20 | {/* */}
21 | {children}
22 |
23 |
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/app/(auth)/page.tsx:
--------------------------------------------------------------------------------
1 | import { BaseChat } from '@/components/chat/BaseChat';
2 | import { Suspense } from 'react';
3 | import { Chat } from '@/components/chat/Chat.client';
4 |
5 |
6 | // Client-side only components
7 | // const Chat = dynamic(() => import('@/components/chat/Chat.client').then(mod => mod.Chat), {
8 | // ssr: false,
9 | // });
10 |
11 | /**
12 | * Landing page component for genfly
13 | * Note: Settings functionality should ONLY be accessed through the sidebar menu.
14 | * Do not add settings button/panel to this landing page as it was intentionally removed
15 | * to keep the UI clean and consistent with the design system.
16 | */
17 | export default async function Home() {
18 | return (
19 |
20 | loading...
}>
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import { handlers } from "auth"
2 | export const { GET, POST } = handlers
3 |
--------------------------------------------------------------------------------
/app/api/chats/[id]/delete/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import { db } from '@/db';
3 | import { chats } from "@/db/schema";
4 | import { eq, and } from "drizzle-orm";
5 | import { auth } from "auth";
6 | export async function POST(
7 | request: Request,
8 | { params }: { params: Promise<{ id: string }> } // 类型定义
9 | ) {
10 | try {
11 | const { id: chatId } = await params;
12 |
13 | const session = await auth();
14 | if (!session?.user?.id) {
15 | return new Response('Unauthorized', {
16 | status: 401,
17 | headers: { 'Content-Type': 'text/plain' },
18 | });
19 | }
20 | const existingChat = await db.select()
21 | .from(chats)
22 | .where(
23 | and(
24 | eq(chats.id, chatId),
25 | eq(chats.userId, session.user.id)
26 | )
27 | )
28 | .limit(1);
29 |
30 | if (!existingChat || existingChat.length === 0) {
31 | return new NextResponse("Chat not found", { status: 404 });
32 | }
33 |
34 | // Delete the chat
35 | const deletedChat = await db.delete(chats)
36 | .where(
37 | and(
38 | eq(chats.id, chatId),
39 | eq(chats.userId, session.user.id)
40 | )
41 | )
42 | .returning();
43 |
44 | if (!deletedChat || deletedChat.length === 0) {
45 | return new NextResponse("Failed to delete chat", { status: 500 });
46 | }
47 |
48 | return NextResponse.json(deletedChat[0]);
49 |
50 | } catch (error) {
51 | console.error("[CHAT_DELETE]", error);
52 | return new NextResponse("Internal Error", { status: 500 });
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/api/chats/[id]/route.ts:
--------------------------------------------------------------------------------
1 | // app/api/chats/[id]/route.ts
2 | import { NextResponse } from 'next/server';
3 | import { db } from '@/db';
4 | import { chats } from '@/db/schema';
5 | import { eq, and } from 'drizzle-orm';
6 | import { auth } from 'auth';
7 | const isUUID = (str: string) => {
8 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
9 | return uuidRegex.test(str);
10 | };
11 |
12 |
13 | export async function GET(
14 | request: Request,
15 | { params }: { params: Promise<{ id: string }> } // 类型定义
16 | ) {
17 | const { id } = await params; // 从 params 中获取 id
18 |
19 | try {
20 | const session = await auth();
21 | if (!session) {
22 | return new Response('Unauthorized', {
23 | status: 401,
24 | headers: { 'Content-Type': 'text/plain' },
25 | });
26 | }
27 |
28 | if (!session?.user?.id) {
29 | return new Response('Unauthorized', { status: 401 });
30 | }
31 |
32 | if (!id) {
33 | return NextResponse.json({ error: 'Chat ID is required' }, { status: 400 });
34 | }
35 |
36 | const chat = await db
37 | .select()
38 | .from(chats)
39 | .where(
40 | and(
41 | isUUID(id) ? eq(chats.id, id) : eq(chats.urlId, id),
42 | eq(chats.userId, session.user.id)
43 | )
44 | )
45 | .limit(1)
46 |
47 | if (!chat.length) {
48 | return NextResponse.json({ error: 'Chat not found' }, { status: 404 });
49 | }
50 |
51 | return NextResponse.json(chat[0]);
52 | } catch (error) {
53 | console.error('Failed to fetch chat:', error);
54 | return NextResponse.json({ error }, { status: 500 });
55 | }
56 | }
--------------------------------------------------------------------------------
/app/api/chats/export/[id]/route.ts:
--------------------------------------------------------------------------------
1 | // app/api/chats/[id]/route.ts
2 | import { NextRequest, NextResponse } from 'next/server';
3 | import { withDb } from '@/db'; // Adjusted import path
4 | import { chats } from '@/db/schema'; // Adjusted import path
5 | import { eq, and } from 'drizzle-orm';
6 | import { auth } from "auth";
7 |
8 | const isUUID = (str: string) => {
9 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
10 | return uuidRegex.test(str);
11 | };
12 |
13 |
14 | export async function GET(request: NextRequest,
15 | { params }: { params: Promise<{ id: string }> } // 类型定义
16 | ) {
17 | const session = await auth();
18 | if (!session?.user?.id) {
19 | return new Response('Unauthorized', {
20 | status: 401,
21 | headers: { 'Content-Type': 'text/plain' },
22 | });
23 | }
24 |
25 | const userId = session.user.id;
26 | const { id } = await params;
27 |
28 | if (!id) {
29 | return NextResponse.json({ error: 'ID is required' }, { status: 400 });
30 | }
31 |
32 | const chat = await withDb((db) =>
33 | db
34 | .select()
35 | .from(chats)
36 | .where(
37 | and(
38 | isUUID(id) ? eq(chats.id, id) : eq(chats.urlId, id),
39 | eq(chats.userId, userId)
40 | )
41 | )
42 | .limit(1)
43 | );
44 |
45 | if (!chat.length) {
46 | return NextResponse.json({ error: 'Chat not found' }, { status: 404 });
47 | }
48 |
49 | return NextResponse.json({
50 | messages: chat[0].messages,
51 | description: chat[0].description,
52 | exportDate: new Date().toISOString(),
53 | }, { status: 200 });
54 | }
--------------------------------------------------------------------------------
/app/api/deploy/route.ts:
--------------------------------------------------------------------------------
1 | // app/api/deploy/route.ts
2 | import { NextResponse } from 'next/server';
3 | import { createScopedLogger } from '@/utils/logger';
4 | import { deployApp } from '@/utils/machines';
5 | import { auth } from 'auth';
6 |
7 | const logger = createScopedLogger('api.deploy');
8 |
9 | export async function POST(request: Request) {
10 |
11 | try {
12 | const data: { appName: string } = await request.json();
13 |
14 | const session = await auth();
15 | if (!session) {
16 | return new Response('Unauthorized', {
17 | status: 401,
18 | headers: { 'Content-Type': 'text/plain' },
19 | });
20 | }
21 |
22 | const { appName } = data;
23 | logger.info('Deployment API called with data:', data);
24 |
25 | try {
26 | const deployData = await deployApp(appName);
27 | // logger.info('Successfully created machine for Fly.io application:', deployData);
28 | return NextResponse.json({
29 | status: 'success',
30 | message: 'Successfully created Fly.io application',
31 | data: deployData,
32 | });
33 | } catch (error) {
34 | logger.error('Error creating Fly.io application:', error);
35 | return NextResponse.json(
36 | {
37 | status: 'error',
38 | message: 'Error creating Fly.io application',
39 | error: error instanceof Error ? error.message : 'Unknown error',
40 | },
41 | { status: 500 }
42 | );
43 | }
44 | } catch (error) {
45 | logger.error('Error in deployment API:', error);
46 | return NextResponse.json(
47 | {
48 | status: 'error',
49 | message: 'Failed to process deployment request',
50 | error: error instanceof Error ? error.message : 'Unknown error',
51 | },
52 | { status: 500 }
53 | );
54 | }
55 | }
56 |
57 | export async function GET() {
58 | return NextResponse.json({
59 | status: 'ready',
60 | message: 'Deployment API is available',
61 | timestamp: new Date().toISOString(),
62 | });
63 | }
64 |
65 | // Handle unsupported methods
66 | export async function OPTIONS() {
67 | return NextResponse.json({ error: 'Method not allowed' }, { status: 405 });
68 | }
--------------------------------------------------------------------------------
/app/api/git/getGitHubRepoContent/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server';
2 |
3 | export async function POST(request: NextRequest) {
4 | try {
5 | const { repoName, path = '' } = await request.json();
6 |
7 | const baseUrl = 'https://api.github.com';
8 | const token = process.env.NEXT_PUBLIC_GITHUB_TOKEN;
9 |
10 | const headers: HeadersInit = {
11 | Accept: 'application/vnd.github.v3+json',
12 | };
13 |
14 | if (token) {
15 | headers.Authorization = 'token ' + token;
16 | }
17 |
18 | const response = await fetch(`${baseUrl}/repos/${repoName}/contents/${path}`, {
19 | headers,
20 | });
21 |
22 | if (!response.ok) {
23 | throw new Error(`HTTP error! status: ${response.status}`);
24 | }
25 |
26 | const data: any = await response.json();
27 |
28 | // If it's a single file, return its content
29 | if (!Array.isArray(data)) {
30 | if (data.type === 'file') {
31 | const content = atob(data.content);
32 | return NextResponse.json([{
33 | name: data.name,
34 | path: data.path,
35 | content,
36 | }]);
37 | }
38 | }
39 |
40 | // Process directory contents recursively
41 | const contents = await Promise.all(
42 | data.map(async (item: any) => {
43 | if (item.type === 'dir') {
44 | // Recursively get contents of subdirectories
45 | const subResponse = await fetch(`${baseUrl}/repos/${repoName}/contents/${item.path}`, {
46 | headers,
47 | });
48 | const subData = await subResponse.json();
49 | return subData;
50 | } else if (item.type === 'file') {
51 | // Fetch file content
52 | const fileResponse = await fetch(item.url, {
53 | headers,
54 | });
55 | const fileData: any = await fileResponse.json();
56 | const content = atob(fileData.content);
57 |
58 | return [{
59 | name: item.name,
60 | path: item.path,
61 | content,
62 | }];
63 | }
64 | return [];
65 | }),
66 | );
67 |
68 | return NextResponse.json(contents.flat());
69 |
70 | } catch (error) {
71 | console.error('Error fetching repo contents:', error);
72 | return NextResponse.json({ error: 'Failed to fetch repo contents' }, { status: 500 });
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/api/health/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function GET(_request: Request) {
4 | return NextResponse.json({
5 | status: 'healthy',
6 | timestamp: new Date().toISOString(),
7 | });
8 | }
--------------------------------------------------------------------------------
/app/api/usage/get-credits/route.ts:
--------------------------------------------------------------------------------
1 |
2 | import { NextResponse } from 'next/server';
3 | import { withDb } from '@/db/edge-db';
4 | import { credits } from '@/db/schema';
5 | import { auth } from 'auth';
6 | import { eq } from 'drizzle-orm';
7 |
8 | export const runtime = 'edge';
9 |
10 | export async function GET(request: Request) {
11 | try {
12 | const session = await auth();
13 | if (!session?.user) {
14 | return new NextResponse("Unauthorized", { status: 401 });
15 | }
16 |
17 | // 获取当前用户信息
18 | const userId = session.user.id;
19 |
20 | // 确保userId不为undefined
21 | if (!userId) {
22 | return new NextResponse("User ID not found", { status: 400 });
23 | }
24 |
25 | try {
26 | const userCredits = await withDb(db => db.select({
27 | id: credits.id,
28 | userId: credits.userId,
29 | credits: credits.credits,
30 | createdAt: credits.createdAt,
31 | usage: credits.usage
32 | }).from(credits)
33 | .where(eq(credits.userId, userId))
34 | .limit(1)
35 | );
36 |
37 | if (userCredits.length === 0) {
38 | return NextResponse.json({ credits: 0 });
39 | }
40 |
41 | return NextResponse.json({ credits: userCredits[0].credits - userCredits[0].usage });
42 | } catch (error) {
43 | console.error('Failed to fetch credits:', error);
44 | return NextResponse.json({ error: 'Failed to fetch credits' }, { status: 500 });
45 | }
46 | } catch (error) {
47 | console.error('Failed to fetch credits:', error);
48 | return NextResponse.json({ error: 'Failed to fetch credits' }, { status: 500 });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/background/page.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import BackgroundMeteor from '@/components/ui/BackgroundMeteor/MeteorShower';
4 | import NatureScene from '@/components/ui/BackgroundMeteor/NatureScene';
5 | import { themeStore } from '@/lib/stores/theme';
6 | import { useStore } from '@nanostores/react';
7 |
8 | export default function Background() {
9 | const theme = useStore(themeStore);
10 |
11 | return (
12 |
13 | {/* {theme === 'dark' ? : } */}
14 |
15 | );
16 | }
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @unocss all;
2 |
3 | :root {
4 | --background: #ffffff;
5 | --foreground: #171717;
6 | }
7 |
8 | @media (prefers-color-scheme: dark) {
9 | :root {
10 | --background: #0a0a0a;
11 | --foreground: #ededed;
12 | }
13 | }
14 |
15 | body {
16 | color: var(--foreground);
17 | background: var(--background);
18 | font-family: Arial, Helvetica, sans-serif;
19 | }
20 |
21 | [data-theme="dark"] body {
22 | background: #0a0a0a;
23 | }
24 |
25 | [data-theme="light"] body {
26 | background: #ffffff;
27 | }
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 | import { Geist, Geist_Mono } from 'next/font/google'
3 | import '@unocss/reset/tailwind.css'
4 | import './globals.css'
5 | import '@/styles/index.scss?url';
6 |
7 |
8 | const geistSans = Geist({
9 | variable: '--font-geist-sans',
10 | subsets: ['latin'],
11 | })
12 |
13 | const geistMono = Geist_Mono({
14 | variable: '--font-geist-mono',
15 | subsets: ['latin'],
16 | })
17 |
18 | export const metadata: Metadata = {
19 | title: 'Genfly | Fast-track your idea to reality',
20 | description: 'Genfly is a platform that allows you to fast-track your idea to reality.',
21 | }
22 |
23 | const THEME_COLOR_SCRIPT = `\
24 | (function() {
25 | function setTutorialKitTheme() {
26 | let theme = localStorage.getItem('bolt_theme');
27 |
28 | if (!theme) {
29 | theme = 'dark';
30 | }
31 |
32 | document.querySelector('html')?.setAttribute('data-theme', theme);
33 | }
34 | setTutorialKitTheme();
35 | })();`;
36 |
37 | export default function RootLayout({
38 | children,
39 | }: Readonly<{
40 | children: React.ReactNode
41 | }>) {
42 | return (
43 |
47 |
48 |
53 |
54 |
57 | {children}
58 |
59 |
60 | )
61 | }
62 |
--------------------------------------------------------------------------------
/app/login/login-git-button.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/Button";
2 | import { signIn } from "auth";
3 | import { FaGithub } from "react-icons/fa";
4 |
5 | interface LoginButtonProps {
6 | className?: string;
7 | }
8 |
9 | export default function LoginGithubButton({ className }: LoginButtonProps) {
10 | return (
11 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/app/login/login-google-button.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/Button";
2 | import { signIn } from "auth";
3 | import { FcGoogle } from "react-icons/fc";
4 |
5 | interface LoginButtonProps {
6 | className?: string;
7 | }
8 |
9 | export default function LoginGoogleButton({ className }: LoginButtonProps) {
10 | return (
11 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/app/login/login-notion-button.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/Button";
2 | import { signIn } from "auth";
3 | import { SiNotion } from "react-icons/si";
4 |
5 | interface LoginButtonProps {
6 | className?: string;
7 | }
8 |
9 | export default function LoginNotionButton({ className }: LoginButtonProps) {
10 | return (
11 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/components/@settings/index.ts:
--------------------------------------------------------------------------------
1 | // Core exports
2 | export { ControlPanel } from './core/ControlPanel';
3 | export type { TabType, TabVisibilityConfig } from './core/types';
4 |
5 | // Constants
6 | export { TAB_LABELS, TAB_DESCRIPTIONS, DEFAULT_TAB_CONFIG } from './core/constants';
7 |
8 | // Shared components
9 | export { TabTile } from './shared/components/TabTile';
10 | export { TabManagement } from './shared/components/TabManagement';
11 |
12 | // Utils
13 | export { getVisibleTabs, reorderTabs, resetToDefaultConfig } from './utils/tab-helpers';
14 | export * from './utils/animations';
15 |
--------------------------------------------------------------------------------
/components/@settings/tabs/connections/types/GitHub.ts:
--------------------------------------------------------------------------------
1 | export interface GitHubUserResponse {
2 | login: string;
3 | avatar_url: string;
4 | html_url: string;
5 | name: string;
6 | bio: string;
7 | public_repos: number;
8 | followers: number;
9 | following: number;
10 | public_gists: number;
11 | created_at: string;
12 | updated_at: string;
13 | }
14 |
15 | export interface GitHubRepoInfo {
16 | name: string;
17 | full_name: string;
18 | html_url: string;
19 | description: string;
20 | stargazers_count: number;
21 | forks_count: number;
22 | default_branch: string;
23 | updated_at: string;
24 | language: string;
25 | languages_url: string;
26 | }
27 |
28 | export interface GitHubOrganization {
29 | login: string;
30 | avatar_url: string;
31 | description: string;
32 | html_url: string;
33 | }
34 |
35 | export interface GitHubEvent {
36 | id: string;
37 | type: string;
38 | created_at: string;
39 | repo: {
40 | name: string;
41 | url: string;
42 | };
43 | payload: {
44 | action?: string;
45 | ref?: string;
46 | ref_type?: string;
47 | description?: string;
48 | };
49 | }
50 |
51 | export interface GitHubLanguageStats {
52 | [key: string]: number;
53 | }
54 |
55 | export interface GitHubStats {
56 | repos: GitHubRepoInfo[];
57 | totalStars: number;
58 | totalForks: number;
59 | organizations: GitHubOrganization[];
60 | recentActivity: GitHubEvent[];
61 | languages: GitHubLanguageStats;
62 | totalGists: number;
63 | }
64 |
65 | export interface GitHubConnection {
66 | user: GitHubUserResponse | null;
67 | token: string;
68 | tokenType: 'classic' | 'fine-grained';
69 | stats?: GitHubStats;
70 | }
71 |
72 | export interface GitHubTokenInfo {
73 | token: string;
74 | scope: string[];
75 | avatar_url: string;
76 | name: string | null;
77 | created_at: string;
78 | followers: number;
79 | }
80 |
81 | export interface GitHubRateLimits {
82 | limit: number;
83 | remaining: number;
84 | reset: Date;
85 | used: number;
86 | }
87 |
88 | export interface GitHubAuthState {
89 | username: string;
90 | tokenInfo: GitHubTokenInfo | null;
91 | isConnected: boolean;
92 | isVerifying: boolean;
93 | isLoadingRepos: boolean;
94 | rateLimits?: GitHubRateLimits;
95 | }
96 |
--------------------------------------------------------------------------------
/components/@settings/tabs/providers/service-status/providers/deepseek.ts:
--------------------------------------------------------------------------------
1 | import { BaseProviderChecker } from '@/components/@settings/tabs/providers/service-status/base-provider';
2 | import type { StatusCheckResult } from '@/components/@settings/tabs/providers/service-status/types';
3 |
4 | export class DeepseekStatusChecker extends BaseProviderChecker {
5 | async checkStatus(): Promise {
6 | try {
7 | /*
8 | * Check status page - Note: Deepseek doesn't have a public status page yet
9 | * so we'll check their API endpoint directly
10 | */
11 | const apiEndpoint = 'https://api.deepseek.com/v1/models';
12 | const apiStatus = await this.checkEndpoint(apiEndpoint);
13 |
14 | // Check their website as a secondary indicator
15 | const websiteStatus = await this.checkEndpoint('https://deepseek.com');
16 |
17 | let status: StatusCheckResult['status'] = 'operational';
18 | let message = 'All systems operational';
19 |
20 | if (apiStatus !== 'reachable' || websiteStatus !== 'reachable') {
21 | status = apiStatus !== 'reachable' ? 'down' : 'degraded';
22 | message = apiStatus !== 'reachable' ? 'API appears to be down' : 'Service may be experiencing issues';
23 | }
24 |
25 | return {
26 | status,
27 | message,
28 | incidents: [], // No public incident tracking available yet
29 | };
30 | } catch (error) {
31 | console.error('Error checking Deepseek status:', error);
32 |
33 | return {
34 | status: 'degraded',
35 | message: 'Unable to determine service status',
36 | incidents: ['Note: Limited status information available'],
37 | };
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/components/@settings/tabs/providers/service-status/providers/hyperbolic.ts:
--------------------------------------------------------------------------------
1 | import { BaseProviderChecker } from '@/components/@settings/tabs/providers/service-status/base-provider';
2 | import type { StatusCheckResult } from '@/components/@settings/tabs/providers/service-status/types';
3 |
4 | export class HyperbolicStatusChecker extends BaseProviderChecker {
5 | async checkStatus(): Promise {
6 | try {
7 | /*
8 | * Check API endpoint directly since Hyperbolic is a newer provider
9 | * and may not have a public status page yet
10 | */
11 | const apiEndpoint = 'https://api.hyperbolic.ai/v1/models';
12 | const apiStatus = await this.checkEndpoint(apiEndpoint);
13 |
14 | // Check their website as a secondary indicator
15 | const websiteStatus = await this.checkEndpoint('https://hyperbolic.ai');
16 |
17 | let status: StatusCheckResult['status'] = 'operational';
18 | let message = 'All systems operational';
19 |
20 | if (apiStatus !== 'reachable' || websiteStatus !== 'reachable') {
21 | status = apiStatus !== 'reachable' ? 'down' : 'degraded';
22 | message = apiStatus !== 'reachable' ? 'API appears to be down' : 'Service may be experiencing issues';
23 | }
24 |
25 | return {
26 | status,
27 | message,
28 | incidents: [], // No public incident tracking available yet
29 | };
30 | } catch (error) {
31 | console.error('Error checking Hyperbolic status:', error);
32 |
33 | return {
34 | status: 'degraded',
35 | message: 'Unable to determine service status',
36 | incidents: ['Note: Limited status information available'],
37 | };
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/components/@settings/tabs/providers/service-status/providers/xai.ts:
--------------------------------------------------------------------------------
1 | import { BaseProviderChecker } from '@/components/@settings/tabs/providers/service-status/base-provider';
2 | import type { StatusCheckResult } from '@/components/@settings/tabs/providers/service-status/types';
3 |
4 | export class XAIStatusChecker extends BaseProviderChecker {
5 | async checkStatus(): Promise {
6 | try {
7 | /*
8 | * Check API endpoint directly since XAI is a newer provider
9 | * and may not have a public status page yet
10 | */
11 | const apiEndpoint = 'https://api.xai.com/v1/models';
12 | const apiStatus = await this.checkEndpoint(apiEndpoint);
13 |
14 | // Check their website as a secondary indicator
15 | const websiteStatus = await this.checkEndpoint('https://x.ai');
16 |
17 | let status: StatusCheckResult['status'] = 'operational';
18 | let message = 'All systems operational';
19 |
20 | if (apiStatus !== 'reachable' || websiteStatus !== 'reachable') {
21 | status = apiStatus !== 'reachable' ? 'down' : 'degraded';
22 | message = apiStatus !== 'reachable' ? 'API appears to be down' : 'Service may be experiencing issues';
23 | }
24 |
25 | return {
26 | status,
27 | message,
28 | incidents: [], // No public incident tracking available yet
29 | };
30 | } catch (error) {
31 | console.error('Error checking XAI status:', error);
32 |
33 | return {
34 | status: 'degraded',
35 | message: 'Unable to determine service status',
36 | incidents: ['Note: Limited status information available'],
37 | };
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/components/@settings/tabs/providers/service-status/types.ts:
--------------------------------------------------------------------------------
1 | import type { IconType } from 'react-icons';
2 |
3 | export type ProviderName =
4 | | 'AmazonBedrock'
5 | | 'Cohere'
6 | | 'Deepseek'
7 | | 'Google'
8 | | 'Groq'
9 | | 'HuggingFace'
10 | | 'Hyperbolic'
11 | | 'Mistral'
12 | | 'OpenRouter'
13 | | 'Perplexity'
14 | | 'Together'
15 | | 'XAI';
16 |
17 | export type ServiceStatus = {
18 | provider: ProviderName;
19 | status: 'operational' | 'degraded' | 'down';
20 | lastChecked: string;
21 | statusUrl?: string;
22 | icon?: IconType;
23 | message?: string;
24 | responseTime?: number;
25 | incidents?: string[];
26 | };
27 |
28 | export interface ProviderConfig {
29 | statusUrl: string;
30 | apiUrl: string;
31 | headers: Record;
32 | testModel: string;
33 | }
34 |
35 | export type ApiResponse = {
36 | error?: {
37 | message: string;
38 | };
39 | message?: string;
40 | model?: string;
41 | models?: Array<{
42 | id?: string;
43 | name?: string;
44 | }>;
45 | data?: Array<{
46 | id?: string;
47 | name?: string;
48 | }>;
49 | };
50 |
51 | export type StatusCheckResult = {
52 | status: 'operational' | 'degraded' | 'down';
53 | message: string;
54 | incidents: string[];
55 | };
56 |
--------------------------------------------------------------------------------
/components/@settings/utils/animations.ts:
--------------------------------------------------------------------------------
1 | import type { Variants } from 'framer-motion';
2 |
3 | export const fadeIn: Variants = {
4 | initial: { opacity: 0 },
5 | animate: { opacity: 1 },
6 | exit: { opacity: 0 },
7 | };
8 |
9 | export const slideIn: Variants = {
10 | initial: { opacity: 0, y: 20 },
11 | animate: { opacity: 1, y: 0 },
12 | exit: { opacity: 0, y: -20 },
13 | };
14 |
15 | export const scaleIn: Variants = {
16 | initial: { opacity: 0, scale: 0.8 },
17 | animate: { opacity: 1, scale: 1 },
18 | exit: { opacity: 0, scale: 0.8 },
19 | };
20 |
21 | export const tabAnimation: Variants = {
22 | initial: { opacity: 0, scale: 0.8, y: 20 },
23 | animate: { opacity: 1, scale: 1, y: 0 },
24 | exit: { opacity: 0, scale: 0.8, y: -20 },
25 | };
26 |
27 | export const overlayAnimation: Variants = {
28 | initial: { opacity: 0 },
29 | animate: { opacity: 1 },
30 | exit: { opacity: 0 },
31 | };
32 |
33 | export const modalAnimation: Variants = {
34 | initial: { opacity: 0, scale: 0.95, y: 20 },
35 | animate: { opacity: 1, scale: 1, y: 0 },
36 | exit: { opacity: 0, scale: 0.95, y: 20 },
37 | };
38 |
39 | export const transition = {
40 | duration: 0.2,
41 | };
42 |
--------------------------------------------------------------------------------
/components/ClientOnly.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useEffect, useState, type ReactNode } from 'react';
4 |
5 | interface ClientOnlyProps {
6 | children: ReactNode | (() => ReactNode);
7 | }
8 |
9 | export function ClientOnly({ children }: ClientOnlyProps) {
10 | const [mounted, setMounted] = useState(false);
11 |
12 | useEffect(() => {
13 | setMounted(true);
14 | }, []);
15 |
16 | if (!mounted) return null;
17 |
18 | return typeof children === 'function' ? children() : children;
19 | }
--------------------------------------------------------------------------------
/components/chat/BaseChat.module.scss:
--------------------------------------------------------------------------------
1 | .BaseChat {
2 | &[data-chat-visible='false'] {
3 | --workbench-inner-width: 100%;
4 | --workbench-left: 0;
5 |
6 | .Chat {
7 | --at-apply: bolt-ease-cubic-bezier;
8 | transition-property: transform, opacity;
9 | transition-duration: 0.3s;
10 | will-change: transform, opacity;
11 | transform: translateX(-50%);
12 | opacity: 0;
13 | }
14 | }
15 | }
16 |
17 | .Chat {
18 | opacity: 1;
19 | }
20 |
21 | .PromptEffectContainer {
22 | --prompt-container-offset: 50px;
23 | --prompt-line-stroke-width: 1px;
24 | position: absolute;
25 | pointer-events: none;
26 | inset: calc(var(--prompt-container-offset) / -2);
27 | width: calc(100% + var(--prompt-container-offset));
28 | height: calc(100% + var(--prompt-container-offset));
29 | }
30 |
31 | .PromptEffectLine {
32 | width: calc(100% - var(--prompt-container-offset) + var(--prompt-line-stroke-width));
33 | height: calc(100% - var(--prompt-container-offset) + var(--prompt-line-stroke-width));
34 | x: calc(var(--prompt-container-offset) / 2 - var(--prompt-line-stroke-width) / 2);
35 | y: calc(var(--prompt-container-offset) / 2 - var(--prompt-line-stroke-width) / 2);
36 | rx: calc(8px - var(--prompt-line-stroke-width));
37 | fill: transparent;
38 | stroke-width: var(--prompt-line-stroke-width);
39 | stroke: url(#line-gradient);
40 | stroke-dasharray: 35px 65px;
41 | stroke-dashoffset: 10;
42 | }
43 |
44 | .PromptShine {
45 | fill: url(#shine-gradient);
46 | mix-blend-mode: overlay;
47 | }
48 |
--------------------------------------------------------------------------------
/components/chat/CodeBlock.module.scss:
--------------------------------------------------------------------------------
1 | .CopyButtonContainer {
2 | button:before {
3 | content: 'Copied';
4 | font-size: 12px;
5 | position: absolute;
6 | left: -53px;
7 | padding: 2px 6px;
8 | height: 30px;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/components/chat/ExamplePrompts.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const EXAMPLE_PROMPTS: any[] = [
4 | // { text: 'Build a todo app in React using Tailwind' },
5 | // { text: 'Build a simple blog using Astro' },
6 | // { text: 'Create a cookie consent form using Material UI' },
7 | // { text: 'Make a space invaders game' },
8 | // { text: 'Make a Tic Tac Toe game in html, css and js only' },
9 | ];
10 |
11 | export function ExamplePrompts(sendMessage?: { (event: React.UIEvent, messageInput?: string): void | undefined }) {
12 | return (
13 |
14 |
20 | {EXAMPLE_PROMPTS.map((examplePrompt, index: number) => {
21 | return (
22 |
31 | );
32 | })}
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/components/chat/FilePreview.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface FilePreviewProps {
4 | files: File[];
5 | imageDataList: string[];
6 | onRemove: (index: number) => void;
7 | }
8 |
9 | const FilePreview: React.FC = ({ files, imageDataList, onRemove }) => {
10 | if (!files || files.length === 0) {
11 | return null;
12 | }
13 |
14 | return (
15 |
16 | {files.map((file, index) => (
17 |
18 | {imageDataList[index] && (
19 |
20 |

21 |
27 |
28 | )}
29 |
30 | ))}
31 |
32 | );
33 | };
34 |
35 | export default FilePreview;
36 |
--------------------------------------------------------------------------------
/components/chat/ScreenshotStateManager.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | interface ScreenshotStateManagerProps {
4 | setUploadedFiles?: (files: File[]) => void;
5 | setImageDataList?: (dataList: string[]) => void;
6 | uploadedFiles: File[];
7 | imageDataList: string[];
8 | }
9 |
10 | export const ScreenshotStateManager = ({
11 | setUploadedFiles,
12 | setImageDataList,
13 | uploadedFiles,
14 | imageDataList,
15 | }: ScreenshotStateManagerProps) => {
16 | useEffect(() => {
17 | if (setUploadedFiles && setImageDataList) {
18 | (window as any).__BOLT_SET_UPLOADED_FILES__ = setUploadedFiles;
19 | (window as any).__BOLT_SET_IMAGE_DATA_LIST__ = setImageDataList;
20 | (window as any).__BOLT_UPLOADED_FILES__ = uploadedFiles;
21 | (window as any).__BOLT_IMAGE_DATA_LIST__ = imageDataList;
22 | }
23 |
24 | return () => {
25 | delete (window as any).__BOLT_SET_UPLOADED_FILES__;
26 | delete (window as any).__BOLT_SET_IMAGE_DATA_LIST__;
27 | delete (window as any).__BOLT_UPLOADED_FILES__;
28 | delete (window as any).__BOLT_IMAGE_DATA_LIST__;
29 | };
30 | }, [setUploadedFiles, setImageDataList, uploadedFiles, imageDataList]);
31 |
32 | return null;
33 | };
34 |
--------------------------------------------------------------------------------
/components/chat/SendButton.client.tsx:
--------------------------------------------------------------------------------
1 | import { AnimatePresence, cubicBezier, motion } from 'framer-motion';
2 |
3 | interface SendButtonProps {
4 | show: boolean;
5 | isStreaming?: boolean;
6 | disabled?: boolean;
7 | onClick?: (event: React.MouseEvent) => void;
8 | onImagesSelected?: (images: File[]) => void;
9 | }
10 |
11 | const customEasingFn = cubicBezier(0.4, 0, 0.2, 1);
12 |
13 | export const SendButton = ({ show, isStreaming, disabled, onClick }: SendButtonProps) => {
14 | return (
15 |
16 | {show ? (
17 | {
25 | event.preventDefault();
26 |
27 | if (!disabled) {
28 | onClick?.(event);
29 | }
30 | }}
31 | >
32 |
33 | {!isStreaming ?
:
}
34 |
35 |
36 | ) : null}
37 |
38 | );
39 | };
40 |
--------------------------------------------------------------------------------
/components/chat/SpeechRecognition.tsx:
--------------------------------------------------------------------------------
1 | import { IconButton } from '@/components/ui/IconButton';
2 | import { classNames } from '@/utils/classNames';
3 | import React from 'react';
4 |
5 | export const SpeechRecognitionButton = ({
6 | isListening,
7 | onStart,
8 | onStop,
9 | disabled,
10 | }: {
11 | isListening: boolean;
12 | onStart: () => void;
13 | onStop: () => void;
14 | disabled: boolean;
15 | }) => {
16 | return (
17 |
25 | {isListening ? : }
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/components/chat/StarterTemplates.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import type { Template } from '@/types/template';
3 | import { STARTER_TEMPLATES } from '@/utils/constants';
4 |
5 | interface FrameworkLinkProps {
6 | template: Template;
7 | }
8 |
9 | const FrameworkLink: React.FC = ({ template }) => (
10 |
16 |
20 |
21 | );
22 |
23 | const StarterTemplates: React.FC = () => {
24 | // Debug: Log available templates and their icons
25 | React.useEffect(() => {
26 | console.log(
27 | 'Available templates:',
28 | STARTER_TEMPLATES.map((t) => ({ name: t.name, icon: t.icon })),
29 | );
30 | }, []);
31 |
32 | return (
33 |
34 |
or start a blank app with your favorite stack
35 |
36 |
37 | {STARTER_TEMPLATES.map((template) => (
38 |
39 | ))}
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export default StarterTemplates;
47 |
--------------------------------------------------------------------------------
/components/chat/ThoughtBox.tsx:
--------------------------------------------------------------------------------
1 | import { useState, type PropsWithChildren } from 'react';
2 |
3 | const ThoughtBox = ({ title, children }: PropsWithChildren<{ title: string }>) => {
4 | const [isExpanded, setIsExpanded] = useState(false);
5 |
6 | return (
7 | setIsExpanded(!isExpanded)}
9 | className={`
10 | bg-bolt-elements-background-depth-2
11 | shadow-md
12 | rounded-lg
13 | cursor-pointer
14 | transition-all
15 | duration-300
16 | ${isExpanded ? 'max-h-96' : 'max-h-13'}
17 | overflow-auto
18 | border border-bolt-elements-borderColor
19 | `}
20 | >
21 |
22 |
23 |
24 | {title}{' '}
25 | {!isExpanded && - Click to expand}
26 |
27 |
28 |
37 | {children}
38 |
39 |
40 | );
41 | };
42 |
43 | export default ThoughtBox;
44 |
--------------------------------------------------------------------------------
/components/chat/UserMessage.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @ts-nocheck
3 | * Preventing TS checks with files presented in the video for a better presentation.
4 | */
5 | import { MODEL_REGEX, PROVIDER_REGEX } from '@/utils/constants';
6 | import { Markdown } from './Markdown';
7 |
8 | interface UserMessageProps {
9 | content: string | Array<{ type: string; text?: string; image?: string }>;
10 | }
11 |
12 | export function UserMessage({ content }: UserMessageProps) {
13 | if (Array.isArray(content)) {
14 | const textItem = content.find((item) => item.type === 'text');
15 | const textContent = stripMetadata(textItem?.text || '');
16 | const images = content.filter((item) => item.type === 'image' && item.image);
17 |
18 | return (
19 |
20 |
21 | {textContent &&
{textContent}}
22 | {images.map((item, index) => (
23 |

30 | ))}
31 |
32 |
33 | );
34 | }
35 |
36 | const textContent = stripMetadata(content);
37 |
38 | return (
39 |
40 | {textContent}
41 |
42 | );
43 | }
44 |
45 | function stripMetadata(content: string) {
46 | return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '');
47 | }
48 |
--------------------------------------------------------------------------------
/components/chat/chatExportAndImport/ExportChatButton.tsx:
--------------------------------------------------------------------------------
1 | import WithTooltip from '@/components/ui/Tooltip';
2 | import { IconButton } from '@/components/ui/IconButton';
3 | import React from 'react';
4 |
5 | export const ExportChatButton = ({ exportChat }: { exportChat?: () => void }) => {
6 | return (
7 |
8 | exportChat?.()}>
9 |
10 |
11 |
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/components/editor/codemirror/BinaryContent.tsx:
--------------------------------------------------------------------------------
1 | export function BinaryContent() {
2 | return (
3 |
4 | File format cannot be displayed.
5 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/components/editor/codemirror/indent.ts:
--------------------------------------------------------------------------------
1 | import { indentLess } from '@codemirror/commands';
2 | import { indentUnit } from '@codemirror/language';
3 | import { EditorSelection, EditorState, Line, type ChangeSpec } from '@codemirror/state';
4 | import { EditorView, type KeyBinding } from '@codemirror/view';
5 |
6 | export const indentKeyBinding: KeyBinding = {
7 | key: 'Tab',
8 | run: indentMore,
9 | shift: indentLess,
10 | };
11 |
12 | function indentMore({ state, dispatch }: EditorView) {
13 | if (state.readOnly) {
14 | return false;
15 | }
16 |
17 | dispatch(
18 | state.update(
19 | changeBySelectedLine(state, (from, to, changes) => {
20 | changes.push({ from, to, insert: state.facet(indentUnit) });
21 | }),
22 | { userEvent: 'input.indent' },
23 | ),
24 | );
25 |
26 | return true;
27 | }
28 |
29 | function changeBySelectedLine(
30 | state: EditorState,
31 | cb: (from: number, to: number | undefined, changes: ChangeSpec[], line: Line) => void,
32 | ) {
33 | return state.changeByRange((range) => {
34 | const changes: ChangeSpec[] = [];
35 |
36 | const line = state.doc.lineAt(range.from);
37 |
38 | // just insert single indent unit at the current cursor position
39 | if (range.from === range.to) {
40 | cb(range.from, undefined, changes, line);
41 | }
42 | // handle the case when multiple characters are selected in a single line
43 | else if (range.from < range.to && range.to <= line.to) {
44 | cb(range.from, range.to, changes, line);
45 | } else {
46 | let atLine = -1;
47 |
48 | // handle the case when selection spans multiple lines
49 | for (let pos = range.from; pos <= range.to; ) {
50 | const line = state.doc.lineAt(pos);
51 |
52 | if (line.number > atLine && (range.empty || range.to > line.from)) {
53 | cb(line.from, undefined, changes, line);
54 | atLine = line.number;
55 | }
56 |
57 | pos = line.to + 1;
58 | }
59 | }
60 |
61 | const changeSet = state.changes(changes);
62 |
63 | return {
64 | changes,
65 | range: EditorSelection.range(changeSet.mapPos(range.anchor, 1), changeSet.mapPos(range.head, 1)),
66 | };
67 | });
68 | }
69 |
--------------------------------------------------------------------------------
/components/sidebar/date-binning.ts:
--------------------------------------------------------------------------------
1 | import { format, isAfter, isThisWeek, isThisYear, isToday, isYesterday, subDays } from 'date-fns';
2 | import type { ChatHistoryItem } from '@/lib/persistence';
3 |
4 | type Bin = { category: string; items: ChatHistoryItem[] };
5 |
6 | export function binDates(_list: ChatHistoryItem[]) {
7 | const list = _list.toSorted((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
8 |
9 | const binLookup: Record = {};
10 | const bins: Array = [];
11 |
12 | list.forEach((item) => {
13 | const category = dateCategory(new Date(item.timestamp));
14 |
15 | if (!(category in binLookup)) {
16 | const bin = {
17 | category,
18 | items: [item],
19 | };
20 |
21 | binLookup[category] = bin;
22 |
23 | bins.push(bin);
24 | } else {
25 | binLookup[category].items.push(item);
26 | }
27 | });
28 |
29 | return bins;
30 | }
31 |
32 | function dateCategory(date: Date) {
33 | if (isToday(date)) {
34 | return 'Today';
35 | }
36 |
37 | if (isYesterday(date)) {
38 | return 'Yesterday';
39 | }
40 |
41 | if (isThisWeek(date)) {
42 | // e.g., "Mon" instead of "Monday"
43 | return format(date, 'EEE');
44 | }
45 |
46 | const thirtyDaysAgo = subDays(new Date(), 30);
47 |
48 | if (isAfter(date, thirtyDaysAgo)) {
49 | return 'Past 30 Days';
50 | }
51 |
52 | if (isThisYear(date)) {
53 | // e.g., "Jan" instead of "January"
54 | return format(date, 'LLL');
55 | }
56 |
57 | // e.g., "Jan 2023" instead of "January 2023"
58 | return format(date, 'LLL yyyy');
59 | }
60 |
--------------------------------------------------------------------------------
/components/sidebar/left.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { useSession } from "next-auth/react"
3 |
4 | export const SidebarLeft = () => {
5 | const { data: session } = useSession();
6 | return (
7 |
8 | {session?.user?.image && (
9 |

10 | )}
11 |
12 |
13 | )
14 | }
--------------------------------------------------------------------------------
/components/ui/BackgroundMeteor/Background.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | // import BackgroundMeteor from '@/components/ui/BackgroundMeteor/MeteorShower';
4 | // import NatureScene from '@/components/ui/BackgroundMeteor/NatureScene';
5 | // import { themeStore, Theme } from '@/lib/stores/theme';
6 | import { useStore } from '@nanostores/react';
7 | import { useEffect } from 'react';
8 |
9 |
10 | export default function Background() {
11 | // const theme = useStore(themeStore);
12 | // useEffect(() => {
13 | // const theme = localStorage.getItem('bolt_theme');
14 | // themeStore.set(theme as Theme || 'dark');
15 | // }, [theme]);
16 |
17 | return (
18 | <>
19 | {/* {theme === 'dark' ? : } */}
20 | >
21 | );
22 | }
--------------------------------------------------------------------------------
/components/ui/BackgroundMeteor/throttle.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates a throttled function that only invokes the provided function at most once per
3 | * specified wait period.
4 | *
5 | * @param func The function to throttle
6 | * @param wait The number of milliseconds to throttle invocations to
7 | * @returns A throttled version of the provided function
8 | */
9 | export function throttle any>(
10 | func: T,
11 | wait: number
12 | ): (...args: Parameters) => void {
13 | let lastCall = 0;
14 | let timeout: number | null = null;
15 |
16 | return function(...args: Parameters) {
17 | const now = Date.now();
18 | const remaining = wait - (now - lastCall);
19 |
20 | if (remaining <= 0) {
21 | if (timeout !== null) {
22 | clearTimeout(timeout);
23 | timeout = null;
24 | }
25 | lastCall = now;
26 | func(...args);
27 | } else if (timeout === null) {
28 | timeout = window.setTimeout(() => {
29 | lastCall = Date.now();
30 | timeout = null;
31 | func(...args);
32 | }, remaining);
33 | }
34 | };
35 | }
36 |
--------------------------------------------------------------------------------
/components/ui/BackgroundRays/index.tsx:
--------------------------------------------------------------------------------
1 | import styles from './styles.module.scss';
2 |
3 | const BackgroundRays = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default BackgroundRays;
19 |
--------------------------------------------------------------------------------
/components/ui/Badge.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import { cva, type VariantProps } from 'class-variance-authority';
5 | import { classNames } from '@/utils/classNames';
6 |
7 | const badgeVariants = cva(
8 | 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-bolt-elements-ring focus:ring-offset-2',
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | 'border-transparent bg-bolt-elements-background text-bolt-elements-textPrimary hover:bg-bolt-elements-background/80',
14 | secondary:
15 | 'border-transparent bg-bolt-elements-background text-bolt-elements-textSecondary hover:bg-bolt-elements-background/80',
16 | destructive: 'border-transparent bg-red-500/10 text-red-500 hover:bg-red-500/20',
17 | outline: 'text-bolt-elements-textPrimary',
18 | },
19 | },
20 | defaultVariants: {
21 | variant: 'default',
22 | },
23 | },
24 | );
25 |
26 | export interface BadgeProps extends React.HTMLAttributes, VariantProps {}
27 |
28 | function Badge({ className, variant, ...props }: BadgeProps) {
29 | return ;
30 | }
31 |
32 | export { Badge, badgeVariants };
33 |
--------------------------------------------------------------------------------
/components/ui/Button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { cva, type VariantProps } from 'class-variance-authority';
3 | import { classNames } from '@/utils/classNames';
4 |
5 | const buttonVariants = cva(
6 | 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-bolt-elements-borderColor disabled:pointer-events-none disabled:opacity-50',
7 | {
8 | variants: {
9 | variant: {
10 | default: 'bg-bolt-elements-background text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-2',
11 | destructive: 'bg-red-500 text-white hover:bg-red-600',
12 | outline:
13 | 'border border-input bg-transparent hover:bg-bolt-elements-background-depth-2 hover:text-bolt-elements-textPrimary',
14 | secondary:
15 | 'bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-2',
16 | ghost: 'hover:bg-bolt-elements-background-depth-1 hover:text-bolt-elements-textPrimary',
17 | link: 'text-bolt-elements-textPrimary underline-offset-4 hover:underline',
18 | },
19 | size: {
20 | default: 'h-9 px-4 py-2',
21 | sm: 'h-8 rounded-md px-3 text-xs',
22 | lg: 'h-10 rounded-md px-8',
23 | icon: 'h-9 w-9',
24 | },
25 | },
26 | defaultVariants: {
27 | variant: 'default',
28 | size: 'default',
29 | },
30 | },
31 | );
32 |
33 | export interface ButtonProps
34 | extends React.ButtonHTMLAttributes,
35 | VariantProps {
36 | _asChild?: boolean;
37 | }
38 |
39 | const Button = React.forwardRef(
40 | ({ className, variant, size, _asChild = false, ...props }, ref) => {
41 | return ;
42 | },
43 | );
44 | Button.displayName = 'Button';
45 |
46 | export { Button, buttonVariants };
47 |
--------------------------------------------------------------------------------
/components/ui/Card.tsx:
--------------------------------------------------------------------------------
1 | import { forwardRef } from 'react';
2 | import { classNames } from '@/utils/classNames';
3 |
4 | export interface CardProps extends React.HTMLAttributes {}
5 |
6 | const Card = forwardRef(({ className, ...props }, ref) => {
7 | return (
8 |
16 | );
17 | });
18 | Card.displayName = 'Card';
19 |
20 | const CardHeader = forwardRef(({ className, ...props }, ref) => {
21 | return ;
22 | });
23 | CardHeader.displayName = 'CardHeader';
24 |
25 | const CardTitle = forwardRef>(
26 | ({ className, ...props }, ref) => {
27 | return (
28 |
33 | );
34 | },
35 | );
36 | CardTitle.displayName = 'CardTitle';
37 |
38 | const CardDescription = forwardRef>(
39 | ({ className, ...props }, ref) => {
40 | return ;
41 | },
42 | );
43 | CardDescription.displayName = 'CardDescription';
44 |
45 | const CardContent = forwardRef(({ className, ...props }, ref) => {
46 | return ;
47 | });
48 | CardContent.displayName = 'CardContent';
49 |
50 | const CardFooter = forwardRef>(({ className, ...props }, ref) => (
51 |
52 | ));
53 | CardFooter.displayName = 'CardFooter';
54 |
55 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
56 |
--------------------------------------------------------------------------------
/components/ui/Collapsible.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
4 |
5 | const Collapsible = CollapsiblePrimitive.Root;
6 | const CollapsibleTrigger = CollapsiblePrimitive.Trigger;
7 | const CollapsibleContent = CollapsiblePrimitive.Content;
8 |
9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent };
10 |
--------------------------------------------------------------------------------
/components/ui/Dropdown.tsx:
--------------------------------------------------------------------------------
1 | import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
2 | import { type ReactNode } from 'react';
3 | import { classNames } from '@/utils/classNames';
4 |
5 | interface DropdownProps {
6 | trigger: ReactNode;
7 | children: ReactNode;
8 | align?: 'start' | 'center' | 'end';
9 | sideOffset?: number;
10 | }
11 |
12 | interface DropdownItemProps {
13 | children: ReactNode;
14 | onSelect?: () => void;
15 | className?: string;
16 | }
17 |
18 | export const DropdownItem = ({ children, onSelect, className }: DropdownItemProps) => (
19 |
30 | {children}
31 |
32 | );
33 |
34 | export const DropdownSeparator = () => ;
35 |
36 | export const Dropdown = ({ trigger, children, align = 'end', sideOffset = 5 }: DropdownProps) => {
37 | return (
38 |
39 | {trigger}
40 |
41 |
42 |
58 | {children}
59 |
60 |
61 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/components/ui/Input.tsx:
--------------------------------------------------------------------------------
1 | import { forwardRef } from 'react';
2 | import { classNames } from '@/utils/classNames';
3 |
4 | export interface InputProps extends React.InputHTMLAttributes {}
5 |
6 | const Input = forwardRef(({ className, type, ...props }, ref) => {
7 | return (
8 |
17 | );
18 | });
19 |
20 | Input.displayName = 'Input';
21 |
22 | export { Input };
23 |
--------------------------------------------------------------------------------
/components/ui/Label.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as LabelPrimitive from '@radix-ui/react-label';
3 | import { classNames } from '@/utils/classNames';
4 |
5 | const Label = React.forwardRef<
6 | React.ElementRef,
7 | React.ComponentPropsWithoutRef
8 | >(({ className, ...props }, ref) => (
9 |
17 | ));
18 | Label.displayName = LabelPrimitive.Root.displayName;
19 |
20 | export { Label };
21 |
--------------------------------------------------------------------------------
/components/ui/LoadingDots.tsx:
--------------------------------------------------------------------------------
1 | import { memo, useEffect, useState } from 'react';
2 |
3 | interface LoadingDotsProps {
4 | text: string;
5 | }
6 |
7 | export const LoadingDots = memo(({ text }: LoadingDotsProps) => {
8 | const [dotCount, setDotCount] = useState(0);
9 |
10 | useEffect(() => {
11 | const interval = setInterval(() => {
12 | setDotCount((prevDotCount) => (prevDotCount + 1) % 4);
13 | }, 500);
14 |
15 | return () => clearInterval(interval);
16 | }, []);
17 |
18 | return (
19 |
20 |
21 | {text}
22 | {'.'.repeat(dotCount)}
23 | ...
24 |
25 |
26 | );
27 | });
28 |
--------------------------------------------------------------------------------
/components/ui/LoadingOverlay.tsx:
--------------------------------------------------------------------------------
1 | export const LoadingOverlay = ({
2 | message = 'Loading...',
3 | progress,
4 | progressText,
5 | }: {
6 | message?: string;
7 | progress?: number;
8 | progressText?: string;
9 | }) => {
10 | return (
11 |
12 |
13 |
17 |
{message}
18 | {progress !== undefined && (
19 |
20 |
26 | {progressText &&
{progressText}
}
27 |
28 | )}
29 |
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/components/ui/PanelHeader.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from 'react';
2 | import { classNames } from '@/utils/classNames';
3 |
4 | interface PanelHeaderProps {
5 | className?: string;
6 | children: React.ReactNode;
7 | }
8 |
9 | export const PanelHeader = memo(({ className, children }: PanelHeaderProps) => {
10 | return (
11 |
17 | {children}
18 |
19 | );
20 | });
21 |
--------------------------------------------------------------------------------
/components/ui/PanelHeaderButton.tsx:
--------------------------------------------------------------------------------
1 | import { memo, type ReactNode } from 'react';
2 | import { classNames } from '@/utils/classNames';
3 |
4 | interface PanelHeaderButtonProps {
5 | className?: string;
6 | disabledClassName?: string;
7 | disabled?: boolean;
8 | children: string | ReactNode | Array;
9 | onClick?: (event: React.MouseEvent) => void;
10 | }
11 |
12 | export const PanelHeaderButton = memo(
13 | ({ className, disabledClassName, disabled = false, children, onClick }: PanelHeaderButtonProps) => {
14 | return (
15 |
34 | );
35 | },
36 | );
37 |
--------------------------------------------------------------------------------
/components/ui/Popover.tsx:
--------------------------------------------------------------------------------
1 | import * as Popover from '@radix-ui/react-popover';
2 | import type { PropsWithChildren, ReactNode } from 'react';
3 |
4 | export default ({
5 | children,
6 | trigger,
7 | side,
8 | align,
9 | }: PropsWithChildren<{
10 | trigger: ReactNode;
11 | side: 'top' | 'right' | 'bottom' | 'left' | undefined;
12 | align: 'center' | 'start' | 'end' | undefined;
13 | }>) => (
14 |
15 | {trigger}
16 |
17 |
18 |
24 | {children}
25 |
26 |
27 |
28 |
29 | );
30 |
--------------------------------------------------------------------------------
/components/ui/Progress-ui.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from 'react';
4 | import * as ProgressPrimitive from '@radix-ui/react-progress';
5 | import { classNames } from '@/utils/classNames';
6 |
7 | const Progress = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, value, ...props }, ref) => (
11 |
19 |
23 |
24 | ))
25 | Progress.displayName = ProgressPrimitive.Root.displayName
26 |
27 | export { Progress }
28 |
--------------------------------------------------------------------------------
/components/ui/Progress.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { classNames } from '@/utils/classNames';
3 |
4 | interface ProgressProps extends React.HTMLAttributes {
5 | value?: number;
6 | }
7 |
8 | const Progress = React.forwardRef(({ className, value, ...props }, ref) => (
9 |
19 | ));
20 | Progress.displayName = 'Progress';
21 |
22 | export { Progress };
23 |
--------------------------------------------------------------------------------
/components/ui/ScrollArea.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
5 | import { classNames } from '@/utils/classNames';
6 |
7 | const ScrollArea = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, children, ...props }, ref) => (
11 |
12 | {children}
13 |
14 |
15 |
16 | ));
17 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
18 |
19 | const ScrollBar = React.forwardRef<
20 | React.ElementRef,
21 | React.ComponentPropsWithoutRef
22 | >(({ className, orientation = 'vertical', ...props }, ref) => (
23 |
36 |
37 |
38 | ));
39 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
40 |
41 | export { ScrollArea, ScrollBar };
42 |
--------------------------------------------------------------------------------
/components/ui/Separator.tsx:
--------------------------------------------------------------------------------
1 | import * as SeparatorPrimitive from '@radix-ui/react-separator';
2 | import { classNames } from '@/utils/classNames';
3 |
4 | interface SeparatorProps {
5 | className?: string;
6 | orientation?: 'horizontal' | 'vertical';
7 | }
8 |
9 | export const Separator = ({ className, orientation = 'horizontal' }: SeparatorProps) => {
10 | return (
11 |
19 | );
20 | };
21 |
22 | export default Separator;
23 |
--------------------------------------------------------------------------------
/components/ui/SettingsButton.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from 'react';
2 | import { IconButton } from '@/components/ui/IconButton';
3 | interface SettingsButtonProps {
4 | onClick: () => void;
5 | }
6 |
7 | export const SettingsButton = memo(({ onClick }: SettingsButtonProps) => {
8 | return (
9 |
16 | );
17 | });
18 |
--------------------------------------------------------------------------------
/components/ui/Slider.tsx:
--------------------------------------------------------------------------------
1 | import { motion } from 'framer-motion';
2 | import { memo, type ReactNode } from 'react';
3 | import { classNames } from '@/utils/classNames';
4 | import { cubicEasingFn } from '@/utils/easings';
5 | import { genericMemo } from '@/utils/react';
6 |
7 | interface SliderOption {
8 | value: T;
9 | text: string;
10 | }
11 |
12 | export interface SliderOptions {
13 | left: SliderOption;
14 | right: SliderOption;
15 | }
16 |
17 | interface SliderProps {
18 | selected: T;
19 | options: SliderOptions;
20 | setSelected?: (selected: T) => void;
21 | }
22 |
23 | export const Slider = genericMemo(({ selected, options, setSelected }: SliderProps) => {
24 | const isLeftSelected = selected === options.left.value;
25 |
26 | return (
27 |
28 | setSelected?.(options.left.value)}>
29 | {options.left.text}
30 |
31 | setSelected?.(options.right.value)}>
32 | {options.right.text}
33 |
34 |
35 | );
36 | });
37 |
38 | interface SliderButtonProps {
39 | selected: boolean;
40 | children: string | ReactNode | Array;
41 | setSelected: () => void;
42 | }
43 |
44 | const SliderButton = memo(({ selected, children, setSelected }: SliderButtonProps) => {
45 | return (
46 |
64 | );
65 | });
66 |
--------------------------------------------------------------------------------
/components/ui/Switch.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from 'react';
2 | import * as SwitchPrimitive from '@radix-ui/react-switch';
3 | import { classNames } from '@/utils/classNames';
4 |
5 | interface SwitchProps {
6 | className?: string;
7 | checked?: boolean;
8 | onCheckedChange?: (event: boolean) => void;
9 | }
10 |
11 | export const Switch = memo(({ className, onCheckedChange, checked }: SwitchProps) => {
12 | return (
13 | onCheckedChange?.(e)}
24 | >
25 |
35 |
36 | );
37 | });
38 |
--------------------------------------------------------------------------------
/components/ui/Tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as TabsPrimitive from '@radix-ui/react-tabs';
3 | import { classNames } from '@/utils/classNames';
4 |
5 | const Tabs = TabsPrimitive.Root;
6 |
7 | const TabsList = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
19 | ));
20 | TabsList.displayName = TabsPrimitive.List.displayName;
21 |
22 | const TabsTrigger = React.forwardRef<
23 | React.ElementRef,
24 | React.ComponentPropsWithoutRef
25 | >(({ className, ...props }, ref) => (
26 |
34 | ));
35 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
36 |
37 | const TabsContent = React.forwardRef<
38 | React.ElementRef,
39 | React.ComponentPropsWithoutRef
40 | >(({ className, ...props }, ref) => (
41 |
49 | ));
50 | TabsContent.displayName = TabsPrimitive.Content.displayName;
51 |
52 | export { Tabs, TabsList, TabsTrigger, TabsContent };
53 |
--------------------------------------------------------------------------------
/components/ui/ThemeSwitch.tsx:
--------------------------------------------------------------------------------
1 | import { useStore } from '@nanostores/react';
2 | import { memo, useEffect, useState } from 'react';
3 | import { themeStore, toggleTheme } from '@/lib/stores/theme';
4 | import { IconButton } from './IconButton';
5 |
6 | interface ThemeSwitchProps {
7 | className?: string;
8 | }
9 |
10 | export const ThemeSwitch = memo(({ className }: ThemeSwitchProps) => {
11 | const theme = useStore(themeStore);
12 | const [domLoaded, setDomLoaded] = useState(false);
13 |
14 | useEffect(() => {
15 | setDomLoaded(true);
16 | }, []);
17 |
18 | return (
19 | domLoaded && (
20 |
27 | )
28 | );
29 | });
30 |
--------------------------------------------------------------------------------
/components/ui/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as Tooltip from '@radix-ui/react-tooltip';
2 | import { forwardRef, type ForwardedRef, type ReactElement } from 'react';
3 |
4 | interface TooltipProps {
5 | tooltip: React.ReactNode;
6 | children: ReactElement;
7 | sideOffset?: number;
8 | className?: string;
9 | arrowClassName?: string;
10 | tooltipStyle?: React.CSSProperties;
11 | position?: 'top' | 'bottom' | 'left' | 'right';
12 | maxWidth?: number;
13 | delay?: number;
14 | }
15 |
16 | const WithTooltip = forwardRef(
17 | (
18 | {
19 | tooltip,
20 | children,
21 | sideOffset = 5,
22 | className = '',
23 | arrowClassName = '',
24 | tooltipStyle = {},
25 | position = 'top',
26 | maxWidth = 250,
27 | delay = 0,
28 | }: TooltipProps,
29 | _ref: ForwardedRef,
30 | ) => {
31 | return (
32 |
33 | {children}
34 |
35 |
63 | {tooltip}
64 |
72 |
73 |
74 |
75 | );
76 | },
77 | );
78 |
79 | export default WithTooltip;
80 |
--------------------------------------------------------------------------------
/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { toast as toastify } from 'react-toastify';
3 |
4 | interface ToastOptions {
5 | type?: 'success' | 'error' | 'info' | 'warning';
6 | duration?: number;
7 | }
8 |
9 | export function useToast() {
10 | const toast = useCallback((message: string, options: ToastOptions = {}) => {
11 | const { type = 'info', duration = 3000 } = options;
12 |
13 | toastify[type](message, {
14 | position: 'bottom-right',
15 | autoClose: duration,
16 | hideProgressBar: false,
17 | closeOnClick: true,
18 | pauseOnHover: true,
19 | draggable: true,
20 | progress: undefined,
21 | theme: 'dark',
22 | });
23 | }, []);
24 |
25 | const success = useCallback(
26 | (message: string, options: Omit = {}) => {
27 | toast(message, { ...options, type: 'success' });
28 | },
29 | [toast],
30 | );
31 |
32 | const error = useCallback(
33 | (message: string, options: Omit = {}) => {
34 | toast(message, { ...options, type: 'error' });
35 | },
36 | [toast],
37 | );
38 |
39 | return { toast, success, error };
40 | }
41 |
--------------------------------------------------------------------------------
/components/workbench/terminal/theme.ts:
--------------------------------------------------------------------------------
1 | import type { ITheme } from '@xterm/xterm';
2 |
3 | const style = getComputedStyle(document.documentElement);
4 | const cssVar = (token: string) => style.getPropertyValue(token) || undefined;
5 |
6 | export function getTerminalTheme(overrides?: ITheme): ITheme {
7 | return {
8 | cursor: cssVar('--bolt-elements-terminal-cursorColor'),
9 | cursorAccent: cssVar('--bolt-elements-terminal-cursorColorAccent'),
10 | foreground: cssVar('--bolt-elements-terminal-textColor'),
11 | background: cssVar('--bolt-elements-terminal-backgroundColor'),
12 | selectionBackground: cssVar('--bolt-elements-terminal-selection-backgroundColor'),
13 | selectionForeground: cssVar('--bolt-elements-terminal-selection-textColor'),
14 | selectionInactiveBackground: cssVar('--bolt-elements-terminal-selection-backgroundColorInactive'),
15 |
16 | // ansi escape code colors
17 | black: cssVar('--bolt-elements-terminal-color-black'),
18 | red: cssVar('--bolt-elements-terminal-color-red'),
19 | green: cssVar('--bolt-elements-terminal-color-green'),
20 | yellow: cssVar('--bolt-elements-terminal-color-yellow'),
21 | blue: cssVar('--bolt-elements-terminal-color-blue'),
22 | magenta: cssVar('--bolt-elements-terminal-color-magenta'),
23 | cyan: cssVar('--bolt-elements-terminal-color-cyan'),
24 | white: cssVar('--bolt-elements-terminal-color-white'),
25 | brightBlack: cssVar('--bolt-elements-terminal-color-brightBlack'),
26 | brightRed: cssVar('--bolt-elements-terminal-color-brightRed'),
27 | brightGreen: cssVar('--bolt-elements-terminal-color-brightGreen'),
28 | brightYellow: cssVar('--bolt-elements-terminal-color-brightYellow'),
29 | brightBlue: cssVar('--bolt-elements-terminal-color-brightBlue'),
30 | brightMagenta: cssVar('--bolt-elements-terminal-color-brightMagenta'),
31 | brightCyan: cssVar('--bolt-elements-terminal-color-brightCyan'),
32 | brightWhite: cssVar('--bolt-elements-terminal-color-brightWhite'),
33 |
34 | ...overrides,
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/db/edge-db.ts:
--------------------------------------------------------------------------------
1 | import { drizzle } from "drizzle-orm/neon-serverless";
2 | import { Pool } from '@neondatabase/serverless';
3 | import * as schema from "./schema";
4 |
5 |
6 | const connectionString = process.env.DATABASE_URL!;
7 |
8 | export function getDb() {
9 | const client = new Pool({ connectionString });
10 | return drizzle(client, { schema });
11 | }
12 |
13 | // 在需要时创建连接并在使用后关闭
14 | export async function withDb(fn: (db: ReturnType) => Promise): Promise {
15 | const client = new Pool({ connectionString });
16 | const db = drizzle(client, { schema });
17 | try {
18 | const result = await fn(db);
19 | await client.end();
20 | return result;
21 | } catch (error) {
22 | await client.end();
23 | throw error;
24 | }
25 | }
--------------------------------------------------------------------------------
/db/index.ts:
--------------------------------------------------------------------------------
1 | // import { drizzle } from "drizzle-orm/postgres-js";
2 | // import postgres from "postgres";
3 | // import { users, chats } from "./schema";
4 |
5 | const connectionString = process.env.DATABASE_URL!;
6 |
7 |
8 | import { drizzle } from "drizzle-orm/neon-serverless";
9 | import { Pool } from '@neondatabase/serverless';
10 |
11 | import * as schema from "./schema";
12 |
13 | // const client = postgres(env.DATABASE_URL);
14 | const client = new Pool({ connectionString });
15 |
16 | export const db = drizzle(client, { schema });
17 |
18 | // 创建一个获取数据库连接的工厂函数
19 | export function getDb() {
20 | return db
21 | }
22 |
23 | // 在需要时创建连接并在使用后关闭
24 | export async function withDb(fn: (db: ReturnType) => Promise): Promise {
25 | try {
26 | const result = await fn(db);
27 | return result;
28 | } catch (error) {
29 | throw error;
30 | }
31 | }
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "drizzle-kit";
2 |
3 |
4 | export default defineConfig({
5 | schema: "./db/schema.ts",
6 | out: "./migrations",
7 | dialect: "postgresql",
8 | dbCredentials: {
9 | url: process.env.DATABASE_URL!,
10 | },
11 | verbose: true
12 | });
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { dirname } from 'node:path'
2 | import { fileURLToPath } from 'node:url'
3 | import { FlatCompat } from '@eslint/eslintrc'
4 |
5 | const __filename = fileURLToPath(import.meta.url)
6 | const __dirname = dirname(__filename)
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname,
10 | })
11 |
12 | const eslintConfig = [
13 | ...compat.extends('next/core-web-vitals', 'next/typescript'),
14 | ]
15 |
16 | export default eslintConfig
17 |
--------------------------------------------------------------------------------
/icons/angular.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/astro.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/chat.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/icons/logo-text.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/icons/logo.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/icons/nativescript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/nextjs.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/nuxt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/qwik.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/remix.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/remotion.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/slidev.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/stars.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/svelte.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/typescript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/vue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/.server/llm/constants.ts:
--------------------------------------------------------------------------------
1 | // see https://docs.anthropic.com/en/docs/about-claude/models
2 | export const MAX_TOKENS = 8000;
3 |
4 | // limits the number of model responses that can be returned in a single request
5 | export const MAX_RESPONSE_SEGMENTS = 2;
6 |
7 | export interface File {
8 | type: 'file';
9 | content: string;
10 | isBinary: boolean;
11 | }
12 |
13 | export interface Folder {
14 | type: 'folder';
15 | }
16 |
17 | type Dirent = File | Folder;
18 |
19 | export type FileMap = Record;
20 |
21 | export const IGNORE_PATTERNS = [
22 | 'node_modules/**',
23 | '.git/**',
24 | 'dist/**',
25 | 'build/**',
26 | '.next/**',
27 | 'coverage/**',
28 | '.cache/**',
29 | '.vscode/**',
30 | '.idea/**',
31 | '**/*.log',
32 | '**/.DS_Store',
33 | '**/npm-debug.log*',
34 | '**/yarn-debug.log*',
35 | '**/yarn-error.log*',
36 | '**/*lock.json',
37 | '**/*lock.yml',
38 | ];
39 |
--------------------------------------------------------------------------------
/lib/.server/llm/switchable-stream.ts:
--------------------------------------------------------------------------------
1 | export default class SwitchableStream extends TransformStream {
2 | private _controller: TransformStreamDefaultController | null = null;
3 | private _currentReader: ReadableStreamDefaultReader | null = null;
4 | private _switches = 0;
5 |
6 | constructor() {
7 | let controllerRef: TransformStreamDefaultController | undefined;
8 |
9 | super({
10 | start(controller) {
11 | controllerRef = controller;
12 | },
13 | });
14 |
15 | if (controllerRef === undefined) {
16 | throw new Error('Controller not properly initialized');
17 | }
18 |
19 | this._controller = controllerRef;
20 | }
21 |
22 | async switchSource(newStream: ReadableStream) {
23 | if (this._currentReader) {
24 | await this._currentReader.cancel();
25 | }
26 |
27 | this._currentReader = newStream.getReader();
28 |
29 | this._pumpStream();
30 |
31 | this._switches++;
32 | }
33 |
34 | private async _pumpStream() {
35 | if (!this._currentReader || !this._controller) {
36 | throw new Error('Stream is not properly initialized');
37 | }
38 |
39 | try {
40 | while (true) {
41 | const { done, value } = await this._currentReader.read();
42 |
43 | if (done) {
44 | break;
45 | }
46 |
47 | this._controller.enqueue(value);
48 | }
49 | } catch (error) {
50 | console.log(error);
51 | this._controller.error(error);
52 | }
53 | }
54 |
55 | close() {
56 | if (this._currentReader) {
57 | this._currentReader.cancel();
58 | }
59 |
60 | this._controller?.terminate();
61 | }
62 |
63 | get switches() {
64 | return this._switches;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/lib/api/connection.ts:
--------------------------------------------------------------------------------
1 | export interface ConnectionStatus {
2 | connected: boolean;
3 | latency: number;
4 | lastChecked: string;
5 | }
6 |
7 | export const checkConnection = async (): Promise => {
8 | try {
9 | // Check if we have network connectivity
10 | const online = navigator.onLine;
11 |
12 | if (!online) {
13 | return {
14 | connected: false,
15 | latency: 0,
16 | lastChecked: new Date().toISOString(),
17 | };
18 | }
19 |
20 | // Try multiple endpoints in case one fails
21 | const endpoints = [
22 | '/api/health',
23 | '/', // Fallback to root route
24 | '/favicon.ico', // Another common fallback
25 | ];
26 |
27 | let latency = 0;
28 | let connected = false;
29 |
30 | for (const endpoint of endpoints) {
31 | try {
32 | const start = performance.now();
33 | const response = await fetch(endpoint, {
34 | method: 'HEAD',
35 | cache: 'no-cache',
36 | });
37 | const end = performance.now();
38 |
39 | if (response.ok) {
40 | latency = Math.round(end - start);
41 | connected = true;
42 | break;
43 | }
44 | } catch (endpointError) {
45 | console.debug(`Failed to connect to ${endpoint}:`, endpointError);
46 | continue;
47 | }
48 | }
49 |
50 | return {
51 | connected,
52 | latency,
53 | lastChecked: new Date().toISOString(),
54 | };
55 | } catch (error) {
56 | console.error('Connection check failed:', error);
57 | return {
58 | connected: false,
59 | latency: 0,
60 | lastChecked: new Date().toISOString(),
61 | };
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/lib/api/cookies.ts:
--------------------------------------------------------------------------------
1 | export function parseCookies(cookieHeader: string | null) {
2 | const cookies: Record = {};
3 |
4 | if (!cookieHeader) {
5 | return cookies;
6 | }
7 |
8 | // Split the cookie string by semicolons and spaces
9 | const items = cookieHeader.split(';').map((cookie) => cookie.trim());
10 |
11 | items.forEach((item) => {
12 | const [name, ...rest] = item.split('=');
13 |
14 | if (name && rest.length > 0) {
15 | // Decode the name and value, and join value parts in case it contains '='
16 | const decodedName = decodeURIComponent(name.trim());
17 | const decodedValue = decodeURIComponent(rest.join('=').trim());
18 | cookies[decodedName] = decodedValue;
19 | }
20 | });
21 |
22 | return cookies;
23 | }
24 |
25 | export function getApiKeysFromCookie(cookieHeader: string | null): Record {
26 | const cookies = parseCookies(cookieHeader);
27 | return cookies.apiKeys ? JSON.parse(cookies.apiKeys) : {};
28 | }
29 |
30 | export function getProviderSettingsFromCookie(cookieHeader: string | null): Record {
31 | const cookies = parseCookies(cookieHeader);
32 | return cookies.providers ? JSON.parse(cookies.providers) : {};
33 | }
34 |
--------------------------------------------------------------------------------
/lib/api/features.ts:
--------------------------------------------------------------------------------
1 | export interface Feature {
2 | id: string;
3 | name: string;
4 | description: string;
5 | viewed: boolean;
6 | releaseDate: string;
7 | }
8 |
9 | export const getFeatureFlags = async (): Promise => {
10 | /*
11 | * TODO: Implement actual feature flags logic
12 | * This is a mock implementation
13 | */
14 | return [
15 | {
16 | id: 'feature-1',
17 | name: 'Dark Mode',
18 | description: 'Enable dark mode for better night viewing',
19 | viewed: true,
20 | releaseDate: '2024-03-15',
21 | },
22 | {
23 | id: 'feature-2',
24 | name: 'Tab Management',
25 | description: 'Customize your tab layout',
26 | viewed: false,
27 | releaseDate: '2024-03-20',
28 | },
29 | ];
30 | };
31 |
32 | export const markFeatureViewed = async (featureId: string): Promise => {
33 | /* TODO: Implement actual feature viewed logic */
34 | console.log(`Marking feature ${featureId} as viewed`);
35 | };
36 |
--------------------------------------------------------------------------------
/lib/api/notifications.ts:
--------------------------------------------------------------------------------
1 | import { logStore } from '@/lib/stores/logs';
2 | import type { LogEntry } from '@/lib/stores/logs';
3 |
4 | export interface Notification {
5 | id: string;
6 | title: string;
7 | message: string;
8 | type: 'info' | 'warning' | 'error' | 'success';
9 | timestamp: string;
10 | read: boolean;
11 | details?: Record;
12 | }
13 |
14 | export interface LogEntryWithRead extends LogEntry {
15 | read: boolean;
16 | }
17 |
18 | export const getNotifications = async (): Promise => {
19 | // Get notifications from the log store
20 | const logs = Object.values(logStore.logs.get());
21 |
22 | return logs
23 | .filter((log) => log.category !== 'system') // Filter out system logs
24 | .map((log) => ({
25 | id: log.id,
26 | title: (log.details?.title as string) || log.message.split('\n')[0],
27 | message: log.message,
28 | type: log.level as 'info' | 'warning' | 'error' | 'success',
29 | timestamp: log.timestamp,
30 | read: logStore.isRead(log.id),
31 | details: log.details,
32 | }))
33 | .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
34 | };
35 |
36 | export const markNotificationRead = async (notificationId: string): Promise => {
37 | logStore.markAsRead(notificationId);
38 | };
39 |
40 | export const clearNotifications = async (): Promise => {
41 | logStore.clearLogs();
42 | };
43 |
44 | export const getUnreadCount = (): number => {
45 | const logs = Object.values(logStore.logs.get()) as LogEntryWithRead[];
46 |
47 | return logs.filter((log) => {
48 | if (!logStore.isRead(log.id)) {
49 | if (log.details?.type === 'update') {
50 | return true;
51 | }
52 |
53 | return log.level === 'error' || log.level === 'warning';
54 | }
55 |
56 | return false;
57 | }).length;
58 | };
59 |
--------------------------------------------------------------------------------
/lib/common/prompt-library.ts:
--------------------------------------------------------------------------------
1 | import { getSystemPrompt } from './prompts/prompts';
2 | import optimized from './prompts/optimized';
3 |
4 | export interface PromptOptions {
5 | cwd: string;
6 | allowedHtmlElements: string[];
7 | modificationTagName: string;
8 | }
9 |
10 | export class PromptLibrary {
11 | static library: Record<
12 | string,
13 | {
14 | label: string;
15 | description: string;
16 | get: (options: PromptOptions) => string;
17 | }
18 | > = {
19 | default: {
20 | label: 'Default Prompt',
21 | description: 'This is the battle tested default system Prompt',
22 | get: (options) => getSystemPrompt(options.cwd),
23 | },
24 | optimized: {
25 | label: 'Optimized Prompt (experimental)',
26 | description: 'an Experimental version of the prompt for lower token usage',
27 | get: (options) => optimized(options),
28 | },
29 | };
30 | static getList() {
31 | return Object.entries(this.library).map(([key, value]) => {
32 | const { label, description } = value;
33 | return {
34 | id: key,
35 | label,
36 | description,
37 | };
38 | });
39 | }
40 | static getPropmtFromLibrary(promptId: string, options: PromptOptions) {
41 | const prompt = this.library[promptId];
42 |
43 | if (!prompt) {
44 | throw 'Prompt Now Found';
45 | }
46 |
47 | return this.library[promptId]?.get(options);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/crypto.ts:
--------------------------------------------------------------------------------
1 | const encoder = new TextEncoder();
2 | const decoder = new TextDecoder();
3 | const IV_LENGTH = 16;
4 |
5 | export async function encrypt(key: string, data: string) {
6 | const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
7 | const cryptoKey = await getKey(key);
8 |
9 | const ciphertext = await crypto.subtle.encrypt(
10 | {
11 | name: 'AES-CBC',
12 | iv,
13 | },
14 | cryptoKey,
15 | encoder.encode(data),
16 | );
17 |
18 | const bundle = new Uint8Array(IV_LENGTH + ciphertext.byteLength);
19 |
20 | bundle.set(new Uint8Array(ciphertext));
21 | bundle.set(iv, ciphertext.byteLength);
22 |
23 | return decodeBase64(bundle);
24 | }
25 |
26 | export async function decrypt(key: string, payload: string) {
27 | const bundle = encodeBase64(payload);
28 |
29 | const iv = new Uint8Array(bundle.buffer, bundle.byteLength - IV_LENGTH);
30 | const ciphertext = new Uint8Array(bundle.buffer, 0, bundle.byteLength - IV_LENGTH);
31 |
32 | const cryptoKey = await getKey(key);
33 |
34 | const plaintext = await crypto.subtle.decrypt(
35 | {
36 | name: 'AES-CBC',
37 | iv,
38 | },
39 | cryptoKey,
40 | ciphertext,
41 | );
42 |
43 | return decoder.decode(plaintext);
44 | }
45 |
46 | async function getKey(key: string) {
47 | return await crypto.subtle.importKey('raw', encodeBase64(key), { name: 'AES-CBC' }, false, ['encrypt', 'decrypt']);
48 | }
49 |
50 | function decodeBase64(encoded: Uint8Array) {
51 | const byteChars = Array.from(encoded, (byte) => String.fromCodePoint(byte));
52 |
53 | return btoa(byteChars.join(''));
54 | }
55 |
56 | function encodeBase64(data: string) {
57 | return Uint8Array.from(atob(data), (ch) => ch.codePointAt(0)!);
58 | }
59 |
--------------------------------------------------------------------------------
/lib/fetch.ts:
--------------------------------------------------------------------------------
1 | type CommonRequest = Omit & { body?: URLSearchParams };
2 |
3 | export async function request(url: string, init?: CommonRequest) {
4 | return fetch(url, init);
5 | }
6 |
--------------------------------------------------------------------------------
/lib/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useMessageParser';
2 | export * from './usePromptEnhancer';
3 | export * from './useShortcuts';
4 | export * from './useSnapScroll';
5 | export * from './useEditChatDescription';
6 | export { default } from './useViewport';
7 | export { useUpdateCheck } from './useUpdateCheck';
8 | export { useFeatures } from './useFeatures';
9 | export { useNotifications } from './useNotifications';
10 | export { useConnectionStatus } from './useConnectionStatus';
11 | export { useDebugStatus } from './useDebugStatus';
12 |
--------------------------------------------------------------------------------
/lib/hooks/useConnectionStatus.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { checkConnection } from '@/lib/api/connection';
3 |
4 | const ACKNOWLEDGED_CONNECTION_ISSUE_KEY = 'bolt_acknowledged_connection_issue';
5 |
6 | type ConnectionIssueType = 'disconnected' | 'high-latency' | null;
7 |
8 | const getAcknowledgedIssue = (): string | null => {
9 | try {
10 | return localStorage.getItem(ACKNOWLEDGED_CONNECTION_ISSUE_KEY);
11 | } catch {
12 | return null;
13 | }
14 | };
15 |
16 | export const useConnectionStatus = () => {
17 | const [hasConnectionIssues, setHasConnectionIssues] = useState(false);
18 | const [currentIssue, setCurrentIssue] = useState(null);
19 | const [acknowledgedIssue, setAcknowledgedIssue] = useState(() => getAcknowledgedIssue());
20 |
21 | const checkStatus = async () => {
22 | try {
23 | const status = await checkConnection();
24 | const issue = !status.connected ? 'disconnected' : status.latency > 1000 ? 'high-latency' : null;
25 |
26 | setCurrentIssue(issue);
27 |
28 | // Only show issues if they're new or different from the acknowledged one
29 | setHasConnectionIssues(issue !== null && issue !== acknowledgedIssue);
30 | } catch (error) {
31 | console.error('Failed to check connection:', error);
32 |
33 | // Show connection issues if we can't even check the status
34 | setCurrentIssue('disconnected');
35 | setHasConnectionIssues(true);
36 | }
37 | };
38 |
39 | // useEffect(() => {
40 | // // Check immediately and then every 10 seconds
41 | // checkStatus();
42 |
43 | // const interval = setInterval(checkStatus, 10 * 1000);
44 |
45 | // return () => clearInterval(interval);
46 | // }, [acknowledgedIssue]);
47 |
48 | const acknowledgeIssue = () => {
49 | setAcknowledgedIssue(currentIssue);
50 | setAcknowledgedIssue(currentIssue);
51 | setHasConnectionIssues(false);
52 | };
53 |
54 | const resetAcknowledgment = () => {
55 | setAcknowledgedIssue(null);
56 | setAcknowledgedIssue(null);
57 | checkStatus();
58 | };
59 |
60 | return { hasConnectionIssues, currentIssue, acknowledgeIssue, resetAcknowledgment };
61 | };
62 |
--------------------------------------------------------------------------------
/lib/hooks/useLocalProviders.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react';
2 | import type { IProviderConfig } from '@/types/model';
3 |
4 | export interface UseLocalProvidersReturn {
5 | localProviders: IProviderConfig[];
6 | refreshLocalProviders: () => void;
7 | }
8 |
9 | export function useLocalProviders(): UseLocalProvidersReturn {
10 | const [localProviders, setLocalProviders] = useState([]);
11 |
12 | const refreshLocalProviders = useCallback(() => {
13 | /*
14 | * Refresh logic for local providers
15 | * This would typically involve checking the status of Ollama and LMStudio
16 | * For now, we'll just return an empty array
17 | */
18 | setLocalProviders([]);
19 | }, []);
20 |
21 | return {
22 | localProviders,
23 | refreshLocalProviders,
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/lib/hooks/useNotifications.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { getNotifications, markNotificationRead, type Notification } from '@/lib/api/notifications';
3 | import { logStore } from '@/lib/stores/logs';
4 | import { useStore } from '@nanostores/react';
5 |
6 | export const useNotifications = () => {
7 | const [hasUnreadNotifications, setHasUnreadNotifications] = useState(false);
8 | const [unreadNotifications, setUnreadNotifications] = useState([]);
9 | const logs = useStore(logStore.logs);
10 |
11 | const checkNotifications = async () => {
12 | try {
13 | const notifications = await getNotifications();
14 | const unread = notifications.filter((n) => !logStore.isRead(n.id));
15 | setUnreadNotifications(unread);
16 | setHasUnreadNotifications(unread.length > 0);
17 | } catch (error) {
18 | console.error('Failed to check notifications:', error);
19 | }
20 | };
21 |
22 | useEffect(() => {
23 | // Check immediately and then every minute
24 | checkNotifications();
25 |
26 | const interval = setInterval(checkNotifications, 60 * 1000);
27 |
28 | return () => clearInterval(interval);
29 | }, [logs]); // Re-run when logs change
30 |
31 | const markAsRead = async (notificationId: string) => {
32 | try {
33 | await markNotificationRead(notificationId);
34 | await checkNotifications();
35 | } catch (error) {
36 | console.error('Failed to mark notification as read:', error);
37 | }
38 | };
39 |
40 | const markAllAsRead = async () => {
41 | try {
42 | const notifications = await getNotifications();
43 | await Promise.all(notifications.map((n) => markNotificationRead(n.id)));
44 | await checkNotifications();
45 | } catch (error) {
46 | console.error('Failed to mark all notifications as read:', error);
47 | }
48 | };
49 |
50 | return { hasUnreadNotifications, unreadNotifications, markAsRead, markAllAsRead };
51 | };
52 |
--------------------------------------------------------------------------------
/lib/hooks/usePromptEnhancer.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import type { ProviderInfo } from '@/types/model';
3 | import { createScopedLogger } from '@/utils/logger';
4 |
5 | const logger = createScopedLogger('usePromptEnhancement');
6 |
7 | export function usePromptEnhancer() {
8 | const [enhancingPrompt, setEnhancingPrompt] = useState(false);
9 | const [promptEnhanced, setPromptEnhanced] = useState(false);
10 |
11 | const resetEnhancer = () => {
12 | setEnhancingPrompt(false);
13 | setPromptEnhanced(false);
14 | };
15 |
16 | const enhancePrompt = async (
17 | input: string,
18 | setInput: (value: string) => void,
19 | model: string,
20 | provider: ProviderInfo,
21 | apiKeys?: Record,
22 | ) => {
23 | setEnhancingPrompt(true);
24 | setPromptEnhanced(false);
25 |
26 | const requestBody: any = {
27 | message: input,
28 | model,
29 | provider,
30 | };
31 |
32 | if (apiKeys) {
33 | requestBody.apiKeys = apiKeys;
34 | }
35 |
36 | const response = await fetch('/api/enhancer', {
37 | method: 'POST',
38 | body: JSON.stringify(requestBody),
39 | });
40 |
41 | const reader = response.body?.getReader();
42 |
43 | const originalInput = input;
44 |
45 | if (reader) {
46 | const decoder = new TextDecoder();
47 |
48 | let _input = '';
49 | let _error;
50 |
51 | try {
52 | setInput('');
53 |
54 | while (true) {
55 | const { value, done } = await reader.read();
56 |
57 | if (done) {
58 | break;
59 | }
60 |
61 | _input += decoder.decode(value);
62 |
63 | logger.trace('Set input', _input);
64 |
65 | setInput(_input);
66 | }
67 | } catch (error) {
68 | _error = error;
69 | setInput(originalInput);
70 | } finally {
71 | if (_error) {
72 | logger.error(_error);
73 | }
74 |
75 | setEnhancingPrompt(false);
76 | setPromptEnhanced(true);
77 |
78 | setTimeout(() => {
79 | setInput(_input);
80 | });
81 | }
82 | }
83 | };
84 |
85 | return { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer };
86 | }
87 |
--------------------------------------------------------------------------------
/lib/hooks/useSearchFilter.ts:
--------------------------------------------------------------------------------
1 | import { useState, useMemo, useCallback } from 'react';
2 | import { debounce } from '@/utils/debounce';
3 | import type { ChatHistoryItem } from '@/lib/persistence';
4 |
5 | interface UseSearchFilterOptions {
6 | items: ChatHistoryItem[];
7 | searchFields?: (keyof ChatHistoryItem)[];
8 | debounceMs?: number;
9 | }
10 |
11 | export function useSearchFilter({
12 | items = [],
13 | searchFields = ['description'],
14 | debounceMs = 300,
15 | }: UseSearchFilterOptions) {
16 | const [searchQuery, setSearchQuery] = useState('');
17 |
18 | const debouncedSetSearch = useCallback(debounce(setSearchQuery, debounceMs), []);
19 |
20 | const handleSearchChange = useCallback(
21 | (event: React.ChangeEvent) => {
22 | debouncedSetSearch(event.target.value);
23 | },
24 | [debouncedSetSearch],
25 | );
26 |
27 | const filteredItems = useMemo(() => {
28 | if (!searchQuery.trim()) {
29 | return items;
30 | }
31 |
32 | const query = searchQuery.toLowerCase();
33 |
34 | return items.filter((item) =>
35 | searchFields.some((field) => {
36 | const value = item[field];
37 |
38 | if (typeof value === 'string') {
39 | return value.toLowerCase().includes(query);
40 | }
41 |
42 | return false;
43 | }),
44 | );
45 | }, [items, searchQuery, searchFields]);
46 |
47 | return {
48 | searchQuery,
49 | filteredItems,
50 | handleSearchChange,
51 | };
52 | }
53 |
--------------------------------------------------------------------------------
/lib/hooks/useUpdateCheck.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { checkForUpdates, acknowledgeUpdate } from '@/lib/api/updates';
3 |
4 | const LAST_ACKNOWLEDGED_VERSION_KEY = 'bolt_last_acknowledged_version';
5 |
6 | export const useUpdateCheck = () => {
7 | const [hasUpdate, setHasUpdate] = useState(false);
8 | const [currentVersion, setCurrentVersion] = useState('');
9 | const [lastAcknowledgedVersion, setLastAcknowledgedVersion] = useState(() => {
10 | try {
11 | return localStorage.getItem(LAST_ACKNOWLEDGED_VERSION_KEY);
12 | } catch {
13 | return null;
14 | }
15 | });
16 |
17 | useEffect(() => {
18 | const checkUpdate = async () => {
19 | try {
20 | const { available, version } = await checkForUpdates();
21 | setCurrentVersion(version);
22 |
23 | // Only show update if it's a new version and hasn't been acknowledged
24 | setHasUpdate(available && version !== lastAcknowledgedVersion);
25 | } catch (error) {
26 | console.error('Failed to check for updates:', error);
27 | }
28 | };
29 |
30 | // Check immediately and then every 30 minutes
31 | checkUpdate();
32 |
33 | const interval = setInterval(checkUpdate, 30 * 60 * 1000);
34 |
35 | return () => clearInterval(interval);
36 | }, [lastAcknowledgedVersion]);
37 |
38 | const handleAcknowledgeUpdate = async () => {
39 | try {
40 | const { version } = await checkForUpdates();
41 | await acknowledgeUpdate(version);
42 |
43 | // Store in localStorage
44 | try {
45 | localStorage.setItem(LAST_ACKNOWLEDGED_VERSION_KEY, version);
46 | } catch (error) {
47 | console.error('Failed to persist acknowledged version:', error);
48 | }
49 |
50 | setLastAcknowledgedVersion(version);
51 | setHasUpdate(false);
52 | } catch (error) {
53 | console.error('Failed to acknowledge update:', error);
54 | }
55 | };
56 |
57 | return { hasUpdate, currentVersion, acknowledgeUpdate: handleAcknowledgeUpdate };
58 | };
59 |
--------------------------------------------------------------------------------
/lib/hooks/useViewport.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | const useViewport = (threshold = 1024) => {
4 | const [isSmallViewport, setIsSmallViewport] = useState(false);
5 |
6 | useEffect(() => {
7 | // Initialize state on client-side
8 | setIsSmallViewport(window.innerWidth < threshold);
9 |
10 | const handleResize = () => setIsSmallViewport(window.innerWidth < threshold);
11 | window.addEventListener('resize', handleResize);
12 |
13 | return () => {
14 | window.removeEventListener('resize', handleResize);
15 | };
16 | }, [threshold]);
17 |
18 | return isSmallViewport;
19 | };
20 |
21 | export default useViewport;
22 |
--------------------------------------------------------------------------------
/lib/modules/llm/providers/anthropic.ts:
--------------------------------------------------------------------------------
1 | import { BaseProvider } from '@/lib/modules/llm/base-provider';
2 | import type { ModelInfo } from '@/lib/modules/llm/types';
3 | import type { LanguageModelV1 } from 'ai';
4 | import type { IProviderSetting } from '@/types/model';
5 | import { createAnthropic } from '@ai-sdk/anthropic';
6 | import { createOpenAI } from '@ai-sdk/openai';
7 |
8 |
9 | export default class AnthropicProvider extends BaseProvider {
10 | name = 'Anthropic';
11 | getApiKeyLink = 'https://console.anthropic.com/settings/keys';
12 |
13 | config = {
14 | apiTokenKey: 'ANTHROPIC_API_KEY',
15 | };
16 |
17 | staticModels: ModelInfo[] = [
18 | {
19 | name: 'anthropic/claude-3.7-sonnet',
20 | label: 'Claude 3.7 Sonnet',
21 | provider: 'Anthropic',
22 | maxTokenAllowed: 128000,
23 | },
24 | {
25 | name: 'anthropic/claude-3.5-sonnet',
26 | label: 'Claude 3.5 Sonnet (new)',
27 | provider: 'Anthropic',
28 | maxTokenAllowed: 8000,
29 | },
30 | // {
31 | // name: 'anthropic/claude-3.5-sonnet-20240620',
32 | // label: 'Claude 3.5 Sonnet (old)',
33 | // provider: 'Anthropic',
34 | // maxTokenAllowed: 8000,
35 | // },
36 | // {
37 | // name: 'anthropic/claude-3.5-haiku',
38 | // label: 'Claude 3.5 Haiku (new)',
39 | // provider: 'Anthropic',
40 | // maxTokenAllowed: 8000,
41 | // },
42 | ];
43 | getModelInstance: (options: {
44 | model: string;
45 | serverEnv?: Record;
46 | apiKeys?: Record;
47 | providerSettings?: Record;
48 | }) => LanguageModelV1 = (options) => {
49 |
50 | const { apiKeys, providerSettings, serverEnv, model } = options;
51 |
52 | const { apiKey } = this.getProviderBaseUrlAndKey({
53 | apiKeys,
54 | providerSettings,
55 | serverEnv: serverEnv as any,
56 | defaultBaseUrlKey: '',
57 | defaultApiTokenKey: 'ANTHROPIC_API_KEY',
58 | });
59 | const anthropic = createOpenAI({
60 | apiKey: process.env.OPEN_ROUTER_API_KEY,
61 | baseURL: "https://openrouter.ai/api/v1",
62 | });
63 |
64 | // const anthropic = createAnthropic({
65 | // apiKey: process.env.ANTHROPIC_API_KEY,
66 | // baseUrl: 'https://api.openai-proxy.org/anthropic/v1',
67 | // });
68 |
69 | return anthropic(model);
70 | };
71 | }
72 |
--------------------------------------------------------------------------------
/lib/modules/llm/providers/cohere.ts:
--------------------------------------------------------------------------------
1 | import { BaseProvider } from '@/lib/modules/llm/base-provider';
2 | import type { ModelInfo } from '@/lib/modules/llm/types';
3 | import type { IProviderSetting } from '@/types/model';
4 | import type { LanguageModelV1 } from 'ai';
5 | import { createCohere } from '@ai-sdk/cohere';
6 |
7 | export default class CohereProvider extends BaseProvider {
8 | name = 'Cohere';
9 | getApiKeyLink = 'https://dashboard.cohere.com/api-keys';
10 |
11 | config = {
12 | apiTokenKey: 'COHERE_API_KEY',
13 | };
14 |
15 | staticModels: ModelInfo[] = [
16 | { name: 'command-r-plus-08-2024', label: 'Command R plus Latest', provider: 'Cohere', maxTokenAllowed: 4096 },
17 | { name: 'command-r-08-2024', label: 'Command R Latest', provider: 'Cohere', maxTokenAllowed: 4096 },
18 | { name: 'command-r-plus', label: 'Command R plus', provider: 'Cohere', maxTokenAllowed: 4096 },
19 | { name: 'command-r', label: 'Command R', provider: 'Cohere', maxTokenAllowed: 4096 },
20 | { name: 'command', label: 'Command', provider: 'Cohere', maxTokenAllowed: 4096 },
21 | { name: 'command-nightly', label: 'Command Nightly', provider: 'Cohere', maxTokenAllowed: 4096 },
22 | { name: 'command-light', label: 'Command Light', provider: 'Cohere', maxTokenAllowed: 4096 },
23 | { name: 'command-light-nightly', label: 'Command Light Nightly', provider: 'Cohere', maxTokenAllowed: 4096 },
24 | { name: 'c4ai-aya-expanse-8b', label: 'c4AI Aya Expanse 8b', provider: 'Cohere', maxTokenAllowed: 4096 },
25 | { name: 'c4ai-aya-expanse-32b', label: 'c4AI Aya Expanse 32b', provider: 'Cohere', maxTokenAllowed: 4096 },
26 | ];
27 |
28 | getModelInstance(options: {
29 | model: string;
30 | serverEnv?: Record;
31 | apiKeys?: Record;
32 | providerSettings?: Record;
33 | }): LanguageModelV1 {
34 | const { model, serverEnv, apiKeys, providerSettings } = options;
35 |
36 | const { apiKey } = this.getProviderBaseUrlAndKey({
37 | apiKeys,
38 | providerSettings: providerSettings?.[this.name],
39 | serverEnv: serverEnv as any,
40 | defaultBaseUrlKey: '',
41 | defaultApiTokenKey: 'COHERE_API_KEY',
42 | });
43 |
44 | if (!apiKey) {
45 | throw new Error(`Missing API key for ${this.name} provider`);
46 | }
47 |
48 | const cohere = createCohere({
49 | apiKey,
50 | });
51 |
52 | return cohere(model);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/modules/llm/providers/deepseek.ts:
--------------------------------------------------------------------------------
1 | import { BaseProvider } from '@/lib/modules/llm/base-provider';
2 | import type { ModelInfo } from '@/lib/modules/llm/types';
3 | import type { IProviderSetting } from '@/types/model';
4 | import type { LanguageModelV1 } from 'ai';
5 | import { createDeepSeek } from '@ai-sdk/deepseek';
6 |
7 | export default class DeepseekProvider extends BaseProvider {
8 | name = 'Deepseek';
9 | getApiKeyLink = 'https://platform.deepseek.com/apiKeys';
10 |
11 | config = {
12 | apiTokenKey: 'DEEPSEEK_API_KEY',
13 | };
14 |
15 | staticModels: ModelInfo[] = [
16 | // { name: 'deepseek-coder', label: 'Deepseek-Coder', provider: 'Deepseek', maxTokenAllowed: 8000 },
17 | { name: 'deepseek/deepseek-chat', label: 'Deepseek-Chat', provider: 'Deepseek', maxTokenAllowed: 8000 },
18 | { name: 'deepseek/deepseek-chat-v3-0324', label: 'deepseek-chat-v3-0324', provider: 'Deepseek', maxTokenAllowed: 8000 },
19 | ];
20 |
21 | getModelInstance(options: {
22 | model: string;
23 | serverEnv?: Record;
24 | apiKeys?: Record;
25 | providerSettings?: Record;
26 | }): LanguageModelV1 {
27 | const { model, serverEnv, apiKeys, providerSettings } = options;
28 |
29 | const { apiKey } = this.getProviderBaseUrlAndKey({
30 | apiKeys,
31 | providerSettings: providerSettings?.[this.name],
32 | serverEnv: serverEnv as any,
33 | defaultBaseUrlKey: '',
34 | defaultApiTokenKey: 'DEEPSEEK_API_KEY',
35 | });
36 |
37 | if (!apiKey) {
38 | throw new Error(`Missing API key for ${this.name} provider`);
39 | }
40 |
41 | const deepseek = createDeepSeek({
42 | apiKey: process.env.DEEPSEEK_API_KEY,
43 | baseURL: 'https://openrouter.ai/api/v1',
44 | });
45 |
46 | return deepseek(model, {
47 | // simulateStreaming: true,
48 | });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/modules/llm/providers/github.ts:
--------------------------------------------------------------------------------
1 | import { BaseProvider } from '@/lib/modules/llm/base-provider';
2 | import type { ModelInfo } from '@/lib/modules/llm/types';
3 | import type { IProviderSetting } from '@/types/model';
4 | import type { LanguageModelV1 } from 'ai';
5 | import { createOpenAI } from '@ai-sdk/openai';
6 |
7 | export default class GithubProvider extends BaseProvider {
8 | name = 'Github';
9 | getApiKeyLink = 'https://github.com/settings/personal-access-tokens';
10 |
11 | config = {
12 | apiTokenKey: 'GITHUB_API_KEY',
13 | };
14 |
15 | // find more in https://github.com/marketplace?type=models
16 | staticModels: ModelInfo[] = [
17 | { name: 'gpt-4o', label: 'GPT-4o', provider: 'Github', maxTokenAllowed: 8000 },
18 | { name: 'o1', label: 'o1-preview', provider: 'Github', maxTokenAllowed: 100000 },
19 | { name: 'o1-mini', label: 'o1-mini', provider: 'Github', maxTokenAllowed: 8000 },
20 | { name: 'gpt-4o-mini', label: 'GPT-4o Mini', provider: 'Github', maxTokenAllowed: 8000 },
21 | { name: 'gpt-4-turbo', label: 'GPT-4 Turbo', provider: 'Github', maxTokenAllowed: 8000 },
22 | { name: 'gpt-4', label: 'GPT-4', provider: 'Github', maxTokenAllowed: 8000 },
23 | { name: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', provider: 'Github', maxTokenAllowed: 8000 },
24 | ];
25 |
26 | getModelInstance(options: {
27 | model: string;
28 | serverEnv?: Record;
29 | apiKeys?: Record;
30 | providerSettings?: Record;
31 | }): LanguageModelV1 {
32 | const { model, serverEnv, apiKeys, providerSettings } = options;
33 |
34 | const { apiKey } = this.getProviderBaseUrlAndKey({
35 | apiKeys,
36 | providerSettings: providerSettings?.[this.name],
37 | serverEnv: serverEnv as any,
38 | defaultBaseUrlKey: '',
39 | defaultApiTokenKey: 'GITHUB_API_KEY',
40 | });
41 |
42 | if (!apiKey) {
43 | throw new Error(`Missing API key for ${this.name} provider`);
44 | }
45 |
46 | const openai = createOpenAI({
47 | baseURL: 'https://models.inference.ai.azure.com',
48 | apiKey,
49 | });
50 |
51 | return openai(model);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/modules/llm/providers/mistral.ts:
--------------------------------------------------------------------------------
1 | import { BaseProvider } from '@/lib/modules/llm/base-provider';
2 | import type { ModelInfo } from '@/lib/modules/llm/types';
3 | import type { IProviderSetting } from '@/types/model';
4 | import type { LanguageModelV1 } from 'ai';
5 | import { createMistral } from '@ai-sdk/mistral';
6 |
7 | export default class MistralProvider extends BaseProvider {
8 | name = 'Mistral';
9 | getApiKeyLink = 'https://console.mistral.ai/api-keys/';
10 |
11 | config = {
12 | apiTokenKey: 'MISTRAL_API_KEY',
13 | };
14 |
15 | staticModels: ModelInfo[] = [
16 | { name: 'open-mistral-7b', label: 'Mistral 7B', provider: 'Mistral', maxTokenAllowed: 8000 },
17 | { name: 'open-mixtral-8x7b', label: 'Mistral 8x7B', provider: 'Mistral', maxTokenAllowed: 8000 },
18 | { name: 'open-mixtral-8x22b', label: 'Mistral 8x22B', provider: 'Mistral', maxTokenAllowed: 8000 },
19 | { name: 'open-codestral-mamba', label: 'Codestral Mamba', provider: 'Mistral', maxTokenAllowed: 8000 },
20 | { name: 'open-mistral-nemo', label: 'Mistral Nemo', provider: 'Mistral', maxTokenAllowed: 8000 },
21 | { name: 'ministral-8b-latest', label: 'Mistral 8B', provider: 'Mistral', maxTokenAllowed: 8000 },
22 | { name: 'mistral-small-latest', label: 'Mistral Small', provider: 'Mistral', maxTokenAllowed: 8000 },
23 | { name: 'codestral-latest', label: 'Codestral', provider: 'Mistral', maxTokenAllowed: 8000 },
24 | { name: 'mistral-large-latest', label: 'Mistral Large Latest', provider: 'Mistral', maxTokenAllowed: 8000 },
25 | ];
26 |
27 | getModelInstance(options: {
28 | model: string;
29 | serverEnv?: Record;
30 | apiKeys?: Record;
31 | providerSettings?: Record;
32 | }): LanguageModelV1 {
33 | const { model, serverEnv, apiKeys, providerSettings } = options;
34 |
35 | const { apiKey } = this.getProviderBaseUrlAndKey({
36 | apiKeys,
37 | providerSettings: providerSettings?.[this.name],
38 | serverEnv: serverEnv as any,
39 | defaultBaseUrlKey: '',
40 | defaultApiTokenKey: 'MISTRAL_API_KEY',
41 | });
42 |
43 | if (!apiKey) {
44 | throw new Error(`Missing API key for ${this.name} provider`);
45 | }
46 |
47 | const mistral = createMistral({
48 | apiKey,
49 | });
50 |
51 | return mistral(model);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/modules/llm/providers/openai-like.ts:
--------------------------------------------------------------------------------
1 | import { BaseProvider, getOpenAILikeModel } from '@/lib/modules/llm/base-provider';
2 | import type { ModelInfo } from '@/lib/modules/llm/types';
3 | import type { IProviderSetting } from '@/types/model';
4 | import type { LanguageModelV1 } from 'ai';
5 |
6 | export default class OpenAILikeProvider extends BaseProvider {
7 | name = 'OpenAILike';
8 | getApiKeyLink = undefined;
9 |
10 | config = {
11 | baseUrlKey: 'OPENAI_LIKE_API_BASE_URL',
12 | apiTokenKey: 'OPENAI_LIKE_API_KEY',
13 | };
14 |
15 | staticModels: ModelInfo[] = [];
16 |
17 | async getDynamicModels(
18 | apiKeys?: Record,
19 | settings?: IProviderSetting,
20 | serverEnv: Record = {},
21 | ): Promise {
22 | const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({
23 | apiKeys,
24 | providerSettings: settings,
25 | serverEnv,
26 | defaultBaseUrlKey: 'OPENAI_LIKE_API_BASE_URL',
27 | defaultApiTokenKey: 'OPENAI_LIKE_API_KEY',
28 | });
29 |
30 | if (!baseUrl || !apiKey) {
31 | return [];
32 | }
33 |
34 | const response = await fetch(`${baseUrl}/models`, {
35 | headers: {
36 | Authorization: `Bearer ${apiKey}`,
37 | },
38 | });
39 |
40 | const res = (await response.json()) as any;
41 |
42 | return res.data.map((model: any) => ({
43 | name: model.id,
44 | label: model.id,
45 | provider: this.name,
46 | maxTokenAllowed: 8000,
47 | }));
48 | }
49 |
50 | getModelInstance(options: {
51 | model: string;
52 | serverEnv?: Record;
53 | apiKeys?: Record;
54 | providerSettings?: Record;
55 | }): LanguageModelV1 {
56 | const { model, serverEnv, apiKeys, providerSettings } = options;
57 |
58 | const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({
59 | apiKeys,
60 | providerSettings: providerSettings?.[this.name],
61 | serverEnv: serverEnv as any,
62 | defaultBaseUrlKey: 'OPENAI_LIKE_API_BASE_URL',
63 | defaultApiTokenKey: 'OPENAI_LIKE_API_KEY',
64 | });
65 |
66 | if (!baseUrl || !apiKey) {
67 | throw new Error(`Missing configuration for ${this.name} provider`);
68 | }
69 |
70 | return getOpenAILikeModel(baseUrl, apiKey, model);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/modules/llm/providers/perplexity.ts:
--------------------------------------------------------------------------------
1 | import { BaseProvider } from '@/lib/modules/llm/base-provider';
2 | import type { ModelInfo } from '@/lib/modules/llm/types';
3 | import type { IProviderSetting } from '@/types/model';
4 | import type { LanguageModelV1 } from 'ai';
5 | import { createOpenAI } from '@ai-sdk/openai';
6 |
7 | export default class PerplexityProvider extends BaseProvider {
8 | name = 'Perplexity';
9 | getApiKeyLink = 'https://www.perplexity.ai/settings/api';
10 |
11 | config = {
12 | apiTokenKey: 'PERPLEXITY_API_KEY',
13 | };
14 |
15 | staticModels: ModelInfo[] = [
16 | {
17 | name: 'llama-3.1-sonar-small-128k-online',
18 | label: 'Sonar Small Online',
19 | provider: 'Perplexity',
20 | maxTokenAllowed: 8192,
21 | },
22 | {
23 | name: 'llama-3.1-sonar-large-128k-online',
24 | label: 'Sonar Large Online',
25 | provider: 'Perplexity',
26 | maxTokenAllowed: 8192,
27 | },
28 | {
29 | name: 'llama-3.1-sonar-huge-128k-online',
30 | label: 'Sonar Huge Online',
31 | provider: 'Perplexity',
32 | maxTokenAllowed: 8192,
33 | },
34 | ];
35 |
36 | getModelInstance(options: {
37 | model: string;
38 | serverEnv?: Record;
39 | apiKeys?: Record;
40 | providerSettings?: Record;
41 | }): LanguageModelV1 {
42 | const { model, serverEnv, apiKeys, providerSettings } = options;
43 |
44 | const { apiKey } = this.getProviderBaseUrlAndKey({
45 | apiKeys,
46 | providerSettings: providerSettings?.[this.name],
47 | serverEnv: serverEnv as any,
48 | defaultBaseUrlKey: '',
49 | defaultApiTokenKey: 'PERPLEXITY_API_KEY',
50 | });
51 |
52 | if (!apiKey) {
53 | throw new Error(`Missing API key for ${this.name} provider`);
54 | }
55 |
56 | const perplexity = createOpenAI({
57 | baseURL: 'https://api.perplexity.ai/',
58 | apiKey,
59 | });
60 |
61 | return perplexity(model);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/modules/llm/providers/xai.ts:
--------------------------------------------------------------------------------
1 | import { BaseProvider } from '@/lib/modules/llm/base-provider';
2 | import type { ModelInfo } from '@/lib/modules/llm/types';
3 | import type { IProviderSetting } from '@/types/model';
4 | import type { LanguageModelV1 } from 'ai';
5 | import { createOpenAI } from '@ai-sdk/openai';
6 |
7 | export default class XAIProvider extends BaseProvider {
8 | name = 'xAI';
9 | getApiKeyLink = 'https://docs.x.ai/docs/quickstart#creating-an-api-key';
10 |
11 | config = {
12 | apiTokenKey: 'XAI_API_KEY',
13 | };
14 |
15 | staticModels: ModelInfo[] = [
16 | { name: 'grok-beta', label: 'xAI Grok Beta', provider: 'xAI', maxTokenAllowed: 8000 },
17 | { name: 'grok-2-1212', label: 'xAI Grok2 1212', provider: 'xAI', maxTokenAllowed: 8000 },
18 | ];
19 |
20 | getModelInstance(options: {
21 | model: string;
22 | serverEnv?: Record;
23 | apiKeys?: Record;
24 | providerSettings?: Record;
25 | }): LanguageModelV1 {
26 | const { model, serverEnv, apiKeys, providerSettings } = options;
27 |
28 | const { apiKey } = this.getProviderBaseUrlAndKey({
29 | apiKeys,
30 | providerSettings: providerSettings?.[this.name],
31 | serverEnv: serverEnv as any,
32 | defaultBaseUrlKey: '',
33 | defaultApiTokenKey: 'XAI_API_KEY',
34 | });
35 |
36 | if (!apiKey) {
37 | throw new Error(`Missing API key for ${this.name} provider`);
38 | }
39 |
40 | const openai = createOpenAI({
41 | baseURL: 'https://api.x.ai/v1',
42 | apiKey,
43 | });
44 |
45 | return openai(model);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/modules/llm/registry.ts:
--------------------------------------------------------------------------------
1 | import AnthropicProvider from './providers/anthropic';
2 | // import CohereProvider from './providers/cohere';
3 | import DeepseekProvider from './providers/deepseek';
4 | import GoogleProvider from './providers/google';
5 | // import GroqProvider from './providers/groq';
6 | // import HuggingFaceProvider from './providers/huggingface';
7 | // import LMStudioProvider from './providers/lmstudio';
8 | // import MistralProvider from './providers/mistral';
9 | // import OllamaProvider from './providers/ollama';
10 | import OpenRouterProvider from './providers/open-router';
11 | // import OpenAILikeProvider from './providers/openai-like';
12 | import OpenAIProvider from './providers/openai';
13 | // import PerplexityProvider from './providers/perplexity';
14 | // import TogetherProvider from './providers/together';
15 | // import XAIProvider from './providers/xai';
16 | // import HyperbolicProvider from './providers/hyperbolic';
17 | // import AmazonBedrockProvider from './providers/amazon-bedrock';
18 | // import GithubProvider from './providers/github';
19 |
20 | export {
21 | AnthropicProvider,
22 | // CohereProvider,
23 | DeepseekProvider,
24 | // GoogleProvider,
25 | // GroqProvider,
26 | // HuggingFaceProvider,
27 | // HyperbolicProvider,
28 | // MistralProvider,
29 | // OllamaProvider,
30 | // OpenAIProvider,
31 | // OpenRouterProvider,
32 | // OpenAILikeProvider,
33 | // PerplexityProvider,
34 | // XAIProvider,
35 | // TogetherProvider,
36 | // LMStudioProvider,
37 | // AmazonBedrockProvider,
38 | // GithubProvider,
39 | };
40 |
--------------------------------------------------------------------------------
/lib/modules/llm/types.ts:
--------------------------------------------------------------------------------
1 | import type { LanguageModelV1 } from 'ai';
2 | import type { IProviderSetting } from '@/types/model';
3 |
4 | export interface ModelInfo {
5 | name: string;
6 | label: string;
7 | provider: string;
8 | maxTokenAllowed: number;
9 | }
10 |
11 | export interface ProviderInfo {
12 | name: string;
13 | staticModels: ModelInfo[];
14 | getDynamicModels?: (
15 | apiKeys?: Record,
16 | settings?: IProviderSetting,
17 | serverEnv?: Record,
18 | ) => Promise;
19 | getModelInstance: (options: {
20 | model: string;
21 | serverEnv?: Record;
22 | apiKeys?: Record;
23 | providerSettings?: Record;
24 | }) => LanguageModelV1;
25 | getApiKeyLink?: string;
26 | labelForGetApiKey?: string;
27 | icon?: string;
28 | }
29 | export interface ProviderConfig {
30 | baseUrlKey?: string;
31 | baseUrl?: string;
32 | apiTokenKey?: string;
33 | }
34 |
--------------------------------------------------------------------------------
/lib/persistence/index.ts:
--------------------------------------------------------------------------------
1 | export * from './localStorage';
2 | // export * from './db';
3 | export * from './useChatHistory';
4 |
--------------------------------------------------------------------------------
/lib/persistence/localStorage.ts:
--------------------------------------------------------------------------------
1 | // Client-side storage utilities
2 | const isClient = typeof window !== 'undefined' && typeof localStorage !== 'undefined';
3 |
4 | export function getLocalStorage(key: string): any | null {
5 | if (!isClient) {
6 | return null;
7 | }
8 |
9 | try {
10 | const item = localStorage.getItem(key);
11 | return item ? JSON.parse(item) : null;
12 | } catch (error) {
13 | console.error(`Error reading from localStorage key "${key}":`, error);
14 | return null;
15 | }
16 | }
17 |
18 | export function setLocalStorage(key: string, value: any): void {
19 | if (!isClient) {
20 | return;
21 | }
22 |
23 | try {
24 | localStorage.setItem(key, JSON.stringify(value));
25 | } catch (error) {
26 | console.error(`Error writing to localStorage key "${key}":`, error);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/persistence/types.ts:
--------------------------------------------------------------------------------
1 | import type { Message } from 'ai';
2 |
3 | export interface IChatMetadata {
4 | gitUrl: string;
5 | gitBranch?: string;
6 | }
7 |
8 | export interface ChatHistoryItem {
9 | id: string;
10 | messages: Message[];
11 | urlId?: string;
12 | description?: string;
13 | timestamp?: string;
14 | metadata?: IChatMetadata;
15 | }
--------------------------------------------------------------------------------
/lib/stores/chat.ts:
--------------------------------------------------------------------------------
1 | import { map } from 'nanostores';
2 |
3 | export const chatStore = map({
4 | started: false,
5 | aborted: false,
6 | showChat: true,
7 | });
8 |
--------------------------------------------------------------------------------
/lib/stores/profile.ts:
--------------------------------------------------------------------------------
1 | import { atom } from 'nanostores';
2 |
3 | interface Profile {
4 | username: string;
5 | bio: string;
6 | avatar: string;
7 | }
8 |
9 | // Initialize with stored profile or defaults
10 | const storedProfile = typeof window !== 'undefined' ? localStorage.getItem('bolt_profile') : null;
11 | const initialProfile: Profile = storedProfile
12 | ? JSON.parse(storedProfile)
13 | : {
14 | username: '',
15 | bio: '',
16 | avatar: '',
17 | };
18 |
19 | export const profileStore = atom(initialProfile);
20 |
21 | export const updateProfile = (updates: Partial) => {
22 | profileStore.set({ ...profileStore.get(), ...updates });
23 |
24 | // Persist to localStorage
25 | if (typeof window !== 'undefined') {
26 | localStorage.setItem('bolt_profile', JSON.stringify(profileStore.get()));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/lib/stores/tabConfigurationStore.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand';
2 |
3 | export interface TabConfig {
4 | id: string;
5 | visible: boolean;
6 | window: 'developer' | 'user';
7 | order: number;
8 | locked?: boolean;
9 | }
10 |
11 | interface TabConfigurationStore {
12 | userTabs: TabConfig[];
13 | developerTabs: TabConfig[];
14 | get: () => { userTabs: TabConfig[]; developerTabs: TabConfig[] };
15 | set: (config: { userTabs: TabConfig[]; developerTabs: TabConfig[] }) => void;
16 | reset: () => void;
17 | }
18 |
19 | const DEFAULT_CONFIG = {
20 | userTabs: [],
21 | developerTabs: [],
22 | };
23 |
24 | export const tabConfigurationStore = create((set, get) => ({
25 | ...DEFAULT_CONFIG,
26 | get: () => ({
27 | userTabs: get().userTabs,
28 | developerTabs: get().developerTabs,
29 | }),
30 | set: (config) => set(config),
31 | reset: () => set(DEFAULT_CONFIG),
32 | }));
33 |
--------------------------------------------------------------------------------
/lib/stores/terminal.ts:
--------------------------------------------------------------------------------
1 | import type { WebContainer, WebContainerProcess } from '@webcontainer/api';
2 | import { atom, type WritableAtom } from 'nanostores';
3 | import type { ITerminal } from '@/types/terminal';
4 | import { newBoltShellProcess, newShellProcess } from '@/utils/shell';
5 | import { coloredText } from '@/utils/terminal';
6 |
7 | export class TerminalStore {
8 | #webcontainer: Promise;
9 | #terminals: Array<{ terminal: ITerminal; process: WebContainerProcess }> = [];
10 | #boltTerminal = newBoltShellProcess();
11 |
12 | showTerminal: WritableAtom = atom(true);
13 |
14 | constructor(webcontainerPromise: Promise) {
15 | this.#webcontainer = webcontainerPromise;
16 | }
17 | get boltTerminal() {
18 | return this.#boltTerminal;
19 | }
20 |
21 | toggleTerminal(value?: boolean) {
22 | this.showTerminal.set(value !== undefined ? value : !this.showTerminal.get());
23 | }
24 | async attachBoltTerminal(terminal: ITerminal) {
25 | try {
26 | const wc = await this.#webcontainer;
27 | await this.#boltTerminal.init(wc, terminal);
28 | } catch (error: any) {
29 | terminal.write(coloredText.red('Failed to spawn bolt shell\n\n') + error.message);
30 | return;
31 | }
32 | }
33 |
34 | async attachTerminal(terminal: ITerminal) {
35 | try {
36 | const shellProcess = await newShellProcess(await this.#webcontainer, terminal);
37 | this.#terminals.push({ terminal, process: shellProcess });
38 | } catch (error: any) {
39 | terminal.write(coloredText.red('Failed to spawn shell\n\n') + error.message);
40 | return;
41 | }
42 | }
43 |
44 | onTerminalResize(cols: number, rows: number) {
45 | for (const { process } of this.#terminals) {
46 | process.resize({ cols, rows });
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/stores/theme.ts:
--------------------------------------------------------------------------------
1 | import { atom } from 'nanostores';
2 | import { logStore } from './logs';
3 |
4 | export type Theme = 'dark' | 'light';
5 |
6 | export const kTheme = 'bolt_theme';
7 |
8 | export function themeIsDark() {
9 | return themeStore.get() === 'dark';
10 | }
11 |
12 | export const DEFAULT_THEME = 'dark';
13 |
14 | // let persistedTheme: Theme | undefined = undefined;
15 |
16 | // // Only access localStorage on the client side
17 | // if (typeof window !== 'undefined') {
18 | // persistedTheme = localStorage.getItem(kTheme) as Theme | undefined;
19 | // }
20 | export const themeStore = atom('dark');
21 |
22 |
23 | export function toggleTheme() {
24 | const currentTheme = themeStore.get();
25 | const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
26 |
27 | // Update the theme store
28 | themeStore.set(newTheme);
29 |
30 | // Update localStorage
31 | localStorage.setItem(kTheme, newTheme);
32 |
33 | // Update the HTML attribute
34 | document.querySelector('html')?.setAttribute('data-theme', newTheme);
35 |
36 | // Update user profile if it exists
37 | try {
38 | const userProfile = localStorage.getItem('bolt_user_profile');
39 |
40 | if (userProfile) {
41 | const profile = JSON.parse(userProfile);
42 | profile.theme = newTheme;
43 | localStorage.setItem('bolt_user_profile', JSON.stringify(profile));
44 | }
45 | } catch (error) {
46 | console.error('Error updating user profile theme:', error);
47 | }
48 |
49 | logStore.logSystem(`Theme changed to ${newTheme} mode`);
50 | // location.reload();
51 | }
52 |
--------------------------------------------------------------------------------
/lib/webcontainer/auth.client.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This client-only module that contains everything related to auth and is used
3 | * to avoid importing `@webcontainer/api` in the server bundle.
4 | */
5 |
6 | export { auth, type AuthAPI } from '@webcontainer/api';
7 |
--------------------------------------------------------------------------------
/lib/webcontainer/index.ts:
--------------------------------------------------------------------------------
1 | import { WebContainer } from '@webcontainer/api';
2 | import { WORK_DIR_NAME } from '@/utils/constants';
3 | import { cleanStackTrace } from '@/utils/stacktrace';
4 |
5 | interface WebContainerContext {
6 | loaded: boolean;
7 | }
8 |
9 | export const webcontainerContext: WebContainerContext = {
10 | loaded: false,
11 | };
12 |
13 |
14 |
15 | export let webcontainer: Promise = new Promise(() => {
16 | // noop for ssr
17 | });
18 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "auth"
2 | import { type NextRequest, NextResponse } from "next/server";
3 |
4 | // Or like this if you need to do something here.
5 | // export default auth((req) => {
6 | // console.log(req.auth) // { session: { user: { ... } } }
7 | // })
8 |
9 | // Read more: https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
10 | export const config = {
11 | matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
12 | }
13 |
14 | export default async function middleware(req: NextRequest) {
15 | if (req.nextUrl.pathname === '/login') {
16 | const session = await auth();
17 |
18 | if (session) {
19 | return NextResponse.redirect(new URL('/', req.url));
20 | }
21 | }
22 | return NextResponse.next();
23 |
24 | }
--------------------------------------------------------------------------------
/migrations/0000_watery_wind_dancer.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE "chats" (
2 | "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3 | "messages" jsonb,
4 | "url_id" text NOT NULL,
5 | "description" text,
6 | "timestamp" timestamp DEFAULT now(),
7 | "metadata" jsonb
8 | );
9 | --> statement-breakpoint
10 | CREATE TABLE "previews" (
11 | "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
12 | "chat_id" uuid,
13 | "base_url" text NOT NULL,
14 | "port" text,
15 | "ready" boolean DEFAULT false,
16 | "is_loading" boolean DEFAULT true,
17 | "loading_progress" integer DEFAULT 0,
18 | "timestamp" timestamp DEFAULT now()
19 | );
20 | --> statement-breakpoint
21 | CREATE TABLE "users" (
22 | "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
23 | "user_id" text,
24 | "username" text,
25 | "avatar" text,
26 | "role" integer,
27 | "platform" text,
28 | "email" text,
29 | "created_at" timestamp DEFAULT now() NOT NULL,
30 | "updated_at" timestamp DEFAULT now() NOT NULL,
31 | CONSTRAINT "users_user_id_unique" UNIQUE("user_id")
32 | );
33 | --> statement-breakpoint
34 | ALTER TABLE "previews" ADD CONSTRAINT "previews_chat_id_chats_id_fk" FOREIGN KEY ("chat_id") REFERENCES "public"."chats"("id") ON DELETE no action ON UPDATE no action;
--------------------------------------------------------------------------------
/migrations/0001_wet_moondragon.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE "account" (
2 | "user_id" uuid NOT NULL,
3 | "type" text NOT NULL,
4 | "provider" text NOT NULL,
5 | "provider_account_id" text NOT NULL,
6 | "refresh_token" text,
7 | "access_token" text,
8 | "expires_at" integer,
9 | "token_type" text,
10 | "scope" text,
11 | "id_token" text,
12 | "session_state" text,
13 | CONSTRAINT "account_provider_provider_account_id_pk" PRIMARY KEY("provider","provider_account_id")
14 | );
15 | --> statement-breakpoint
16 | CREATE TABLE "auth_user" (
17 | "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
18 | "name" text,
19 | "email" text,
20 | "email_verified" timestamp,
21 | "image" text,
22 | CONSTRAINT "auth_user_email_unique" UNIQUE("email")
23 | );
24 | --> statement-breakpoint
25 | CREATE TABLE "authenticator" (
26 | "credential_id" text NOT NULL,
27 | "user_id" uuid NOT NULL,
28 | "provider_account_id" text NOT NULL,
29 | "credential_public_key" text NOT NULL,
30 | "counter" integer NOT NULL,
31 | "credential_device_type" text NOT NULL,
32 | "credential_backed_up" boolean NOT NULL,
33 | "transports" text,
34 | CONSTRAINT "authenticator_user_id_credential_id_pk" PRIMARY KEY("user_id","credential_id"),
35 | CONSTRAINT "authenticator_credential_id_unique" UNIQUE("credential_id")
36 | );
37 | --> statement-breakpoint
38 | CREATE TABLE "session" (
39 | "session_token" text PRIMARY KEY NOT NULL,
40 | "user_id" uuid NOT NULL,
41 | "expires" timestamp NOT NULL
42 | );
43 | --> statement-breakpoint
44 | CREATE TABLE "verification_token" (
45 | "identifier" text NOT NULL,
46 | "token" text NOT NULL,
47 | "expires" timestamp NOT NULL,
48 | CONSTRAINT "verification_token_identifier_token_pk" PRIMARY KEY("identifier","token")
49 | );
50 | --> statement-breakpoint
51 | ALTER TABLE "account" ADD CONSTRAINT "account_user_id_auth_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."auth_user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
52 | ALTER TABLE "authenticator" ADD CONSTRAINT "authenticator_user_id_auth_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."auth_user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
53 | ALTER TABLE "session" ADD CONSTRAINT "session_user_id_auth_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."auth_user"("id") ON DELETE cascade ON UPDATE no action;
--------------------------------------------------------------------------------
/migrations/0003_brave_golden_guardian.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "chats" ADD COLUMN "user_id" text;--> statement-breakpoint
2 | ALTER TABLE "chats" ADD CONSTRAINT "chats_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE cascade;
--------------------------------------------------------------------------------
/migrations/0004_woozy_silverclaw.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE "usage" (
2 | "id" text PRIMARY KEY NOT NULL,
3 | "userId" text NOT NULL,
4 | "credits" integer DEFAULT 0 NOT NULL,
5 | "modelName" text NOT NULL,
6 | "provider" text NOT NULL,
7 | "createdAt" timestamp DEFAULT now() NOT NULL
8 | );
9 | --> statement-breakpoint
10 | ALTER TABLE "usage" ADD CONSTRAINT "usage_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
--------------------------------------------------------------------------------
/migrations/0005_broken_quicksilver.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "usage" ALTER COLUMN "id" SET DATA TYPE uuid;--> statement-breakpoint
2 | ALTER TABLE "usage" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();
--------------------------------------------------------------------------------
/migrations/0006_mature_kat_farrell.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "usage" ALTER COLUMN "modelName" DROP NOT NULL;--> statement-breakpoint
2 | ALTER TABLE "usage" ALTER COLUMN "provider" DROP NOT NULL;
--------------------------------------------------------------------------------
/migrations/0007_cloudy_luckman.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE "usage" CASCADE;
--------------------------------------------------------------------------------
/migrations/0008_fast_makkari.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE "usage" (
2 | "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3 | "userId" text NOT NULL,
4 | "credits" integer DEFAULT 0 NOT NULL,
5 | "modelName" text,
6 | "provider" text,
7 | "createdAt" timestamp DEFAULT now() NOT NULL
8 | );
9 | --> statement-breakpoint
10 | ALTER TABLE "usage" ADD CONSTRAINT "usage_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
--------------------------------------------------------------------------------
/migrations/0009_ambiguous_turbo.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE "credits" (
2 | "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3 | "userId" text NOT NULL,
4 | "credits" integer DEFAULT 0 NOT NULL,
5 | "usage" integer DEFAULT 0 NOT NULL,
6 | "modelName" text,
7 | "provider" text,
8 | "createdAt" timestamp DEFAULT now() NOT NULL
9 | );
10 | --> statement-breakpoint
11 | DROP TABLE "usage" CASCADE;--> statement-breakpoint
12 | ALTER TABLE "credits" ADD CONSTRAINT "credits_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
--------------------------------------------------------------------------------
/migrations/meta/_journal.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "7",
3 | "dialect": "postgresql",
4 | "entries": [
5 | {
6 | "idx": 0,
7 | "version": "7",
8 | "when": 1741927129417,
9 | "tag": "0000_watery_wind_dancer",
10 | "breakpoints": true
11 | },
12 | {
13 | "idx": 1,
14 | "version": "7",
15 | "when": 1741959867581,
16 | "tag": "0001_wet_moondragon",
17 | "breakpoints": true
18 | },
19 | {
20 | "idx": 2,
21 | "version": "7",
22 | "when": 1741960341238,
23 | "tag": "0002_far_meltdown",
24 | "breakpoints": true
25 | },
26 | {
27 | "idx": 3,
28 | "version": "7",
29 | "when": 1742006673578,
30 | "tag": "0003_brave_golden_guardian",
31 | "breakpoints": true
32 | },
33 | {
34 | "idx": 4,
35 | "version": "7",
36 | "when": 1742444509620,
37 | "tag": "0004_woozy_silverclaw",
38 | "breakpoints": true
39 | },
40 | {
41 | "idx": 5,
42 | "version": "7",
43 | "when": 1742445227727,
44 | "tag": "0005_broken_quicksilver",
45 | "breakpoints": true
46 | },
47 | {
48 | "idx": 6,
49 | "version": "7",
50 | "when": 1742445575561,
51 | "tag": "0006_mature_kat_farrell",
52 | "breakpoints": true
53 | },
54 | {
55 | "idx": 7,
56 | "version": "7",
57 | "when": 1742445790597,
58 | "tag": "0007_cloudy_luckman",
59 | "breakpoints": true
60 | },
61 | {
62 | "idx": 8,
63 | "version": "7",
64 | "when": 1742445847864,
65 | "tag": "0008_fast_makkari",
66 | "breakpoints": true
67 | },
68 | {
69 | "idx": 9,
70 | "version": "7",
71 | "when": 1742448111877,
72 | "tag": "0009_ambiguous_turbo",
73 | "breakpoints": true
74 | }
75 | ]
76 | }
--------------------------------------------------------------------------------
/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from 'next'
2 |
3 | const nextConfig: NextConfig = {
4 | /* config options here */
5 | reactStrictMode: false,
6 | eslint: {
7 | // 完全禁用 ESLint 检查
8 | ignoreDuringBuilds: true,
9 | },
10 | }
11 |
12 | export default nextConfig
13 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | '@unocss/postcss': {
4 | content: ['./app/**/*.{html,js,ts,jsx,tsx}'],
5 | },
6 | },
7 | }
8 |
--------------------------------------------------------------------------------
/public/_favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/_favicon.ico
--------------------------------------------------------------------------------
/public/_favicon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/icons/AmazonBedrock.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/icons/Anthropic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/Cohere.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/Deepseek.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/public/icons/Default.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/Google.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/Groq.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/HuggingFace.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/LMStudio.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/public/icons/Mistral.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/Ollama.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/OpenAI.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/OpenAILike.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/OpenRouter.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/Perplexity.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/Together.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/public/icons/xAI.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/public/logo-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/logo-01.png
--------------------------------------------------------------------------------
/public/logo-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/logo-02.png
--------------------------------------------------------------------------------
/public/logo-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/logo-03.png
--------------------------------------------------------------------------------
/public/logo-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/logo-04.png
--------------------------------------------------------------------------------
/public/logo-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/logo-200.png
--------------------------------------------------------------------------------
/public/logo-dark-styled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/logo-dark-styled.png
--------------------------------------------------------------------------------
/public/logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/logo-dark.png
--------------------------------------------------------------------------------
/public/logo-light-styled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/logo-light-styled.png
--------------------------------------------------------------------------------
/public/logo-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/logo-light.png
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/social_preview_index.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/public/social_preview_index.jpg
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/styles/animations.scss:
--------------------------------------------------------------------------------
1 | .animated {
2 | animation-fill-mode: both;
3 | animation-duration: var(--animate-duration, 0.2s);
4 | animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
5 |
6 | &.fadeInRight {
7 | animation-name: fadeInRight;
8 | }
9 |
10 | &.fadeOutRight {
11 | animation-name: fadeOutRight;
12 | }
13 | }
14 |
15 | @keyframes fadeInRight {
16 | from {
17 | opacity: 0;
18 | transform: translate3d(100%, 0, 0);
19 | }
20 |
21 | to {
22 | opacity: 1;
23 | transform: translate3d(0, 0, 0);
24 | }
25 | }
26 |
27 | @keyframes fadeOutRight {
28 | from {
29 | opacity: 1;
30 | }
31 |
32 | to {
33 | opacity: 0;
34 | transform: translate3d(100%, 0, 0);
35 | }
36 | }
37 |
38 | .dropdown-animation {
39 | opacity: 0;
40 | animation: fadeMoveDown 0.15s forwards;
41 | animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
42 | }
43 |
44 | @keyframes fadeMoveDown {
45 | to {
46 | opacity: 1;
47 | transform: translateY(6px);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/styles/components/code.scss:
--------------------------------------------------------------------------------
1 | .actions .shiki {
2 | background-color: var(--bolt-elements-actions-code-background) !important;
3 | }
4 |
5 | .shiki {
6 | &:not(:has(.actions), .actions *) {
7 | background-color: var(--bolt-elements-messages-code-background) !important;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/styles/components/resize-handle.scss:
--------------------------------------------------------------------------------
1 | @use '../z-index';
2 |
3 | [data-resize-handle] {
4 | position: relative;
5 |
6 | &[data-panel-group-direction='horizontal']:after {
7 | content: '';
8 | position: absolute;
9 | top: 0;
10 | bottom: 0;
11 | left: -6px;
12 | right: -5px;
13 | z-index: z-index.$zIndexMax;
14 | }
15 |
16 | &[data-panel-group-direction='vertical']:after {
17 | content: '';
18 | position: absolute;
19 | left: 0;
20 | right: 0;
21 | top: -5px;
22 | bottom: -6px;
23 | z-index: z-index.$zIndexMax;
24 | }
25 |
26 | &[data-resize-handle-state='hover']:after,
27 | &[data-resize-handle-state='drag']:after {
28 | background-color: #8882;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/styles/components/terminal.scss:
--------------------------------------------------------------------------------
1 | .xterm {
2 | padding: 1rem;
3 | }
4 |
--------------------------------------------------------------------------------
/styles/components/toast.scss:
--------------------------------------------------------------------------------
1 | .Toastify__toast {
2 | --at-apply: shadow-md;
3 |
4 | background-color: var(--bolt-elements-bg-depth-2);
5 | color: var(--bolt-elements-textPrimary);
6 | border: 1px solid var(--bolt-elements-borderColor);
7 | }
8 |
9 | .Toastify__close-button {
10 | color: var(--bolt-elements-item-contentDefault);
11 | opacity: 1;
12 | transition: none;
13 |
14 | &:hover {
15 | color: var(--bolt-elements-item-contentActive);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/styles/index.scss:
--------------------------------------------------------------------------------
1 | @use 'variables.scss';
2 | @use 'z-index.scss';
3 | @use 'animations.scss';
4 | @use 'components/terminal.scss';
5 | @use 'components/resize-handle.scss';
6 | @use 'components/code.scss';
7 | @use 'components/editor.scss';
8 | @use 'components/toast.scss';
9 |
10 | html,
11 | body {
12 | height: 100%;
13 | width: 100%;
14 | }
15 |
16 | :root {
17 | --gradient-opacity: 0.8;
18 | --primary-color: rgba(158, 117, 240, var(--gradient-opacity));
19 | --secondary-color: rgba(138, 43, 226, var(--gradient-opacity));
20 | --accent-color: rgba(128, 59, 239, var(--gradient-opacity));
21 | // --primary-color: rgba(147, 112, 219, var(--gradient-opacity));
22 | // --secondary-color: rgba(138, 43, 226, var(--gradient-opacity));
23 | // --accent-color: rgba(180, 170, 220, var(--gradient-opacity));
24 | }
25 |
26 | // [data-theme="light"] #root {
27 | // background-color: #FFFFFF !important;
28 | // }
29 |
30 | // [data-theme="dark"] #root {
31 | // background-color: #000000 !important;
32 | // }
33 |
34 | // [data-theme="light"] canvas {
35 | // background-color: #FFFFFF !important;
36 | // }
37 |
38 | // [data-theme="dark"] canvas {
39 | // background-color: #000000 !important;
40 | // }
41 |
42 | .scrollbar-hide::-webkit-scrollbar {
43 | display: none;
44 | }
45 |
46 | .scrollbar-hide {
47 | scrollbar-width: none;
48 | }
--------------------------------------------------------------------------------
/styles/z-index.scss:
--------------------------------------------------------------------------------
1 | $zIndexMax: 999;
2 |
3 | .z-logo {
4 | z-index: $zIndexMax - 1;
5 | }
6 |
7 | .z-sidebar {
8 | z-index: $zIndexMax - 2;
9 | }
10 |
11 | .z-port-dropdown {
12 | z-index: $zIndexMax - 3;
13 | }
14 |
15 | .z-iframe-overlay {
16 | z-index: $zIndexMax - 4;
17 | }
18 |
19 | .z-prompt {
20 | z-index: 2;
21 | }
22 |
23 | .z-workbench {
24 | z-index: 3;
25 | }
26 |
27 | .z-file-tree-breadcrumb {
28 | z-index: $zIndexMax - 1;
29 | }
30 |
31 | .z-max {
32 | z-index: $zIndexMax;
33 | }
34 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "types": [
4 | "@types/dom-speech-recognition"
5 | ],
6 | "incremental": true,
7 | "target": "ES2017",
8 | "jsx": "preserve",
9 | "lib": ["dom", "dom.iterable", "esnext"],
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "paths": {
13 | "@/*": ["./*"],
14 | "auth": ["./auth"]
15 | },
16 | "resolveJsonModule": true,
17 | "allowJs": true,
18 | "strict": true,
19 | "noEmit": true,
20 | "esModuleInterop": true,
21 | "isolatedModules": true,
22 | "skipLibCheck": true,
23 | "plugins": [
24 | {
25 | "name": "next"
26 | }
27 | ]
28 | },
29 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
30 | "exclude": ["node_modules"]
31 | }
32 |
--------------------------------------------------------------------------------
/types/actions.ts:
--------------------------------------------------------------------------------
1 | export type ActionType = 'file' | 'shell';
2 |
3 | export interface BaseAction {
4 | content: string;
5 | }
6 |
7 | export interface FileAction extends BaseAction {
8 | type: 'file';
9 | filePath: string;
10 | }
11 |
12 | export interface ShellAction extends BaseAction {
13 | type: 'shell';
14 | }
15 |
16 | export interface StartAction extends BaseAction {
17 | type: 'start';
18 | }
19 |
20 | export type BoltAction = FileAction | ShellAction | StartAction;
21 |
22 | export type BoltActionData = BoltAction | BaseAction;
23 |
24 | export interface ActionAlert {
25 | type: string;
26 | title: string;
27 | description: string;
28 | content: string;
29 | source?: 'terminal' | 'preview'; // Add source to differentiate between terminal and preview errors
30 | }
31 |
--------------------------------------------------------------------------------
/types/artifact.ts:
--------------------------------------------------------------------------------
1 | export interface BoltArtifactData {
2 | id: string;
3 | title: string;
4 | type?: string | undefined;
5 | }
6 |
--------------------------------------------------------------------------------
/types/context.ts:
--------------------------------------------------------------------------------
1 | export type ContextAnnotation =
2 | | {
3 | type: 'codeContext';
4 | files: string[];
5 | }
6 | | {
7 | type: 'chatSummary';
8 | summary: string;
9 | chatId: string;
10 | };
11 |
12 | export type ProgressAnnotation = {
13 | type: 'progress';
14 | label: string;
15 | status: 'in-progress' | 'complete';
16 | order: number;
17 | message: string;
18 | };
19 |
--------------------------------------------------------------------------------
/types/global.d.ts:
--------------------------------------------------------------------------------
1 | interface Window {
2 | showDirectoryPicker(): Promise;
3 | webkitSpeechRecognition: typeof SpeechRecognition;
4 | SpeechRecognition: typeof SpeechRecognition;
5 | }
6 |
7 | interface Performance {
8 | memory?: {
9 | jsHeapSizeLimit: number;
10 | totalJSHeapSize: number;
11 | usedJSHeapSize: number;
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/types/model.ts:
--------------------------------------------------------------------------------
1 | import type { ModelInfo } from '@/lib/modules/llm/types';
2 |
3 | export type ProviderInfo = {
4 | staticModels: ModelInfo[];
5 | name: string;
6 | getDynamicModels?: (
7 | providerName: string,
8 | apiKeys?: Record,
9 | providerSettings?: IProviderSetting,
10 | serverEnv?: Record,
11 | ) => Promise;
12 | getApiKeyLink?: string;
13 | labelForGetApiKey?: string;
14 | icon?: string;
15 | };
16 |
17 | export interface IProviderSetting {
18 | enabled?: boolean;
19 | baseUrl?: string;
20 | }
21 |
22 | export type IProviderConfig = ProviderInfo & {
23 | settings: IProviderSetting;
24 | };
25 |
--------------------------------------------------------------------------------
/types/template.ts:
--------------------------------------------------------------------------------
1 | export interface Template {
2 | name: string;
3 | label: string;
4 | description: string;
5 | githubRepo: string;
6 | tags?: string[];
7 | icon?: string;
8 | categorys?: string[];
9 | }
10 |
--------------------------------------------------------------------------------
/types/terminal.ts:
--------------------------------------------------------------------------------
1 | export interface ITerminal {
2 | readonly cols?: number;
3 | readonly rows?: number;
4 |
5 | reset: () => void;
6 | write: (data: string) => void;
7 | onData: (cb: (data: string) => void) => void;
8 | input: (data: string) => void;
9 | }
10 |
--------------------------------------------------------------------------------
/types/theme.ts:
--------------------------------------------------------------------------------
1 | export type Theme = 'dark' | 'light';
2 |
--------------------------------------------------------------------------------
/utils/buffer.ts:
--------------------------------------------------------------------------------
1 | export function bufferWatchEvents(timeInMs: number, cb: (events: T[]) => unknown) {
2 | let timeoutId: number | undefined;
3 | let events: T[] = [];
4 |
5 | // keep track of the processing of the previous batch so we can wait for it
6 | let processing: Promise = Promise.resolve();
7 |
8 | const scheduleBufferTick = () => {
9 | timeoutId = self.setTimeout(async () => {
10 | // we wait until the previous batch is entirely processed so events are processed in order
11 | await processing;
12 |
13 | if (events.length > 0) {
14 | processing = Promise.resolve(cb(events));
15 | }
16 |
17 | timeoutId = undefined;
18 | events = [];
19 | }, timeInMs);
20 | };
21 |
22 | return (...args: T) => {
23 | events.push(args);
24 |
25 | if (!timeoutId) {
26 | scheduleBufferTick();
27 | }
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/utils/classNames.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 Jed Watson.
3 | * Licensed under the MIT License (MIT), see:
4 | *
5 | * @link http://jedwatson.github.io/classnames
6 | */
7 |
8 | type ClassNamesArg = undefined | string | Record | ClassNamesArg[];
9 |
10 | /**
11 | * A simple JavaScript utility for conditionally joining classNames together.
12 | *
13 | * @param args A series of classes or object with key that are class and values
14 | * that are interpreted as boolean to decide whether or not the class
15 | * should be included in the final class.
16 | */
17 | export function classNames(...args: ClassNamesArg[]): string {
18 | let classes = '';
19 |
20 | for (const arg of args) {
21 | classes = appendClass(classes, parseValue(arg));
22 | }
23 |
24 | return classes;
25 | }
26 |
27 | function parseValue(arg: ClassNamesArg) {
28 | if (typeof arg === 'string' || typeof arg === 'number') {
29 | return arg;
30 | }
31 |
32 | if (typeof arg !== 'object') {
33 | return '';
34 | }
35 |
36 | if (Array.isArray(arg)) {
37 | return classNames(...arg);
38 | }
39 |
40 | let classes = '';
41 |
42 | for (const key in arg) {
43 | if (arg[key]) {
44 | classes = appendClass(classes, key);
45 | }
46 | }
47 |
48 | return classes;
49 | }
50 |
51 | function appendClass(value: string, newClass: string | undefined) {
52 | if (!newClass) {
53 | return value;
54 | }
55 |
56 | if (value) {
57 | return value + ' ' + newClass;
58 | }
59 |
60 | return value + newClass;
61 | }
62 |
--------------------------------------------------------------------------------
/utils/cloudflare.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparrow-js/an-codeAI/9cb9ed5ddadf45e6819e638da3584893ddc7329d/utils/cloudflare.ts
--------------------------------------------------------------------------------
/utils/constants.ts:
--------------------------------------------------------------------------------
1 | import { LLMManager } from '@/lib/modules/llm/manager';
2 | import type { Template } from '@/types/template';
3 |
4 | export const WORK_DIR_NAME = 'project';
5 | export const WORK_DIR = `/home/${WORK_DIR_NAME}`;
6 | export const MODIFICATIONS_TAG_NAME = 'bolt_file_modifications';
7 | export const MODEL_REGEX = /^\[Model: (.*?)\]\n\n/;
8 | export const PROVIDER_REGEX = /\[Provider: (.*?)\]\n\n/;
9 | export const DEFAULT_MODEL = 'anthropic/claude-3.7-sonnet';
10 | export const PROMPT_COOKIE_KEY = 'cachedPrompt';
11 |
12 | const llmManager = LLMManager.getInstance();
13 |
14 | export const PROVIDER_LIST = llmManager.getAllProviders();
15 | export const DEFAULT_PROVIDER = llmManager.getDefaultProvider();
16 |
17 | export const providerBaseUrlEnvKeys: Record = {};
18 | PROVIDER_LIST.forEach((provider) => {
19 | providerBaseUrlEnvKeys[provider.name] = {
20 | baseUrlKey: provider.config.baseUrlKey,
21 | apiTokenKey: provider.config.apiTokenKey,
22 | };
23 | });
24 |
25 | // starter Templates
26 |
27 | export const STARTER_TEMPLATES: Template[] = [
28 | {
29 | name: 'vite-ts-template',
30 | label: 'React + Vite + typescript',
31 | description: 'React starter template powered by Vite for fast development experience',
32 | githubRepo: 'wordixai/vite-ts-template',
33 | tags: ['react', 'vite', 'frontend'],
34 | icon: 'i-bolt:react',
35 | categorys: ['Landing Page', 'Marketing', 'Information Display']
36 | },
37 | {
38 | name: 'vite-ts-sass-template',
39 | label: 'React + Vite + typescript',
40 | description: 'React starter template powered by Vite for fast development experience',
41 | githubRepo: 'wordixai/vite-ts-sass-template',
42 | tags: ['react', 'vite', 'frontend'],
43 | icon: 'i-bolt:react',
44 | categorys: ['SaaS System', 'Cloud Management System', 'Subscription Service System', 'Dashboard System', "Productivity Tool"]
45 | }
46 | ];
47 |
--------------------------------------------------------------------------------
/utils/debounce.ts:
--------------------------------------------------------------------------------
1 | export function debounce any>(func: T, wait: number): (...args: Parameters) => void {
2 | let timeout: NodeJS.Timeout;
3 |
4 | return function executedFunction(...args: Parameters) {
5 | const later = () => {
6 | clearTimeout(timeout);
7 | func(...args);
8 | };
9 |
10 | clearTimeout(timeout);
11 | timeout = setTimeout(later, wait);
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/utils/diff.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import { extractRelativePath } from './diff';
3 | import { WORK_DIR } from './constants';
4 |
5 | describe('Diff', () => {
6 | it('should strip out Work_dir', () => {
7 | const filePath = `${WORK_DIR}/index.js`;
8 | const result = extractRelativePath(filePath);
9 | expect(result).toBe('index.js');
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/utils/easings.ts:
--------------------------------------------------------------------------------
1 | import { cubicBezier } from 'framer-motion';
2 |
3 | export const cubicEasingFn = cubicBezier(0.4, 0, 0.2, 1);
4 |
--------------------------------------------------------------------------------
/utils/folderImport.ts:
--------------------------------------------------------------------------------
1 | import type { Message } from 'ai';
2 | import { generateId } from './fileUtils';
3 | import { detectProjectCommands, createCommandsMessage, escapeBoltTags } from './projectCommands';
4 |
5 | export const createChatFromFolder = async (
6 | files: File[],
7 | binaryFiles: string[],
8 | folderName: string,
9 | ): Promise => {
10 | const fileArtifacts = await Promise.all(
11 | files.map(async (file) => {
12 | return new Promise<{ content: string; path: string }>((resolve, reject) => {
13 | const reader = new FileReader();
14 |
15 | reader.onload = () => {
16 | const content = reader.result as string;
17 | const relativePath = file.webkitRelativePath.split('/').slice(1).join('/');
18 | resolve({
19 | content,
20 | path: relativePath,
21 | });
22 | };
23 | reader.onerror = reject;
24 | reader.readAsText(file);
25 | });
26 | }),
27 | );
28 |
29 | const commands = await detectProjectCommands(fileArtifacts);
30 | const commandsMessage = createCommandsMessage(commands);
31 |
32 | const binaryFilesMessage =
33 | binaryFiles.length > 0
34 | ? `\n\nSkipped ${binaryFiles.length} binary files:\n${binaryFiles.map((f) => `- ${f}`).join('\n')}`
35 | : '';
36 |
37 | const filesMessage: Message = {
38 | role: 'assistant',
39 | content: `I've imported the contents of the "${folderName}" folder.${binaryFilesMessage}
40 |
41 |
42 | ${fileArtifacts
43 | .map(
44 | (file) => `
45 | ${escapeBoltTags(file.content)}
46 | `,
47 | )
48 | .join('\n\n')}
49 | `,
50 | id: generateId(),
51 | createdAt: new Date(),
52 | };
53 |
54 | const userMessage: Message = {
55 | role: 'user',
56 | id: generateId(),
57 | content: `Import the "${folderName}" folder`,
58 | createdAt: new Date(),
59 | };
60 |
61 | const messages = [userMessage, filesMessage];
62 |
63 | if (commandsMessage) {
64 | messages.push({
65 | role: 'user',
66 | id: generateId(),
67 | content: 'Setup the codebase and Start the application',
68 | });
69 | messages.push(commandsMessage);
70 | }
71 |
72 | return messages;
73 | };
74 |
--------------------------------------------------------------------------------
/utils/formatSize.ts:
--------------------------------------------------------------------------------
1 | export function formatSize(bytes: number): string {
2 | const units = ['B', 'KB', 'MB', 'GB', 'TB'];
3 | let size = bytes;
4 | let unitIndex = 0;
5 |
6 | while (size >= 1024 && unitIndex < units.length - 1) {
7 | size /= 1024;
8 | unitIndex++;
9 | }
10 |
11 | return `${size.toFixed(1)} ${units[unitIndex]}`;
12 | }
13 |
--------------------------------------------------------------------------------
/utils/mobile.ts:
--------------------------------------------------------------------------------
1 | export function isMobile() {
2 | // we use sm: as the breakpoint for mobile. It's currently set to 640px
3 | return globalThis.innerWidth < 640;
4 | }
5 |
--------------------------------------------------------------------------------
/utils/os.ts:
--------------------------------------------------------------------------------
1 | // Helper to detect OS
2 | export const isMac = typeof navigator !== 'undefined' ? navigator.platform.toLowerCase().includes('mac') : false;
3 | export const isWindows = typeof navigator !== 'undefined' ? navigator.platform.toLowerCase().includes('win') : false;
4 | export const isLinux = typeof navigator !== 'undefined' ? navigator.platform.toLowerCase().includes('linux') : false;
5 |
--------------------------------------------------------------------------------
/utils/path.ts:
--------------------------------------------------------------------------------
1 | // Browser-compatible path utilities
2 | import type { ParsedPath } from 'path';
3 | import pathBrowserify from 'path-browserify';
4 |
5 | /**
6 | * A browser-compatible path utility that mimics Node's path module
7 | * Using path-browserify for consistent behavior in browser environments
8 | */
9 | export const path = {
10 | join: (...paths: string[]): string => pathBrowserify.join(...paths),
11 | dirname: (path: string): string => pathBrowserify.dirname(path),
12 | basename: (path: string, ext?: string): string => pathBrowserify.basename(path, ext),
13 | extname: (path: string): string => pathBrowserify.extname(path),
14 | relative: (from: string, to: string): string => pathBrowserify.relative(from, to),
15 | isAbsolute: (path: string): boolean => pathBrowserify.isAbsolute(path),
16 | normalize: (path: string): string => pathBrowserify.normalize(path),
17 | parse: (path: string): ParsedPath => pathBrowserify.parse(path),
18 | format: (pathObject: ParsedPath): string => pathBrowserify.format(pathObject),
19 | } as const;
20 |
--------------------------------------------------------------------------------
/utils/promises.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | export function withResolvers(): PromiseWithResolvers {
4 | if (typeof Promise.withResolvers === 'function') {
5 | return Promise.withResolvers();
6 | }
7 |
8 | let resolve!: (value: T | PromiseLike) => void;
9 | let reject!: (reason?: any) => void;
10 |
11 | const promise = new Promise((_resolve, _reject) => {
12 | resolve = _resolve;
13 | reject = _reject;
14 | });
15 |
16 | return {
17 | resolve,
18 | reject,
19 | promise,
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/utils/react.ts:
--------------------------------------------------------------------------------
1 | import { memo, type JSX } from 'react';
2 |
3 | export const genericMemo: >(
4 | component: T,
5 | propsAreEqual?: (prevProps: React.ComponentProps, nextProps: React.ComponentProps) => boolean,
6 | ) => T & { displayName?: string } = memo;
7 |
--------------------------------------------------------------------------------
/utils/stacktrace.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Cleans webcontainer URLs from stack traces to show relative paths instead
3 | */
4 | export function cleanStackTrace(stackTrace: string): string {
5 | // Function to clean a single URL
6 | const cleanUrl = (url: string): string => {
7 | const regex = /^https?:\/\/[^\/]+\.webcontainer-api\.io(\/.*)?$/;
8 |
9 | if (!regex.test(url)) {
10 | return url;
11 | }
12 |
13 | const pathRegex = /^https?:\/\/[^\/]+\.webcontainer-api\.io\/(.*?)$/;
14 | const match = url.match(pathRegex);
15 |
16 | return match?.[1] || '';
17 | };
18 |
19 | // Split the stack trace into lines and process each line
20 | return stackTrace
21 | .split('\n')
22 | .map((line) => {
23 | // Match any URL in the line that contains webcontainer-api.io
24 | return line.replace(/(https?:\/\/[^\/]+\.webcontainer-api\.io\/[^\s\)]+)/g, (match) => cleanUrl(match));
25 | })
26 | .join('\n');
27 | }
28 |
--------------------------------------------------------------------------------
/utils/stripIndent.ts:
--------------------------------------------------------------------------------
1 | export function stripIndents(value: string): string;
2 | export function stripIndents(strings: TemplateStringsArray, ...values: any[]): string;
3 | export function stripIndents(arg0: string | TemplateStringsArray, ...values: any[]) {
4 | if (typeof arg0 !== 'string') {
5 | const processedString = arg0.reduce((acc, curr, i) => {
6 | acc += curr + (values[i] ?? '');
7 | return acc;
8 | }, '');
9 |
10 | return _stripIndents(processedString);
11 | }
12 |
13 | return _stripIndents(arg0);
14 | }
15 |
16 | function _stripIndents(value: string) {
17 | return value
18 | .split('\n')
19 | .map((line) => line.trim())
20 | .join('\n')
21 | .trimStart()
22 | .replace(/[\r\n]$/, '');
23 | }
24 |
--------------------------------------------------------------------------------
/utils/terminal.ts:
--------------------------------------------------------------------------------
1 | const reset = '\x1b[0m';
2 |
3 | export const escapeCodes = {
4 | reset,
5 | clear: '\x1b[g',
6 | red: '\x1b[1;31m',
7 | };
8 |
9 | export const coloredText = {
10 | red: (text: string) => `${escapeCodes.red}${text}${reset}`,
11 | };
12 |
--------------------------------------------------------------------------------
/utils/throttle.ts:
--------------------------------------------------------------------------------
1 | export function throttle void>(func: T, limit: number): T {
2 | let inThrottle: boolean;
3 | let lastFunc: ReturnType;
4 | let lastRan: number;
5 |
6 | return ((...args: Parameters) => {
7 | if (!inThrottle) {
8 | func(...args);
9 | lastRan = Date.now();
10 | inThrottle = true;
11 | } else {
12 | clearTimeout(lastFunc);
13 | lastFunc = setTimeout(() => {
14 | if (Date.now() - lastRan >= limit) {
15 | func(...args);
16 | lastRan = Date.now();
17 | }
18 | }, limit - (Date.now() - lastRan));
19 | }
20 | }) as T;
21 | }
--------------------------------------------------------------------------------
/utils/types.ts:
--------------------------------------------------------------------------------
1 | interface OllamaModelDetails {
2 | parent_model: string;
3 | format: string;
4 | family: string;
5 | families: string[];
6 | parameter_size: string;
7 | quantization_level: string;
8 | }
9 |
10 | export interface OllamaModel {
11 | name: string;
12 | model: string;
13 | modified_at: string;
14 | size: number;
15 | digest: string;
16 | details: OllamaModelDetails;
17 | }
18 |
19 | export interface OllamaApiResponse {
20 | models: OllamaModel[];
21 | }
22 |
--------------------------------------------------------------------------------
/utils/unreachable.ts:
--------------------------------------------------------------------------------
1 | export function unreachable(message: string): never {
2 | throw new Error(`Unreachable: ${message}`);
3 | }
4 |
--------------------------------------------------------------------------------