From 2cbc13e441f370b9ca5f484c6a969fcf0bbb24b0 Mon Sep 17 00:00:00 2001 From: fly6516 Date: Sat, 14 Jun 2025 11:33:17 +0800 Subject: [PATCH 01/32] feat: migrate prototype of ai-optimized-editor feature --- src/app/actions/ai-improve.ts | 143 ++++++++++++++ src/components/ai-optimized-editor.tsx | 250 +++++++++++++++++++++++++ src/types/ai-improve.ts | 19 ++ 3 files changed, 412 insertions(+) create mode 100644 src/app/actions/ai-improve.ts create mode 100644 src/components/ai-optimized-editor.tsx create mode 100644 src/types/ai-improve.ts diff --git a/src/app/actions/ai-improve.ts b/src/app/actions/ai-improve.ts new file mode 100644 index 0000000..e438bf2 --- /dev/null +++ b/src/app/actions/ai-improve.ts @@ -0,0 +1,143 @@ +"use server"; + +import { + OptimizeCodeInput, + OptimizeCodeOutput, + OptimizeCodeOutputSchema, +} from "@/types/ai-improve"; +import { deepseek } from "@/lib/ai"; +import { CoreMessage, generateText } from "ai"; +import prisma from "@/lib/prisma"; + +/** + * 调用AI优化代码 + * @param input 包含代码、错误信息、题目ID的输入 + * @returns 优化后的代码和说明 + */ +export const optimizeCode = async ( + input: OptimizeCodeInput +): Promise => { + const model = deepseek("chat"); + + // 获取题目详情(如果提供了problemId) + let problemDetails = ""; + + if (input.problemId) { + try { + // 尝试获取英文描述 + const problemLocalizationEn = await prisma.problemLocalization.findUnique({ + where: { + problemId_locale_type: { + problemId: input.problemId, + locale: "en", + type: "DESCRIPTION", + }, + }, + include: { + problem: true, + }, + }); + + if (problemLocalizationEn) { + problemDetails = ` +Problem Requirements: +------------------- +Description: ${problemLocalizationEn.content} + `; + } else { + // 回退到中文描述 + const problemLocalizationZh = await prisma.problemLocalization.findUnique({ + where: { + problemId_locale_type: { + problemId: input.problemId, + locale: "zh", + type: "DESCRIPTION", + }, + }, + include: { + problem: true, + }, + }); + + if (problemLocalizationZh) { + problemDetails = ` +Problem Requirements: +------------------- +Description: ${problemLocalizationZh.content} + `; + console.warn(`Fallback to Chinese description for problemId: ${input.problemId}`); + } else { + problemDetails = "Problem description not found in any language."; + console.warn(`No description found for problemId: ${input.problemId}`); + } + } + } catch (error) { + console.error("Failed to fetch problem details:", error); + problemDetails = "Error fetching problem description."; + } + } + + + // 构建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"] +} +`; + console.log("Prompt:", prompt); + + // 发送请求给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"); + } + + console.log("LLM response:", llmResponseJson); + return validationResult.data; +}; \ No newline at end of file diff --git a/src/components/ai-optimized-editor.tsx b/src/components/ai-optimized-editor.tsx new file mode 100644 index 0000000..87d1c2c --- /dev/null +++ b/src/components/ai-optimized-editor.tsx @@ -0,0 +1,250 @@ +"use client"; + +import { useState } from "react"; +import { DiffEditor } from "@monaco-editor/react"; +import { optimizeCode } from "@/app/actions/ai-improve"; +import { OptimizeCodeInput } from "@/types/ai-improve"; +import dynamic from "next/dynamic"; +import { highlighter } from "@/lib/shiki"; +import type { editor } from "monaco-editor"; +import { Loading } from "@/components/loading"; +import { shikiToMonaco } from "@shikijs/monaco"; +import { useProblem } from "@/hooks/use-problem"; +import type { Monaco } from "@monaco-editor/react"; +import { useCallback, useEffect, useRef } from "react"; +import { connectToLanguageServer } from "@/lib/language-server"; +import type { MonacoLanguageClient } from "monaco-languageclient"; +import { DefaultEditorOptionConfig } from "@/config/editor-option"; + +// 动态导入Monaco Editor +const Editor = dynamic( + async () => { + 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 { + editor, + setEditor, + setMarkers, + setWebSocket, + currentLang, + currentPath, + currentTheme, + currentValue, + changeValue, + currentEditorLanguageConfig, + currentLanguageServerConfig, + } = useProblem(); + + const monacoLanguageClientRef = useRef(null); + + // 保持原有AI优化的状态 + const [showDiff, setShowDiff] = useState(false); + const [optimizedCode, setOptimizedCode] = useState(""); + const [isOptimizing, setIsOptimizing] = useState(false); + const [error, setError] = useState(null); + + // 重用useProblem的状态管理 + const currentCode = currentValue || initialCode; + + const handleCodeChange = useCallback((value: string | undefined) => { + if (value !== undefined) { + changeValue(value); + if (onCodeChange) { + onCodeChange(value); + } + } + }, [onCodeChange, changeValue]); + + // 保持原有LSP连接逻辑 + const connectLSP = useCallback(async () => { + if (!(currentLang && editor)) return; + + if (monacoLanguageClientRef.current) { + monacoLanguageClientRef.current.stop(); + monacoLanguageClientRef.current = null; + setWebSocket(null); + } + + if (!currentEditorLanguageConfig || !currentLanguageServerConfig) return; + + try { + const { client: monacoLanguageClient, webSocket } = await connectToLanguageServer( + currentEditorLanguageConfig, + currentLanguageServerConfig + ); + monacoLanguageClientRef.current = monacoLanguageClient; + setWebSocket(webSocket); + } catch (error) { + console.error("Failed to connect to LSP:", error); + } + }, [ + currentEditorLanguageConfig, + currentLang, + currentLanguageServerConfig, + editor, + setWebSocket, + ]); + + useEffect(() => { + connectLSP(); + }, [connectLSP]); + + useEffect(() => { + return () => { + if (monacoLanguageClientRef.current) { + monacoLanguageClientRef.current.stop(); + monacoLanguageClientRef.current = null; + setWebSocket(null); + } + }; + }, [setWebSocket]); + + const handleEditorWillMount = useCallback((monaco: Monaco) => { + shikiToMonaco(highlighter, monaco); + }, []); + + const handleOnMount = useCallback( + async (editor: editor.IStandaloneCodeEditor) => { + setEditor(editor); + await connectLSP(); + }, + [setEditor, connectLSP] + ); + + const handleEditorValidation = useCallback( + (markers: editor.IMarker[]) => { + setMarkers(markers); + }, + [setMarkers] + ); + + 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 ( +
+ {/* 保持原有AI优化按钮 */} +
+ + + {showDiff && ( + + )} +
+ + {error && ( +
+ {error} +
+ )} + +
+ {showDiff ? ( + + ) : ( + } + className="h-full w-full" + /> + )} +
+
+ ); +} \ 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 From 57f52b67faa8841a1ebde72f99bc6dd5d1949b84 Mon Sep 17 00:00:00 2001 From: fly6516 Date: Sat, 14 Jun 2025 13:33:26 +0800 Subject: [PATCH 02/32] =?UTF-8?q?refactor(components):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=20AI=E4=BC=98=E5=8C=96=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 AIProblemEditor 组件改为 AIEditorWrapper 组件 - 移除与语言服务器相关的逻辑和状态管理- 简化了代码结构,提高了组件的可复用性和可维护性 - 优化了 AI 代码优化功能,增加了应用优化结果的按钮 -调整了样式,使界面更加直观 --- src/components/ai-optimized-editor.tsx | 346 ++++++++----------------- 1 file changed, 110 insertions(+), 236 deletions(-) diff --git a/src/components/ai-optimized-editor.tsx b/src/components/ai-optimized-editor.tsx index 87d1c2c..68b082d 100644 --- a/src/components/ai-optimized-editor.tsx +++ b/src/components/ai-optimized-editor.tsx @@ -1,250 +1,124 @@ "use client"; -import { useState } from "react"; +import { useCallback, useState } from "react"; import { DiffEditor } from "@monaco-editor/react"; import { optimizeCode } from "@/app/actions/ai-improve"; -import { OptimizeCodeInput } from "@/types/ai-improve"; -import dynamic from "next/dynamic"; -import { highlighter } from "@/lib/shiki"; -import type { editor } from "monaco-editor"; -import { Loading } from "@/components/loading"; -import { shikiToMonaco } from "@shikijs/monaco"; -import { useProblem } from "@/hooks/use-problem"; -import type { Monaco } from "@monaco-editor/react"; -import { useCallback, useEffect, useRef } from "react"; -import { connectToLanguageServer } from "@/lib/language-server"; -import type { MonacoLanguageClient } from "monaco-languageclient"; -import { DefaultEditorOptionConfig } from "@/config/editor-option"; +import type { OptimizeCodeInput } from "@/types/ai-improve"; +import { CoreEditor } from "./core-editor"; // 引入你刚刚的组件 +// import { Loading } from "@/components/loading"; +import type { LanguageServerConfig } from "@/generated/client"; -// 动态导入Monaco Editor -const Editor = dynamic( - async () => { - await import("vscode"); - const monaco = await import("monaco-editor"); +interface AIEditorWrapperProps { + language?: string; + value?: string; + path?: string; + problemId?: string; + languageServerConfigs?: LanguageServerConfig[]; + onChange?: (value: string) => void; + className?: string; +} - 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 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 handleOptimize = useCallback(async () => { + if (!problemId || !currentCode) 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("AI 优化失败,请稍后重试"); + console.error(err); + } finally { + setIsOptimizing(false); } -); + }, [currentCode, problemId]); -export function AIProblemEditor({ - initialCode = "", - problemId = "", - onCodeChange - }: { - initialCode?: string; - problemId?: string; - onCodeChange?: (code: string) => void; -}) { - const { - editor, - setEditor, - setMarkers, - setWebSocket, - currentLang, - currentPath, - currentTheme, - currentValue, - changeValue, - currentEditorLanguageConfig, - currentLanguageServerConfig, - } = useProblem(); + const handleApplyOptimized = useCallback(() => { + setCurrentCode(optimizedCode); + onChange?.(optimizedCode); + setShowDiff(false); + }, [optimizedCode, onChange]); - const monacoLanguageClientRef = useRef(null); + return ( +
+
+ - // 保持原有AI优化的状态 - const [showDiff, setShowDiff] = useState(false); - const [optimizedCode, setOptimizedCode] = useState(""); - const [isOptimizing, setIsOptimizing] = useState(false); - const [error, setError] = useState(null); - - // 重用useProblem的状态管理 - const currentCode = currentValue || initialCode; - - const handleCodeChange = useCallback((value: string | undefined) => { - if (value !== undefined) { - changeValue(value); - if (onCodeChange) { - onCodeChange(value); - } - } - }, [onCodeChange, changeValue]); - - // 保持原有LSP连接逻辑 - const connectLSP = useCallback(async () => { - if (!(currentLang && editor)) return; - - if (monacoLanguageClientRef.current) { - monacoLanguageClientRef.current.stop(); - monacoLanguageClientRef.current = null; - setWebSocket(null); - } - - if (!currentEditorLanguageConfig || !currentLanguageServerConfig) return; - - try { - const { client: monacoLanguageClient, webSocket } = await connectToLanguageServer( - currentEditorLanguageConfig, - currentLanguageServerConfig - ); - monacoLanguageClientRef.current = monacoLanguageClient; - setWebSocket(webSocket); - } catch (error) { - console.error("Failed to connect to LSP:", error); - } - }, [ - currentEditorLanguageConfig, - currentLang, - currentLanguageServerConfig, - editor, - setWebSocket, - ]); - - useEffect(() => { - connectLSP(); - }, [connectLSP]); - - useEffect(() => { - return () => { - if (monacoLanguageClientRef.current) { - monacoLanguageClientRef.current.stop(); - monacoLanguageClientRef.current = null; - setWebSocket(null); - } - }; - }, [setWebSocket]); - - const handleEditorWillMount = useCallback((monaco: Monaco) => { - shikiToMonaco(highlighter, monaco); - }, []); - - const handleOnMount = useCallback( - async (editor: editor.IStandaloneCodeEditor) => { - setEditor(editor); - await connectLSP(); - }, - [setEditor, connectLSP] - ); - - const handleEditorValidation = useCallback( - (markers: editor.IMarker[]) => { - setMarkers(markers); - }, - [setMarkers] - ); - - 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 ( -
- {/* 保持原有AI优化按钮 */} -
+ {showDiff && ( +
- - {showDiff && ( - - )} -
- - {error && ( -
- {error} -
- )} - -
- {showDiff ? ( - - ) : ( - } - className="h-full w-full" - /> - )} -
+ +
+ )}
- ); -} \ No newline at end of file + + {error && ( +
{error}
+ )} + +
+ {showDiff ? ( + + ) : ( + + )} +
+
+ ); +}; From 69d377403a91102f5cad2c308fbd4ced8ce600e8 Mon Sep 17 00:00:00 2001 From: fly6516 Date: Mon, 16 Jun 2025 18:37:25 +0800 Subject: [PATCH 03/32] =?UTF-8?q?feat(=E9=97=AE=E9=A2=98=E7=BC=96=E8=BE=91?= =?UTF-8?q?):=20add=20problem-editor=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了编辑问题描述、解决方案、详细信息、代码模板和测试用例的组件 - 实现了问题编辑页面的基本布局和功能 - 增加了富文本预览和对比功能 - 支持多种编程语言的代码编辑- 提供了测试用例的添加和删除功能 --- .../(app)/problem-editor/[problemId]/page.tsx | 32 +++++ src/components/creater/edit-code-panel.tsx | 80 ++++++++++++ .../creater/edit-description-panel.tsx | 75 ++++++++++++ src/components/creater/edit-detail-panel.tsx | 67 ++++++++++ .../creater/edit-solution-panel.tsx | 75 ++++++++++++ .../creater/edit-testcase-panel.tsx | 115 ++++++++++++++++++ 6 files changed, 444 insertions(+) create mode 100644 src/app/(app)/problem-editor/[problemId]/page.tsx create mode 100644 src/components/creater/edit-code-panel.tsx create mode 100644 src/components/creater/edit-description-panel.tsx create mode 100644 src/components/creater/edit-detail-panel.tsx create mode 100644 src/components/creater/edit-solution-panel.tsx create mode 100644 src/components/creater/edit-testcase-panel.tsx diff --git a/src/app/(app)/problem-editor/[problemId]/page.tsx b/src/app/(app)/problem-editor/[problemId]/page.tsx new file mode 100644 index 0000000..44bb27f --- /dev/null +++ b/src/app/(app)/problem-editor/[problemId]/page.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { ProblemFlexLayout } from "@/features/problems/components/problem-flexlayout"; +import { EditDescriptionPanel } from "@/components/creater/edit-description-panel"; +import { EditSolutionPanel } from "@/components/creater/edit-solution-panel"; +import { EditTestcasePanel } from "@/components/creater/edit-testcase-panel"; +import { EditDetailPanel } from "@/components/creater/edit-detail-panel"; +import { EditCodePanel } from "@/components/creater/edit-code-panel"; + +interface ProblemEditorPageProps { + params: Promise<{ problemId: string }>; +} + +export default async function ProblemEditorPage({ + params, +}: ProblemEditorPageProps) { + const { problemId } = await params; + + const components: Record = { + description: , + solution: , + detail: , + code: , + testcase: , + }; + + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/src/components/creater/edit-code-panel.tsx b/src/components/creater/edit-code-panel.tsx new file mode 100644 index 0000000..5af31d6 --- /dev/null +++ b/src/components/creater/edit-code-panel.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { useState } from "react"; +import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Button } from "@/components/ui/button"; +import { CoreEditor } from "@/components/core-editor"; + +interface Template { + id: string; + language: string; + code: string; +} + +interface EditCodePanelProps { + problemId: string; +} + +export const EditCodePanel = ({ + problemId, +}: EditCodePanelProps) => { + const [language, setLanguage] = useState("typescript"); + const [templates, setTemplates] = useState([ + { + id: "1", + language: "typescript", + code: `// TypeScript模板示例\nfunction twoSum(nums: number[], target: number): number[] {\n const map = new Map();\n for (let i = 0; i < nums.length; i++) {\n const complement = target - nums[i];\n if (map.has(complement)) {\n return [map.get(complement), i];\n }\n map.set(nums[i], i);\n }\n return [];\n}` + }, + { + id: "2", + language: "python", + code: "# Python模板示例\ndef two_sum(nums, target):\n num_dict = {}\n for i, num in enumerate(nums):\n complement = target - num\n if complement in num_dict:\n return [num_dict[complement], i]\n num_dict[num] = i\n return []" + } + ]); + + const currentTemplate = templates.find(t => t.language === language) || templates[0]; + + const handleCodeChange = (value: string | undefined) => { + if (!value) return; + + setTemplates(templates.map(t => + t.language === language + ? { ...t, code: value } + : t + )); + }; + + return ( +
+
+ + +
+ +
+ {currentTemplate && ( + + )} +
+ + +
+ ); +}; \ No newline at end of file diff --git a/src/components/creater/edit-description-panel.tsx b/src/components/creater/edit-description-panel.tsx new file mode 100644 index 0000000..ef0cbcc --- /dev/null +++ b/src/components/creater/edit-description-panel.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { useState } from "react"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Button } from "@/components/ui/button"; +import MdxPreview from "@/components/mdx-preview"; + +interface EditDescriptionPanelProps { + problemId: string; +} + +export const EditDescriptionPanel = ({ + problemId, +}: EditDescriptionPanelProps) => { + const [title, setTitle] = useState(`Problem ${problemId} Title`); + const [content, setContent] = useState(`Problem ${problemId} Description Content...`); + const [viewMode, setViewMode] = useState<'edit' | 'preview' | 'compare'>('edit'); + + return ( +
+
+ + setTitle(e.target.value)} + placeholder="输入题目标题" + /> +
+
+ + + +
+ +
+
+