feat(ai): intergration of ai-optimized-editor

- 在问题编辑器中添加 AI 优化代码功能
- 实现代码模板的获取和展示- 优化 AI 代码生成的提示信息
- 调整代码对比编辑器的样式和功能
- 修复 JSON 解析问题
This commit is contained in:
fly6516 2025-06-21 16:08:38 +08:00
parent ab598459a2
commit fdbc1f06b2
3 changed files with 149 additions and 148 deletions

View File

@ -17,13 +17,29 @@ import prisma from "@/lib/prisma";
export const optimizeCode = async ( export const optimizeCode = async (
input: OptimizeCodeInput input: OptimizeCodeInput
): Promise<OptimizeCodeOutput> => { ): Promise<OptimizeCodeOutput> => {
const model = deepseek("chat"); const model = deepseek("deepseek-chat");
// 获取题目详情如果提供了problemId // 获取题目详情如果提供了problemId
let problemDetails = ""; let problemDetails = "";
let templateDetails = "";
if (input.problemId) { if (input.problemId) {
try { 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( const problemLocalizationEn = await prisma.problemLocalization.findUnique(
{ {
@ -102,6 +118,12 @@ Error message (if any): ${input.error || "No error message provided"}
${problemDetails} ${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. Respond ONLY with the JSON object containing the optimized code and explanations.
Format: Format:
{ {
@ -127,9 +149,13 @@ Format:
} }
// 解析LLM响应 // 解析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; let llmResponseJson;
try { try {
const cleanedText = text.trim(); const cleanedText = jsonString.trim();
llmResponseJson = JSON.parse(cleanedText); llmResponseJson = JSON.parse(cleanedText);
} catch (error) { } catch (error) {
console.error("Failed to parse LLM response as JSON:", error); console.error("Failed to parse LLM response as JSON:", error);

View File

@ -1,127 +1,80 @@
"use client"; "use client";
import { useCallback, useState } from "react"; import React, { useState, useEffect } from "react";
import { DiffEditor } from "@monaco-editor/react"; import { DiffEditor } from "@monaco-editor/react";
import { useMonacoTheme } from "@/hooks/use-monaco-theme";
import { optimizeCode } from "@/app/actions/ai-improve"; import { optimizeCode } from "@/app/actions/ai-improve";
import type { OptimizeCodeInput } from "@/types/ai-improve"; import { AIOptimizeButton } from "@/features/problems/code/components/toolbar/actions/AIOptimizeButton";
import { CoreEditor } from "./core-editor"; // 引入你刚刚的组件
// import { Loading } from "@/components/loading";
import type { LanguageServerConfig } from "@/generated/client";
interface AIEditorWrapperProps { interface AIEditorWrapperProps {
language?: string; language: string;
value?: string; originalCode: string;
path?: string; onReset: () => void;
problemId?: string;
languageServerConfigs?: LanguageServerConfig[];
onChange?: (value: string) => void;
className?: string;
} }
export const AIEditorWrapper = ({ export const AIEditorWrapper = ({ language, originalCode, onReset }: AIEditorWrapperProps) => {
language, const [optimizedCode, setOptimizedCode] = useState<string>("");
value, const [loading, setLoading] = useState(false);
path, const { theme } = useMonacoTheme();
problemId,
languageServerConfigs,
onChange,
}: // className,
AIEditorWrapperProps) => {
const [currentCode, setCurrentCode] = useState(value ?? "");
const [optimizedCode, setOptimizedCode] = useState("");
const [isOptimizing, setIsOptimizing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [showDiff, setShowDiff] = useState(false);
const handleCodeChange = useCallback( // 自动在组件首次挂载后执行 AI 优化
(val: string) => { useEffect(() => {
setCurrentCode(val); if (!optimizedCode) {
onChange?.(val); handleOptimize();
}, }
[onChange] // eslint-disable-next-line react-hooks/exhaustive-deps
); }, []);
const handleOptimize = useCallback(async () => { const handleOptimize = async () => {
if (!problemId || !currentCode) return; setLoading(true);
setIsOptimizing(true); try {
setError(null); const res = await optimizeCode({
code: originalCode,
error: "",
problemId: "",
});
setOptimizedCode(res.optimizedCode);
} catch (err) {
console.error("优化失败", err);
setOptimizedCode("// 优化失败,请稍后重试");
} finally {
setLoading(false);
}
};
try { const handleClick = () => {
const input: OptimizeCodeInput = { if (optimizedCode) {
code: currentCode, // 已有优化,点击返回
problemId, setOptimizedCode("");
}; onReset();
const result = await optimizeCode(input); } else {
setOptimizedCode(result.optimizedCode); // 手动触发优化(如果需要)
setShowDiff(true); handleOptimize();
} catch (err) { }
setError("AI 优化失败,请稍后重试"); };
console.error(err);
} finally {
setIsOptimizing(false);
}
}, [currentCode, problemId]);
const handleApplyOptimized = useCallback(() => { return (
setCurrentCode(optimizedCode); <div className="w-full h-[80vh] flex flex-col gap-4">
onChange?.(optimizedCode); <div>
setShowDiff(false); <AIOptimizeButton
}, [optimizedCode, onChange]); loading={loading}
hasOptimized={!!optimizedCode}
onClick={handleClick}
/>
</div>
return ( {optimizedCode && (
<div className="flex flex-col h-full w-full"> <div className="flex-1">
<div className="flex items-center justify-between p-4"> <DiffEditor
<button language={language}
onClick={handleOptimize} original={originalCode}
disabled={isOptimizing} modified={optimizedCode}
className="px-4 py-2 bg-primary text-white rounded hover:bg-primary/90" height="100%"
> theme={theme}
{isOptimizing ? "优化中..." : "AI优化代码"} options={{ readOnly: true, renderSideBySide: true, automaticLayout: true }}
</button> />
</div>
{showDiff && ( )}
<div className="space-x-2"> </div>
<button );
onClick={() => setShowDiff(false)}
className="px-4 py-2 bg-secondary text-white rounded"
>
</button>
<button
onClick={handleApplyOptimized}
className="px-4 py-2 bg-green-500 text-white rounded"
>
</button>
</div>
)}
</div>
{error && (
<div className="p-3 bg-red-100 text-red-600 rounded-md">{error}</div>
)}
<div className="flex-grow overflow-hidden">
{showDiff ? (
<DiffEditor
original={currentCode}
modified={optimizedCode}
language={language}
theme="vs-dark"
className="h-full w-full"
options={{ readOnly: true, minimap: { enabled: false } }}
/>
) : (
<CoreEditor
language={language}
value={currentCode}
path={path}
languageServerConfigs={languageServerConfigs}
onChange={handleCodeChange}
className="h-full w-full"
/>
)}
</div>
</div>
);
}; };

View File

@ -1,46 +1,68 @@
"use client"; "use client";
import { useEffect } from "react"; import { useState, useEffect } from "react";
import { CoreEditor } from "@/components/core-editor"; import { CoreEditor } from "@/components/core-editor";
import { useProblemEditorStore } from "@/stores/problem-editor"; import { useProblemEditorStore } from "@/stores/problem-editor";
import type { LanguageServerConfig, Template } from "@/generated/client"; import type { LanguageServerConfig, Template } from "@/generated/client";
import { AIEditorWrapper } from "@/components/ai-optimized-editor";
interface ProblemEditorProps { interface ProblemEditorProps {
problemId: string; problemId: string;
templates: Template[]; templates: Template[];
languageServerConfigs?: LanguageServerConfig[]; languageServerConfigs?: LanguageServerConfig[];
} }
export const ProblemEditor = ({ export const ProblemEditor = ({
problemId, problemId,
templates, templates,
languageServerConfigs, languageServerConfigs,
}: ProblemEditorProps) => { }: ProblemEditorProps) => {
const { const {
language, language,
value, value,
path, path,
setProblem, setProblem,
setValue, setValue,
setEditor, setEditor,
setLspWebSocket, setLspWebSocket,
setMarkers, setMarkers,
} = useProblemEditorStore(); } = useProblemEditorStore();
useEffect(() => { const [useAIEditor, setUseAIEditor] = useState(false);
setProblem(problemId, templates);
}, [problemId, setProblem, templates]);
return ( useEffect(() => {
<CoreEditor setProblem(problemId, templates);
language={language} }, [problemId, setProblem, templates]);
value={value}
path={path} return (
languageServerConfigs={languageServerConfigs} <div className="w-full h-[85vh] relative">
onEditorReady={setEditor} {!useAIEditor ? (
onLspWebSocketReady={setLspWebSocket} <>
onMarkersReady={setMarkers} <button
onChange={setValue} className="absolute right-4 top-4 bg-blue-600 text-white px-3 py-1 rounded z-10"
/> onClick={() => setUseAIEditor(true)}
); >
AI
</button>
<CoreEditor
language={language}
value={value}
path={path}
languageServerConfigs={languageServerConfigs}
onEditorReady={setEditor}
onLspWebSocketReady={setLspWebSocket}
onMarkersReady={setMarkers}
onChange={setValue}
className="h-[80vh] w-full"
/>
</>
) : (
<AIEditorWrapper
language={language}
originalCode={value}
onReset={() => setUseAIEditor(false)}
/>
)}
</div>
);
}; };