feat: add dynamic file structure context in AI chat

- Improved file structure formatting with tree-like visualization
- Added filtering for ignored files and folders
- Added scripts section to template context
- Fixed folder hierarchy display with proper indentation
- Maintains sorting with folders first, then files alphabetically
- Now uses actual project files instead of template structure

Example output:
├── app/
│   ├── api/
│   └── page.tsx
├── components/
└── package.json
This commit is contained in:
Akhileshrangani4 2024-11-30 15:53:17 -05:00
parent 1bb518b1af
commit f79115974c
3 changed files with 45 additions and 6 deletions

View File

@ -2,11 +2,44 @@ 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" import { templateConfigs } from "@/lib/templates"
import { TFile, TFolder } from "@/lib/types"
import { ignoredFiles, ignoredFolders } from "@/components/editor/AIChat/lib/ignored-paths"
const anthropic = new Anthropic({ const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!, apiKey: process.env.ANTHROPIC_API_KEY!,
}) })
// Format file structure for context
function formatFileStructure(items: (TFile | TFolder)[] | undefined, prefix = ""): string {
if (!items || !Array.isArray(items)) {
return "No files available"
}
// Sort items to show folders first, then files
const sortedItems = [...items].sort((a, b) => {
if (a.type === b.type) return a.name.localeCompare(b.name)
return a.type === "folder" ? -1 : 1
})
return sortedItems
.map((item) => {
if (item.type === "file" && !ignoredFiles.some(
(pattern) => item.name.endsWith(pattern.replace("*", "")) || item.name === pattern
)) {
return `${prefix}├── ${item.name}`
} else if (
item.type === "folder" &&
!ignoredFolders.some((folder) => folder === item.name)
) {
const folderContent = formatFileStructure(item.children, `${prefix}`)
return `${prefix}├── ${item.name}/\n${folderContent}`
}
return null
})
.filter(Boolean)
.join("\n")
}
export async function POST(request: Request) { export async function POST(request: Request) {
try { try {
const user = await currentUser() const user = await currentUser()
@ -60,6 +93,7 @@ export async function POST(request: Request) {
fileName, fileName,
line, line,
templateType, templateType,
files,
} = await request.json() } = await request.json()
// Get template configuration // Get template configuration
@ -70,16 +104,17 @@ export async function POST(request: Request) {
? ` ? `
Project Template: ${templateConfig.name} Project Template: ${templateConfig.name}
File Structure: Current File Structure:
${Object.entries(templateConfig.fileStructure) ${files ? formatFileStructure(files) : "No files available"}
.map(([path, info]) => `${path} - ${info.description}`)
.join("\n")}
Conventions: Conventions:
${templateConfig.conventions.join("\n")} ${templateConfig.conventions.join("\n")}
Dependencies: Dependencies:
${JSON.stringify(templateConfig.dependencies, null, 2)} ${JSON.stringify(templateConfig.dependencies, null, 2)}
Scripts:
${JSON.stringify(templateConfig.scripts, null, 2)}
` `
: "" : ""

View File

@ -148,7 +148,8 @@ export default function AIChat({
abortControllerRef, abortControllerRef,
activeFileContent, activeFileContent,
false, false,
templateType templateType,
files
) )
} }

View File

@ -1,3 +1,4 @@
import { TFolder, TFile } from "@/lib/types"
import React from "react" import React from "react"
// Stringify content for chat message component // Stringify content for chat message component
@ -91,7 +92,8 @@ export const handleSend = async (
abortControllerRef: React.MutableRefObject<AbortController | null>, abortControllerRef: React.MutableRefObject<AbortController | null>,
activeFileContent: string, activeFileContent: string,
isEditMode: boolean = false, isEditMode: boolean = false,
templateType: string templateType: string,
files: (TFile | TFolder)[]
) => { ) => {
// 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
@ -142,6 +144,7 @@ export const handleSend = async (
activeFileContent: activeFileContent, activeFileContent: activeFileContent,
isEditMode: isEditMode, isEditMode: isEditMode,
templateType: templateType, templateType: templateType,
files: files,
}), }),
signal: abortControllerRef.current.signal, signal: abortControllerRef.current.signal,
}) })