diff --git a/frontend/app/api/ai/route.ts b/frontend/app/api/ai/route.ts index d894dab..7a95a8b 100644 --- a/frontend/app/api/ai/route.ts +++ b/frontend/app/api/ai/route.ts @@ -1,6 +1,7 @@ import { currentUser } from "@clerk/nextjs" import { Anthropic } from "@anthropic-ai/sdk" import { TIERS } from "@/lib/tiers" +import { templateConfigs } from "@/lib/templates" const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, @@ -56,13 +57,35 @@ export async function POST(request: Request) { activeFileContent, isEditMode, fileName, - line + line, + templateType } = await request.json() + // Get template configuration + const templateConfig = templateConfigs[templateType] + + // Create template context + const templateContext = templateConfig ? ` +Project Template: ${templateConfig.name} + +File Structure: +${Object.entries(templateConfig.fileStructure) + .map(([path, info]) => `${path} - ${info.description}`) + .join('\n')} + +Conventions: +${templateConfig.conventions.join('\n')} + +Dependencies: +${JSON.stringify(templateConfig.dependencies, null, 2)} +` : '' + // Create system message based on mode let systemMessage if (isEditMode) { - systemMessage = `You are an AI code editor. Your task is to modify the given code based on the user's instructions. Only output the modified code, without any explanations or markdown formatting. The code should be a direct replacement for the existing code. If there is no code to modify, refer to the active file content and only output the code that is relevant to the user's instructions. + systemMessage = `You are an AI code editor working in a ${templateType} project. Your task is to modify the given code based on the user's instructions. Only output the modified code, without any explanations or markdown formatting. The code should be a direct replacement for the existing code. If there is no code to modify, refer to the active file content and only output the code that is relevant to the user's instructions. + +${templateContext} File: ${fileName} Line: ${line} @@ -77,7 +100,7 @@ Instructions: ${messages[0].content} Respond only with the modified code that can directly replace the existing code.` } else { - systemMessage = `You are an intelligent programming assistant. Please respond to the following request concisely. If your response includes code, please format it using triple backticks (\`\`\`) with the appropriate language identifier. For example: + systemMessage = `You are an intelligent programming assistant for a ${templateType} project. Please respond to the following request concisely. If your response includes code, please format it using triple backticks (\`\`\`) with the appropriate language identifier. For example: \`\`\`python print("Hello, World!") @@ -85,6 +108,9 @@ Respond only with the modified code that can directly replace the existing code. Provide a clear and concise explanation along with any code snippets. Keep your response brief and to the point. +This is the project template: +${templateContext} + ${context ? `Context:\n${context}\n` : ""} ${activeFileContent ? `Active File Content:\n${activeFileContent}\n` : ""}` } diff --git a/frontend/components/editor/AIChat/index.tsx b/frontend/components/editor/AIChat/index.tsx index 20543a6..bb6db27 100644 --- a/frontend/components/editor/AIChat/index.tsx +++ b/frontend/components/editor/AIChat/index.tsx @@ -17,6 +17,7 @@ export default function AIChat({ editorRef, lastCopiedRangeRef, files, + templateType, }: AIChatProps) { // Initialize socket and messages const { socket } = useSocket() @@ -143,7 +144,9 @@ export default function AIChat({ setIsGenerating, setIsLoading, abortControllerRef, - activeFileContent + activeFileContent, + false, + templateType ) // Clear context tabs after sending setContextTabs([]) diff --git a/frontend/components/editor/AIChat/lib/chatUtils.ts b/frontend/components/editor/AIChat/lib/chatUtils.ts index 3838967..d453783 100644 --- a/frontend/components/editor/AIChat/lib/chatUtils.ts +++ b/frontend/components/editor/AIChat/lib/chatUtils.ts @@ -90,7 +90,8 @@ export const handleSend = async ( setIsLoading: React.Dispatch>, abortControllerRef: React.MutableRefObject, activeFileContent: string, - isEditMode: boolean = false + isEditMode: boolean = false, + templateType: string ) => { // Return if input is empty and context is null if (input.trim() === "" && !context) return @@ -141,6 +142,7 @@ export const handleSend = async ( context: context || undefined, activeFileContent: activeFileContent, isEditMode: isEditMode, + templateType: templateType, }), signal: abortControllerRef.current.signal, } diff --git a/frontend/components/editor/AIChat/types/index.ts b/frontend/components/editor/AIChat/types/index.ts index bc69ac7..d8c1373 100644 --- a/frontend/components/editor/AIChat/types/index.ts +++ b/frontend/components/editor/AIChat/types/index.ts @@ -1,3 +1,4 @@ +import { TemplateConfig } from "@/lib/templates" import { TFile, TFolder } from "@/lib/types" import * as monaco from "monaco-editor" import { Socket } from "socket.io-client" @@ -53,6 +54,8 @@ export interface AIChatProps { endLine: number } | null> files: (TFile | TFolder)[] + templateType: string + templateConfig?: TemplateConfig } // Chat input props interface diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx index 4f6b047..b8fd2a9 100644 --- a/frontend/components/editor/index.tsx +++ b/frontend/components/editor/index.tsx @@ -1233,6 +1233,7 @@ export default function CodeEditor({ editorRef={{ current: editorRef }} lastCopiedRangeRef={lastCopiedRangeRef} files={files} + templateType={sandboxData.type} /> diff --git a/frontend/components/editor/navbar/run.tsx b/frontend/components/editor/navbar/run.tsx index c6020aa..cb6b8f7 100644 --- a/frontend/components/editor/navbar/run.tsx +++ b/frontend/components/editor/navbar/run.tsx @@ -7,6 +7,7 @@ import { Sandbox } from "@/lib/types" import { Play, StopCircle } from "lucide-react" import { useEffect, useRef } from "react" import { toast } from "sonner" +import { templateConfigs } from "@/lib/templates" export default function RunButtonModal({ isRunning, @@ -42,10 +43,7 @@ export default function RunButtonModal({ setIsPreviewCollapsed(true) previewPanelRef.current?.collapse() } else if (!isRunning && terminals.length < 4) { - const command = - sandboxData.type === "streamlit" - ? "./venv/bin/streamlit run main.py --server.runOnSave true" - : "npm run dev" + const command = templateConfigs[sandboxData.type]?.runCommand || "npm run dev" try { // Create a new terminal with the appropriate command diff --git a/frontend/lib/templates/index.ts b/frontend/lib/templates/index.ts new file mode 100644 index 0000000..1b38d9e --- /dev/null +++ b/frontend/lib/templates/index.ts @@ -0,0 +1,248 @@ +export interface TemplateConfig { + id: string + name: string, + runCommand: string, + fileStructure: { + [key: string]: { + purpose: string + description: string + } + } + conventions: string[] + dependencies?: { + [key: string]: string + } + scripts?: { + [key: string]: string + } + } + + export const templateConfigs: { [key: string]: TemplateConfig } = { + reactjs: { + id: "reactjs", + name: "React", + runCommand: "npm run dev", + fileStructure: { + "src/": { + purpose: "source", + description: "Contains all React components and application logic" + }, + "src/components/": { + purpose: "components", + description: "Reusable React components" + }, + "src/lib/": { + purpose: "utilities", + description: "Utility functions and shared code" + }, + "src/App.tsx": { + purpose: "entry", + description: "Main application component" + }, + "src/index.tsx": { + purpose: "entry", + description: "Application entry point" + }, + "src/index.css": { + purpose: "styles", + description: "Global CSS styles" + }, + "public/": { + purpose: "static", + description: "Static assets and index.html" + }, + "tsconfig.json": { + purpose: "config", + description: "TypeScript configuration" + }, + "vite.config.ts": { + purpose: "config", + description: "Vite bundler configuration" + }, + "package.json": { + purpose: "config", + description: "Project dependencies and scripts" + } + }, + conventions: [ + "Use functional components with hooks", + "Follow React naming conventions (PascalCase for components)", + "Keep components small and focused", + "Use TypeScript for type safety" + ], + dependencies: { + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-slot": "^1.1.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "lucide-react": "^0.441.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.5.2", + "tailwindcss-animate": "^1.0.7" + }, + scripts: { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + } + }, + // Next.js template config + nextjs: { + id: "nextjs", + name: "NextJS", + runCommand: "npm run dev", + fileStructure: { + "pages/": { + purpose: "routing", + description: "Page components and API routes" + }, + "pages/api/": { + purpose: "api", + description: "API route handlers" + }, + "pages/_app.tsx": { + purpose: "entry", + description: "Application wrapper component" + }, + "pages/index.tsx": { + purpose: "page", + description: "Homepage component" + }, + "public/": { + purpose: "static", + description: "Static assets and files" + }, + "styles/": { + purpose: "styles", + description: "CSS modules and global styles" + }, + "styles/globals.css": { + purpose: "styles", + description: "Global CSS styles" + }, + "styles/Home.module.css": { + purpose: "styles", + description: "Homepage-specific styles" + }, + "next.config.js": { + purpose: "config", + description: "Next.js configuration" + }, + "next-env.d.ts": { + purpose: "types", + description: "Next.js TypeScript declarations" + }, + "tsconfig.json": { + purpose: "config", + description: "TypeScript configuration" + }, + "package.json": { + purpose: "config", + description: "Project dependencies and scripts" + } + }, + conventions: [ + "Use file-system based routing", + "Keep API routes in pages/api", + "Use CSS Modules for component styles", + "Follow Next.js data fetching patterns" + ], + dependencies: { + "next": "^14.1.0", + "react": "^18.2.0", + "react-dom": "18.2.0", + "tailwindcss": "^3.4.1" + }, + scripts: { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + } + }, + // Streamlit template config + streamlit: { + id: "streamlit", + name: "Streamlit", + runCommand: "./venv/bin/streamlit run main.py --server.runOnSave true", + fileStructure: { + "main.py": { + purpose: "entry", + description: "Main Streamlit application file" + }, + "requirements.txt": { + purpose: "dependencies", + description: "Python package dependencies" + }, + "Procfile": { + purpose: "deployment", + description: "Deployment configuration for hosting platforms" + }, + "venv/": { + purpose: "environment", + description: "Python virtual environment directory" + } + }, + conventions: [ + "Use Streamlit components for UI", + "Follow PEP 8 style guide", + "Keep dependencies in requirements.txt", + "Use virtual environment for isolation" + ], + dependencies: { + "streamlit": "^1.40.0", + "altair": "^5.5.0" + }, + scripts: { + "start": "streamlit run main.py", + "dev": "./venv/bin/streamlit run main.py --server.runOnSave true" + } + }, + // HTML template config + vanillajs: { + id: "vanillajs", + name: "HTML/JS", + runCommand: "npm run dev", + fileStructure: { + "index.html": { + purpose: "entry", + description: "Main HTML entry point" + }, + "style.css": { + purpose: "styles", + description: "Global CSS styles" + }, + "script.js": { + purpose: "scripts", + description: "JavaScript application logic" + }, + "package.json": { + purpose: "config", + description: "Project dependencies and scripts" + }, + "package-lock.json": { + purpose: "config", + description: "Locked dependency versions" + }, + "vite.config.js": { + purpose: "config", + description: "Vite bundler configuration" + } + }, + conventions: [ + "Use semantic HTML elements", + "Keep CSS modular and organized", + "Write clean, modular JavaScript", + "Follow modern ES6+ practices" + ], + dependencies: { + "vite": "^5.0.12" + }, + scripts: { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + } + } +}