refactor: optimize code

This commit is contained in:
cfngc4594 2025-06-22 11:23:40 +08:00
parent c4622fa586
commit 0dea7a4229
12 changed files with 366 additions and 191 deletions

View File

@ -157,6 +157,14 @@
} }
}, },
"WorkspaceEditorHeader": { "WorkspaceEditorHeader": {
"ViewToggleButton": {
"ShowCodeView": "Show code view",
"ShowDiffView": "Show diff view"
},
"OptimizeButton":{
"TooltipContent": "Optimize Code",
"Error": "Error occurred while optimizing code, please try again later."
},
"LspStatusButton": { "LspStatusButton": {
"TooltipContent": "Language Server" "TooltipContent": "Language Server"
}, },

View File

@ -157,6 +157,14 @@
} }
}, },
"WorkspaceEditorHeader": { "WorkspaceEditorHeader": {
"ViewToggleButton": {
"ShowCodeView": "查看源代码",
"ShowDiffView": "查看优化代码"
},
"OptimizeButton":{
"TooltipContent": "优化代码",
"Error": "优化代码时出错,请稍后重试。"
},
"LspStatusButton": { "LspStatusButton": {
"TooltipContent": "语言服务" "TooltipContent": "语言服务"
}, },

View File

@ -66,7 +66,9 @@ export const analyzeComplexity = async (
return validationResult.data; return validationResult.data;
}; };
export const getAnalysis = async (submissionId: string):Promise<CodeAnalysis> => { export const getAnalysis = async (
submissionId: string
): Promise<CodeAnalysis> => {
const session = await auth(); const session = await auth();
if (!session?.user?.id) { if (!session?.user?.id) {
@ -85,3 +87,32 @@ export const getAnalysis = async (submissionId: string):Promise<CodeAnalysis> =>
return analysis; return analysis;
}; };
export const optimizeCode = async (content: string): Promise<string> => {
const model = openai("gpt-4o-mini");
const prompt = `
Optimize the following code snippet for better performance, readability, and maintainability.
Provide ONLY the improved code without any explanations, comments, or additional text.
Code to optimize:
\`\`\`
${content}
\`\`\`
Respond ONLY with the optimized code. Do not include any other text or markdown formatting like \`\`\`.
`;
const messages: CoreMessage[] = [{ role: "user", content: prompt }];
try {
const response = await generateText({
model: model,
messages: messages,
});
return response.text.trim();
} catch (error) {
console.error("Error optimizing code:", error);
throw new Error("Failed to optimize code");
}
};

View File

@ -1,68 +1,127 @@
"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 { useMonacoTheme } from "@/hooks/use-monaco-theme"; import type { OptimizeCodeInput } from "@/types/ai-improve";
import React, { useState, useEffect, useCallback } from "react"; import { CoreEditor } from "./core-editor"; // 引入你刚刚的组件
import { useProblemEditorStore } from "@/stores/problem-editor"; // import { Loading } from "@/components/loading";
import type { LanguageServerConfig } from "@/generated/client";
export const AIEditorWrapper = () => { interface AIEditorWrapperProps {
const { language?: string;
value?: string;
path?: string;
problemId?: string;
languageServerConfigs?: LanguageServerConfig[];
onChange?: (value: string) => void;
className?: string;
}
export const AIEditorWrapper = ({
language, language,
value: originalCode, value,
setLoading, path,
AIgenerate, problemId,
LastOptimizedCode, languageServerConfigs,
setLastOptimizedCode, onChange,
} = useProblemEditorStore(); }: // 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 [optimizedCode, setOptimizedCode] = useState<string>(""); const handleCodeChange = useCallback(
const { theme } = useMonacoTheme(); (val: string) => {
setCurrentCode(val);
onChange?.(val);
},
[onChange]
);
const handleOptimize = useCallback(async () => { const handleOptimize = useCallback(async () => {
setLoading(true); if (!problemId || !currentCode) return;
try { setIsOptimizing(true);
const res = await optimizeCode({ setError(null);
code: originalCode,
error: "",
problemId: "",
});
setOptimizedCode(res.optimizedCode);
setLastOptimizedCode(res.optimizedCode);
} catch (err) {
console.error("优化失败", err);
setOptimizedCode("// 优化失败,请稍后重试");
} finally {
setLoading(false);
}
}, [originalCode, setLoading, setLastOptimizedCode]);
useEffect(() => { try {
if (AIgenerate) { const input: OptimizeCodeInput = {
handleOptimize(); code: currentCode,
} else if (LastOptimizedCode) { problemId,
setOptimizedCode(LastOptimizedCode); };
const result = await optimizeCode(input);
setOptimizedCode(result.optimizedCode);
setShowDiff(true);
} catch (err) {
setError("AI 优化失败,请稍后重试");
console.error(err);
} finally {
setIsOptimizing(false);
} }
}, [AIgenerate, LastOptimizedCode, handleOptimize]); }, [currentCode, problemId]);
const handleApplyOptimized = useCallback(() => {
setCurrentCode(optimizedCode);
onChange?.(optimizedCode);
setShowDiff(false);
}, [optimizedCode, onChange]);
return ( return (
<div className="w-full h-[80vh] flex flex-col gap-4"> <div className="flex flex-col h-full w-full">
{optimizedCode && ( <div className="flex items-center justify-between p-4">
<div className="flex-1"> <button
<DiffEditor onClick={handleOptimize}
language={language} disabled={isOptimizing}
original={originalCode} className="px-4 py-2 bg-primary text-white rounded hover:bg-primary/90"
modified={optimizedCode} >
height="100%" {isOptimizing ? "优化中..." : "AI优化代码"}
theme={theme} </button>
options={{
readOnly: true, {showDiff && (
renderSideBySide: true, <div className="space-x-2">
automaticLayout: true, <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>
)} )}
</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

@ -0,0 +1,70 @@
"use client";
import dynamic from "next/dynamic";
import type { editor } from "monaco-editor";
import { useCallback, useRef } from "react";
import { getHighlighter } from "@/lib/shiki";
import { Loading } from "@/components/loading";
import { shikiToMonaco } from "@shikijs/monaco";
import type { Monaco } from "@monaco-editor/react";
import { DEFAULT_EDITOR_OPTIONS } from "@/config/editor";
import { useMonacoTheme } from "@/hooks/use-monaco-theme";
const MonacoEditor = dynamic(
async () => {
const react = await import("@monaco-editor/react");
return react.DiffEditor;
},
{
ssr: false,
loading: () => <Loading />,
}
);
interface CoreDiffEditorProps {
language?: string;
original?: string;
modified?: string;
onEditorReady?: (editor: editor.IStandaloneDiffEditor) => void;
className?: string;
}
export const CoreDiffEditor = ({
language,
original,
modified,
onEditorReady,
className,
}: CoreDiffEditorProps) => {
const { theme } = useMonacoTheme();
const editorRef = useRef<editor.IStandaloneDiffEditor | null>(null);
const handleBeforeMount = useCallback((monaco: Monaco) => {
const highlighter = getHighlighter();
shikiToMonaco(highlighter, monaco);
}, []);
const handleOnMount = useCallback(
(editor: editor.IStandaloneDiffEditor) => {
editorRef.current = editor;
onEditorReady?.(editor);
},
[onEditorReady]
);
return (
<MonacoEditor
theme={theme}
language={language}
original={original}
modified={modified}
beforeMount={handleBeforeMount}
onMount={handleOnMount}
options={{ ...DEFAULT_EDITOR_OPTIONS, readOnly: true }}
loading={<Loading />}
className={className}
/>
);
};

View File

@ -2,8 +2,8 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { CoreEditor } from "@/components/core-editor"; import { CoreEditor } from "@/components/core-editor";
import { CoreDiffEditor } from "@/components/core-diff-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 {
@ -20,30 +20,23 @@ export const ProblemEditor = ({
const { const {
language, language,
value, value,
optimizedCode,
path, path,
setProblem, setProblem,
setValue, setValue,
setEditor, setEditor,
setDiffEditor,
setLspWebSocket, setLspWebSocket,
setMarkers, setMarkers,
useAIEditor, showDiffView,
// setUseAIEditor
} = useProblemEditorStore(); } = useProblemEditorStore();
useEffect(() => { useEffect(() => {
setProblem(problemId, templates); setProblem(problemId, templates);
}, [problemId, setProblem, templates]); }, [problemId, setProblem, templates]);
if (!optimizedCode) {
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}
@ -53,12 +46,27 @@ export const ProblemEditor = ({
onLspWebSocketReady={setLspWebSocket} onLspWebSocketReady={setLspWebSocket}
onMarkersReady={setMarkers} onMarkersReady={setMarkers}
onChange={setValue} onChange={setValue}
className="h-[80vh] w-full"
/> />
</> );
}
return showDiffView ? (
<CoreDiffEditor
language={language}
original={value}
modified={optimizedCode}
onEditorReady={setDiffEditor}
/>
) : ( ) : (
<AIEditorWrapper /> <CoreEditor
)} language={language}
</div> value={optimizedCode}
path={path}
languageServerConfigs={languageServerConfigs}
onEditorReady={setEditor}
onLspWebSocketReady={setLspWebSocket}
onMarkersReady={setMarkers}
onChange={setValue}
/>
); );
}; };

View File

@ -1,44 +0,0 @@
"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>
);
};

View File

@ -1,42 +0,0 @@
"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>
);
};

View File

@ -0,0 +1,52 @@
"use client";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { optimizeCode } from "@/app/actions/analyze";
import { LoaderCircleIcon, Wand2Icon } from "lucide-react";
import { TooltipButton } from "@/components/tooltip-button";
import { useProblemEditorStore } from "@/stores/problem-editor";
import { useProblemEditorActions } from "@/features/problems/code/hooks/use-problem-editor-actions";
export const OptimizeButton = () => {
const t = useTranslations("WorkspaceEditorHeader.OptimizeButton");
const { value, setOptimizedCode } = useProblemEditorStore();
const { canExecute } = useProblemEditorActions();
// const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleClick = async () => {
// setError(null);
setIsLoading(true);
setOptimizedCode("");
try {
const optimizedCode = await optimizeCode(value);
setOptimizedCode(optimizedCode);
} catch (error) {
console.error("Error analyzing complexity:", error);
setOptimizedCode("");
// setError(t("Error"));
} finally {
setIsLoading(false);
}
};
return (
<TooltipButton
tooltipContent={t("TooltipContent")}
onClick={handleClick}
disabled={!canExecute || isLoading}
>
{isLoading ? (
<LoaderCircleIcon
className="opacity-60 animate-spin"
size={16}
strokeWidth={2}
aria-hidden="true"
/>
) : (
<Wand2Icon size={16} strokeWidth={2} aria-hidden="true" />
)}
</TooltipButton>
);
};

View File

@ -0,0 +1,24 @@
"use client";
import { useTranslations } from "next-intl";
import { CodeXmlIcon, GitCompareIcon } from "lucide-react";
import { TooltipButton } from "@/components/tooltip-button";
import { useProblemEditorStore } from "@/stores/problem-editor";
export const ViewToggleButton = () => {
const t = useTranslations("WorkspaceEditorHeader.ViewToggleButton");
const { showDiffView, toggleView } = useProblemEditorStore();
return (
<TooltipButton
tooltipContent={showDiffView ? t("ShowCodeView") : t("ShowDiffView")}
onClick={toggleView}
>
{showDiffView ? (
<CodeXmlIcon size={16} strokeWidth={2} aria-hidden="true" />
) : (
<GitCompareIcon size={16} strokeWidth={2} aria-hidden="true" />
)}
</TooltipButton>
);
};

View File

@ -1,3 +1,5 @@
"use client";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
CopyButton, CopyButton,
@ -7,16 +9,19 @@ import {
ResetButton, ResetButton,
UndoButton, UndoButton,
} from "@/features/problems/code/components/toolbar"; } from "@/features/problems/code/components/toolbar";
import { AnalyzeButton } from "./actions/analyze-button"; import { useProblemEditorStore } from "@/stores/problem-editor";
import { LspConnectionIndicator } from "./controls/lsp-connection-indicator"; import { AnalyzeButton } from "@/features/problems/code/components/toolbar/actions/analyze-button";
import { AIDisplayButton } from "@/features/problems/code/components/toolbar/actions/AIDisplayButton"; import { OptimizeButton } from "@/features/problems/code/components/toolbar/actions/optimize-button";
import { AIOptimizeButton } from "@/features/problems/code/components/toolbar/actions/AIOptimizeButton"; import { ViewToggleButton } from "@/features/problems/code/components/toolbar/actions/view-toggle-button";
import { LspConnectionIndicator } from "@/features/problems/code/components/toolbar/controls/lsp-connection-indicator";
interface CodeToolbarProps { interface CodeToolbarProps {
className?: string; className?: string;
} }
export const CodeToolbar = async ({ className }: CodeToolbarProps) => { export const CodeToolbar = ({ className }: CodeToolbarProps) => {
const { optimizedCode } = useProblemEditorStore();
return ( return (
<header <header
className={cn("relative flex h-8 flex-none items-center", className)} className={cn("relative flex h-8 flex-none items-center", className)}
@ -27,8 +32,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 /> {optimizedCode && <ViewToggleButton />}
<AIDisplayButton /> <OptimizeButton />
<AnalyzeButton /> <AnalyzeButton />
<ResetButton /> <ResetButton />
<UndoButton /> <UndoButton />

View File

@ -12,28 +12,26 @@ type ProblemEditorState = {
problem: Problem | null; problem: Problem | null;
language: Language; language: Language;
value: string; value: string;
optimizedCode: string;
path: string; path: string;
editor: editor.IStandaloneCodeEditor | null; editor: editor.IStandaloneCodeEditor | null;
diffEditor: editor.IStandaloneDiffEditor | null;
lspWebSocket: WebSocket | null; lspWebSocket: WebSocket | null;
markers: editor.IMarker[]; markers: editor.IMarker[];
useAIEditor: boolean; showDiffView: boolean;
loading: boolean;
AIgenerate: boolean;
LastOptimizedCode: string;
}; };
type ProblemEditorAction = { type ProblemEditorAction = {
setProblem: (problemId: string, templates: Template[]) => void; setProblem: (problemId: string, templates: Template[]) => void;
setLanguage: (language: Language) => void; setLanguage: (language: Language) => void;
setValue: (value: string) => void; setValue: (value: string) => void;
setOptimizedCode: (value: string) => void;
setPath: (path: string) => void; setPath: (path: string) => void;
setEditor: (editor: editor.IStandaloneCodeEditor) => void; setEditor: (editor: editor.IStandaloneCodeEditor) => void;
setDiffEditor: (diffEditor: editor.IStandaloneDiffEditor) => void;
setLspWebSocket: (lspWebSocket: WebSocket) => void; setLspWebSocket: (lspWebSocket: WebSocket) => void;
setMarkers: (markers: editor.IMarker[]) => void; setMarkers: (markers: editor.IMarker[]) => void;
setUseAIEditor: (flag: boolean) => void; toggleView: () => void;
setLoading: (flag: boolean) => void;
setAIgenerate: (flag: boolean) => void;
setLastOptimizedCode: (code: string) => void;
}; };
type ProblemEditorStore = ProblemEditorState & ProblemEditorAction; type ProblemEditorStore = ProblemEditorState & ProblemEditorAction;
@ -42,18 +40,13 @@ export const useProblemEditorStore = create<ProblemEditorStore>((set, get) => ({
problem: null, problem: null,
language: Language.c, language: Language.c,
value: "", value: "",
optimizedCode: "",
path: "", path: "",
editor: null, editor: null,
diffEditor: null,
lspWebSocket: null, lspWebSocket: null,
markers: [], markers: [],
useAIEditor: false, showDiffView: 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);
@ -80,10 +73,13 @@ export const useProblemEditorStore = create<ProblemEditorStore>((set, get) => ({
} }
set({ value }); set({ value });
}, },
setOptimizedCode: (optimizedCode) => set({ optimizedCode }),
setPath: (path) => set({ path }), setPath: (path) => set({ path }),
setEditor: (editor) => set({ editor }), setEditor: (editor) => set({ editor }),
setDiffEditor: (diffEditor) => set({ diffEditor }),
setLspWebSocket: (lspWebSocket) => set({ lspWebSocket }), setLspWebSocket: (lspWebSocket) => set({ lspWebSocket }),
setMarkers: (markers) => set({ markers }), setMarkers: (markers) => set({ markers }),
toggleView: () => set((state) => ({ showDiffView: !state.showDiffView })),
})); }));
const getStoredItem = <T extends string>( const getStoredItem = <T extends string>(