├── .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 | [![genfly.dev: AI-Powered Full-Stack Web Development in the Browser](https://github.com/user-attachments/assets/563b1902-465f-4b84-b05c-69548f5872ec)](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 | 21 | 26 | 27 |
17 | 18 | [![](https://github.com/user-attachments/assets/812571f9-4377-4da1-9f31-3b1f2d385338)](https://github.com/user-attachments/assets/812571f9-4377-4da1-9f31-3b1f2d385338) 19 | 20 | 22 | 23 | [![](https://github.com/user-attachments/assets/426b01bf-2b20-4dfe-a63c-6150d1308fc9)](https://github.com/user-attachments/assets/426b01bf-2b20-4dfe-a63c-6150d1308fc9) 24 | 25 |
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 |