feat: integrate template awareness into AI assistant

- Add template configurations with file structures and conventions
- Update AI route handler to include template context in system messages
- Pass template type through AIChat component
- Add template-specific run commands
- Enhance AI responses with project structure knowledge
- Move hardcoded run commands from navbar/run.tsx to templates/index.ts

This improves the AI's understanding of different project templates (React, Next.js, Streamlit, Vanilla JS) and enables more contextual assistance based on the project type.
This commit is contained in:
Akhileshrangani4 2024-11-24 00:22:10 -05:00
parent d87f818241
commit 46c715b1eb
7 changed files with 290 additions and 9 deletions

View File

@ -1,6 +1,7 @@
import { currentUser } from "@clerk/nextjs" import { currentUser } from "@clerk/nextjs"
import { Anthropic } from "@anthropic-ai/sdk" import { Anthropic } from "@anthropic-ai/sdk"
import { TIERS } from "@/lib/tiers" import { TIERS } from "@/lib/tiers"
import { templateConfigs } from "@/lib/templates"
const anthropic = new Anthropic({ const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!, apiKey: process.env.ANTHROPIC_API_KEY!,
@ -56,13 +57,35 @@ export async function POST(request: Request) {
activeFileContent, activeFileContent,
isEditMode, isEditMode,
fileName, fileName,
line line,
templateType
} = await request.json() } = 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 // Create system message based on mode
let systemMessage let systemMessage
if (isEditMode) { 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} File: ${fileName}
Line: ${line} Line: ${line}
@ -77,7 +100,7 @@ Instructions: ${messages[0].content}
Respond only with the modified code that can directly replace the existing code.` Respond only with the modified code that can directly replace the existing code.`
} else { } 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 \`\`\`python
print("Hello, World!") 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. 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` : ""} ${context ? `Context:\n${context}\n` : ""}
${activeFileContent ? `Active File Content:\n${activeFileContent}\n` : ""}` ${activeFileContent ? `Active File Content:\n${activeFileContent}\n` : ""}`
} }

View File

@ -17,6 +17,7 @@ export default function AIChat({
editorRef, editorRef,
lastCopiedRangeRef, lastCopiedRangeRef,
files, files,
templateType,
}: AIChatProps) { }: AIChatProps) {
// Initialize socket and messages // Initialize socket and messages
const { socket } = useSocket() const { socket } = useSocket()
@ -143,7 +144,9 @@ export default function AIChat({
setIsGenerating, setIsGenerating,
setIsLoading, setIsLoading,
abortControllerRef, abortControllerRef,
activeFileContent activeFileContent,
false,
templateType
) )
// Clear context tabs after sending // Clear context tabs after sending
setContextTabs([]) setContextTabs([])

View File

@ -90,7 +90,8 @@ export const handleSend = async (
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>, setIsLoading: React.Dispatch<React.SetStateAction<boolean>>,
abortControllerRef: React.MutableRefObject<AbortController | null>, abortControllerRef: React.MutableRefObject<AbortController | null>,
activeFileContent: string, activeFileContent: string,
isEditMode: boolean = false isEditMode: boolean = false,
templateType: string
) => { ) => {
// Return if input is empty and context is null // Return if input is empty and context is null
if (input.trim() === "" && !context) return if (input.trim() === "" && !context) return
@ -141,6 +142,7 @@ export const handleSend = async (
context: context || undefined, context: context || undefined,
activeFileContent: activeFileContent, activeFileContent: activeFileContent,
isEditMode: isEditMode, isEditMode: isEditMode,
templateType: templateType,
}), }),
signal: abortControllerRef.current.signal, signal: abortControllerRef.current.signal,
} }

View File

@ -1,3 +1,4 @@
import { TemplateConfig } from "@/lib/templates"
import { TFile, TFolder } from "@/lib/types" import { TFile, TFolder } from "@/lib/types"
import * as monaco from "monaco-editor" import * as monaco from "monaco-editor"
import { Socket } from "socket.io-client" import { Socket } from "socket.io-client"
@ -53,6 +54,8 @@ export interface AIChatProps {
endLine: number endLine: number
} | null> } | null>
files: (TFile | TFolder)[] files: (TFile | TFolder)[]
templateType: string
templateConfig?: TemplateConfig
} }
// Chat input props interface // Chat input props interface

View File

@ -1233,6 +1233,7 @@ export default function CodeEditor({
editorRef={{ current: editorRef }} editorRef={{ current: editorRef }}
lastCopiedRangeRef={lastCopiedRangeRef} lastCopiedRangeRef={lastCopiedRangeRef}
files={files} files={files}
templateType={sandboxData.type}
/> />
</ResizablePanel> </ResizablePanel>
</> </>

View File

@ -7,6 +7,7 @@ import { Sandbox } from "@/lib/types"
import { Play, StopCircle } from "lucide-react" import { Play, StopCircle } from "lucide-react"
import { useEffect, useRef } from "react" import { useEffect, useRef } from "react"
import { toast } from "sonner" import { toast } from "sonner"
import { templateConfigs } from "@/lib/templates"
export default function RunButtonModal({ export default function RunButtonModal({
isRunning, isRunning,
@ -42,10 +43,7 @@ export default function RunButtonModal({
setIsPreviewCollapsed(true) setIsPreviewCollapsed(true)
previewPanelRef.current?.collapse() previewPanelRef.current?.collapse()
} else if (!isRunning && terminals.length < 4) { } else if (!isRunning && terminals.length < 4) {
const command = const command = templateConfigs[sandboxData.type]?.runCommand || "npm run dev"
sandboxData.type === "streamlit"
? "./venv/bin/streamlit run main.py --server.runOnSave true"
: "npm run dev"
try { try {
// Create a new terminal with the appropriate command // Create a new terminal with the appropriate command

View File

@ -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"
}
}
}