From c4622fa586f1534ec441a32455c57e250f3a5043 Mon Sep 17 00:00:00 2001 From: cfngc4594 Date: Sun, 22 Jun 2025 02:36:13 +0800 Subject: [PATCH] feat(ai): integrate AI code optimization with template support and editor toggling --- src/app/actions/ai-improve.ts | 32 +++- src/components/ai-optimized-editor.tsx | 153 ++++++------------ src/components/problem-editor.tsx | 38 +++-- .../toolbar/actions/AIDisplayButton.tsx | 44 +++++ .../toolbar/actions/AIOptimizeButton.tsx | 42 +++++ .../code/components/toolbar/code-toolbar.tsx | 4 + src/stores/problem-editor.ts | 16 ++ 7 files changed, 210 insertions(+), 119 deletions(-) create mode 100644 src/features/problems/code/components/toolbar/actions/AIDisplayButton.tsx create mode 100644 src/features/problems/code/components/toolbar/actions/AIOptimizeButton.tsx diff --git a/src/app/actions/ai-improve.ts b/src/app/actions/ai-improve.ts index d73a47a..b8a0901 100644 --- a/src/app/actions/ai-improve.ts +++ b/src/app/actions/ai-improve.ts @@ -5,9 +5,9 @@ import { OptimizeCodeOutput, OptimizeCodeOutputSchema, } from "@/types/ai-improve"; +import prisma from "@/lib/prisma"; import { deepseek } from "@/lib/ai"; import { CoreMessage, generateText } from "ai"; -import prisma from "@/lib/prisma"; /** * 调用AI优化代码 @@ -17,13 +17,29 @@ import prisma from "@/lib/prisma"; export const optimizeCode = async ( input: OptimizeCodeInput ): Promise => { - const model = deepseek("chat"); + const model = deepseek("deepseek-chat"); // 获取题目详情(如果提供了problemId) let problemDetails = ""; + let templateDetails = ""; if (input.problemId) { try { + const templates = await prisma.template.findMany({ + where: { problemId: input.problemId }, + }); + if (templates && templates.length > 0) { + const tplStrings = templates + .map( + (t) => + `Template (${t.language}):\n-------------------\n\`\`\`\n${t.content}\n\`\`\`` + ) + .join("\n"); + templateDetails = `\nCode Templates:\n-------------------\n${tplStrings}`; + } else { + templateDetails = "\nNo code templates found for this problem."; + } + // 尝试获取英文描述 const problemLocalizationEn = await prisma.problemLocalization.findUnique( { @@ -102,6 +118,12 @@ Error message (if any): ${input.error || "No error message provided"} ${problemDetails} +The following is the code template section, do not modify the part that gives the same code as the code template + +${templateDetails} + +Write the code in conjunction with the topic and fill in the gaps in the code + Respond ONLY with the JSON object containing the optimized code and explanations. Format: { @@ -127,9 +149,13 @@ Format: } // 解析LLM响应 + const fenceMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/); + const jsonMatch = fenceMatch ? fenceMatch[1] : text.match(/\{[\s\S]*}/)?.[0]; + const jsonString = jsonMatch ? jsonMatch.trim() : text.trim(); + let llmResponseJson; try { - const cleanedText = text.trim(); + const cleanedText = jsonString.trim(); llmResponseJson = JSON.parse(cleanedText); } catch (error) { console.error("Failed to parse LLM response as JSON:", error); diff --git a/src/components/ai-optimized-editor.tsx b/src/components/ai-optimized-editor.tsx index adfd522..76b968b 100644 --- a/src/components/ai-optimized-editor.tsx +++ b/src/components/ai-optimized-editor.tsx @@ -1,127 +1,68 @@ "use client"; -import { useCallback, useState } from "react"; import { DiffEditor } from "@monaco-editor/react"; import { optimizeCode } from "@/app/actions/ai-improve"; -import type { OptimizeCodeInput } from "@/types/ai-improve"; -import { CoreEditor } from "./core-editor"; // 引入你刚刚的组件 -// import { Loading } from "@/components/loading"; -import type { LanguageServerConfig } from "@/generated/client"; +import { useMonacoTheme } from "@/hooks/use-monaco-theme"; +import React, { useState, useEffect, useCallback } from "react"; +import { useProblemEditorStore } from "@/stores/problem-editor"; -interface AIEditorWrapperProps { - language?: string; - value?: string; - path?: string; - problemId?: string; - languageServerConfigs?: LanguageServerConfig[]; - onChange?: (value: string) => void; - className?: string; -} +export const AIEditorWrapper = () => { + const { + language, + value: originalCode, + setLoading, + AIgenerate, + LastOptimizedCode, + setLastOptimizedCode, + } = useProblemEditorStore(); -export const AIEditorWrapper = ({ - language, - value, - path, - problemId, - languageServerConfigs, - onChange, -}: // className, -AIEditorWrapperProps) => { - const [currentCode, setCurrentCode] = useState(value ?? ""); - const [optimizedCode, setOptimizedCode] = useState(""); - const [isOptimizing, setIsOptimizing] = useState(false); - const [error, setError] = useState(null); - const [showDiff, setShowDiff] = useState(false); - - const handleCodeChange = useCallback( - (val: string) => { - setCurrentCode(val); - onChange?.(val); - }, - [onChange] - ); + const [optimizedCode, setOptimizedCode] = useState(""); + const { theme } = useMonacoTheme(); const handleOptimize = useCallback(async () => { - if (!problemId || !currentCode) return; - setIsOptimizing(true); - setError(null); - + setLoading(true); try { - const input: OptimizeCodeInput = { - code: currentCode, - problemId, - }; - const result = await optimizeCode(input); - setOptimizedCode(result.optimizedCode); - setShowDiff(true); + const res = await optimizeCode({ + code: originalCode, + error: "", + problemId: "", + }); + setOptimizedCode(res.optimizedCode); + setLastOptimizedCode(res.optimizedCode); } catch (err) { - setError("AI 优化失败,请稍后重试"); - console.error(err); + console.error("优化失败", err); + setOptimizedCode("// 优化失败,请稍后重试"); } finally { - setIsOptimizing(false); + setLoading(false); } - }, [currentCode, problemId]); + }, [originalCode, setLoading, setLastOptimizedCode]); - const handleApplyOptimized = useCallback(() => { - setCurrentCode(optimizedCode); - onChange?.(optimizedCode); - setShowDiff(false); - }, [optimizedCode, onChange]); + useEffect(() => { + if (AIgenerate) { + handleOptimize(); + } else if (LastOptimizedCode) { + setOptimizedCode(LastOptimizedCode); + } + }, [AIgenerate, LastOptimizedCode, handleOptimize]); return ( -
-
- - - {showDiff && ( -
- - -
- )} -
- - {error && ( -
{error}
- )} - -
- {showDiff ? ( +
+ {optimizedCode && ( +
- ) : ( - - )} -
+
+ )}
); }; diff --git a/src/components/problem-editor.tsx b/src/components/problem-editor.tsx index c9a8adc..79947d4 100644 --- a/src/components/problem-editor.tsx +++ b/src/components/problem-editor.tsx @@ -3,6 +3,7 @@ import { useEffect } from "react"; import { CoreEditor } from "@/components/core-editor"; import { useProblemEditorStore } from "@/stores/problem-editor"; +import { AIEditorWrapper } from "@/components/ai-optimized-editor"; import type { LanguageServerConfig, Template } from "@/generated/client"; interface ProblemEditorProps { @@ -25,6 +26,8 @@ export const ProblemEditor = ({ setEditor, setLspWebSocket, setMarkers, + useAIEditor, + // setUseAIEditor } = useProblemEditorStore(); useEffect(() => { @@ -32,15 +35,30 @@ export const ProblemEditor = ({ }, [problemId, setProblem, templates]); return ( - +
+ {!useAIEditor ? ( + <> + {/* setUseAIEditor(true)}*/} + {/*>*/} + {/* AI 优化代码*/} + {/**/} + + + ) : ( + + )} +
); }; diff --git a/src/features/problems/code/components/toolbar/actions/AIDisplayButton.tsx b/src/features/problems/code/components/toolbar/actions/AIDisplayButton.tsx new file mode 100644 index 0000000..8461109 --- /dev/null +++ b/src/features/problems/code/components/toolbar/actions/AIDisplayButton.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { TooltipButton } from "@/components/tooltip-button"; +import { useProblemEditorStore } from "@/stores/problem-editor"; +import { ArrowLeftRight, LoaderCircleIcon, Undo2Icon } from "lucide-react"; + +export const AIDisplayButton = () => { + const { useAIEditor, setUseAIEditor, setAIgenerate, loading } = + useProblemEditorStore(); + + const handleClick = () => { + setAIgenerate(false); + if (!loading) { + setUseAIEditor(!useAIEditor); + } + }; + + const tooltipContent = loading + ? "AI 正在优化中…" + : useAIEditor + ? "返回原始编辑器" + : "查看 AI 优化代码"; + + return ( + + {loading ? ( + + ); +}; diff --git a/src/features/problems/code/components/toolbar/actions/AIOptimizeButton.tsx b/src/features/problems/code/components/toolbar/actions/AIOptimizeButton.tsx new file mode 100644 index 0000000..15cdc70 --- /dev/null +++ b/src/features/problems/code/components/toolbar/actions/AIOptimizeButton.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { Wand2Icon } from "lucide-react"; +import { TooltipButton } from "@/components/tooltip-button"; +//import { LoaderCircleIcon, Undo2Icon } from "lucide-react"; +import { useProblemEditorStore } from "@/stores/problem-editor"; + +export const AIOptimizeButton = () => { + const { useAIEditor, setUseAIEditor, setAIgenerate, loading } = + useProblemEditorStore(); + + const handleClick = () => { + setAIgenerate(true); + if (!loading) { + setUseAIEditor(!useAIEditor); + } + }; + + // ? "AI 正在优化中…" + // : useAIEditor + // ? "返回原始编辑器" + // : "使用 AI 优化代码"; + + const tooltipContent = "使用 AI 优化代码"; // 仅保留默认提示内容 + + return ( + + {loading ? // className="opacity-60 animate-spin" // + ); +}; diff --git a/src/features/problems/code/components/toolbar/code-toolbar.tsx b/src/features/problems/code/components/toolbar/code-toolbar.tsx index 989b91a..fdfa3f5 100644 --- a/src/features/problems/code/components/toolbar/code-toolbar.tsx +++ b/src/features/problems/code/components/toolbar/code-toolbar.tsx @@ -9,6 +9,8 @@ import { } from "@/features/problems/code/components/toolbar"; import { AnalyzeButton } from "./actions/analyze-button"; import { LspConnectionIndicator } from "./controls/lsp-connection-indicator"; +import { AIDisplayButton } from "@/features/problems/code/components/toolbar/actions/AIDisplayButton"; +import { AIOptimizeButton } from "@/features/problems/code/components/toolbar/actions/AIOptimizeButton"; interface CodeToolbarProps { className?: string; @@ -25,6 +27,8 @@ export const CodeToolbar = async ({ className }: CodeToolbarProps) => {
+ + diff --git a/src/stores/problem-editor.ts b/src/stores/problem-editor.ts index 10863f8..e16c306 100644 --- a/src/stores/problem-editor.ts +++ b/src/stores/problem-editor.ts @@ -16,6 +16,10 @@ type ProblemEditorState = { editor: editor.IStandaloneCodeEditor | null; lspWebSocket: WebSocket | null; markers: editor.IMarker[]; + useAIEditor: boolean; + loading: boolean; + AIgenerate: boolean; + LastOptimizedCode: string; }; type ProblemEditorAction = { @@ -26,6 +30,10 @@ type ProblemEditorAction = { setEditor: (editor: editor.IStandaloneCodeEditor) => void; setLspWebSocket: (lspWebSocket: WebSocket) => void; setMarkers: (markers: editor.IMarker[]) => void; + setUseAIEditor: (flag: boolean) => void; + setLoading: (flag: boolean) => void; + setAIgenerate: (flag: boolean) => void; + setLastOptimizedCode: (code: string) => void; }; type ProblemEditorStore = ProblemEditorState & ProblemEditorAction; @@ -38,6 +46,14 @@ export const useProblemEditorStore = create((set, get) => ({ editor: null, lspWebSocket: null, markers: [], + useAIEditor: false, + loading: false, + AIgenerate: false, + LastOptimizedCode: "", + setLastOptimizedCode: (code) => set({ LastOptimizedCode: code }), + setAIgenerate: (flag) => set({ AIgenerate: flag }), + setLoading: (loading) => set({ loading }), + setUseAIEditor: (loading) => set({ useAIEditor: loading }), setProblem: (problemId, templates) => { const language = getLanguage(problemId); const value = getValue(problemId, language, templates);