mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-07-04 01:10:53 +00:00
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:
parent
79d56204ce
commit
22ce35ca7d
150
src/app/actions/ai-testcase.ts
Normal file
150
src/app/actions/ai-testcase.ts
Normal file
@ -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<AITestCaseOutput> => {
|
||||||
|
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": <RANDOMIZED_DATA> // 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;
|
||||||
|
};
|
@ -1,11 +1,12 @@
|
|||||||
"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,
|
||||||
@ -37,6 +38,7 @@ export default function EditTestcasePanel({
|
|||||||
setTestcases([]);
|
setTestcases([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [problemId]);
|
}, [problemId]);
|
||||||
|
|
||||||
@ -46,11 +48,36 @@ export default function EditTestcasePanel({
|
|||||||
{
|
{
|
||||||
id: `new-${Date.now()}`,
|
id: `new-${Date.now()}`,
|
||||||
expectedOutput: "",
|
expectedOutput: "",
|
||||||
inputs: [{ name: "input1", value: "" }],
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleRemoveTestcase = (index: number) => {
|
const handleRemoveTestcase = (index: number) => {
|
||||||
const newTestcases = [...testcases];
|
const newTestcases = [...testcases];
|
||||||
newTestcases.splice(index, 1);
|
newTestcases.splice(index, 1);
|
||||||
@ -93,9 +120,41 @@ export default function EditTestcasePanel({
|
|||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>测试用例</CardTitle>
|
<CardTitle>测试用例</CardTitle>
|
||||||
|
<div className="flex items-center space-x-1"> {/* space-x-1 让按钮更接近 */}
|
||||||
<Button type="button" onClick={handleAddTestcase}>
|
<Button type="button" onClick={handleAddTestcase}>
|
||||||
添加测试用例
|
添加测试用例
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@ -176,5 +235,6 @@ export default function EditTestcasePanel({
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
21
src/types/ai-testcase.ts
Normal file
21
src/types/ai-testcase.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {z} from "zod";
|
||||||
|
|
||||||
|
export const AITestCaseInputSchema = z.object({
|
||||||
|
problemId: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type AITestCaseInput = z.infer<typeof AITestCaseInputSchema>
|
||||||
|
|
||||||
|
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<typeof AITestCaseOutputSchema>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user