mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-07-04 09:20:53 +00:00
style: normalize quotes and indentation
This commit is contained in:
parent
e85b8e967b
commit
cd1127e051
@ -25,7 +25,8 @@ export const optimizeCode = async (
|
|||||||
if (input.problemId) {
|
if (input.problemId) {
|
||||||
try {
|
try {
|
||||||
// 尝试获取英文描述
|
// 尝试获取英文描述
|
||||||
const problemLocalizationEn = await prisma.problemLocalization.findUnique({
|
const problemLocalizationEn = await prisma.problemLocalization.findUnique(
|
||||||
|
{
|
||||||
where: {
|
where: {
|
||||||
problemId_locale_type: {
|
problemId_locale_type: {
|
||||||
problemId: input.problemId,
|
problemId: input.problemId,
|
||||||
@ -36,7 +37,8 @@ export const optimizeCode = async (
|
|||||||
include: {
|
include: {
|
||||||
problem: true,
|
problem: true,
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (problemLocalizationEn) {
|
if (problemLocalizationEn) {
|
||||||
problemDetails = `
|
problemDetails = `
|
||||||
@ -46,7 +48,8 @@ Description: ${problemLocalizationEn.content}
|
|||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
// 回退到中文描述
|
// 回退到中文描述
|
||||||
const problemLocalizationZh = await prisma.problemLocalization.findUnique({
|
const problemLocalizationZh =
|
||||||
|
await prisma.problemLocalization.findUnique({
|
||||||
where: {
|
where: {
|
||||||
problemId_locale_type: {
|
problemId_locale_type: {
|
||||||
problemId: input.problemId,
|
problemId: input.problemId,
|
||||||
@ -65,10 +68,14 @@ Problem Requirements:
|
|||||||
-------------------
|
-------------------
|
||||||
Description: ${problemLocalizationZh.content}
|
Description: ${problemLocalizationZh.content}
|
||||||
`;
|
`;
|
||||||
console.warn(`Fallback to Chinese description for problemId: ${input.problemId}`);
|
console.warn(
|
||||||
|
`Fallback to Chinese description for problemId: ${input.problemId}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
problemDetails = "Problem description not found in any language.";
|
problemDetails = "Problem description not found in any language.";
|
||||||
console.warn(`No description found for problemId: ${input.problemId}`);
|
console.warn(
|
||||||
|
`No description found for problemId: ${input.problemId}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -77,7 +84,6 @@ Description: ${problemLocalizationZh.content}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 构建AI提示词
|
// 构建AI提示词
|
||||||
const prompt = `
|
const prompt = `
|
||||||
Analyze the following programming code for potential errors, inefficiencies or code style issues.
|
Analyze the following programming code for potential errors, inefficiencies or code style issues.
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import {AITestCaseInput, AITestCaseOutput, AITestCaseOutputSchema} from "@/types/ai-testcase";
|
import {
|
||||||
|
AITestCaseInput,
|
||||||
|
AITestCaseOutput,
|
||||||
|
AITestCaseOutputSchema,
|
||||||
|
} from "@/types/ai-testcase";
|
||||||
|
|
||||||
import { deepseek } from "@/lib/ai";
|
import { deepseek } from "@/lib/ai";
|
||||||
import { CoreMessage, generateText } from "ai";
|
import { CoreMessage, generateText } from "ai";
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param input
|
* @param input
|
||||||
@ -22,7 +25,8 @@ export const generateAITestcase = async (
|
|||||||
if (input.problemId) {
|
if (input.problemId) {
|
||||||
try {
|
try {
|
||||||
// 尝试获取英文描述
|
// 尝试获取英文描述
|
||||||
const problemLocalizationEn = await prisma.problemLocalization.findUnique({
|
const problemLocalizationEn = await prisma.problemLocalization.findUnique(
|
||||||
|
{
|
||||||
where: {
|
where: {
|
||||||
problemId_locale_type: {
|
problemId_locale_type: {
|
||||||
problemId: input.problemId,
|
problemId: input.problemId,
|
||||||
@ -33,7 +37,8 @@ export const generateAITestcase = async (
|
|||||||
include: {
|
include: {
|
||||||
problem: true,
|
problem: true,
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (problemLocalizationEn) {
|
if (problemLocalizationEn) {
|
||||||
problemDetails = `
|
problemDetails = `
|
||||||
@ -43,7 +48,8 @@ Description: ${problemLocalizationEn.content}
|
|||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
// 回退到中文描述
|
// 回退到中文描述
|
||||||
const problemLocalizationZh = await prisma.problemLocalization.findUnique({
|
const problemLocalizationZh =
|
||||||
|
await prisma.problemLocalization.findUnique({
|
||||||
where: {
|
where: {
|
||||||
problemId_locale_type: {
|
problemId_locale_type: {
|
||||||
problemId: input.problemId,
|
problemId: input.problemId,
|
||||||
@ -62,10 +68,14 @@ Problem Requirements:
|
|||||||
-------------------
|
-------------------
|
||||||
Description: ${problemLocalizationZh.content}
|
Description: ${problemLocalizationZh.content}
|
||||||
`;
|
`;
|
||||||
console.warn(`Fallback to Chinese description for problemId: ${input.problemId}`);
|
console.warn(
|
||||||
|
`Fallback to Chinese description for problemId: ${input.problemId}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
problemDetails = "Problem description not found in any language.";
|
problemDetails = "Problem description not found in any language.";
|
||||||
console.warn(`No description found for problemId: ${input.problemId}`);
|
console.warn(
|
||||||
|
`No description found for problemId: ${input.problemId}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -74,8 +84,6 @@ Description: ${problemLocalizationZh.content}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 构建AI提示词
|
// 构建AI提示词
|
||||||
const prompt = `
|
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:
|
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:
|
||||||
@ -128,16 +136,13 @@ Respond **ONLY** with this JSON structure.
|
|||||||
// 解析LLM响应
|
// 解析LLM响应
|
||||||
let llmResponseJson;
|
let llmResponseJson;
|
||||||
try {
|
try {
|
||||||
llmResponseJson = JSON.parse(text)
|
llmResponseJson = JSON.parse(text);
|
||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to parse LLM response as JSON:", error);
|
console.error("Failed to parse LLM response as JSON:", error);
|
||||||
console.error("LLM raw output:", text);
|
console.error("LLM raw output:", text);
|
||||||
throw new Error("Invalid JSON response from LLM");
|
throw new Error("Invalid JSON response from LLM");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 验证响应格式
|
// 验证响应格式
|
||||||
const validationResult = AITestCaseOutputSchema.safeParse(llmResponseJson);
|
const validationResult = AITestCaseOutputSchema.safeParse(llmResponseJson);
|
||||||
if (!validationResult.success) {
|
if (!validationResult.success) {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
// app/actions/get-problem-data.ts
|
// app/actions/get-problem-data.ts
|
||||||
'use server';
|
"use server";
|
||||||
|
|
||||||
import prisma from '@/lib/prisma';
|
import prisma from "@/lib/prisma";
|
||||||
import { Locale } from '@/generated/client';
|
import { Locale } from "@/generated/client";
|
||||||
import { serialize } from 'next-mdx-remote/serialize';
|
import { serialize } from "next-mdx-remote/serialize";
|
||||||
|
|
||||||
export async function getProblemData(problemId: string, locale?: string) {
|
export async function getProblemData(problemId: string, locale?: string) {
|
||||||
const selectedLocale = locale as Locale;
|
const selectedLocale = locale as Locale;
|
||||||
@ -13,7 +13,7 @@ export async function getProblemData(problemId: string, locale?: string) {
|
|||||||
include: {
|
include: {
|
||||||
templates: true,
|
templates: true,
|
||||||
testcases: {
|
testcases: {
|
||||||
include: { inputs: true }
|
include: { inputs: true },
|
||||||
},
|
},
|
||||||
localizations: {
|
localizations: {
|
||||||
where: {
|
where: {
|
||||||
@ -24,13 +24,13 @@ export async function getProblemData(problemId: string, locale?: string) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!problem) {
|
if (!problem) {
|
||||||
throw new Error('Problem not found');
|
throw new Error("Problem not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const getContent = (type: string) =>
|
const getContent = (type: string) =>
|
||||||
problem.localizations.find(loc => loc.type === type)?.content || '';
|
problem.localizations.find((loc) => loc.type === type)?.content || "";
|
||||||
|
|
||||||
const rawDescription = getContent('DESCRIPTION');
|
const rawDescription = getContent("DESCRIPTION");
|
||||||
|
|
||||||
const mdxDescription = await serialize(rawDescription, {
|
const mdxDescription = await serialize(rawDescription, {
|
||||||
parseFrontmatter: false,
|
parseFrontmatter: false,
|
||||||
@ -43,18 +43,18 @@ export async function getProblemData(problemId: string, locale?: string) {
|
|||||||
isPublished: problem.isPublished,
|
isPublished: problem.isPublished,
|
||||||
timeLimit: problem.timeLimit,
|
timeLimit: problem.timeLimit,
|
||||||
memoryLimit: problem.memoryLimit,
|
memoryLimit: problem.memoryLimit,
|
||||||
title: getContent('TITLE'),
|
title: getContent("TITLE"),
|
||||||
description: rawDescription,
|
description: rawDescription,
|
||||||
mdxDescription,
|
mdxDescription,
|
||||||
solution: getContent('SOLUTION'),
|
solution: getContent("SOLUTION"),
|
||||||
templates: problem.templates.map(t => ({
|
templates: problem.templates.map((t) => ({
|
||||||
language: t.language,
|
language: t.language,
|
||||||
content: t.content,
|
content: t.content,
|
||||||
})),
|
})),
|
||||||
testcases: problem.testcases.map(tc => ({
|
testcases: problem.testcases.map((tc) => ({
|
||||||
id: tc.id,
|
id: tc.id,
|
||||||
expectedOutput: tc.expectedOutput,
|
expectedOutput: tc.expectedOutput,
|
||||||
inputs: tc.inputs.map(input => ({
|
inputs: tc.inputs.map((input) => ({
|
||||||
name: input.name,
|
name: input.name,
|
||||||
value: input.value,
|
value: input.value,
|
||||||
})),
|
})),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// src/app/actions/getProblemLocales.ts
|
// src/app/actions/getProblemLocales.ts
|
||||||
'use server';
|
"use server";
|
||||||
|
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
@ -7,8 +7,8 @@ export async function getProblemLocales(problemId: string): Promise<string[]> {
|
|||||||
const locales = await prisma.problemLocalization.findMany({
|
const locales = await prisma.problemLocalization.findMany({
|
||||||
where: { problemId },
|
where: { problemId },
|
||||||
select: { locale: true },
|
select: { locale: true },
|
||||||
distinct: ['locale'],
|
distinct: ["locale"],
|
||||||
});
|
});
|
||||||
|
|
||||||
return locales.map(l => l.locale);
|
return locales.map((l) => l.locale);
|
||||||
}
|
}
|
||||||
|
@ -25,18 +25,21 @@ export const AIEditorWrapper = ({
|
|||||||
problemId,
|
problemId,
|
||||||
languageServerConfigs,
|
languageServerConfigs,
|
||||||
onChange,
|
onChange,
|
||||||
// className,
|
}: // className,
|
||||||
}: AIEditorWrapperProps) => {
|
AIEditorWrapperProps) => {
|
||||||
const [currentCode, setCurrentCode] = useState(value ?? "");
|
const [currentCode, setCurrentCode] = useState(value ?? "");
|
||||||
const [optimizedCode, setOptimizedCode] = useState("");
|
const [optimizedCode, setOptimizedCode] = useState("");
|
||||||
const [isOptimizing, setIsOptimizing] = useState(false);
|
const [isOptimizing, setIsOptimizing] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [showDiff, setShowDiff] = useState(false);
|
const [showDiff, setShowDiff] = useState(false);
|
||||||
|
|
||||||
const handleCodeChange = useCallback((val: string) => {
|
const handleCodeChange = useCallback(
|
||||||
|
(val: string) => {
|
||||||
setCurrentCode(val);
|
setCurrentCode(val);
|
||||||
onChange?.(val);
|
onChange?.(val);
|
||||||
}, [onChange]);
|
},
|
||||||
|
[onChange]
|
||||||
|
);
|
||||||
|
|
||||||
const handleOptimize = useCallback(async () => {
|
const handleOptimize = useCallback(async () => {
|
||||||
if (!problemId || !currentCode) return;
|
if (!problemId || !currentCode) return;
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { getProblemData } from '@/app/actions/getProblem';
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { updateProblemTemplate } from '@/components/creater/problem-maintain';
|
import { updateProblemTemplate } from "@/components/creater/problem-maintain";
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from "@/components/ui/label";
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { CoreEditor } from '@/components/core-editor';
|
import { CoreEditor } from "@/components/core-editor";
|
||||||
import { Language } from '@/generated/client';
|
import { Language } from "@/generated/client";
|
||||||
import { toast } from 'sonner';
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface Template {
|
interface Template {
|
||||||
language: string;
|
language: string;
|
||||||
@ -21,7 +21,7 @@ interface EditCodePanelProps {
|
|||||||
|
|
||||||
export default function EditCodePanel({ problemId }: EditCodePanelProps) {
|
export default function EditCodePanel({ problemId }: EditCodePanelProps) {
|
||||||
const [codeTemplate, setCodeTemplate] = useState<Template>({
|
const [codeTemplate, setCodeTemplate] = useState<Template>({
|
||||||
language: 'cpp',
|
language: "cpp",
|
||||||
content: `// 默认代码模板 for Problem ${problemId}`,
|
content: `// 默认代码模板 for Problem ${problemId}`,
|
||||||
});
|
});
|
||||||
const [templates, setTemplates] = useState<Template[]>([]);
|
const [templates, setTemplates] = useState<Template[]>([]);
|
||||||
@ -31,18 +31,20 @@ export default function EditCodePanel({ problemId }: EditCodePanelProps) {
|
|||||||
try {
|
try {
|
||||||
const problem = await getProblemData(problemId);
|
const problem = await getProblemData(problemId);
|
||||||
setTemplates(problem.templates);
|
setTemplates(problem.templates);
|
||||||
const sel = problem.templates.find(t => t.language === 'cpp') || problem.templates[0];
|
const sel =
|
||||||
|
problem.templates.find((t) => t.language === "cpp") ||
|
||||||
|
problem.templates[0];
|
||||||
if (sel) setCodeTemplate(sel);
|
if (sel) setCodeTemplate(sel);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载问题数据失败:', err);
|
console.error("加载问题数据失败:", err);
|
||||||
toast.error('加载问题数据失败');
|
toast.error("加载问题数据失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchTemplates();
|
fetchTemplates();
|
||||||
}, [problemId]);
|
}, [problemId]);
|
||||||
|
|
||||||
const handleLanguageChange = (language: string) => {
|
const handleLanguageChange = (language: string) => {
|
||||||
const sel = templates.find(t => t.language === language);
|
const sel = templates.find((t) => t.language === language);
|
||||||
if (sel) setCodeTemplate(sel);
|
if (sel) setCodeTemplate(sel);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -54,13 +56,13 @@ export default function EditCodePanel({ problemId }: EditCodePanelProps) {
|
|||||||
codeTemplate.content
|
codeTemplate.content
|
||||||
);
|
);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
toast.success('保存成功');
|
toast.success("保存成功");
|
||||||
} else {
|
} else {
|
||||||
toast.error('保存失败');
|
toast.error("保存失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存异常:', error);
|
console.error("保存异常:", error);
|
||||||
toast.error('保存异常');
|
toast.error("保存异常");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -77,9 +79,9 @@ export default function EditCodePanel({ problemId }: EditCodePanelProps) {
|
|||||||
id="language-select"
|
id="language-select"
|
||||||
className="block w-full p-2 border border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700"
|
className="block w-full p-2 border border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700"
|
||||||
value={codeTemplate.language}
|
value={codeTemplate.language}
|
||||||
onChange={e => handleLanguageChange(e.target.value)}
|
onChange={(e) => handleLanguageChange(e.target.value)}
|
||||||
>
|
>
|
||||||
{templates.map(t => (
|
{templates.map((t) => (
|
||||||
<option key={t.language} value={t.language}>
|
<option key={t.language} value={t.language}>
|
||||||
{t.language.toUpperCase()}
|
{t.language.toUpperCase()}
|
||||||
</option>
|
</option>
|
||||||
@ -94,8 +96,8 @@ export default function EditCodePanel({ problemId }: EditCodePanelProps) {
|
|||||||
language={codeTemplate.language}
|
language={codeTemplate.language}
|
||||||
value={codeTemplate.content}
|
value={codeTemplate.content}
|
||||||
path={`/${problemId}.${codeTemplate.language}`}
|
path={`/${problemId}.${codeTemplate.language}`}
|
||||||
onChange={value =>
|
onChange={(value) =>
|
||||||
setCodeTemplate({ ...codeTemplate, content: value || '' })
|
setCodeTemplate({ ...codeTemplate, content: value || "" })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,16 +12,25 @@ import { getProblemLocales } from "@/app/actions/getProblemLocales";
|
|||||||
import { Accordion } from "@/components/ui/accordion";
|
import { Accordion } from "@/components/ui/accordion";
|
||||||
import { VideoEmbed } from "@/components/content/video-embed";
|
import { VideoEmbed } from "@/components/content/video-embed";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { updateProblemDescription, updateProblemTitle } from '@/components/creater/problem-maintain';
|
import {
|
||||||
|
updateProblemDescription,
|
||||||
|
updateProblemTitle,
|
||||||
|
} from "@/components/creater/problem-maintain";
|
||||||
import { Locale } from "@/generated/client";
|
import { Locale } from "@/generated/client";
|
||||||
|
|
||||||
export default function EditDescriptionPanel({ problemId }: { problemId: string }) {
|
export default function EditDescriptionPanel({
|
||||||
|
problemId,
|
||||||
|
}: {
|
||||||
|
problemId: string;
|
||||||
|
}) {
|
||||||
const [locales, setLocales] = useState<string[]>([]);
|
const [locales, setLocales] = useState<string[]>([]);
|
||||||
const [currentLocale, setCurrentLocale] = useState<string>("");
|
const [currentLocale, setCurrentLocale] = useState<string>("");
|
||||||
const [customLocale, setCustomLocale] = useState("");
|
const [customLocale, setCustomLocale] = useState("");
|
||||||
|
|
||||||
const [description, setDescription] = useState({ title: "", content: "" });
|
const [description, setDescription] = useState({ title: "", content: "" });
|
||||||
const [viewMode, setViewMode] = useState<"edit" | "preview" | "compare">("edit");
|
const [viewMode, setViewMode] = useState<"edit" | "preview" | "compare">(
|
||||||
|
"edit"
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchLocales() {
|
async function fetchLocales() {
|
||||||
@ -31,7 +40,7 @@ export default function EditDescriptionPanel({ problemId }: { problemId: string
|
|||||||
if (langs.length > 0) setCurrentLocale(langs[0]);
|
if (langs.length > 0) setCurrentLocale(langs[0]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('获取语言列表失败');
|
toast.error("获取语言列表失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchLocales();
|
fetchLocales();
|
||||||
@ -42,10 +51,13 @@ export default function EditDescriptionPanel({ problemId }: { problemId: string
|
|||||||
async function fetchProblem() {
|
async function fetchProblem() {
|
||||||
try {
|
try {
|
||||||
const data = await getProblemData(problemId, currentLocale);
|
const data = await getProblemData(problemId, currentLocale);
|
||||||
setDescription({ title: data?.title || "", content: data?.description || "" });
|
setDescription({
|
||||||
|
title: data?.title || "",
|
||||||
|
content: data?.description || "",
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('加载题目描述失败');
|
toast.error("加载题目描述失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchProblem();
|
fetchProblem();
|
||||||
@ -63,21 +75,29 @@ export default function EditDescriptionPanel({ problemId }: { problemId: string
|
|||||||
|
|
||||||
const handleSave = async (): Promise<void> => {
|
const handleSave = async (): Promise<void> => {
|
||||||
if (!currentLocale) {
|
if (!currentLocale) {
|
||||||
toast.error('请选择语言');
|
toast.error("请选择语言");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const locale = currentLocale as Locale;
|
const locale = currentLocale as Locale;
|
||||||
const resTitle = await updateProblemTitle(problemId, locale, description.title);
|
const resTitle = await updateProblemTitle(
|
||||||
const resDesc = await updateProblemDescription(problemId, locale, description.content);
|
problemId,
|
||||||
|
locale,
|
||||||
|
description.title
|
||||||
|
);
|
||||||
|
const resDesc = await updateProblemDescription(
|
||||||
|
problemId,
|
||||||
|
locale,
|
||||||
|
description.content
|
||||||
|
);
|
||||||
if (resTitle.success && resDesc.success) {
|
if (resTitle.success && resDesc.success) {
|
||||||
toast.success('保存成功');
|
toast.success("保存成功");
|
||||||
} else {
|
} else {
|
||||||
toast.error('保存失败');
|
toast.error("保存失败");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('保存异常');
|
toast.error("保存异常");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,7 +137,9 @@ export default function EditDescriptionPanel({ problemId }: { problemId: string
|
|||||||
<Input
|
<Input
|
||||||
id="description-title"
|
id="description-title"
|
||||||
value={description.title}
|
value={description.title}
|
||||||
onChange={(e) => setDescription({ ...description, title: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setDescription({ ...description, title: e.target.value })
|
||||||
|
}
|
||||||
placeholder="输入题目标题"
|
placeholder="输入题目标题"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -134,7 +156,9 @@ export default function EditDescriptionPanel({ problemId }: { problemId: string
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={viewMode === "preview" ? "default" : "outline"}
|
variant={viewMode === "preview" ? "default" : "outline"}
|
||||||
onClick={() => setViewMode(viewMode === "preview" ? "edit" : "preview")}
|
onClick={() =>
|
||||||
|
setViewMode(viewMode === "preview" ? "edit" : "preview")
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{viewMode === "preview" ? "取消" : "预览"}
|
{viewMode === "preview" ? "取消" : "预览"}
|
||||||
</Button>
|
</Button>
|
||||||
@ -148,12 +172,20 @@ export default function EditDescriptionPanel({ problemId }: { problemId: string
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 编辑/预览区域 */}
|
{/* 编辑/预览区域 */}
|
||||||
<div className={viewMode === "compare" ? "grid grid-cols-2 gap-6" : "flex flex-col gap-6"}>
|
<div
|
||||||
|
className={
|
||||||
|
viewMode === "compare"
|
||||||
|
? "grid grid-cols-2 gap-6"
|
||||||
|
: "flex flex-col gap-6"
|
||||||
|
}
|
||||||
|
>
|
||||||
{(viewMode === "edit" || viewMode === "compare") && (
|
{(viewMode === "edit" || viewMode === "compare") && (
|
||||||
<div className="relative h-[600px]">
|
<div className="relative h-[600px]">
|
||||||
<CoreEditor
|
<CoreEditor
|
||||||
value={description.content}
|
value={description.content}
|
||||||
onChange={(newVal) => setDescription({ ...description, content: newVal || "" })}
|
onChange={(newVal) =>
|
||||||
|
setDescription({ ...description, content: newVal || "" })
|
||||||
|
}
|
||||||
language="markdown"
|
language="markdown"
|
||||||
className="absolute inset-0 rounded-md border border-input"
|
className="absolute inset-0 rounded-md border border-input"
|
||||||
/>
|
/>
|
||||||
@ -161,7 +193,10 @@ export default function EditDescriptionPanel({ problemId }: { problemId: string
|
|||||||
)}
|
)}
|
||||||
{viewMode !== "edit" && (
|
{viewMode !== "edit" && (
|
||||||
<div className="prose dark:prose-invert">
|
<div className="prose dark:prose-invert">
|
||||||
<MdxPreview source={description.content} components={{ Accordion, VideoEmbed }} />
|
<MdxPreview
|
||||||
|
source={description.content}
|
||||||
|
components={{ Accordion, VideoEmbed }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { getProblemData } from "@/app/actions/getProblem";
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { updateProblemDetail } from '@/components/creater/problem-maintain';
|
import { updateProblemDetail } from "@/components/creater/problem-maintain";
|
||||||
import { Difficulty } from "@/generated/client";
|
import { Difficulty } from "@/generated/client";
|
||||||
|
|
||||||
export default function EditDetailPanel({ problemId }: { problemId: string }) {
|
export default function EditDetailPanel({ problemId }: { problemId: string }) {
|
||||||
@ -32,7 +32,7 @@ export default function EditDetailPanel({ problemId }: { problemId: string }) {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取题目信息失败:", error);
|
console.error("获取题目信息失败:", error);
|
||||||
toast.error('加载详情失败');
|
toast.error("加载详情失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchData();
|
fetchData();
|
||||||
@ -66,13 +66,13 @@ export default function EditDetailPanel({ problemId }: { problemId: string }) {
|
|||||||
isPublished: problemDetails.isPublished,
|
isPublished: problemDetails.isPublished,
|
||||||
});
|
});
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
toast.success('保存成功');
|
toast.success("保存成功");
|
||||||
} else {
|
} else {
|
||||||
toast.error('保存失败');
|
toast.error("保存失败");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('保存异常:', err);
|
console.error("保存异常:", err);
|
||||||
toast.error('保存异常');
|
toast.error("保存异常");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -138,7 +138,10 @@ export default function EditDetailPanel({ problemId }: { problemId: string }) {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={problemDetails.isPublished}
|
checked={problemDetails.isPublished}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setProblemDetails({ ...problemDetails, isPublished: e.target.checked })
|
setProblemDetails({
|
||||||
|
...problemDetails,
|
||||||
|
isPublished: e.target.checked,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 focus:ring-2"
|
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 focus:ring-2"
|
||||||
/>
|
/>
|
||||||
|
@ -12,16 +12,22 @@ import { getProblemLocales } from "@/app/actions/getProblemLocales";
|
|||||||
import { Accordion } from "@/components/ui/accordion";
|
import { Accordion } from "@/components/ui/accordion";
|
||||||
import { VideoEmbed } from "@/components/content/video-embed";
|
import { VideoEmbed } from "@/components/content/video-embed";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { updateProblemSolution } from '@/components/creater/problem-maintain';
|
import { updateProblemSolution } from "@/components/creater/problem-maintain";
|
||||||
import { Locale } from "@/generated/client";
|
import { Locale } from "@/generated/client";
|
||||||
|
|
||||||
export default function EditSolutionPanel({ problemId }: { problemId: string }) {
|
export default function EditSolutionPanel({
|
||||||
|
problemId,
|
||||||
|
}: {
|
||||||
|
problemId: string;
|
||||||
|
}) {
|
||||||
const [locales, setLocales] = useState<string[]>([]);
|
const [locales, setLocales] = useState<string[]>([]);
|
||||||
const [currentLocale, setCurrentLocale] = useState<string>("");
|
const [currentLocale, setCurrentLocale] = useState<string>("");
|
||||||
const [customLocale, setCustomLocale] = useState("");
|
const [customLocale, setCustomLocale] = useState("");
|
||||||
|
|
||||||
const [solution, setSolution] = useState({ title: "", content: "" });
|
const [solution, setSolution] = useState({ title: "", content: "" });
|
||||||
const [viewMode, setViewMode] = useState<"edit" | "preview" | "compare">("edit");
|
const [viewMode, setViewMode] = useState<"edit" | "preview" | "compare">(
|
||||||
|
"edit"
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchLocales() {
|
async function fetchLocales() {
|
||||||
@ -31,7 +37,7 @@ export default function EditSolutionPanel({ problemId }: { problemId: string })
|
|||||||
if (langs.length > 0) setCurrentLocale(langs[0]);
|
if (langs.length > 0) setCurrentLocale(langs[0]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('获取语言列表失败');
|
toast.error("获取语言列表失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchLocales();
|
fetchLocales();
|
||||||
@ -42,10 +48,13 @@ export default function EditSolutionPanel({ problemId }: { problemId: string })
|
|||||||
async function fetchSolution() {
|
async function fetchSolution() {
|
||||||
try {
|
try {
|
||||||
const data = await getProblemData(problemId, currentLocale);
|
const data = await getProblemData(problemId, currentLocale);
|
||||||
setSolution({ title: (data?.title || "") + " 解析", content: data?.solution || "" });
|
setSolution({
|
||||||
|
title: (data?.title || "") + " 解析",
|
||||||
|
content: data?.solution || "",
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('加载题目解析失败');
|
toast.error("加载题目解析失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchSolution();
|
fetchSolution();
|
||||||
@ -53,7 +62,7 @@ export default function EditSolutionPanel({ problemId }: { problemId: string })
|
|||||||
|
|
||||||
const handleAddCustomLocale = () => {
|
const handleAddCustomLocale = () => {
|
||||||
if (customLocale && !locales.includes(customLocale)) {
|
if (customLocale && !locales.includes(customLocale)) {
|
||||||
setLocales(prev => [...prev, customLocale]);
|
setLocales((prev) => [...prev, customLocale]);
|
||||||
setCurrentLocale(customLocale);
|
setCurrentLocale(customLocale);
|
||||||
setCustomLocale("");
|
setCustomLocale("");
|
||||||
setSolution({ title: "", content: "" });
|
setSolution({ title: "", content: "" });
|
||||||
@ -62,20 +71,24 @@ export default function EditSolutionPanel({ problemId }: { problemId: string })
|
|||||||
|
|
||||||
const handleSave = async (): Promise<void> => {
|
const handleSave = async (): Promise<void> => {
|
||||||
if (!currentLocale) {
|
if (!currentLocale) {
|
||||||
toast.error('请选择语言');
|
toast.error("请选择语言");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const locale = currentLocale as Locale;
|
const locale = currentLocale as Locale;
|
||||||
const res = await updateProblemSolution(problemId, locale, solution.content);
|
const res = await updateProblemSolution(
|
||||||
|
problemId,
|
||||||
|
locale,
|
||||||
|
solution.content
|
||||||
|
);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
toast.success('保存成功');
|
toast.success("保存成功");
|
||||||
} else {
|
} else {
|
||||||
toast.error('保存失败');
|
toast.error("保存失败");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('保存异常');
|
toast.error("保存异常");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,7 +118,9 @@ export default function EditSolutionPanel({ problemId }: { problemId: string })
|
|||||||
value={customLocale}
|
value={customLocale}
|
||||||
onChange={(e) => setCustomLocale(e.target.value)}
|
onChange={(e) => setCustomLocale(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Button type="button" onClick={handleAddCustomLocale}>添加</Button>
|
<Button type="button" onClick={handleAddCustomLocale}>
|
||||||
|
添加
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -115,7 +130,9 @@ export default function EditSolutionPanel({ problemId }: { problemId: string })
|
|||||||
<Input
|
<Input
|
||||||
id="solution-title"
|
id="solution-title"
|
||||||
value={solution.title}
|
value={solution.title}
|
||||||
onChange={(e) => setSolution({ ...solution, title: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setSolution({ ...solution, title: e.target.value })
|
||||||
|
}
|
||||||
placeholder="输入题解标题"
|
placeholder="输入题解标题"
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
@ -123,20 +140,46 @@ export default function EditSolutionPanel({ problemId }: { problemId: string })
|
|||||||
|
|
||||||
{/* 编辑/预览切换 */}
|
{/* 编辑/预览切换 */}
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Button type="button" variant={viewMode === "edit" ? "default" : "outline"} onClick={() => setViewMode("edit")}>编辑</Button>
|
<Button
|
||||||
<Button type="button" variant={viewMode === "preview" ? "default" : "outline"} onClick={() => setViewMode(viewMode === "preview" ? "edit" : "preview")}>
|
type="button"
|
||||||
|
variant={viewMode === "edit" ? "default" : "outline"}
|
||||||
|
onClick={() => setViewMode("edit")}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={viewMode === "preview" ? "default" : "outline"}
|
||||||
|
onClick={() =>
|
||||||
|
setViewMode(viewMode === "preview" ? "edit" : "preview")
|
||||||
|
}
|
||||||
|
>
|
||||||
{viewMode === "preview" ? "取消" : "预览"}
|
{viewMode === "preview" ? "取消" : "预览"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="button" variant={viewMode === "compare" ? "default" : "outline"} onClick={() => setViewMode("compare")}>对比</Button>
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={viewMode === "compare" ? "default" : "outline"}
|
||||||
|
onClick={() => setViewMode("compare")}
|
||||||
|
>
|
||||||
|
对比
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 编辑/预览区域 */}
|
{/* 编辑/预览区域 */}
|
||||||
<div className={viewMode === "compare" ? "grid grid-cols-2 gap-6" : "flex flex-col gap-6"}>
|
<div
|
||||||
|
className={
|
||||||
|
viewMode === "compare"
|
||||||
|
? "grid grid-cols-2 gap-6"
|
||||||
|
: "flex flex-col gap-6"
|
||||||
|
}
|
||||||
|
>
|
||||||
{(viewMode === "edit" || viewMode === "compare") && (
|
{(viewMode === "edit" || viewMode === "compare") && (
|
||||||
<div className="relative h-[600px]">
|
<div className="relative h-[600px]">
|
||||||
<CoreEditor
|
<CoreEditor
|
||||||
value={solution.content}
|
value={solution.content}
|
||||||
onChange={(val) => setSolution({ ...solution, content: val || "" })}
|
onChange={(val) =>
|
||||||
|
setSolution({ ...solution, content: val || "" })
|
||||||
|
}
|
||||||
language="markdown"
|
language="markdown"
|
||||||
className="absolute inset-0 rounded-md border border-input"
|
className="absolute inset-0 rounded-md border border-input"
|
||||||
/>
|
/>
|
||||||
@ -144,12 +187,17 @@ export default function EditSolutionPanel({ problemId }: { problemId: string })
|
|||||||
)}
|
)}
|
||||||
{viewMode !== "edit" && (
|
{viewMode !== "edit" && (
|
||||||
<div className="prose dark:prose-invert">
|
<div className="prose dark:prose-invert">
|
||||||
<MdxPreview source={solution.content} components={{ Accordion, VideoEmbed }} />
|
<MdxPreview
|
||||||
|
source={solution.content}
|
||||||
|
components={{ Accordion, VideoEmbed }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button type="button" onClick={handleSave}>保存更改</Button>
|
<Button type="button" onClick={handleSave}>
|
||||||
|
保存更改
|
||||||
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
@ -20,7 +20,11 @@ interface Testcase {
|
|||||||
inputs: { name: string; value: string }[];
|
inputs: { name: string; value: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EditTestcasePanel({ problemId }: { problemId: string }) {
|
export default function EditTestcasePanel({
|
||||||
|
problemId,
|
||||||
|
}: {
|
||||||
|
problemId: string;
|
||||||
|
}) {
|
||||||
const [testcases, setTestcases] = useState<Testcase[]>([]);
|
const [testcases, setTestcases] = useState<Testcase[]>([]);
|
||||||
const [isGenerating, setIsGenerating] = useState(false);
|
const [isGenerating, setIsGenerating] = useState(false);
|
||||||
|
|
||||||
@ -42,7 +46,11 @@ export default function EditTestcasePanel({ problemId }: { problemId: string })
|
|||||||
const handleAddTestcase = () =>
|
const handleAddTestcase = () =>
|
||||||
setTestcases((prev) => [
|
setTestcases((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{ id: `new-${Date.now()}-${Math.random()}`, expectedOutput: "", inputs: [{ name: "input1", value: "" }] },
|
{
|
||||||
|
id: `new-${Date.now()}-${Math.random()}`,
|
||||||
|
expectedOutput: "",
|
||||||
|
inputs: [{ name: "input1", value: "" }],
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// AI 生成测试用例
|
// AI 生成测试用例
|
||||||
@ -52,7 +60,11 @@ export default function EditTestcasePanel({ problemId }: { problemId: string })
|
|||||||
const ai = await generateAITestcase({ problemId });
|
const ai = await generateAITestcase({ problemId });
|
||||||
setTestcases((prev) => [
|
setTestcases((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{ id: `new-${Date.now()}-${Math.random()}`, expectedOutput: ai.expectedOutput, inputs: ai.inputs },
|
{
|
||||||
|
id: `new-${Date.now()}-${Math.random()}`,
|
||||||
|
expectedOutput: ai.expectedOutput,
|
||||||
|
inputs: ai.inputs,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
|
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -106,7 +118,10 @@ export default function EditTestcasePanel({ problemId }: { problemId: string })
|
|||||||
const handleAddInput = (tIdx: number) =>
|
const handleAddInput = (tIdx: number) =>
|
||||||
setTestcases((prev) => {
|
setTestcases((prev) => {
|
||||||
const c = [...prev];
|
const c = [...prev];
|
||||||
const inputs = [...c[tIdx].inputs, { name: `input${c[tIdx].inputs.length + 1}`, value: "" }];
|
const inputs = [
|
||||||
|
...c[tIdx].inputs,
|
||||||
|
{ name: `input${c[tIdx].inputs.length + 1}`, value: "" },
|
||||||
|
];
|
||||||
c[tIdx] = { ...c[tIdx], inputs };
|
c[tIdx] = { ...c[tIdx], inputs };
|
||||||
return c;
|
return c;
|
||||||
});
|
});
|
||||||
@ -125,20 +140,32 @@ export default function EditTestcasePanel({ problemId }: { problemId: string })
|
|||||||
try {
|
try {
|
||||||
for (let i = 0; i < testcases.length; i++) {
|
for (let i = 0; i < testcases.length; i++) {
|
||||||
const tc = testcases[i];
|
const tc = testcases[i];
|
||||||
if (tc.expectedOutput.trim() === "" || tc.inputs.some(inp => !inp.name.trim() || !inp.value.trim())) {
|
if (
|
||||||
|
tc.expectedOutput.trim() === "" ||
|
||||||
|
tc.inputs.some((inp) => !inp.name.trim() || !inp.value.trim())
|
||||||
|
) {
|
||||||
toast.error(`第 ${i + 1} 个测试用例存在空的输入或输出,保存失败`);
|
toast.error(`第 ${i + 1} 个测试用例存在空的输入或输出,保存失败`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tc.id.startsWith("new-")) {
|
if (tc.id.startsWith("new-")) {
|
||||||
const res = await addProblemTestcase(problemId, tc.expectedOutput, tc.inputs);
|
const res = await addProblemTestcase(
|
||||||
|
problemId,
|
||||||
|
tc.expectedOutput,
|
||||||
|
tc.inputs
|
||||||
|
);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
toast.success(`新增测试用例 ${i + 1} 成功`);
|
toast.success(`新增测试用例 ${i + 1} 成功`);
|
||||||
} else {
|
} else {
|
||||||
toast.error(`新增测试用例 ${i + 1} 失败`);
|
toast.error(`新增测试用例 ${i + 1} 失败`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const res = await updateProblemTestcase(problemId, tc.id, tc.expectedOutput, tc.inputs);
|
const res = await updateProblemTestcase(
|
||||||
|
problemId,
|
||||||
|
tc.id,
|
||||||
|
tc.expectedOutput,
|
||||||
|
tc.inputs
|
||||||
|
);
|
||||||
if (res.success) toast.success(`更新测试用例 ${i + 1} 成功`);
|
if (res.success) toast.success(`更新测试用例 ${i + 1} 成功`);
|
||||||
else toast.error(`更新测试用例 ${i + 1} 失败`);
|
else toast.error(`更新测试用例 ${i + 1} 失败`);
|
||||||
}
|
}
|
||||||
@ -173,7 +200,10 @@ export default function EditTestcasePanel({ problemId }: { problemId: string })
|
|||||||
<div key={tc.id} className="border p-4 rounded space-y-4">
|
<div key={tc.id} className="border p-4 rounded space-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h3 className="font-medium">测试用例 {idx + 1}</h3>
|
<h3 className="font-medium">测试用例 {idx + 1}</h3>
|
||||||
<Button variant="destructive" onClick={() => handleRemoveTestcase(idx)}>
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => handleRemoveTestcase(idx)}
|
||||||
|
>
|
||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -181,7 +211,9 @@ export default function EditTestcasePanel({ problemId }: { problemId: string })
|
|||||||
<Label>预期输出</Label>
|
<Label>预期输出</Label>
|
||||||
<Input
|
<Input
|
||||||
value={tc.expectedOutput}
|
value={tc.expectedOutput}
|
||||||
onChange={(e) => handleExpectedOutputChange(idx, e.target.value)}
|
onChange={(e) =>
|
||||||
|
handleExpectedOutputChange(idx, e.target.value)
|
||||||
|
}
|
||||||
placeholder="输入预期输出"
|
placeholder="输入预期输出"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -196,7 +228,9 @@ export default function EditTestcasePanel({ problemId }: { problemId: string })
|
|||||||
<Label>名称</Label>
|
<Label>名称</Label>
|
||||||
<Input
|
<Input
|
||||||
value={inp.name}
|
value={inp.name}
|
||||||
onChange={(e) => handleInputChange(idx, iIdx, "name", e.target.value)}
|
onChange={(e) =>
|
||||||
|
handleInputChange(idx, iIdx, "name", e.target.value)
|
||||||
|
}
|
||||||
placeholder="参数名称"
|
placeholder="参数名称"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -204,12 +238,17 @@ export default function EditTestcasePanel({ problemId }: { problemId: string })
|
|||||||
<Label>值</Label>
|
<Label>值</Label>
|
||||||
<Input
|
<Input
|
||||||
value={inp.value}
|
value={inp.value}
|
||||||
onChange={(e) => handleInputChange(idx, iIdx, "value", e.target.value)}
|
onChange={(e) =>
|
||||||
|
handleInputChange(idx, iIdx, "value", e.target.value)
|
||||||
|
}
|
||||||
placeholder="参数值"
|
placeholder="参数值"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{iIdx > 0 && (
|
{iIdx > 0 && (
|
||||||
<Button variant="outline" onClick={() => handleRemoveInput(idx, iIdx)}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleRemoveInput(idx, iIdx)}
|
||||||
|
>
|
||||||
删除输入
|
删除输入
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
@ -2,9 +2,23 @@
|
|||||||
|
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { Difficulty, Locale, ProblemContentType, Language } from "@/generated/client";
|
import {
|
||||||
|
Difficulty,
|
||||||
|
Locale,
|
||||||
|
ProblemContentType,
|
||||||
|
Language,
|
||||||
|
} from "@/generated/client";
|
||||||
|
|
||||||
export async function updateProblemDetail(problemId: string, data: { displayId?: number; difficulty?: Difficulty; timeLimit?: number; memoryLimit?: number; isPublished?: boolean }) {
|
export async function updateProblemDetail(
|
||||||
|
problemId: string,
|
||||||
|
data: {
|
||||||
|
displayId?: number;
|
||||||
|
difficulty?: Difficulty;
|
||||||
|
timeLimit?: number;
|
||||||
|
memoryLimit?: number;
|
||||||
|
isPublished?: boolean;
|
||||||
|
}
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const updatedProblem = await prisma.problem.update({
|
const updatedProblem = await prisma.problem.update({
|
||||||
where: { id: problemId },
|
where: { id: problemId },
|
||||||
@ -13,8 +27,8 @@ export async function updateProblemDetail(problemId: string, data: { displayId?:
|
|||||||
difficulty: data.difficulty,
|
difficulty: data.difficulty,
|
||||||
timeLimit: data.timeLimit,
|
timeLimit: data.timeLimit,
|
||||||
memoryLimit: data.memoryLimit,
|
memoryLimit: data.memoryLimit,
|
||||||
isPublished: data.isPublished
|
isPublished: data.isPublished,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath(`/problem-editor/${problemId}`);
|
revalidatePath(`/problem-editor/${problemId}`);
|
||||||
@ -25,25 +39,29 @@ export async function updateProblemDetail(problemId: string, data: { displayId?:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateProblemDescription(problemId: string, locale: Locale, content: string) {
|
export async function updateProblemDescription(
|
||||||
|
problemId: string,
|
||||||
|
locale: Locale,
|
||||||
|
content: string
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const updatedLocalization = await prisma.problemLocalization.upsert({
|
const updatedLocalization = await prisma.problemLocalization.upsert({
|
||||||
where: {
|
where: {
|
||||||
problemId_locale_type: {
|
problemId_locale_type: {
|
||||||
problemId: problemId,
|
problemId: problemId,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
type: ProblemContentType.DESCRIPTION
|
type: ProblemContentType.DESCRIPTION,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
problemId: problemId,
|
problemId: problemId,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
type: ProblemContentType.DESCRIPTION,
|
type: ProblemContentType.DESCRIPTION,
|
||||||
content: content
|
content: content,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
content: content
|
content: content,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath(`/problem-editor/${problemId}`);
|
revalidatePath(`/problem-editor/${problemId}`);
|
||||||
@ -54,25 +72,29 @@ export async function updateProblemDescription(problemId: string, locale: Locale
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateProblemSolution(problemId: string, locale: Locale, content: string) {
|
export async function updateProblemSolution(
|
||||||
|
problemId: string,
|
||||||
|
locale: Locale,
|
||||||
|
content: string
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const updatedLocalization = await prisma.problemLocalization.upsert({
|
const updatedLocalization = await prisma.problemLocalization.upsert({
|
||||||
where: {
|
where: {
|
||||||
problemId_locale_type: {
|
problemId_locale_type: {
|
||||||
problemId: problemId,
|
problemId: problemId,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
type: ProblemContentType.SOLUTION
|
type: ProblemContentType.SOLUTION,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
problemId: problemId,
|
problemId: problemId,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
type: ProblemContentType.SOLUTION,
|
type: ProblemContentType.SOLUTION,
|
||||||
content: content
|
content: content,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
content: content
|
content: content,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath(`/problem-editor/${problemId}`);
|
revalidatePath(`/problem-editor/${problemId}`);
|
||||||
@ -83,23 +105,27 @@ export async function updateProblemSolution(problemId: string, locale: Locale, c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateProblemTemplate(problemId: string, language: Language, content: string) {
|
export async function updateProblemTemplate(
|
||||||
|
problemId: string,
|
||||||
|
language: Language,
|
||||||
|
content: string
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const updatedTemplate = await prisma.template.upsert({
|
const updatedTemplate = await prisma.template.upsert({
|
||||||
where: {
|
where: {
|
||||||
problemId_language: {
|
problemId_language: {
|
||||||
problemId: problemId,
|
problemId: problemId,
|
||||||
language: language
|
language: language,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
problemId: problemId,
|
problemId: problemId,
|
||||||
language: language,
|
language: language,
|
||||||
content: content
|
content: content,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
content: content
|
content: content,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath(`/problem-editor/${problemId}`);
|
revalidatePath(`/problem-editor/${problemId}`);
|
||||||
@ -110,19 +136,24 @@ export async function updateProblemTemplate(problemId: string, language: Languag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateProblemTestcase(problemId: string, testcaseId: string, expectedOutput: string, inputs: { name: string; value: string }[]) {
|
export async function updateProblemTestcase(
|
||||||
|
problemId: string,
|
||||||
|
testcaseId: string,
|
||||||
|
expectedOutput: string,
|
||||||
|
inputs: { name: string; value: string }[]
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
// Update testcase
|
// Update testcase
|
||||||
const updatedTestcase = await prisma.testcase.update({
|
const updatedTestcase = await prisma.testcase.update({
|
||||||
where: { id: testcaseId },
|
where: { id: testcaseId },
|
||||||
data: {
|
data: {
|
||||||
expectedOutput: expectedOutput
|
expectedOutput: expectedOutput,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete old inputs
|
// Delete old inputs
|
||||||
await prisma.testcaseInput.deleteMany({
|
await prisma.testcaseInput.deleteMany({
|
||||||
where: { testcaseId: testcaseId }
|
where: { testcaseId: testcaseId },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create new inputs
|
// Create new inputs
|
||||||
@ -131,15 +162,15 @@ export async function updateProblemTestcase(problemId: string, testcaseId: strin
|
|||||||
testcaseId: testcaseId,
|
testcaseId: testcaseId,
|
||||||
index: index,
|
index: index,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
value: input.value
|
value: input.value,
|
||||||
}))
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath(`/problem-editor/${problemId}`);
|
revalidatePath(`/problem-editor/${problemId}`);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
testcase: updatedTestcase,
|
testcase: updatedTestcase,
|
||||||
inputs: createdInputs
|
inputs: createdInputs,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update problem testcase:", error);
|
console.error("Failed to update problem testcase:", error);
|
||||||
@ -147,14 +178,18 @@ export async function updateProblemTestcase(problemId: string, testcaseId: strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addProblemTestcase(problemId: string, expectedOutput: string, inputs: { name: string; value: string }[]) {
|
export async function addProblemTestcase(
|
||||||
|
problemId: string,
|
||||||
|
expectedOutput: string,
|
||||||
|
inputs: { name: string; value: string }[]
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
// Create testcase
|
// Create testcase
|
||||||
const newTestcase = await prisma.testcase.create({
|
const newTestcase = await prisma.testcase.create({
|
||||||
data: {
|
data: {
|
||||||
problemId: problemId,
|
problemId: problemId,
|
||||||
expectedOutput: expectedOutput
|
expectedOutput: expectedOutput,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create inputs
|
// Create inputs
|
||||||
@ -163,15 +198,15 @@ export async function addProblemTestcase(problemId: string, expectedOutput: stri
|
|||||||
testcaseId: newTestcase.id,
|
testcaseId: newTestcase.id,
|
||||||
index: index,
|
index: index,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
value: input.value
|
value: input.value,
|
||||||
}))
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath(`/problem-editor/${problemId}`);
|
revalidatePath(`/problem-editor/${problemId}`);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
testcase: newTestcase,
|
testcase: newTestcase,
|
||||||
inputs: createdInputs
|
inputs: createdInputs,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to add problem testcase:", error);
|
console.error("Failed to add problem testcase:", error);
|
||||||
@ -179,10 +214,13 @@ export async function addProblemTestcase(problemId: string, expectedOutput: stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteProblemTestcase(problemId: string, testcaseId: string) {
|
export async function deleteProblemTestcase(
|
||||||
|
problemId: string,
|
||||||
|
testcaseId: string
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const deletedTestcase = await prisma.testcase.delete({
|
const deletedTestcase = await prisma.testcase.delete({
|
||||||
where: { id: testcaseId }
|
where: { id: testcaseId },
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath(`/problem-editor/${problemId}`);
|
revalidatePath(`/problem-editor/${problemId}`);
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
import {z} from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const AITestCaseInputSchema = z.object({
|
export const AITestCaseInputSchema = z.object({
|
||||||
problemId: z.string(),
|
problemId: z.string(),
|
||||||
})
|
});
|
||||||
|
|
||||||
export type AITestCaseInput = z.infer<typeof AITestCaseInputSchema>
|
export type AITestCaseInput = z.infer<typeof AITestCaseInputSchema>;
|
||||||
|
|
||||||
const input = z.object({
|
const input = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
value: z.string()
|
value: z.string(),
|
||||||
})
|
});
|
||||||
|
|
||||||
export const AITestCaseOutputSchema = z.object({
|
export const AITestCaseOutputSchema = z.object({
|
||||||
expectedOutput: z.string(),
|
expectedOutput: z.string(),
|
||||||
inputs: z.array(input)
|
inputs: z.array(input),
|
||||||
})
|
});
|
||||||
|
|
||||||
export type AITestCaseOutput = z.infer<typeof AITestCaseOutputSchema>
|
|
||||||
|
|
||||||
|
|
||||||
|
export type AITestCaseOutput = z.infer<typeof AITestCaseOutputSchema>;
|
||||||
|
Loading…
Reference in New Issue
Block a user