diff --git a/frontend/app/api/ai/route.ts b/frontend/app/api/ai/route.ts index 9df0801..6ede340 100644 --- a/frontend/app/api/ai/route.ts +++ b/frontend/app/api/ai/route.ts @@ -1,16 +1,22 @@ -import { currentUser } from "@clerk/nextjs" -import { Anthropic } from "@anthropic-ai/sdk" -import { TIERS } from "@/lib/tiers" +import { + ignoredFiles, + ignoredFolders, +} from "@/components/editor/AIChat/lib/ignored-paths" import { templateConfigs } from "@/lib/templates" +import { TIERS } from "@/lib/tiers" import { TFile, TFolder } from "@/lib/types" -import { ignoredFiles, ignoredFolders } from "@/components/editor/AIChat/lib/ignored-paths" +import { Anthropic } from "@anthropic-ai/sdk" +import { currentUser } from "@clerk/nextjs" const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, }) -// Format file structure for context -function formatFileStructure(items: (TFile | TFolder)[] | undefined, prefix = ""): string { +// Format file structure for context +function formatFileStructure( + items: (TFile | TFolder)[] | undefined, + prefix = "" +): string { if (!items || !Array.isArray(items)) { return "No files available" } @@ -23,15 +29,23 @@ function formatFileStructure(items: (TFile | TFolder)[] | undefined, prefix = "" return sortedItems .map((item) => { - if (item.type === "file" && !ignoredFiles.some( - (pattern) => item.name.endsWith(pattern.replace("*", "")) || item.name === pattern - )) { + if ( + item.type === "file" && + !ignoredFiles.some( + (pattern) => + item.name.endsWith(pattern.replace("*", "")) || + item.name === pattern + ) + ) { return `${prefix}├── ${item.name}` } else if ( - item.type === "folder" && + item.type === "folder" && !ignoredFolders.some((folder) => folder === item.name) ) { - const folderContent = formatFileStructure(item.children, `${prefix}│ `) + const folderContent = formatFileStructure( + item.children, + `${prefix}│ ` + ) return `${prefix}├── ${item.name}/\n${folderContent}` } return null @@ -94,6 +108,7 @@ export async function POST(request: Request) { line, templateType, files, + projectName, } = await request.json() // Get template configuration @@ -138,13 +153,23 @@ 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 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: + systemMessage = `You are an intelligent programming assistant for a ${templateType} project. Please respond to the following request concisely. When providing code: - \`\`\`python - print("Hello, World!") - \`\`\` - - Provide a clear and concise explanation along with any code snippets. Keep your response brief and to the point. +1. Format it using triple backticks (\`\`\`) with the appropriate language identifier. +2. Always specify the complete file path in the format: + ${projectName}/filepath/to/file.ext + +3. If creating a new file, specify the path as: + ${projectName}/filepath/to/file.ext (new file) + +4. Format your code blocks as: + +${projectName}/filepath/to/file.ext +\`\`\`language +code here +\`\`\` + +If multiple files are involved, repeat the format for each file. 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} diff --git a/frontend/components/editor/AIChat/index.tsx b/frontend/components/editor/AIChat/index.tsx index d9d57d4..5d3ff69 100644 --- a/frontend/components/editor/AIChat/index.tsx +++ b/frontend/components/editor/AIChat/index.tsx @@ -21,6 +21,7 @@ export default function AIChat({ handleApplyCode, mergeDecorationsCollection, setMergeDecorationsCollection, + projectName, }: AIChatProps) { // Initialize socket and messages const { socket } = useSocket() @@ -152,7 +153,8 @@ export default function AIChat({ activeFileContent, false, templateType, - files + files, + projectName ) } diff --git a/frontend/components/editor/AIChat/lib/chatUtils.ts b/frontend/components/editor/AIChat/lib/chatUtils.ts index 770fb84..1e3e956 100644 --- a/frontend/components/editor/AIChat/lib/chatUtils.ts +++ b/frontend/components/editor/AIChat/lib/chatUtils.ts @@ -93,7 +93,8 @@ export const handleSend = async ( activeFileContent: string, isEditMode: boolean = false, templateType: string, - files: (TFile | TFolder)[] + files: (TFile | TFolder)[], + projectName: string ) => { // Return if input is empty and context is null if (input.trim() === "" && !context) return @@ -144,7 +145,8 @@ export const handleSend = async ( activeFileContent: activeFileContent, isEditMode: isEditMode, templateType: templateType, - files: files, + files: files, + projectName: projectName, }), signal: abortControllerRef.current.signal, }) @@ -241,3 +243,9 @@ export const looksLikeCode = (text: string): boolean => { return codeIndicators.some((pattern) => pattern.test(text)) } + +// Add this new function after looksLikeCode function +export const isFilePath = (text: string): boolean => { + // Match patterns like project/path/to/file.ext or project/path/to/file.ext (new file) + return /^[a-zA-Z0-9_-]+\/[\w/-]+\.[a-zA-Z0-9]+(\s+\(new file\))?$/.test(text) +} diff --git a/frontend/components/editor/AIChat/lib/markdownComponents.tsx b/frontend/components/editor/AIChat/lib/markdownComponents.tsx index a0c5554..0a6dcd3 100644 --- a/frontend/components/editor/AIChat/lib/markdownComponents.tsx +++ b/frontend/components/editor/AIChat/lib/markdownComponents.tsx @@ -1,11 +1,11 @@ -import { Check, CornerUpLeft, X } from "lucide-react" +import { Check, CornerUpLeft, X, FileText } from "lucide-react" import monaco from "monaco-editor" import { Components } from "react-markdown" import { Prism as SyntaxHighlighter } from "react-syntax-highlighter" import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism" import { Button } from "../../../ui/button" import ApplyButton from "../ApplyButton" -import { stringifyContent } from "./chatUtils" +import { stringifyContent, isFilePath } from "./chatUtils" // Create markdown components for chat message component export const createMarkdownComponents = ( @@ -126,8 +126,20 @@ export const createMarkdownComponents = ( ) }, // Render markdown elements - p: ({ node, children, ...props }) => - renderMarkdownElement({ node, children, ...props }), + p: ({ node, children, ...props }) => { + const content = stringifyContent(children) + + if (isFilePath(content)) { + return ( +