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:
parent
0ae89341d2
commit
4e1c5cac27
@ -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 (
|
||||||
|
@ -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 />}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -1288,6 +1288,7 @@ export default function CodeEditor({
|
|||||||
handleApplyCode={handleApplyCode}
|
handleApplyCode={handleApplyCode}
|
||||||
mergeDecorationsCollection={mergeDecorationsCollection}
|
mergeDecorationsCollection={mergeDecorationsCollection}
|
||||||
setMergeDecorationsCollection={setMergeDecorationsCollection}
|
setMergeDecorationsCollection={setMergeDecorationsCollection}
|
||||||
|
selectFile={selectFile}
|
||||||
/>
|
/>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</>
|
</>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user