121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
import { html } from "@codemirror/lang-html";
|
|
import { javascript } from "@codemirror/lang-javascript";
|
|
import { json } from "@codemirror/lang-json";
|
|
import { python } from "@codemirror/lang-python";
|
|
import type { Extension } from "@codemirror/state";
|
|
import { basicLightInit } from "@uiw/codemirror-theme-basic";
|
|
import { monokaiInit } from "@uiw/codemirror-theme-monokai";
|
|
import CodeMirror, { type ViewUpdate } from "@uiw/react-codemirror";
|
|
import { useTheme } from "next-themes";
|
|
import { useCallback, useMemo } from "react";
|
|
|
|
import type {
|
|
CanvasArtifactCode,
|
|
ProgrammingLanguageOptions,
|
|
} from "../../types/artifact";
|
|
import { useCanvasSelection } from "../../hooks/useCanvasSelection";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Themes — match code-editor.tsx defaults
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const darkTheme = monokaiInit({
|
|
settings: {
|
|
background: "transparent",
|
|
gutterBackground: "transparent",
|
|
gutterForeground: "#555",
|
|
gutterActiveForeground: "#fff",
|
|
fontSize: "var(--text-sm)",
|
|
},
|
|
});
|
|
|
|
const lightTheme = basicLightInit({
|
|
settings: {
|
|
background: "transparent",
|
|
fontSize: "var(--text-sm)",
|
|
},
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Language detection
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function getLanguageExtension(
|
|
lang: ProgrammingLanguageOptions,
|
|
): Extension | null {
|
|
switch (lang) {
|
|
case "typescript":
|
|
return javascript({ typescript: true, jsx: true });
|
|
case "javascript":
|
|
return javascript({ jsx: true });
|
|
case "python":
|
|
return python();
|
|
case "html":
|
|
return html();
|
|
case "json":
|
|
return json();
|
|
// cpp, java, rust, sql, xml, clojure, csharp, php → plain text fallback
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Component
|
|
// ---------------------------------------------------------------------------
|
|
|
|
interface CodeArtifactRendererProps {
|
|
content: CanvasArtifactCode;
|
|
isStreaming?: boolean;
|
|
}
|
|
|
|
export function CodeArtifactRenderer({ content, isStreaming: _isStreaming }: CodeArtifactRendererProps) {
|
|
const { resolvedTheme } = useTheme();
|
|
const { handleCodeSelectionChange } = useCanvasSelection();
|
|
|
|
const extensions = useMemo<Extension[]>(() => {
|
|
const ext = getLanguageExtension(content.language);
|
|
return ext ? [ext] : [];
|
|
}, [content.language]);
|
|
|
|
const handleUpdate = useCallback(
|
|
(update: ViewUpdate) => {
|
|
if (!update.selectionSet) return;
|
|
const { from, to } = update.state.selection.main;
|
|
handleCodeSelectionChange(from, to);
|
|
},
|
|
[handleCodeSelectionChange],
|
|
);
|
|
|
|
if (!content.code) {
|
|
return (
|
|
<div className="h-full flex items-center justify-center">
|
|
<p className="text-sm text-muted-foreground">
|
|
空白代码,发送消息让 AI 填充内容
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="h-full overflow-auto">
|
|
<CodeMirror
|
|
value={content.code}
|
|
readOnly
|
|
height="100%"
|
|
className="h-full font-mono [&_.cm-editor]:h-full [&_.cm-focused]:outline-none!"
|
|
theme={resolvedTheme === "dark" ? darkTheme : lightTheme}
|
|
extensions={extensions}
|
|
onUpdate={handleUpdate}
|
|
basicSetup={{
|
|
lineNumbers: true,
|
|
foldGutter: true,
|
|
highlightActiveLine: false,
|
|
highlightActiveLineGutter: false,
|
|
autocompletion: false,
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|