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.
This commit is contained in:
Dioxide 2025-06-18 21:14:50 +08:00 committed by cfngc4594
parent 9b7d7a8cfd
commit 16623f4b4d

View File

@ -1,180 +1,240 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import {useState, useEffect} from "react";
import { Label } from "@/components/ui/label"; import {generateAITestcase} from "@/app/actions/ai-testcase";
import { Input } from "@/components/ui/input"; import {Label} from "@/components/ui/label";
import { Button } from "@/components/ui/button"; import {Input} from "@/components/ui/input";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import {Button} from "@/components/ui/button";
import { getProblemData } from "@/app/actions/getProblem"; import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
import {getProblemData} from "@/app/actions/getProblem";
export default function EditTestcasePanel({ export default function EditTestcasePanel({
problemId, problemId,
}: { }: {
problemId: string; problemId: string;
}) { }) {
const [testcases, setTestcases] = useState< const [testcases, setTestcases] = useState<
Array<{ Array<{
id: string; id: string;
expectedOutput: string; expectedOutput: string;
inputs: Array<{ inputs: Array<{
name: string; name: string;
value: string; value: string;
}>; }>;
}> }>
>([]); >([]);
useEffect(() => { useEffect(() => {
async function fetchData() { async function fetchData() {
try { try {
const problemData = await getProblemData(problemId); const problemData = await getProblemData(problemId);
if (problemData && problemData.testcases) { if (problemData && problemData.testcases) {
setTestcases(problemData.testcases); setTestcases(problemData.testcases);
} else { } else {
setTestcases([]); 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 = () => { const handleRemoveTestcase = (index: number) => {
setTestcases([ const newTestcases = [...testcases];
...testcases, newTestcases.splice(index, 1);
{ setTestcases(newTestcases);
id: `new-${Date.now()}`, };
expectedOutput: "",
inputs: [{ name: "input1", value: "" }],
},
]);
};
const handleRemoveTestcase = (index: number) => { const handleInputChange = (
const newTestcases = [...testcases]; testcaseIndex: number,
newTestcases.splice(index, 1); inputIndex: number,
setTestcases(newTestcases); field: "name" | "value",
}; value: string
) => {
const newTestcases = [...testcases];
newTestcases[testcaseIndex].inputs[inputIndex][field] = value;
setTestcases(newTestcases);
};
const handleInputChange = ( const handleExpectedOutputChange = (testcaseIndex: number, value: string) => {
testcaseIndex: number, const newTestcases = [...testcases];
inputIndex: number, newTestcases[testcaseIndex].expectedOutput = value;
field: "name" | "value", setTestcases(newTestcases);
value: string };
) => {
const newTestcases = [...testcases];
newTestcases[testcaseIndex].inputs[inputIndex][field] = value;
setTestcases(newTestcases);
};
const handleExpectedOutputChange = (testcaseIndex: number, value: string) => { const handleAddInput = (testcaseIndex: number) => {
const newTestcases = [...testcases]; const newTestcases = [...testcases];
newTestcases[testcaseIndex].expectedOutput = value; newTestcases[testcaseIndex].inputs.push({
setTestcases(newTestcases); name: `input${newTestcases[testcaseIndex].inputs.length + 1}`,
}; value: "",
});
setTestcases(newTestcases);
};
const handleAddInput = (testcaseIndex: number) => { const handleRemoveInput = (testcaseIndex: number, inputIndex: number) => {
const newTestcases = [...testcases]; const newTestcases = [...testcases];
newTestcases[testcaseIndex].inputs.push({ newTestcases[testcaseIndex].inputs.splice(inputIndex, 1);
name: `input${newTestcases[testcaseIndex].inputs.length + 1}`, setTestcases(newTestcases);
value: "", };
});
setTestcases(newTestcases);
};
const handleRemoveInput = (testcaseIndex: number, inputIndex: number) => { return (
const newTestcases = [...testcases]; <Card className="w-full">
newTestcases[testcaseIndex].inputs.splice(inputIndex, 1); <CardHeader>
setTestcases(newTestcases); <CardTitle></CardTitle>
}; <div className="flex items-center space-x-1"> {/* space-x-1 让按钮更接近 */}
<Button type="button" onClick={handleAddTestcase}>
return (
<Card className="w-full"> </Button>
<CardHeader>
<CardTitle></CardTitle>
<Button type="button" onClick={handleAddTestcase}>
</Button>
</CardHeader>
<CardContent>
<div className="space-y-6">
{testcases.map((testcase, index) => (
<div key={testcase.id} className="border p-4 rounded-md space-y-4">
<div className="flex justify-between items-center">
<h3 className="text-lg font-medium"> {index + 1}</h3>
<Button <Button
type="button" type="button"
variant="destructive" className="flex items-center gap-1"
onClick={() => handleRemoveTestcase(index)} onClick={handleAITestcase}
disabled={isGenerating}
style={{
opacity: isGenerating ? 0.7 : 1,
cursor: isGenerating ? 'not-allowed' : 'pointer'
}}
> >
<svg
data-testid="geist-icon"
height="16"
strokeLinejoin="round"
style={{color: isGenerating ? '#888' : "currentColor"}}
viewBox="0 0 16 16"
width="16"
>
<path
d="M2.5 0.5V0H3.5V0.5C3.5 1.60457 4.39543 2.5 5.5 2.5H6V3V3.5H5.5C4.39543 3.5 3.5 4.39543 3.5 5.5V6H3H2.5V5.5C2.5 4.39543 1.60457 3.5 0.5 3.5H0V3V2.5H0.5C1.60457 2.5 2.5 1.60457 2.5 0.5Z"
fill="currentColor"></path>
<path
d="M14.5 4.5V5H13.5V4.5C13.5 3.94772 13.0523 3.5 12.5 3.5H12V3V2.5H12.5C13.0523 2.5 13.5 2.05228 13.5 1.5V1H14H14.5V1.5C14.5 2.05228 14.9477 2.5 15.5 2.5H16V3V3.5H15.5C14.9477 3.5 14.5 3.94772 14.5 4.5Z"
fill="currentColor"></path>
<path
d="M8.40706 4.92939L8.5 4H9.5L9.59294 4.92939C9.82973 7.29734 11.7027 9.17027 14.0706 9.40706L15 9.5V10.5L14.0706 10.5929C11.7027 10.8297 9.82973 12.7027 9.59294 15.0706L9.5 16H8.5L8.40706 15.0706C8.17027 12.7027 6.29734 10.8297 3.92939 10.5929L3 10.5V9.5L3.92939 9.40706C6.29734 9.17027 8.17027 7.29734 8.40706 4.92939Z"
fill="currentColor"></path>
</svg>
{isGenerating ? '生成中...' : '使用AI生成测试用例'}
</Button> </Button>
</div> </div>
</CardHeader>
<CardContent>
<div className="space-y-6">
{testcases.map((testcase, index) => (
<div key={testcase.id} className="border p-4 rounded-md space-y-4">
<div className="flex justify-between items-center">
<h3 className="text-lg font-medium"> {index + 1}</h3>
<Button
type="button"
variant="destructive"
onClick={() => handleRemoveTestcase(index)}
>
</Button>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`expected-output-${index}`}></Label> <Label htmlFor={`expected-output-${index}`}></Label>
<Input <Input
id={`expected-output-${index}`} id={`expected-output-${index}`}
value={testcase.expectedOutput} value={testcase.expectedOutput}
onChange={(e) => handleExpectedOutputChange(index, e.target.value)} onChange={(e) => handleExpectedOutputChange(index, e.target.value)}
placeholder="输入预期输出" placeholder="输入预期输出"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<Label></Label> <Label></Label>
<Button type="button" onClick={() => handleAddInput(index)}> <Button type="button" onClick={() => handleAddInput(index)}>
</Button> </Button>
</div> </div>
{testcase.inputs.map((input, inputIndex) => ( {testcase.inputs.map((input, inputIndex) => (
<div key={input.name} className="grid grid-cols-2 gap-4"> <div key={input.name} className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`input-name-${index}-${inputIndex}`}> <Label htmlFor={`input-name-${index}-${inputIndex}`}>
</Label> </Label>
<Input <Input
id={`input-name-${index}-${inputIndex}`} id={`input-name-${index}-${inputIndex}`}
value={input.name} value={input.name}
onChange={(e) => onChange={(e) =>
handleInputChange(index, inputIndex, "name", e.target.value) handleInputChange(index, inputIndex, "name", e.target.value)
} }
placeholder="输入参数名称" placeholder="输入参数名称"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor={`input-value-${index}-${inputIndex}`}> <Label htmlFor={`input-value-${index}-${inputIndex}`}>
</Label> </Label>
<Input <Input
id={`input-value-${index}-${inputIndex}`} id={`input-value-${index}-${inputIndex}`}
value={input.value} value={input.value}
onChange={(e) => onChange={(e) =>
handleInputChange(index, inputIndex, "value", e.target.value) handleInputChange(index, inputIndex, "value", e.target.value)
} }
placeholder="输入参数值" placeholder="输入参数值"
/> />
</div> </div>
{inputIndex > 0 && ( {inputIndex > 0 && (
<Button <Button
type="button" type="button"
variant="outline" variant="outline"
onClick={() => handleRemoveInput(index, inputIndex)} onClick={() => handleRemoveInput(index, inputIndex)}
className="w-full" className="w-full"
> >
</Button> </Button>
)} )}
</div>
))}
</div>
</div> </div>
))} ))}
</div>
</div> </div>
))} </CardContent>
</div> </Card>
</CardContent>
</Card> );
);
} }