2024-12-01 18:52:02 -05:00
|
|
|
import { useSocket } from "@/context/SocketContext"
|
|
|
|
import { TTab } from "@/lib/types"
|
|
|
|
import { Check, CornerUpLeft, FileText, X } from "lucide-react"
|
2024-11-30 20:56:56 -05:00
|
|
|
import monaco from "monaco-editor"
|
2024-11-04 14:21:13 -05:00
|
|
|
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"
|
2024-11-30 20:56:56 -05:00
|
|
|
import ApplyButton from "../ApplyButton"
|
2024-12-01 18:52:02 -05:00
|
|
|
import { isFilePath, stringifyContent } from "./chatUtils"
|
2024-11-04 14:21:13 -05:00
|
|
|
|
2024-11-17 12:35:56 -05:00
|
|
|
// Create markdown components for chat message component
|
2024-11-04 14:21:13 -05:00
|
|
|
export const createMarkdownComponents = (
|
|
|
|
renderCopyButton: (text: any) => JSX.Element,
|
|
|
|
renderMarkdownElement: (props: any) => JSX.Element,
|
2024-11-30 20:56:56 -05:00
|
|
|
askAboutCode: (code: any) => void,
|
|
|
|
activeFileName: string,
|
|
|
|
activeFileContent: string,
|
|
|
|
editorRef: any,
|
|
|
|
handleApplyCode: (mergedCode: string) => void,
|
2024-12-01 18:52:02 -05:00
|
|
|
selectFile: (tab: TTab) => void,
|
2024-11-30 20:56:56 -05:00
|
|
|
mergeDecorationsCollection?: monaco.editor.IEditorDecorationsCollection,
|
|
|
|
setMergeDecorationsCollection?: (collection: undefined) => void
|
2024-11-04 14:21:13 -05:00
|
|
|
): Components => ({
|
2024-11-17 12:35:56 -05:00
|
|
|
code: ({
|
|
|
|
node,
|
|
|
|
className,
|
|
|
|
children,
|
|
|
|
...props
|
|
|
|
}: {
|
|
|
|
node?: import("hast").Element
|
|
|
|
className?: string
|
|
|
|
children?: React.ReactNode
|
|
|
|
[key: string]: any
|
2024-11-04 14:21:13 -05:00
|
|
|
}) => {
|
|
|
|
const match = /language-(\w+)/.exec(className || "")
|
|
|
|
|
|
|
|
return match ? (
|
2024-11-30 01:33:54 -05:00
|
|
|
<div className="relative border border-input rounded-md mt-8 my-2 translate-y-[-1rem]">
|
|
|
|
<div className="absolute top-0 left-0 px-2 py-1 text-xs font-semibold text-gray-200 rounded-tl">
|
2024-11-04 14:21:13 -05:00
|
|
|
{match[1]}
|
|
|
|
</div>
|
2024-11-30 01:33:54 -05:00
|
|
|
<div className="sticky top-0 right-0 flex justify-end z-10">
|
|
|
|
<div className="flex border border-input shadow-lg bg-background rounded-md">
|
|
|
|
{renderCopyButton(children)}
|
|
|
|
<div className="w-px bg-input"></div>
|
2024-11-30 20:56:56 -05:00
|
|
|
{!mergeDecorationsCollection ? (
|
|
|
|
<ApplyButton
|
|
|
|
code={String(children)}
|
|
|
|
activeFileName={activeFileName}
|
|
|
|
activeFileContent={activeFileContent}
|
|
|
|
editorRef={editorRef}
|
|
|
|
onApply={handleApplyCode}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
<Button
|
|
|
|
onClick={() => {
|
|
|
|
if (
|
|
|
|
setMergeDecorationsCollection &&
|
|
|
|
mergeDecorationsCollection &&
|
|
|
|
editorRef?.current
|
|
|
|
) {
|
|
|
|
mergeDecorationsCollection.clear()
|
|
|
|
setMergeDecorationsCollection(undefined)
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
size="sm"
|
|
|
|
variant="ghost"
|
|
|
|
className="p-1 h-6"
|
|
|
|
title="Accept Changes"
|
|
|
|
>
|
|
|
|
<Check className="w-4 h-4 text-green-500" />
|
|
|
|
</Button>
|
|
|
|
<div className="w-px bg-input"></div>
|
|
|
|
<Button
|
|
|
|
onClick={() => {
|
|
|
|
if (
|
|
|
|
setMergeDecorationsCollection &&
|
|
|
|
mergeDecorationsCollection &&
|
|
|
|
editorRef?.current
|
|
|
|
) {
|
|
|
|
editorRef.current.getModel()?.setValue(activeFileContent)
|
|
|
|
mergeDecorationsCollection.clear()
|
|
|
|
setMergeDecorationsCollection(undefined)
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
size="sm"
|
|
|
|
variant="ghost"
|
|
|
|
className="p-1 h-6"
|
|
|
|
title="Discard Changes"
|
|
|
|
>
|
|
|
|
<X className="w-4 h-4 text-red-500" />
|
|
|
|
</Button>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
<div className="w-px bg-input"></div>
|
2024-11-30 01:33:54 -05:00
|
|
|
<Button
|
|
|
|
onClick={(e) => {
|
|
|
|
e.preventDefault()
|
|
|
|
e.stopPropagation()
|
|
|
|
askAboutCode(children)
|
|
|
|
}}
|
|
|
|
size="sm"
|
|
|
|
variant="ghost"
|
|
|
|
className="p-1 h-6"
|
|
|
|
>
|
|
|
|
<CornerUpLeft className="w-4 h-4" />
|
|
|
|
</Button>
|
|
|
|
</div>
|
2024-11-04 14:21:13 -05:00
|
|
|
</div>
|
2024-11-30 01:33:54 -05:00
|
|
|
<SyntaxHighlighter
|
|
|
|
style={vscDarkPlus as any}
|
|
|
|
language={match[1]}
|
|
|
|
PreTag="div"
|
|
|
|
customStyle={{
|
|
|
|
margin: 0,
|
|
|
|
padding: "0.5rem",
|
|
|
|
fontSize: "0.875rem",
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{stringifyContent(children)}
|
|
|
|
</SyntaxHighlighter>
|
2024-11-04 14:21:13 -05:00
|
|
|
</div>
|
|
|
|
) : (
|
2024-11-17 12:35:56 -05:00
|
|
|
<code className={className} {...props}>
|
|
|
|
{children}
|
|
|
|
</code>
|
2024-11-04 14:21:13 -05:00
|
|
|
)
|
|
|
|
},
|
2024-11-17 12:35:56 -05:00
|
|
|
// Render markdown elements
|
2024-12-01 14:28:48 -05:00
|
|
|
p: ({ node, children, ...props }) => {
|
|
|
|
const content = stringifyContent(children)
|
2024-12-01 18:52:02 -05:00
|
|
|
const { socket } = useSocket()
|
|
|
|
|
2024-12-01 14:28:48 -05:00
|
|
|
if (isFilePath(content)) {
|
2024-12-01 18:52:02 -05:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-01 14:28:48 -05:00
|
|
|
return (
|
2024-12-01 18:52:02 -05:00
|
|
|
<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>
|
2024-12-01 14:28:48 -05:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
2024-12-01 18:52:02 -05:00
|
|
|
|
2024-12-01 14:28:48 -05:00
|
|
|
return renderMarkdownElement({ node, children, ...props })
|
|
|
|
},
|
2024-11-17 12:35:56 -05:00
|
|
|
h1: ({ node, children, ...props }) =>
|
|
|
|
renderMarkdownElement({ node, children, ...props }),
|
|
|
|
h2: ({ node, children, ...props }) =>
|
|
|
|
renderMarkdownElement({ node, children, ...props }),
|
|
|
|
h3: ({ node, children, ...props }) =>
|
|
|
|
renderMarkdownElement({ node, children, ...props }),
|
|
|
|
h4: ({ node, children, ...props }) =>
|
|
|
|
renderMarkdownElement({ node, children, ...props }),
|
|
|
|
h5: ({ node, children, ...props }) =>
|
|
|
|
renderMarkdownElement({ node, children, ...props }),
|
|
|
|
h6: ({ node, children, ...props }) =>
|
|
|
|
renderMarkdownElement({ node, children, ...props }),
|
2024-11-04 14:21:13 -05:00
|
|
|
ul: (props) => (
|
2024-11-17 12:35:56 -05:00
|
|
|
<ul className="list-disc pl-6 mb-4 space-y-2">{props.children}</ul>
|
2024-11-04 14:21:13 -05:00
|
|
|
),
|
|
|
|
ol: (props) => (
|
2024-11-17 12:35:56 -05:00
|
|
|
<ol className="list-decimal pl-6 mb-4 space-y-2">{props.children}</ol>
|
2024-11-04 14:21:13 -05:00
|
|
|
),
|
|
|
|
})
|