From 22ce35ca7d7ec6f4567bc62dd2352b245b16cdb2 Mon Sep 17 00:00:00 2001 From: Dioxide <604834022@qq.com> Date: Wed, 18 Jun 2025 21:14:50 +0800 Subject: [PATCH] The window is basically complete, but there are some issues need to fix. 1. The window won't scroll when complete. 2. The AI needs the context to generate new inputs, I don't know how to write. --- src/app/actions/ai-testcase.ts | 150 +++++++ .../creater/edit-testcase-panel.tsx | 368 ++++++++++-------- src/types/ai-testcase.ts | 21 + 3 files changed, 385 insertions(+), 154 deletions(-) create mode 100644 src/app/actions/ai-testcase.ts create mode 100644 src/types/ai-testcase.ts diff --git a/src/app/actions/ai-testcase.ts b/src/app/actions/ai-testcase.ts new file mode 100644 index 0000000..5bdabc6 --- /dev/null +++ b/src/app/actions/ai-testcase.ts @@ -0,0 +1,150 @@ +"use server"; + +import {AITestCaseInput, AITestCaseOutput, AITestCaseOutputSchema} from "@/types/ai-testcase"; + +import { deepseek } from "@/lib/ai"; +import { CoreMessage, generateText } from "ai"; +import prisma from "@/lib/prisma"; + + +/** + * + * @param input + * @returns + */ +export const generateAITestcase = async ( + input: AITestCaseInput +): Promise => { + const model = deepseek("deepseek-chat"); + + 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 problem statement to get the expected input structure, constraints, and output logic. Generate **novel, randomized** inputs/outputs that strictly adhere to the problem's requirements. Focus on: +Your entire response/output is going to consist of a single JSON object {}, and you will NOT wrap it within JSON Markdown markers. + +1. **Input Data Structure**: Identify required formats (e.g., arrays, integers, strings). +2. **Input Constraints**: Determine valid ranges (e.g., array length: 2–100, integers: -1000 to 1000) and edge cases. +3. **Output Logic**: Ensure outputs correctly reflect problem-specific operations. +4. **Randomization**: + Vary input magnitudes (mix min/max/-edge values with mid-range randomness) + Use diverse data distributions (e.g., sorted/unsorted, negative/positive values) + Avoid patterns from existing examples + +Your entire response/output is going to consist of a single JSON object {}, and you will NOT wrap it within JSON Markdown markers. + +Here is the problem description: + +${problemDetails} + +Respond **ONLY** with this JSON structure. +***Do not wrap the json codes in JSON markers*** : +{ + "expectedOutput": "Randomized output (e.g., [-5, 100] instead of [1, 2])", + "inputs": [ + { + "name": "Parameter 1", + "value": // Use string to wrap actual JSON types (arrays/numbers/strings) + }, + ... // Add parameters as needed + ] +} + + +`; + + // 发送请求给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 { + llmResponseJson = JSON.parse(text) + + + } 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 = AITestCaseOutputSchema.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/creater/edit-testcase-panel.tsx b/src/components/creater/edit-testcase-panel.tsx index 81ec644..7c83cd5 100644 --- a/src/components/creater/edit-testcase-panel.tsx +++ b/src/components/creater/edit-testcase-panel.tsx @@ -1,180 +1,240 @@ "use client"; -import { useState, useEffect } from "react"; -import { Label } from "@/components/ui/label"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { getProblemData } from "@/app/actions/getProblem"; +import {useState, useEffect} from "react"; +import {generateAITestcase} from "@/app/actions/ai-testcase"; +import {Label} from "@/components/ui/label"; +import {Input} from "@/components/ui/input"; +import {Button} from "@/components/ui/button"; +import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"; +import {getProblemData} from "@/app/actions/getProblem"; export default function EditTestcasePanel({ - problemId, + problemId, }: { - problemId: string; + problemId: string; }) { - const [testcases, setTestcases] = useState< - Array<{ - id: string; - expectedOutput: string; - inputs: Array<{ - name: string; - value: string; - }>; - }> - >([]); + const [testcases, setTestcases] = useState< + Array<{ + id: string; + expectedOutput: string; + inputs: Array<{ + name: string; + value: string; + }>; + }> + >([]); - useEffect(() => { - async function fetchData() { - try { - const problemData = await getProblemData(problemId); - if (problemData && problemData.testcases) { - setTestcases(problemData.testcases); - } else { - setTestcases([]); + useEffect(() => { + async function fetchData() { + try { + const problemData = await getProblemData(problemId); + if (problemData && problemData.testcases) { + setTestcases(problemData.testcases); + } else { + setTestcases([]); + } + } catch (error) { + console.error("加载测试用例失败:", error); + setTestcases([]); + } + } + + fetchData(); + }, [problemId]); + + const handleAddTestcase = () => { + setTestcases([ + ...testcases, + { + id: `new-${Date.now()}`, + expectedOutput: "", + inputs: [{name: "input1", value: ""}], + }, + ]); + }; + + const [isGenerating, setIsGenerating] = useState(false); + + const handleAITestcase = async () => { + setIsGenerating(true); + try { + const AIOutputParsed = await generateAITestcase({problemId: problemId}); + setTestcases([ + ...testcases, + { + id: `new-${Date.now()}`, + expectedOutput: AIOutputParsed.expectedOutput, + inputs: AIOutputParsed.inputs + } + ]) + window.scrollTo({ + top: document.body.scrollHeight, + behavior: 'smooth', + }); + } catch (error) { + console.error(error) + } finally { + setIsGenerating(false); } - } catch (error) { - console.error("加载测试用例失败:", error); - setTestcases([]); - } } - fetchData(); - }, [problemId]); - const handleAddTestcase = () => { - setTestcases([ - ...testcases, - { - id: `new-${Date.now()}`, - expectedOutput: "", - inputs: [{ name: "input1", value: "" }], - }, - ]); - }; + const handleRemoveTestcase = (index: number) => { + const newTestcases = [...testcases]; + newTestcases.splice(index, 1); + setTestcases(newTestcases); + }; - const handleRemoveTestcase = (index: number) => { - const newTestcases = [...testcases]; - newTestcases.splice(index, 1); - setTestcases(newTestcases); - }; + const handleInputChange = ( + testcaseIndex: number, + inputIndex: number, + field: "name" | "value", + value: string + ) => { + const newTestcases = [...testcases]; + newTestcases[testcaseIndex].inputs[inputIndex][field] = value; + setTestcases(newTestcases); + }; - const handleInputChange = ( - testcaseIndex: number, - inputIndex: number, - field: "name" | "value", - value: string - ) => { - const newTestcases = [...testcases]; - newTestcases[testcaseIndex].inputs[inputIndex][field] = value; - setTestcases(newTestcases); - }; + const handleExpectedOutputChange = (testcaseIndex: number, value: string) => { + const newTestcases = [...testcases]; + newTestcases[testcaseIndex].expectedOutput = value; + setTestcases(newTestcases); + }; - const handleExpectedOutputChange = (testcaseIndex: number, value: string) => { - const newTestcases = [...testcases]; - newTestcases[testcaseIndex].expectedOutput = value; - setTestcases(newTestcases); - }; + const handleAddInput = (testcaseIndex: number) => { + const newTestcases = [...testcases]; + newTestcases[testcaseIndex].inputs.push({ + name: `input${newTestcases[testcaseIndex].inputs.length + 1}`, + value: "", + }); + setTestcases(newTestcases); + }; - const handleAddInput = (testcaseIndex: number) => { - const newTestcases = [...testcases]; - newTestcases[testcaseIndex].inputs.push({ - name: `input${newTestcases[testcaseIndex].inputs.length + 1}`, - value: "", - }); - setTestcases(newTestcases); - }; + const handleRemoveInput = (testcaseIndex: number, inputIndex: number) => { + const newTestcases = [...testcases]; + newTestcases[testcaseIndex].inputs.splice(inputIndex, 1); + setTestcases(newTestcases); + }; - const handleRemoveInput = (testcaseIndex: number, inputIndex: number) => { - const newTestcases = [...testcases]; - newTestcases[testcaseIndex].inputs.splice(inputIndex, 1); - setTestcases(newTestcases); - }; - - return ( - - - 测试用例 - - - -
- {testcases.map((testcase, index) => ( -
-
-

测试用例 {index + 1}

+ return ( + + + 测试用例 +
{/* space-x-1 让按钮更接近 */} + -
+
+ + +
+ {testcases.map((testcase, index) => ( +
+
+

测试用例 {index + 1}

+ +
-
- - handleExpectedOutputChange(index, e.target.value)} - placeholder="输入预期输出" - /> -
+
+ + handleExpectedOutputChange(index, e.target.value)} + placeholder="输入预期输出" + /> +
-
-
- - -
+
+
+ + +
- {testcase.inputs.map((input, inputIndex) => ( -
-
- - - handleInputChange(index, inputIndex, "name", e.target.value) - } - placeholder="输入参数名称" - /> -
-
- - - handleInputChange(index, inputIndex, "value", e.target.value) - } - placeholder="输入参数值" - /> -
- {inputIndex > 0 && ( - - )} + {testcase.inputs.map((input, inputIndex) => ( +
+
+ + + handleInputChange(index, inputIndex, "name", e.target.value) + } + placeholder="输入参数名称" + /> +
+
+ + + handleInputChange(index, inputIndex, "value", e.target.value) + } + placeholder="输入参数值" + /> +
+ {inputIndex > 0 && ( + + )} +
+ ))} +
))} -
- ))} -
-
- - ); + + + + ); } diff --git a/src/types/ai-testcase.ts b/src/types/ai-testcase.ts new file mode 100644 index 0000000..a142d13 --- /dev/null +++ b/src/types/ai-testcase.ts @@ -0,0 +1,21 @@ +import {z} from "zod"; + +export const AITestCaseInputSchema = z.object({ + problemId: z.string(), +}) + +export type AITestCaseInput = z.infer + +const input = z.object({ + name: z.string(), + value: z.string() +}) + +export const AITestCaseOutputSchema = z.object({ + expectedOutput: z.string(), + inputs: z.array(input) +}) + +export type AITestCaseOutput = z.infer + +