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 13c4b57ecc
commit c99e4076e1
3 changed files with 412 additions and 0 deletions

View File

@ -0,0 +1,143 @@
"use server";
import {
OptimizeCodeInput,
OptimizeCodeOutput,
OptimizeCodeOutputSchema,
} from "@/types/ai-improve";
import { deepseek } from "@/lib/ai";
import { CoreMessage, generateText } from "ai";
import prisma from "@/lib/prisma";
/**
* AI优化代码
* @param input ID的输入
* @returns
*/
export const optimizeCode = async (
input: OptimizeCodeInput
): Promise<OptimizeCodeOutput> => {
const model = deepseek("chat");
// 获取题目详情如果提供了problemId
let problemDetails = "";
if (input.problemId) {
try {
// 尝试获取英文描述
const problemLocalizationEn = await prisma.problemLocalization.findUnique({
where: {
problemId_locale_type: {
problemId: input.problemId,
locale: "en",
type: "DESCRIPTION",
},
},
include: {
problem: true,
},
});
if (problemLocalizationEn) {
problemDetails = `
Problem Requirements:
-------------------
Description: ${problemLocalizationEn.content}
`;
} else {
// 回退到中文描述
const problemLocalizationZh = await prisma.problemLocalization.findUnique({
where: {
problemId_locale_type: {
problemId: input.problemId,
locale: "zh",
type: "DESCRIPTION",
},
},
include: {
problem: true,
},
});
if (problemLocalizationZh) {
problemDetails = `
Problem Requirements:
-------------------
Description: ${problemLocalizationZh.content}
`;
console.warn(`Fallback to Chinese description for problemId: ${input.problemId}`);
} else {
problemDetails = "Problem description not found in any language.";
console.warn(`No description found for problemId: ${input.problemId}`);
}
}
} catch (error) {
console.error("Failed to fetch problem details:", error);
problemDetails = "Error fetching problem description.";
}
}
// 构建AI提示词
const prompt = `
Analyze the following programming code for potential errors, inefficiencies or code style issues.
Provide an optimized version of the code with explanations. Focus on:
1. Fixing any syntax errors
2. Improving performance
3. Enhancing code readability
4. Following best practices
Original code:
\`\`\`
${input.code}
\`\`\`
Error message (if any): ${input.error || "No error message provided"}
${problemDetails}
Respond ONLY with the JSON object containing the optimized code and explanations.
Format:
{
"optimizedCode": "optimized code here",
"explanation": "explanation of changes made",
"issuesFixed": ["list of issues fixed"]
}
`;
console.log("Prompt:", prompt);
// 发送请求给OpenAI
const messages: CoreMessage[] = [{ role: "user", content: prompt }];
let text;
try {
const response = await generateText({
model: model,
messages: messages,
});
text = response.text;
} catch (error) {
console.error("Error generating text with OpenAI:", error);
throw new Error("Failed to generate response from OpenAI");
}
// 解析LLM响应
let llmResponseJson;
try {
const cleanedText = text.trim();
llmResponseJson = JSON.parse(cleanedText);
} catch (error) {
console.error("Failed to parse LLM response as JSON:", error);
console.error("LLM raw output:", text);
throw new Error("Invalid JSON response from LLM");
}
// 验证响应格式
const validationResult = OptimizeCodeOutputSchema.safeParse(llmResponseJson);
if (!validationResult.success) {
console.error("Zod validation failed:", validationResult.error.format());
throw new Error("Response validation failed");
}
console.log("LLM response:", llmResponseJson);
return validationResult.data;
};

View File

@ -0,0 +1,250 @@
"use client";
import { useState } from "react";
import { DiffEditor } from "@monaco-editor/react";
import { optimizeCode } from "@/app/actions/ai-improve";
import { OptimizeCodeInput } from "@/types/ai-improve";
import dynamic from "next/dynamic";
import { highlighter } from "@/lib/shiki";
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";
// 动态导入Monaco Editor
const Editor = dynamic(
async () => {
await import("vscode");
const monaco = await import("monaco-editor");
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === "json") {
return new Worker(
new URL("monaco-editor/esm/vs/language/json/json.worker.js", import.meta.url)
);
}
if (label === "css" || label === "scss" || label === "less") {
return new Worker(
new URL("monaco-editor/esm/vs/language/css/css.worker.js", import.meta.url)
);
}
if (label === "html" || label === "handlebars" || label === "razor") {
return new Worker(
new URL("monaco-editor/esm/vs/language/html/html.worker.js", import.meta.url)
);
}
if (label === "typescript" || label === "javascript") {
return new Worker(
new URL("monaco-editor/esm/vs/language/typescript/ts.worker.js", import.meta.url)
);
}
return new Worker(
new URL("monaco-editor/esm/vs/editor/editor.worker.js", import.meta.url)
);
},
};
const { loader } = await import("@monaco-editor/react");
loader.config({ monaco });
return (await import("@monaco-editor/react")).Editor;
},
{
ssr: false,
loading: () => <Loading />,
}
);
export function AIProblemEditor({
initialCode = "",
problemId = "",
onCodeChange
}: {
initialCode?: string;
problemId?: string;
onCodeChange?: (code: string) => void;
}) {
const {
editor,
setEditor,
setMarkers,
setWebSocket,
currentLang,
currentPath,
currentTheme,
currentValue,
changeValue,
currentEditorLanguageConfig,
currentLanguageServerConfig,
} = useProblem();
const monacoLanguageClientRef = useRef<MonacoLanguageClient | null>(null);
// 保持原有AI优化的状态
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
onClick={handleOptimizeCode}
disabled={isOptimizing || !currentCode}
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
>
{isOptimizing ? "优化中..." : "AI优化代码"}
</button>
{showDiff && (
<button
onClick={() => setShowDiff(!showDiff)}
className="px-4 py-2 bg-secondary text-secondary-foreground rounded-md"
>
{"隐藏对比"}
</button>
)}
</div>
{error && (
<div className="p-3 bg-destructive/10 text-destructive rounded-md">
{error}
</div>
)}
<div className="flex-grow overflow-hidden">
{showDiff ? (
<DiffEditor
original={currentCode}
modified={optimizedCode}
language={currentLang}
theme={currentTheme}
className="h-full w-full"
options={{
readOnly: true,
minimap: { enabled: false }
}}
/>
) : (
<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>
);
}

19
src/types/ai-improve.ts Normal file
View File

@ -0,0 +1,19 @@
import { z } from "zod";
// 优化代码的输入类型
export const OptimizeCodeInputSchema = z.object({
code: z.string(), // 用户输入的代码
error: z.string().optional(), // 可选的错误信息
problemId: z.string().optional(), // 可选的题目ID
});
export type OptimizeCodeInput = z.infer<typeof OptimizeCodeInputSchema>;
// 优化代码的输出类型
export const OptimizeCodeOutputSchema = z.object({
optimizedCode: z.string(), // 优化后的代码
explanation: z.string(), // 优化说明
issuesFixed: z.array(z.string()).optional(), // 修复的问题列表
});
export type OptimizeCodeOutput = z.infer<typeof OptimizeCodeOutputSchema>;