├── .env ├── .gitignore ├── .vscode └── launch.json ├── README.md ├── app ├── api │ ├── chat │ │ └── route.ts │ └── sandbox │ │ └── route.ts ├── favicon.ico ├── fonts │ ├── GeistMonoVF.woff │ └── GeistVF.woff ├── globals.css ├── layout.tsx ├── page.tsx └── providers.tsx ├── components.json ├── components ├── auth-dialog.tsx ├── auth-form.tsx ├── capsule-code.tsx ├── capsule-interpreter.tsx ├── capsule-preview.tsx ├── capsule-web.tsx ├── chat-input.tsx ├── chat-picker.tsx ├── chat-settings.tsx ├── chat.tsx ├── code-theme.css ├── code-view.tsx ├── copy-button.tsx ├── navbar.tsx ├── preview.tsx └── ui │ ├── alert.tsx │ ├── avatar.tsx │ ├── button.tsx │ ├── dialog.tsx │ ├── dropdown-menu.tsx │ ├── input.tsx │ ├── label.tsx │ ├── select.tsx │ ├── tabs.tsx │ └── tooltip.tsx ├── lib ├── auth.ts ├── duration.ts ├── messages.ts ├── models.json ├── models.ts ├── prompt.ts ├── ratelimit.ts ├── schema.ts ├── supabase.ts ├── templates.json ├── templates.ts ├── types.ts └── utils.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public └── thirdparty │ ├── logos │ ├── anthropic.svg │ ├── fireworks.svg │ ├── fireworksai.svg │ ├── google.svg │ ├── groq.svg │ ├── mistral.svg │ ├── ollama.svg │ ├── openai.svg │ └── togetherai.svg │ └── templates │ └── nextjs-developer.svg ├── sandbox-templates ├── gradio-developer │ ├── app.py │ ├── e2b.Dockerfile │ └── e2b.toml ├── nextjs-developer │ ├── _app.tsx │ ├── compile_page.sh │ ├── e2b.Dockerfile │ └── e2b.toml ├── streamlit-developer │ ├── app.py │ ├── e2b.Dockerfile │ └── e2b.toml └── vue-developer │ ├── e2b.Dockerfile │ ├── e2b.toml │ └── nuxt.config.ts ├── tailwind.config.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | # Get your E2B API key here https://e2b.dev/docs/getting-started/api-key 2 | E2B_API_KEY= 3 | # Get your Anthropic API key here https://console.anthropic.com 4 | ANTHROPIC_API_KEY= 5 | 6 | OPENAI_API_KEY= 7 | 8 | # Other providers 9 | 10 | GROQ_API_KEY= 11 | FIREWORKS_API_KEY= 12 | TOGETHER_AI_API_KEY= 13 | GOOGLE_AI_API_KEY= 14 | GOOGLE_GENERATIVE_AI_API_KEY= 15 | MISTRAL_API_KEY= 16 | 17 | ### Optional env vars 18 | 19 | # Domain of the site 20 | NEXT_PUBLIC_SITE_URL= 21 | 22 | # Disabling API key and base URL input in the chat 23 | NEXT_PUBLIC_NO_API_KEY_INPUT= 24 | NEXT_PUBLIC_NO_BASE_URL_INPUT= 25 | 26 | # Rate limit 27 | RATE_LIMIT_MAX_REQUESTS= 28 | RATE_LIMIT_WINDOW= 29 | 30 | # Vercel/Upstash KV (short URLs, rate limiting) 31 | KV_URL= 32 | KV_REST_API_URL= 33 | KV_REST_API_TOKEN= 34 | KV_REST_API_READ_ONLY_TOKEN= 35 | 36 | # Supabase (auth) 37 | NEXT_PUBLIC_ENABLE_SUPABASE=false 38 | NEXT_PUBLIC_SUPABASE_URL= 39 | NEXT_PUBLIC_SUPABASE_ANON_KEY= 40 | 41 | # PostHog (analytics) 42 | NEXT_PUBLIC_ENABLE_POSTHOG=true 43 | NEXT_PUBLIC_POSTHOG_KEY= 44 | NEXT_PUBLIC_POSTHOG_HOST= -------------------------------------------------------------------------------- /.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.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Next.js: debug server-side", 6 | "type": "node-terminal", 7 | "request": "launch", 8 | "command": "npm run dev" 9 | }, 10 | { 11 | "name": "Next.js: debug client-side", 12 | "type": "chrome", 13 | "request": "launch", 14 | "url": "http://localhost:3001" 15 | }, 16 | { 17 | "name": "Next.js: debug full stack", 18 | "type": "node-terminal", 19 | "request": "launch", 20 | "command": "npm run dev", 21 | "serverReadyAction": { 22 | "pattern": "- Local:.+(https?://.+)", 23 | "uriFormat": "%s", 24 | "action": "debugWithChrome" 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /app/api/chat/route.ts: -------------------------------------------------------------------------------- 1 | import { Duration } from "@/lib/duration"; 2 | import { 3 | getDefaultMode, 4 | getModelClient, 5 | LLMModel, 6 | LLMModelConfig, 7 | } from "@/lib/models"; 8 | import { toPrompt } from "@/lib/prompt"; 9 | import ratelimit from "@/lib/ratelimit"; 10 | import { Templates } from "@/lib/templates"; 11 | import { CoreMessage, LanguageModel, streamObject } from "ai"; 12 | import { capsuleSchema as schema } from "@/lib/schema"; 13 | 14 | const ratelimitMaxRequests = process.env.RATE_LIMIT_MAX_REQUESTS 15 | ? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) 16 | : 100; 17 | 18 | const ratelimitWindow = process.env.RATE_LIMIT_WINDOW 19 | ? (process.env.RATE_LIMIT_WINDOW as Duration) 20 | : "1d"; 21 | 22 | export async function POST(req: Request) { 23 | const { 24 | messages, 25 | userID, 26 | template, 27 | model, 28 | config, 29 | }: { 30 | messages: CoreMessage[]; 31 | userID: string; 32 | template: Templates; 33 | model: LLMModel; 34 | config: LLMModelConfig; 35 | } = await req.json(); 36 | 37 | const limit = !config.apiKey 38 | ? await ratelimit(userID, ratelimitMaxRequests, ratelimitWindow) 39 | : false; 40 | 41 | if (limit) { 42 | return new Response("You have reached your request limit for the day.", { 43 | status: 429, 44 | headers: { 45 | "X-Ratelimit-Limit": limit.amount.toString(), 46 | "X-RateLimit-Remaining": limit.remaining.toString(), 47 | "X-RateLimit-Reset": limit.reset.toString(), 48 | }, 49 | }); 50 | } 51 | 52 | const { 53 | model: modelNameString, 54 | apiKey: modelApiKey, 55 | ...modelParams 56 | } = config; 57 | 58 | const modelClient = getModelClient(model, config); 59 | 60 | const stream = await streamObject({ 61 | model: modelClient as LanguageModel, 62 | schema, 63 | system: toPrompt(template), 64 | messages, 65 | mode: getDefaultMode(model), 66 | ...modelParams, 67 | }); 68 | 69 | return stream.toTextStreamResponse(); 70 | } 71 | -------------------------------------------------------------------------------- /app/api/sandbox/route.ts: -------------------------------------------------------------------------------- 1 | import { CapsuleSchema } from "@/lib/schema"; 2 | import { ExecutionResultWeb } from "@/lib/types"; 3 | import { CodeInterpreter, Sandbox } from "@e2b/code-interpreter"; 4 | 5 | const sandboxTimeout = 10 * 60 * 1000; 6 | 7 | export async function POST(req: Request) { 8 | const { 9 | capsule, 10 | userID, 11 | apiKey, 12 | }: { capsule: CapsuleSchema; userID: string; apiKey?: string } = 13 | await req.json(); 14 | 15 | let sbx: Sandbox | CodeInterpreter | undefined = undefined; 16 | 17 | if (capsule.template === "code-interpreter-multilang") { 18 | sbx = await CodeInterpreter.create({ 19 | metadata: { template: capsule.template, userID: userID }, 20 | timeoutMs: sandboxTimeout, 21 | apiKey, 22 | }); 23 | } else { 24 | sbx = await Sandbox.create(capsule.template, { 25 | metadata: { template: capsule.template, userID: userID }, 26 | timeoutMs: sandboxTimeout, 27 | apiKey, 28 | }); 29 | } 30 | 31 | if (capsule.additional_dependencies) { 32 | if (sbx instanceof CodeInterpreter) { 33 | await sbx.notebook.execCell(capsule.install_dependencies_command); 34 | } else if (sbx instanceof Sandbox) { 35 | await sbx.commands.run(capsule.install_dependencies_command); 36 | } 37 | } 38 | 39 | if (capsule.code && Array.isArray(capsule.code)) { 40 | capsule.code.forEach(async (file) => { 41 | await sbx.files.write(file.file_path, file.file_content); 42 | }); 43 | } else { 44 | await sbx.files.write(capsule.file_path, capsule.code); 45 | } 46 | 47 | return new Response( 48 | JSON.stringify({ 49 | sbxId: sbx?.sandboxID, 50 | template: capsule.template, 51 | url: `https://${sbx?.getHost(capsule.port || 80)}`, 52 | } as ExecutionResultWeb) 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuluruvineeth/codecapsule/011c7be477c5af65ff51215e9d31d3cdb0188bb2/app/favicon.ico -------------------------------------------------------------------------------- /app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuluruvineeth/codecapsule/011c7be477c5af65ff51215e9d31d3cdb0188bb2/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuluruvineeth/codecapsule/011c7be477c5af65ff51215e9d31d3cdb0188bb2/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | font-family: Arial, Helvetica, sans-serif; 7 | } 8 | 9 | @layer utilities { 10 | .text-balance { 11 | text-wrap: balance; 12 | } 13 | } 14 | 15 | @layer base { 16 | :root { 17 | --background: 0 0% 100%; 18 | --foreground: 0 0% 3.9%; 19 | --card: 0 0% 100%; 20 | --card-foreground: 0 0% 3.9%; 21 | --popover: 0 0% 100%; 22 | --popover-foreground: 0 0% 3.9%; 23 | --primary: 0 0% 9%; 24 | --primary-foreground: 0 0% 98%; 25 | --secondary: 0 0% 96.1%; 26 | --secondary-foreground: 0 0% 9%; 27 | --muted: 0 0% 96.1%; 28 | --muted-foreground: 0 0% 45.1%; 29 | --accent: 0 0% 96.1%; 30 | --accent-foreground: 0 0% 9%; 31 | --destructive: 0 84.2% 60.2%; 32 | --destructive-foreground: 0 0% 98%; 33 | --border: 0 0% 89.8%; 34 | --input: 0 0% 89.8%; 35 | --ring: 0 0% 3.9%; 36 | --chart-1: 12 76% 61%; 37 | --chart-2: 173 58% 39%; 38 | --chart-3: 197 37% 24%; 39 | --chart-4: 43 74% 66%; 40 | --chart-5: 27 87% 67%; 41 | --radius: 0.5rem; 42 | } 43 | .dark { 44 | --background: 0 0% 3.9%; 45 | --foreground: 0 0% 98%; 46 | --card: 0 0% 3.9%; 47 | --card-foreground: 0 0% 98%; 48 | --popover: 0 0% 3.9%; 49 | --popover-foreground: 0 0% 98%; 50 | --primary: 0 0% 98%; 51 | --primary-foreground: 0 0% 9%; 52 | --secondary: 0 0% 14.9%; 53 | --secondary-foreground: 0 0% 98%; 54 | --muted: 0 0% 14.9%; 55 | --muted-foreground: 0 0% 63.9%; 56 | --accent: 0 0% 14.9%; 57 | --accent-foreground: 0 0% 98%; 58 | --destructive: 0 62.8% 30.6%; 59 | --destructive-foreground: 0 0% 98%; 60 | --border: 0 0% 14.9%; 61 | --input: 0 0% 14.9%; 62 | --ring: 0 0% 83.1%; 63 | --chart-1: 220 70% 50%; 64 | --chart-2: 160 60% 45%; 65 | --chart-3: 30 80% 55%; 66 | --chart-4: 280 65% 60%; 67 | --chart-5: 340 75% 55%; 68 | } 69 | } 70 | 71 | @layer base { 72 | * { 73 | @apply border-border; 74 | } 75 | body { 76 | @apply bg-background text-foreground; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import localFont from "next/font/local"; 3 | import "./globals.css"; 4 | import { PostHogProvider, ThemeProvider } from "./providers"; 5 | 6 | const geistSans = localFont({ 7 | src: "./fonts/GeistVF.woff", 8 | variable: "--font-geist-sans", 9 | weight: "100 900", 10 | }); 11 | const geistMono = localFont({ 12 | src: "./fonts/GeistMonoVF.woff", 13 | variable: "--font-geist-mono", 14 | weight: "100 900", 15 | }); 16 | 17 | export const metadata: Metadata = { 18 | title: "Create Next App", 19 | description: "Generated by create next app", 20 | }; 21 | 22 | export default function RootLayout({ 23 | children, 24 | }: Readonly<{ 25 | children: React.ReactNode; 26 | }>) { 27 | return ( 28 | 29 | 30 | 33 | 39 | {children} 40 | 41 | 42 | 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { AuthDialog } from "@/components/auth-dialog"; 4 | import { Chat } from "@/components/chat"; 5 | import { ChatInput } from "@/components/chat-input"; 6 | import { ChatPicker } from "@/components/chat-picker"; 7 | import { NavBar } from "@/components/navbar"; 8 | import { AuthViewType, useAuth } from "@/lib/auth"; 9 | import { supabase } from "@/lib/supabase"; 10 | import Image from "next/image"; 11 | import React, { useEffect, useState } from "react"; 12 | import { useLocalStorage } from "usehooks-ts"; 13 | import modelsList from "@/lib/models.json"; 14 | import templates, { TemplateId } from "@/lib/templates"; 15 | import { ChatSettings } from "@/components/chat-settings"; 16 | import { LLMModelConfig } from "@/lib/models"; 17 | import { Message, toAISDKMessages, toMessageImage } from "@/lib/messages"; 18 | import { DeepPartial } from "ai"; 19 | import { CapsuleSchema, capsuleSchema as schema } from "@/lib/schema"; 20 | import { experimental_useObject as useObject } from "ai/react"; 21 | import { usePostHog } from "posthog-js/react"; 22 | import { ExecutionResult } from "@/lib/types"; 23 | import { Preview } from "@/components/preview"; 24 | 25 | export default function Home() { 26 | const [isAuthDialogOpen, setAuthDialog] = useState(false); 27 | const [authView, setAuthView] = useState("sign_in"); 28 | const { session } = useAuth(setAuthDialog, setAuthView); 29 | const [chatInput, setChatInput] = useLocalStorage("chat", ""); 30 | const [files, setFiles] = useState([]); 31 | const [selectedTemplate, setSelectedTemplate] = useState<"auto" | TemplateId>( 32 | "auto" 33 | ); 34 | const [languageModel, setLanguageModel] = useLocalStorage("languageModel", { 35 | model: "gpt-4o-mini", 36 | }); 37 | 38 | const [messages, setMessages] = useState([]); 39 | const [capsule, setCapsule] = useState>(); 40 | const [currentTab, setCurrentTab] = useState<"code" | "capsule">("code"); 41 | const [isPreviewLoading, setIsPreviewLoading] = useState(false); 42 | const [result, setResult] = useState(); 43 | const posthog = usePostHog(); 44 | 45 | function setCurrentPreview(preview: { 46 | capsule: DeepPartial | undefined; 47 | result: ExecutionResult | undefined; 48 | }) { 49 | setCapsule(preview.capsule); 50 | setResult(preview.result); 51 | } 52 | 53 | function setMessage(message: Partial, index?: number) { 54 | setMessages((previousMessages) => { 55 | const updatedMessages = [...previousMessages]; 56 | updatedMessages[index ?? previousMessages.length - 1] = { 57 | ...previousMessages[index ?? previousMessages.length - 1], 58 | ...message, 59 | }; 60 | 61 | return updatedMessages; 62 | }); 63 | } 64 | 65 | const { object, submit, isLoading, stop, error } = useObject({ 66 | api: "/api/chat", 67 | schema, 68 | onFinish: async ({ object: capsule, error }) => { 69 | if (!error) { 70 | console.log("code", capsule); 71 | setIsPreviewLoading(true); 72 | posthog.capture("capsule_generated", { 73 | template: capsule?.template, 74 | }); 75 | 76 | const response = await fetch("/api/sandbox", { 77 | method: "POST", 78 | body: JSON.stringify({ 79 | capsule, 80 | userID: session?.user.id, 81 | }), 82 | }); 83 | 84 | const result = await response.json(); 85 | console.log("result", result); 86 | posthog.capture("sandbox_created", { url: result.url }); 87 | 88 | setResult(result); 89 | setCurrentPreview({ capsule, result }); 90 | setMessage({ result }); 91 | setCurrentTab("capsule"); 92 | setIsPreviewLoading(false); 93 | } 94 | }, 95 | }); 96 | 97 | useEffect(() => { 98 | if (object) { 99 | setCapsule(object); 100 | const content: Message["content"] = [ 101 | { type: "text", text: object.commentary || "" }, 102 | { type: "code", text: object.code || "" }, 103 | ]; 104 | 105 | if (!lastMessage || lastMessage.role !== "assistant") { 106 | addMessage({ 107 | role: "assistant", 108 | content, 109 | object, 110 | }); 111 | } 112 | 113 | if (lastMessage && lastMessage.role === "assistant") { 114 | setMessage({ 115 | content, 116 | object, 117 | }); 118 | } 119 | } 120 | }, [object]); 121 | 122 | useEffect(() => { 123 | if (error) stop(); 124 | }, [error]); 125 | 126 | function retry() { 127 | submit({ 128 | userID: session?.user?.id, 129 | messages: toAISDKMessages(messages), 130 | template: currentTemplate, 131 | model: currentModel, 132 | config: languageModel, 133 | }); 134 | } 135 | 136 | function logout() { 137 | supabase 138 | ? supabase.auth.signOut() 139 | : console.warn("Supabase is not initialized"); 140 | } 141 | 142 | const currentModel = modelsList.models.find( 143 | (model) => model.id === languageModel.model 144 | ); 145 | 146 | const currentTemplate = 147 | selectedTemplate === "auto" 148 | ? templates 149 | : { [selectedTemplate]: templates[selectedTemplate] }; 150 | 151 | const lastMessage = messages[messages.length - 1]; 152 | 153 | function handleUndo() { 154 | setMessages((previousMessages) => [...previousMessages.slice(0 - 2)]); 155 | setCurrentPreview({ capsule: undefined, result: undefined }); 156 | } 157 | 158 | function handleClearChat() { 159 | stop(); 160 | setChatInput(""); 161 | setFiles([]); 162 | setMessages([]); 163 | setCapsule(undefined); 164 | setResult(undefined); 165 | setCurrentTab("code"); 166 | setIsPreviewLoading(false); 167 | } 168 | 169 | function handleSocialClick(target: "github" | "x" | "discord") { 170 | posthog.capture(`${target}_click`); 171 | } 172 | 173 | function handleSaveInputChange(e: React.ChangeEvent) { 174 | setChatInput(e.target.value); 175 | } 176 | 177 | function addMessage(message: Message) { 178 | setMessages((previousMessages) => [...previousMessages, message]); 179 | return [...messages, message]; 180 | } 181 | 182 | async function handleSubmitAuth(e: React.FormEvent) { 183 | e.preventDefault(); 184 | 185 | // if (!session) { 186 | // return setAuthDialog(true); 187 | // } 188 | 189 | if (isLoading) { 190 | stop(); 191 | } 192 | 193 | const content: Message["content"] = [{ type: "text", text: chatInput }]; 194 | const images = await toMessageImage(files); 195 | 196 | if (images.length > 0) { 197 | images.forEach((image) => { 198 | content.push({ type: "image", image }); 199 | }); 200 | } 201 | 202 | const updatedMessages = addMessage({ 203 | role: "user", 204 | content, 205 | }); 206 | 207 | submit({ 208 | userID: session?.user?.id, 209 | messages: toAISDKMessages(updatedMessages), 210 | template: currentTemplate, 211 | model: currentModel, 212 | config: languageModel, 213 | }); 214 | 215 | setChatInput(""); 216 | setFiles([]); 217 | setCurrentTab("code"); 218 | 219 | posthog.capture("chat_submit", { 220 | template: selectedTemplate, 221 | model: languageModel.model, 222 | }); 223 | } 224 | 225 | function handleFileChange(files: File[]) { 226 | setFiles(files); 227 | } 228 | 229 | function handleLanguageModelChange(e: LLMModelConfig) { 230 | setLanguageModel({ ...languageModel, ...e }); 231 | } 232 | 233 | return ( 234 |
235 | {/* {supabase && ( 236 | 242 | )} */} 243 |
244 |
249 | setAuthDialog(true)} 252 | signOut={logout} 253 | onClear={handleClearChat} 254 | canClear={messages.length > 0} 255 | canUndo={messages.length > 1 && !isLoading} 256 | onUndo={handleUndo} 257 | onSocialClick={handleSocialClick} 258 | /> 259 | 264 | 276 | 284 | 290 | 291 |
292 | setCapsule(undefined)} 301 | /> 302 |
303 |
304 | ); 305 | } 306 | -------------------------------------------------------------------------------- /app/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import posthog from "posthog-js"; 4 | import { ReactNode } from "react"; 5 | import { PostHogProvider as PostHogProviderJS } from "posthog-js/react"; 6 | import { ThemeProviderProps } from "next-themes/dist/types"; 7 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 8 | 9 | if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_ENABLE_POSTHOG) { 10 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY ?? "", { 11 | api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, 12 | person_profiles: "identified_only", 13 | session_recording: { 14 | recordCrossOriginIframes: true, 15 | }, 16 | }); 17 | } 18 | 19 | export function PostHogProvider({ children }: { children: ReactNode }) { 20 | return process.env.NEXT_PUBLIC_ENABLE_POSTHOG ? ( 21 | {children} 22 | ) : ( 23 | children 24 | ); 25 | } 26 | 27 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 28 | return {children}; 29 | } 30 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /components/auth-dialog.tsx: -------------------------------------------------------------------------------- 1 | import { AuthViewType } from "@/lib/auth"; 2 | import { SupabaseClient } from "@supabase/supabase-js"; 3 | import { Dialog, DialogContent, DialogTitle } from "./ui/dialog"; 4 | import { VisuallyHidden } from "@radix-ui/react-visually-hidden"; 5 | import AuthForm from "./auth-form"; 6 | 7 | export function AuthDialog({ 8 | open, 9 | setOpen, 10 | supabase, 11 | view, 12 | }: { 13 | open: boolean; 14 | setOpen: (open: boolean) => void; 15 | supabase: SupabaseClient; 16 | view: AuthViewType; 17 | }) { 18 | return ( 19 | 20 | 21 | 22 | Sign in to CodeCapsule 23 | 24 | 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /components/auth-form.tsx: -------------------------------------------------------------------------------- 1 | import { AuthViewType } from "@/lib/auth"; 2 | import { Auth } from "@supabase/auth-ui-react"; 3 | import { ThemeSupa } from "@supabase/auth-ui-shared"; 4 | import { SupabaseClient } from "@supabase/supabase-js"; 5 | import { RefreshCcw } from "lucide-react"; 6 | 7 | function AuthForm({ 8 | supabase, 9 | view, 10 | }: { 11 | supabase: SupabaseClient; 12 | view: AuthViewType; 13 | }) { 14 | return ( 15 |
16 |

17 |
18 | 19 |
20 | Sign in to CodeCapsule 21 |

22 |
23 | 66 |
67 |
68 | ); 69 | } 70 | 71 | export default AuthForm; 72 | -------------------------------------------------------------------------------- /components/capsule-code.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { CodeView } from "./code-view"; 3 | import { Download, FileText } from "lucide-react"; 4 | import { 5 | Tooltip, 6 | TooltipContent, 7 | TooltipProvider, 8 | TooltipTrigger, 9 | } from "./ui/tooltip"; 10 | import { CopyButton } from "./copy-button"; 11 | import { Button } from "./ui/button"; 12 | 13 | export function CapsuleCode({ 14 | files, 15 | }: { 16 | files: { name: string; content: string }[]; 17 | }) { 18 | const [currentFile, setCurrentFile] = useState(files[0].name); 19 | const currentFileContent = files.find( 20 | (file) => file.name === currentFile 21 | )?.content; 22 | 23 | function download(filename: string, content: string) { 24 | const blob = new Blob([content], { type: "text/plain" }); 25 | const url = window.URL.createObjectURL(blob); 26 | const a = document.createElement("a"); 27 | a.style.display = "none"; 28 | a.href = url; 29 | a.download = filename; 30 | document.body.appendChild(a); 31 | a.click(); 32 | window.URL.revokeObjectURL(url); 33 | document.body.removeChild(a); 34 | } 35 | return ( 36 |
37 |
38 |
39 | {files.map((file) => ( 40 |
setCurrentFile(file.name)} 46 | > 47 | 48 | {file.name} 49 |
50 | ))} 51 |
52 |
53 | 54 | 55 | 56 | 60 | 61 | Copy 62 | 63 | 64 | 65 | 66 | 67 | 77 | 78 | Download 79 | 80 | 81 |
82 |
83 |
84 | 88 |
89 |
90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /components/capsule-interpreter.tsx: -------------------------------------------------------------------------------- 1 | import { ExecutionResultInterpreter } from "@/lib/types"; 2 | import { Alert, AlertDescription, AlertTitle } from "./ui/alert"; 3 | import { Terminal } from "lucide-react"; 4 | import Image from "next/image"; 5 | 6 | function LogsOutput({ 7 | stdout, 8 | stderr, 9 | }: { 10 | stdout: string[]; 11 | stderr: string[]; 12 | }) { 13 | if (stdout.length === 0 && stderr.length === 0) return null; 14 | 15 | return ( 16 |
17 | {stdout && 18 | stdout.length > 0 && 19 | stdout.map((out: string, index: number) => ( 20 |
21 |             {out}
22 |           
23 | ))} 24 | {stderr && 25 | stderr.length > 0 && 26 | stderr.map((out: string, index: number) => ( 27 |
28 |             {out}
29 |           
30 | ))} 31 |
32 | ); 33 | } 34 | 35 | export function CapsuleInterpreter({ 36 | result, 37 | }: { 38 | result: ExecutionResultInterpreter; 39 | }) { 40 | const { cellResults, stdout, stderr, runtimeError } = result; 41 | 42 | if (runtimeError) { 43 | const { name, value, tracebackRaw } = runtimeError; 44 | return ( 45 |
46 | 47 | 48 | 49 | {name}: {value} 50 | 51 | 52 | {tracebackRaw} 53 | 54 | 55 |
56 | ); 57 | } 58 | 59 | if (cellResults.length > 0) { 60 | const imgInBase64 = cellResults[0].png; 61 | return ( 62 |
63 |
64 | result 70 |
71 | 72 |
73 | ); 74 | } 75 | 76 | if (stdout.length > 0 || stderr.length > 0) { 77 | ; 78 | } 79 | 80 | return No output or logs; 81 | } 82 | -------------------------------------------------------------------------------- /components/capsule-preview.tsx: -------------------------------------------------------------------------------- 1 | import { ExecutionResult, ExecutionResultWeb } from "@/lib/types"; 2 | import { CapsuleWeb } from "./capsule-web"; 3 | 4 | export function CapsulePreview({ result }: { result: ExecutionResultWeb }) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /components/capsule-web.tsx: -------------------------------------------------------------------------------- 1 | import { ExecutionResult, ExecutionResultWeb } from "@/lib/types"; 2 | import { useState } from "react"; 3 | import { 4 | Tooltip, 5 | TooltipContent, 6 | TooltipProvider, 7 | TooltipTrigger, 8 | } from "./ui/tooltip"; 9 | import { Button } from "./ui/button"; 10 | import { RotateCcw, RotateCw } from "lucide-react"; 11 | import { CopyButton } from "./copy-button"; 12 | 13 | export function CapsuleWeb({ result }: { result: ExecutionResultWeb }) { 14 | const [iframeKey, setIframeKey] = useState(0); 15 | 16 | function refreshIframe() { 17 | setIframeKey((prevKey) => prevKey + 1); 18 | } 19 | 20 | return ( 21 |
22 |