From e1b5231f90003b156833ee210b9c7fdb129ba220 Mon Sep 17 00:00:00 2001 From: fly6516 Date: Fri, 16 May 2025 02:40:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E6=B7=BB=E5=8A=A0=20AI=20=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=BC=98=E5=8C=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现了 AI代码优化的功能,包括语法错误修复、性能提升、代码可读性增强等 - 新增 AIProblemEditor 组件,用于展示和编辑优化后的代码 - 在问题页面集成 AI 优化功能,用户可以切换普通编辑器和 AI 优化编辑器 - 添加了优化代码的输入输出类型定义和验证 --- src/actions/ai-improve.ts | 104 +++++++++++++++++ src/app/(app)/problems/[id]/page.tsx | 49 +++++++- src/components/ai-optimized-editor.tsx | 153 +++++++++++++++++++++++++ src/lib/ai.ts | 8 ++ src/types/ai-improve.ts | 19 +++ 5 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 src/actions/ai-improve.ts create mode 100644 src/components/ai-optimized-editor.tsx create mode 100644 src/lib/ai.ts create mode 100644 src/types/ai-improve.ts diff --git a/src/actions/ai-improve.ts b/src/actions/ai-improve.ts new file mode 100644 index 0000000..ab36e07 --- /dev/null +++ b/src/actions/ai-improve.ts @@ -0,0 +1,104 @@ +"use server"; + +import { + OptimizeCodeInput, + OptimizeCodeOutput, + OptimizeCodeOutputSchema, +} from "@/types/ai-improve"; +import { openai } from "@/lib/ai"; +import { CoreMessage, generateText } from "ai"; +import { prisma } from "@/lib/prisma"; // Prisma客户端 + +/** + * 调用AI优化代码 + * @param input 包含代码、错误信息、题目ID的输入 + * @returns 优化后的代码和说明 + */ +export const optimizeCode = async ( + input: OptimizeCodeInput +): Promise => { + const model = openai("gpt-4o-mini"); + + // 获取题目详情(如果提供了problemId) + let problemDetails = ""; + if (input.problemId) { + try { + const problem = await prisma.problem.findUnique({ + where: { problemId: input.problemId }, + }); + if (problem) { + problemDetails = ` +Problem Requirements: +------------------- +Description: ${problem.description} +Input: ${problem.inputSpec} +Output: ${problem.outputSpec} +Test Cases: ${JSON.stringify(problem.testCases)} + `; + } + } catch (error) { + console.error("Failed to fetch problem details:", error); + } + } + + // 构建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"] +} +`; + + // 发送请求给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"); + } + + return validationResult.data; +}; \ No newline at end of file diff --git a/src/app/(app)/problems/[id]/page.tsx b/src/app/(app)/problems/[id]/page.tsx index 488e101..901c6fc 100644 --- a/src/app/(app)/problems/[id]/page.tsx +++ b/src/app/(app)/problems/[id]/page.tsx @@ -13,6 +13,7 @@ import { useEffect, useState } from "react"; import { useTranslations } from "next-intl"; import Dockview from "@/components/dockview"; import { useDockviewStore } from "@/stores/dockview"; +import { AIProblemEditor } from "@/components/ai-optimized-editor"; interface ProblemPageProps { locale: Locale; @@ -38,11 +39,57 @@ export default function ProblemPage({ const [key, setKey] = useState(0); const { setApi } = useDockviewStore(); const t = useTranslations("ProblemPage"); + const pathname = usePathname(); + const problemId = pathname.split("/").pop(); // 从URL提取problemId + + // AI优化相关状态 + const [showAIEditor, setShowAIEditor] = useState(false); + const [userCode, setUserCode] = useState(`function example() { + // 初始代码 + return "Hello World"; +}`); + + // 修改Code面板内容以包含切换功能 + const CodeWithToggle = ( +
+
+ + + {showAIEditor && ( + + )} +
+ + {showAIEditor ? ( + + ) : ( + // 原始Code组件保持不变 +
+ {Code} +
+ )} +
+ ); useEffect(() => { setKey((prevKey) => prevKey + 1); }, [locale]); + // 修改Dockview配置:更新Code面板引用 return ( { + 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: () => , + } +); + +export function AIProblemEditor({ + initialCode = "", + problemId = "", + onCodeChange +}: { + initialCode?: string; + problemId?: string; + onCodeChange?: (code: string) => void; +}) { + const [showDiff, setShowDiff] = useState(false); + const [optimizedCode, setOptimizedCode] = useState(""); + const [isOptimizing, setIsOptimizing] = useState(false); + const [currentCode, setCurrentCode] = useState(initialCode); + const [error, setError] = useState(null); + + const handleCodeChange = useCallback((value: string | undefined) => { + if (value !== undefined) { + setCurrentCode(value); + if (onCodeChange) { + onCodeChange(value); + } + } + }, [onCodeChange]); + + 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 ( +
+
+ + + {showDiff && ( + + )} +
+ + {error && ( +
+ {error} +
+ )} + + {showDiff ? ( + + ) : ( + + )} +
+ ); +} \ No newline at end of file diff --git a/src/lib/ai.ts b/src/lib/ai.ts new file mode 100644 index 0000000..95e527c --- /dev/null +++ b/src/lib/ai.ts @@ -0,0 +1,8 @@ +import "server-only"; + +import { createOpenAI } from "@ai-sdk/openai"; + +export const openai = createOpenAI({ + apiKey: process.env.OPENAI_API_KEY || "", + baseURL: process.env.OPENAI_BASE_URL || "", +}); \ No newline at end of file diff --git a/src/types/ai-improve.ts b/src/types/ai-improve.ts new file mode 100644 index 0000000..5d55afe --- /dev/null +++ b/src/types/ai-improve.ts @@ -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; + +// 优化代码的输出类型 +export const OptimizeCodeOutputSchema = z.object({ + optimizedCode: z.string(), // 优化后的代码 + explanation: z.string(), // 优化说明 + issuesFixed: z.array(z.string()).optional(), // 修复的问题列表 +}); + +export type OptimizeCodeOutput = z.infer; \ No newline at end of file