feat: Add clickable file paths in AI chat responses

- Detect file paths with dots in directory names (e.g. next/styles/SignIn.module.css)
- Create new files when path ends with "(new file)"
- Use existing socket connection and file management system
This commit is contained in:
Akhileshrangani4 2024-12-01 18:52:02 -05:00
parent 89804c5906
commit d840aad3e9
6 changed files with 66 additions and 12 deletions

View File

@ -19,6 +19,7 @@ export default function ChatMessage({
editorRef, editorRef,
mergeDecorationsCollection, mergeDecorationsCollection,
setMergeDecorationsCollection, setMergeDecorationsCollection,
selectFile,
}: MessageProps) { }: MessageProps) {
// State for expanded message index // State for expanded message index
const [expandedMessageIndex, setExpandedMessageIndex] = useState< const [expandedMessageIndex, setExpandedMessageIndex] = useState<
@ -115,8 +116,9 @@ export default function ChatMessage({
activeFileContent, activeFileContent,
editorRef, editorRef,
handleApplyCode, handleApplyCode,
selectFile,
mergeDecorationsCollection, mergeDecorationsCollection,
setMergeDecorationsCollection setMergeDecorationsCollection,
) )
return ( return (

View File

@ -19,6 +19,7 @@ export default function AIChat({
files, files,
templateType, templateType,
handleApplyCode, handleApplyCode,
selectFile,
mergeDecorationsCollection, mergeDecorationsCollection,
setMergeDecorationsCollection, setMergeDecorationsCollection,
projectName, projectName,
@ -225,6 +226,7 @@ export default function AIChat({
editorRef={editorRef} editorRef={editorRef}
mergeDecorationsCollection={mergeDecorationsCollection} mergeDecorationsCollection={mergeDecorationsCollection}
setMergeDecorationsCollection={setMergeDecorationsCollection} setMergeDecorationsCollection={setMergeDecorationsCollection}
selectFile={selectFile}
/> />
))} ))}
{isLoading && <LoadingDots />} {isLoading && <LoadingDots />}

View File

@ -1,4 +1,4 @@
import { TFolder, TFile } from "@/lib/types" import { TFile, TFolder } from "@/lib/types"
import React from "react" import React from "react"
// Stringify content for chat message component // Stringify content for chat message component
@ -246,6 +246,8 @@ export const looksLikeCode = (text: string): boolean => {
// Add this new function after looksLikeCode function // Add this new function after looksLikeCode function
export const isFilePath = (text: string): boolean => { export const isFilePath = (text: string): boolean => {
// Match patterns like project/path/to/file.ext or project/path/to/file.ext (new file) // Match patterns like next/styles/SignIn.module.css or path/to/file.ext (new file)
return /^[a-zA-Z0-9_-]+\/[\w/-]+\.[a-zA-Z0-9]+(\s+\(new file\))?$/.test(text) const pattern =
/^(?:[a-zA-Z0-9_.-]+\/)*[a-zA-Z0-9_.-]+\.[a-zA-Z0-9]+(\s+\(new file\))?$/
return pattern.test(text)
} }

View File

@ -1,11 +1,13 @@
import { Check, CornerUpLeft, X, FileText } from "lucide-react" import { useSocket } from "@/context/SocketContext"
import { TTab } from "@/lib/types"
import { Check, CornerUpLeft, FileText, X } from "lucide-react"
import monaco from "monaco-editor" import monaco from "monaco-editor"
import { Components } from "react-markdown" import { Components } from "react-markdown"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter" import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism" import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"
import { Button } from "../../../ui/button" import { Button } from "../../../ui/button"
import ApplyButton from "../ApplyButton" import ApplyButton from "../ApplyButton"
import { stringifyContent, isFilePath } from "./chatUtils" import { isFilePath, stringifyContent } from "./chatUtils"
// Create markdown components for chat message component // Create markdown components for chat message component
export const createMarkdownComponents = ( export const createMarkdownComponents = (
@ -16,6 +18,7 @@ export const createMarkdownComponents = (
activeFileContent: string, activeFileContent: string,
editorRef: any, editorRef: any,
handleApplyCode: (mergedCode: string) => void, handleApplyCode: (mergedCode: string) => void,
selectFile: (tab: TTab) => void,
mergeDecorationsCollection?: monaco.editor.IEditorDecorationsCollection, mergeDecorationsCollection?: monaco.editor.IEditorDecorationsCollection,
setMergeDecorationsCollection?: (collection: undefined) => void setMergeDecorationsCollection?: (collection: undefined) => void
): Components => ({ ): Components => ({
@ -128,12 +131,54 @@ export const createMarkdownComponents = (
// Render markdown elements // Render markdown elements
p: ({ node, children, ...props }) => { p: ({ node, children, ...props }) => {
const content = stringifyContent(children) const content = stringifyContent(children)
const { socket } = useSocket()
if (isFilePath(content)) { if (isFilePath(content)) {
const isNewFile = content.endsWith("(new file)")
const filePath = (
isNewFile ? content.replace(" (new file)", "") : content
)
.split("/")
.filter((part, index) => index !== 0)
.join("/")
const handleFileClick = () => {
if (isNewFile) {
socket?.emit(
"createFile",
{
name: filePath,
},
(response: any) => {
if (response.success) {
const tab: TTab = {
id: filePath,
name: filePath.split("/").pop() || "",
saved: true,
type: "file",
}
selectFile(tab)
}
}
)
} else {
const tab: TTab = {
id: filePath,
name: filePath.split("/").pop() || "",
saved: true,
type: "file",
}
selectFile(tab)
}
}
return ( return (
<div className="relative group flex items-center gap-2 px-2 py-1 bg-secondary/50 rounded-md my-2 text-xs w-fit"> <div
<FileText className="h-4 w-4 text-muted-foreground" /> onClick={handleFileClick}
<span className="font-mono">{content}</span> className="group flex items-center gap-2 px-2 py-1 bg-secondary/50 rounded-md my-2 text-xs hover:bg-secondary cursor-pointer w-fit"
>
<FileText className="h-4 w-4" />
<span className="font-mono group-hover:underline">{content}</span>
</div> </div>
) )
} }

View File

@ -1,5 +1,5 @@
import { TemplateConfig } from "@/lib/templates" import { TemplateConfig } from "@/lib/templates"
import { TFile, TFolder } from "@/lib/types" import { TFile, TFolder, TTab } 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"
@ -60,6 +60,7 @@ export interface AIChatProps {
handleApplyCode: (mergedCode: string) => void handleApplyCode: (mergedCode: string) => void
mergeDecorationsCollection?: monaco.editor.IEditorDecorationsCollection mergeDecorationsCollection?: monaco.editor.IEditorDecorationsCollection
setMergeDecorationsCollection?: (collection: undefined) => void setMergeDecorationsCollection?: (collection: undefined) => void
selectFile: (tab: TTab) => void
} }
// Chat input props interface // Chat input props interface
@ -115,6 +116,7 @@ export interface MessageProps {
editorRef: any editorRef: any
mergeDecorationsCollection?: monaco.editor.IEditorDecorationsCollection mergeDecorationsCollection?: monaco.editor.IEditorDecorationsCollection
setMergeDecorationsCollection?: (collection: undefined) => void setMergeDecorationsCollection?: (collection: undefined) => void
selectFile: (tab: TTab) => void
} }
// Context tabs props interface // Context tabs props interface

View File

@ -1288,6 +1288,7 @@ export default function CodeEditor({
handleApplyCode={handleApplyCode} handleApplyCode={handleApplyCode}
mergeDecorationsCollection={mergeDecorationsCollection} mergeDecorationsCollection={mergeDecorationsCollection}
setMergeDecorationsCollection={setMergeDecorationsCollection} setMergeDecorationsCollection={setMergeDecorationsCollection}
selectFile={selectFile}
/> />
</ResizablePanel> </ResizablePanel>
</> </>