mirror of
https://github.com/massbug/judge4c.git
synced 2025-07-04 15:50:51 +00:00
commit
9c092eeda2
@ -5,9 +5,9 @@ import {
|
|||||||
OptimizeCodeOutput,
|
OptimizeCodeOutput,
|
||||||
OptimizeCodeOutputSchema,
|
OptimizeCodeOutputSchema,
|
||||||
} from "@/types/ai-improve";
|
} from "@/types/ai-improve";
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
import { deepseek } from "@/lib/ai";
|
import { deepseek } from "@/lib/ai";
|
||||||
import { CoreMessage, generateText } from "ai";
|
import { CoreMessage, generateText } from "ai";
|
||||||
import prisma from "@/lib/prisma";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用AI优化代码
|
* 调用AI优化代码
|
||||||
@ -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);
|
||||||
|
@ -1,127 +1,68 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, 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 { useMonacoTheme } from "@/hooks/use-monaco-theme";
|
||||||
import { CoreEditor } from "./core-editor"; // 引入你刚刚的组件
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
// import { Loading } from "@/components/loading";
|
import { useProblemEditorStore } from "@/stores/problem-editor";
|
||||||
import type { LanguageServerConfig } from "@/generated/client";
|
|
||||||
|
|
||||||
interface AIEditorWrapperProps {
|
export const AIEditorWrapper = () => {
|
||||||
language?: string;
|
const {
|
||||||
value?: string;
|
|
||||||
path?: string;
|
|
||||||
problemId?: string;
|
|
||||||
languageServerConfigs?: LanguageServerConfig[];
|
|
||||||
onChange?: (value: string) => void;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AIEditorWrapper = ({
|
|
||||||
language,
|
language,
|
||||||
value,
|
value: originalCode,
|
||||||
path,
|
setLoading,
|
||||||
problemId,
|
AIgenerate,
|
||||||
languageServerConfigs,
|
LastOptimizedCode,
|
||||||
onChange,
|
setLastOptimizedCode,
|
||||||
}: // className,
|
} = useProblemEditorStore();
|
||||||
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(
|
const [optimizedCode, setOptimizedCode] = useState<string>("");
|
||||||
(val: string) => {
|
const { theme } = useMonacoTheme();
|
||||||
setCurrentCode(val);
|
|
||||||
onChange?.(val);
|
|
||||||
},
|
|
||||||
[onChange]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleOptimize = useCallback(async () => {
|
const handleOptimize = useCallback(async () => {
|
||||||
if (!problemId || !currentCode) return;
|
setLoading(true);
|
||||||
setIsOptimizing(true);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const input: OptimizeCodeInput = {
|
const res = await optimizeCode({
|
||||||
code: currentCode,
|
code: originalCode,
|
||||||
problemId,
|
error: "",
|
||||||
};
|
problemId: "",
|
||||||
const result = await optimizeCode(input);
|
});
|
||||||
setOptimizedCode(result.optimizedCode);
|
setOptimizedCode(res.optimizedCode);
|
||||||
setShowDiff(true);
|
setLastOptimizedCode(res.optimizedCode);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError("AI 优化失败,请稍后重试");
|
console.error("优化失败", err);
|
||||||
console.error(err);
|
setOptimizedCode("// 优化失败,请稍后重试");
|
||||||
} finally {
|
} finally {
|
||||||
setIsOptimizing(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [currentCode, problemId]);
|
}, [originalCode, setLoading, setLastOptimizedCode]);
|
||||||
|
|
||||||
const handleApplyOptimized = useCallback(() => {
|
useEffect(() => {
|
||||||
setCurrentCode(optimizedCode);
|
if (AIgenerate) {
|
||||||
onChange?.(optimizedCode);
|
handleOptimize();
|
||||||
setShowDiff(false);
|
} else if (LastOptimizedCode) {
|
||||||
}, [optimizedCode, onChange]);
|
setOptimizedCode(LastOptimizedCode);
|
||||||
|
}
|
||||||
|
}, [AIgenerate, LastOptimizedCode, handleOptimize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full w-full">
|
<div className="w-full h-[80vh] flex flex-col gap-4">
|
||||||
<div className="flex items-center justify-between p-4">
|
{optimizedCode && (
|
||||||
<button
|
<div className="flex-1">
|
||||||
onClick={handleOptimize}
|
|
||||||
disabled={isOptimizing}
|
|
||||||
className="px-4 py-2 bg-primary text-white rounded hover:bg-primary/90"
|
|
||||||
>
|
|
||||||
{isOptimizing ? "优化中..." : "AI优化代码"}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{showDiff && (
|
|
||||||
<div className="space-x-2">
|
|
||||||
<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
|
<DiffEditor
|
||||||
original={currentCode}
|
language={language}
|
||||||
|
original={originalCode}
|
||||||
modified={optimizedCode}
|
modified={optimizedCode}
|
||||||
language={language}
|
height="100%"
|
||||||
theme="vs-dark"
|
theme={theme}
|
||||||
className="h-full w-full"
|
options={{
|
||||||
options={{ readOnly: true, minimap: { enabled: false } }}
|
readOnly: true,
|
||||||
|
renderSideBySide: true,
|
||||||
|
automaticLayout: true,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<CoreEditor
|
|
||||||
language={language}
|
|
||||||
value={currentCode}
|
|
||||||
path={path}
|
|
||||||
languageServerConfigs={languageServerConfigs}
|
|
||||||
onChange={handleCodeChange}
|
|
||||||
className="h-full w-full"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import { useEffect } from "react";
|
import { 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 { AIEditorWrapper } from "@/components/ai-optimized-editor";
|
||||||
import type { LanguageServerConfig, Template } from "@/generated/client";
|
import type { LanguageServerConfig, Template } from "@/generated/client";
|
||||||
|
|
||||||
interface ProblemEditorProps {
|
interface ProblemEditorProps {
|
||||||
@ -25,6 +26,8 @@ export const ProblemEditor = ({
|
|||||||
setEditor,
|
setEditor,
|
||||||
setLspWebSocket,
|
setLspWebSocket,
|
||||||
setMarkers,
|
setMarkers,
|
||||||
|
useAIEditor,
|
||||||
|
// setUseAIEditor
|
||||||
} = useProblemEditorStore();
|
} = useProblemEditorStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -32,6 +35,15 @@ export const ProblemEditor = ({
|
|||||||
}, [problemId, setProblem, templates]);
|
}, [problemId, setProblem, templates]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="w-full h-[85vh] relative">
|
||||||
|
{!useAIEditor ? (
|
||||||
|
<>
|
||||||
|
{/*<button*/}
|
||||||
|
{/* className="absolute right-4 top-4 bg-blue-600 text-white px-3 py-1 rounded z-10"*/}
|
||||||
|
{/* onClick={() => setUseAIEditor(true)}*/}
|
||||||
|
{/*>*/}
|
||||||
|
{/* AI 优化代码*/}
|
||||||
|
{/*</button>*/}
|
||||||
<CoreEditor
|
<CoreEditor
|
||||||
language={language}
|
language={language}
|
||||||
value={value}
|
value={value}
|
||||||
@ -41,6 +53,12 @@ export const ProblemEditor = ({
|
|||||||
onLspWebSocketReady={setLspWebSocket}
|
onLspWebSocketReady={setLspWebSocket}
|
||||||
onMarkersReady={setMarkers}
|
onMarkersReady={setMarkers}
|
||||||
onChange={setValue}
|
onChange={setValue}
|
||||||
|
className="h-[80vh] w-full"
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<AIEditorWrapper />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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 (
|
||||||
|
<TooltipButton
|
||||||
|
tooltipContent={tooltipContent}
|
||||||
|
onClick={handleClick}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<LoaderCircleIcon
|
||||||
|
className="opacity-60 animate-spin"
|
||||||
|
size={16}
|
||||||
|
strokeWidth={2}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
) : useAIEditor ? (
|
||||||
|
<Undo2Icon size={16} strokeWidth={2} aria-hidden="true" />
|
||||||
|
) : (
|
||||||
|
<ArrowLeftRight size={16} strokeWidth={2} aria-hidden="true" />
|
||||||
|
)}
|
||||||
|
</TooltipButton>
|
||||||
|
);
|
||||||
|
};
|
@ -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 (
|
||||||
|
<TooltipButton
|
||||||
|
tooltipContent={tooltipContent}
|
||||||
|
onClick={handleClick}
|
||||||
|
disabled={loading || useAIEditor}
|
||||||
|
>
|
||||||
|
{loading ? // className="opacity-60 animate-spin" // <LoaderCircleIcon
|
||||||
|
// size={16}
|
||||||
|
// strokeWidth={2}
|
||||||
|
// aria-hidden="true"
|
||||||
|
// />
|
||||||
|
null : useAIEditor ? null : ( // <Undo2Icon size={16} strokeWidth={2} aria-hidden="true" />
|
||||||
|
<Wand2Icon size={16} strokeWidth={2} aria-hidden="true" />
|
||||||
|
)}
|
||||||
|
</TooltipButton>
|
||||||
|
);
|
||||||
|
};
|
@ -9,6 +9,8 @@ import {
|
|||||||
} from "@/features/problems/code/components/toolbar";
|
} from "@/features/problems/code/components/toolbar";
|
||||||
import { AnalyzeButton } from "./actions/analyze-button";
|
import { AnalyzeButton } from "./actions/analyze-button";
|
||||||
import { LspConnectionIndicator } from "./controls/lsp-connection-indicator";
|
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 {
|
interface CodeToolbarProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -25,6 +27,8 @@ export const CodeToolbar = async ({ className }: CodeToolbarProps) => {
|
|||||||
<LspConnectionIndicator />
|
<LspConnectionIndicator />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<AIOptimizeButton />
|
||||||
|
<AIDisplayButton />
|
||||||
<AnalyzeButton />
|
<AnalyzeButton />
|
||||||
<ResetButton />
|
<ResetButton />
|
||||||
<UndoButton />
|
<UndoButton />
|
||||||
|
@ -16,6 +16,10 @@ type ProblemEditorState = {
|
|||||||
editor: editor.IStandaloneCodeEditor | null;
|
editor: editor.IStandaloneCodeEditor | null;
|
||||||
lspWebSocket: WebSocket | null;
|
lspWebSocket: WebSocket | null;
|
||||||
markers: editor.IMarker[];
|
markers: editor.IMarker[];
|
||||||
|
useAIEditor: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
AIgenerate: boolean;
|
||||||
|
LastOptimizedCode: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProblemEditorAction = {
|
type ProblemEditorAction = {
|
||||||
@ -26,6 +30,10 @@ type ProblemEditorAction = {
|
|||||||
setEditor: (editor: editor.IStandaloneCodeEditor) => void;
|
setEditor: (editor: editor.IStandaloneCodeEditor) => void;
|
||||||
setLspWebSocket: (lspWebSocket: WebSocket) => void;
|
setLspWebSocket: (lspWebSocket: WebSocket) => void;
|
||||||
setMarkers: (markers: editor.IMarker[]) => 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;
|
type ProblemEditorStore = ProblemEditorState & ProblemEditorAction;
|
||||||
@ -38,6 +46,14 @@ export const useProblemEditorStore = create<ProblemEditorStore>((set, get) => ({
|
|||||||
editor: null,
|
editor: null,
|
||||||
lspWebSocket: null,
|
lspWebSocket: null,
|
||||||
markers: [],
|
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) => {
|
setProblem: (problemId, templates) => {
|
||||||
const language = getLanguage(problemId);
|
const language = getLanguage(problemId);
|
||||||
const value = getValue(problemId, language, templates);
|
const value = getValue(problemId, language, templates);
|
||||||
|
Loading…
Reference in New Issue
Block a user