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

View File

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

View File

@ -1,4 +1,4 @@
import { TFolder, TFile } from "@/lib/types"
import { TFile, TFolder } from "@/lib/types"
import React from "react"
// Stringify content for chat message component
@ -246,6 +246,8 @@ export const looksLikeCode = (text: string): boolean => {
// 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)
// Match patterns like next/styles/SignIn.module.css or path/to/file.ext (new file)
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 { 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, isFilePath } from "./chatUtils"
import { isFilePath, stringifyContent } from "./chatUtils"
// Create markdown components for chat message component
export const createMarkdownComponents = (
@ -16,6 +18,7 @@ export const createMarkdownComponents = (
activeFileContent: string,
editorRef: any,
handleApplyCode: (mergedCode: string) => void,
selectFile: (tab: TTab) => void,
mergeDecorationsCollection?: monaco.editor.IEditorDecorationsCollection,
setMergeDecorationsCollection?: (collection: undefined) => void
): Components => ({
@ -128,12 +131,54 @@ export const createMarkdownComponents = (
// Render markdown elements
p: ({ node, children, ...props }) => {
const content = stringifyContent(children)
const { socket } = useSocket()
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 (
<div className="relative group flex items-center gap-2 px-2 py-1 bg-secondary/50 rounded-md my-2 text-xs w-fit">
<FileText className="h-4 w-4 text-muted-foreground" />
<span className="font-mono">{content}</span>
<div
onClick={handleFileClick}
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>
)
}

View File

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

View File

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