feat: migrate prototype of ai-optimized-editor feature

This commit is contained in:
fly6516 2025-06-14 11:33:17 +08:00 committed by cfngc4594
parent 6da7671242
commit 4801e9588e

View File

@ -1,124 +1,250 @@
"use client"; "use client";
import { useCallback, useState } from "react"; import { useState } from "react";
import { DiffEditor } from "@monaco-editor/react"; import { DiffEditor } from "@monaco-editor/react";
import { optimizeCode } from "@/app/actions/ai-improve"; import { optimizeCode } from "@/app/actions/ai-improve";
import type { OptimizeCodeInput } from "@/types/ai-improve"; import { OptimizeCodeInput } from "@/types/ai-improve";
import { CoreEditor } from "./core-editor"; // 引入你刚刚的组件 import dynamic from "next/dynamic";
// import { Loading } from "@/components/loading"; import { highlighter } from "@/lib/shiki";
import type { LanguageServerConfig } from "@/generated/client"; import type { editor } from "monaco-editor";
import { Loading } from "@/components/loading";
import { shikiToMonaco } from "@shikijs/monaco";
import { useProblem } from "@/hooks/use-problem";
import type { Monaco } from "@monaco-editor/react";
import { useCallback, useEffect, useRef } from "react";
import { connectToLanguageServer } from "@/lib/language-server";
import type { MonacoLanguageClient } from "monaco-languageclient";
import { DefaultEditorOptionConfig } from "@/config/editor-option";
interface AIEditorWrapperProps { // 动态导入Monaco Editor
language?: string; const Editor = dynamic(
value?: string; async () => {
path?: string; await import("vscode");
problemId?: string; const monaco = await import("monaco-editor");
languageServerConfigs?: LanguageServerConfig[];
onChange?: (value: string) => void;
className?: string;
}
export const AIEditorWrapper = ({ self.MonacoEnvironment = {
language, getWorker(_, label) {
value, if (label === "json") {
path, return new Worker(
problemId, new URL("monaco-editor/esm/vs/language/json/json.worker.js", import.meta.url)
languageServerConfigs, );
onChange, }
// className, if (label === "css" || label === "scss" || label === "less") {
}: AIEditorWrapperProps) => { return new Worker(
const [currentCode, setCurrentCode] = useState(value ?? ""); new URL("monaco-editor/esm/vs/language/css/css.worker.js", import.meta.url)
const [optimizedCode, setOptimizedCode] = useState(""); );
const [isOptimizing, setIsOptimizing] = useState(false); }
const [error, setError] = useState<string | null>(null); if (label === "html" || label === "handlebars" || label === "razor") {
const [showDiff, setShowDiff] = useState(false); return new Worker(
new URL("monaco-editor/esm/vs/language/html/html.worker.js", import.meta.url)
const handleCodeChange = useCallback((val: string) => { );
setCurrentCode(val); }
onChange?.(val); if (label === "typescript" || label === "javascript") {
}, [onChange]); return new Worker(
new URL("monaco-editor/esm/vs/language/typescript/ts.worker.js", import.meta.url)
const handleOptimize = useCallback(async () => { );
if (!problemId || !currentCode) return; }
setIsOptimizing(true); return new Worker(
setError(null); new URL("monaco-editor/esm/vs/editor/editor.worker.js", import.meta.url)
);
try { },
const input: OptimizeCodeInput = { };
code: currentCode, const { loader } = await import("@monaco-editor/react");
problemId, loader.config({ monaco });
}; return (await import("@monaco-editor/react")).Editor;
const result = await optimizeCode(input); },
setOptimizedCode(result.optimizedCode); {
setShowDiff(true); ssr: false,
} catch (err) { loading: () => <Loading />,
setError("AI 优化失败,请稍后重试");
console.error(err);
} finally {
setIsOptimizing(false);
} }
}, [currentCode, problemId]); );
const handleApplyOptimized = useCallback(() => { export function AIProblemEditor({
setCurrentCode(optimizedCode); initialCode = "",
onChange?.(optimizedCode); problemId = "",
setShowDiff(false); onCodeChange
}, [optimizedCode, onChange]); }: {
initialCode?: string;
problemId?: string;
onCodeChange?: (code: string) => void;
}) {
const {
editor,
setEditor,
setMarkers,
setWebSocket,
currentLang,
currentPath,
currentTheme,
currentValue,
changeValue,
currentEditorLanguageConfig,
currentLanguageServerConfig,
} = useProblem();
return ( const monacoLanguageClientRef = useRef<MonacoLanguageClient | null>(null);
<div className="flex flex-col h-full w-full">
<div className="flex items-center justify-between p-4">
<button
onClick={handleOptimize}
disabled={isOptimizing}
className="px-4 py-2 bg-primary text-white rounded hover:bg-primary/90"
>
{isOptimizing ? "优化中..." : "AI优化代码"}
</button>
{showDiff && ( // 保持原有AI优化的状态
<div className="space-x-2"> const [showDiff, setShowDiff] = useState(false);
const [optimizedCode, setOptimizedCode] = useState("");
const [isOptimizing, setIsOptimizing] = useState(false);
const [error, setError] = useState<string | null>(null);
// 重用useProblem的状态管理
const currentCode = currentValue || initialCode;
const handleCodeChange = useCallback((value: string | undefined) => {
if (value !== undefined) {
changeValue(value);
if (onCodeChange) {
onCodeChange(value);
}
}
}, [onCodeChange, changeValue]);
// 保持原有LSP连接逻辑
const connectLSP = useCallback(async () => {
if (!(currentLang && editor)) return;
if (monacoLanguageClientRef.current) {
monacoLanguageClientRef.current.stop();
monacoLanguageClientRef.current = null;
setWebSocket(null);
}
if (!currentEditorLanguageConfig || !currentLanguageServerConfig) return;
try {
const { client: monacoLanguageClient, webSocket } = await connectToLanguageServer(
currentEditorLanguageConfig,
currentLanguageServerConfig
);
monacoLanguageClientRef.current = monacoLanguageClient;
setWebSocket(webSocket);
} catch (error) {
console.error("Failed to connect to LSP:", error);
}
}, [
currentEditorLanguageConfig,
currentLang,
currentLanguageServerConfig,
editor,
setWebSocket,
]);
useEffect(() => {
connectLSP();
}, [connectLSP]);
useEffect(() => {
return () => {
if (monacoLanguageClientRef.current) {
monacoLanguageClientRef.current.stop();
monacoLanguageClientRef.current = null;
setWebSocket(null);
}
};
}, [setWebSocket]);
const handleEditorWillMount = useCallback((monaco: Monaco) => {
shikiToMonaco(highlighter, monaco);
}, []);
const handleOnMount = useCallback(
async (editor: editor.IStandaloneCodeEditor) => {
setEditor(editor);
await connectLSP();
},
[setEditor, connectLSP]
);
const handleEditorValidation = useCallback(
(markers: editor.IMarker[]) => {
setMarkers(markers);
},
[setMarkers]
);
const handleOptimizeCode = useCallback(async () => {
if (!currentCode || !problemId) return;
setIsOptimizing(true);
setError(null);
try {
const input: OptimizeCodeInput = {
code: currentCode,
problemId
};
const result = await optimizeCode(input);
setOptimizedCode(result.optimizedCode);
setShowDiff(true);
} catch (err) {
setError("代码优化失败,请重试");
console.error(err);
} finally {
setIsOptimizing(false);
}
}, [currentCode, problemId]);
return (
<div className="flex flex-col h-full w-full">
{/* 保持原有AI优化按钮 */}
<div className="flex justify-between items-center p-4">
<button <button
onClick={() => setShowDiff(false)} onClick={handleOptimizeCode}
className="px-4 py-2 bg-secondary text-white rounded" disabled={isOptimizing || !currentCode}
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
> >
{isOptimizing ? "优化中..." : "AI优化代码"}
</button> </button>
<button
onClick={handleApplyOptimized}
className="px-4 py-2 bg-green-500 text-white rounded"
>
</button>
</div>
)}
</div>
{error && ( {showDiff && (
<div className="p-3 bg-red-100 text-red-600 rounded-md">{error}</div> <button
)} onClick={() => setShowDiff(!showDiff)}
className="px-4 py-2 bg-secondary text-secondary-foreground rounded-md"
>
{"隐藏对比"}
</button>
)}
</div>
<div className="flex-grow overflow-hidden"> {error && (
{showDiff ? ( <div className="p-3 bg-destructive/10 text-destructive rounded-md">
<DiffEditor {error}
original={currentCode} </div>
modified={optimizedCode} )}
language={language}
theme="vs-dark" <div className="flex-grow overflow-hidden">
className="h-full w-full" {showDiff ? (
options={{ readOnly: true, minimap: { enabled: false } }} <DiffEditor
/> original={currentCode}
) : ( modified={optimizedCode}
<CoreEditor language={currentLang}
language={language} theme={currentTheme}
value={currentCode} className="h-full w-full"
path={path} options={{
languageServerConfigs={languageServerConfigs} readOnly: true,
onChange={handleCodeChange} minimap: { enabled: false }
className="h-full w-full" }}
/> />
)} ) : (
<Editor
language={currentLang}
theme={currentTheme}
path={currentPath}
value={currentCode}
beforeMount={handleEditorWillMount}
onMount={handleOnMount}
onChange={handleCodeChange}
onValidate={handleEditorValidation}
options={DefaultEditorOptionConfig}
loading={<Loading />}
className="h-full w-full"
/>
)}
</div>
</div> </div>
</div> );
); }
};