,
+ label: "Prompt Test Cases",
+ onClick: () => navigate(`/tenant/ai/prompts/${row.id}/test-cases`),
+ },
{
icon:
,
label: "Edit Prompt",
diff --git a/src/pages/tenant/PromptTestCaseCreate.tsx b/src/pages/tenant/PromptTestCaseCreate.tsx
new file mode 100644
index 0000000..deb6182
--- /dev/null
+++ b/src/pages/tenant/PromptTestCaseCreate.tsx
@@ -0,0 +1,342 @@
+import { useEffect, useState, type ReactElement } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { Layout } from "@/components/layout/Layout";
+import {
+ FormField,
+ FormTextArea,
+ FormTagInput,
+ PrimaryButton,
+ SecondaryButton,
+} from "@/components/shared";
+import { ArrowLeft } from "lucide-react";
+import { aiService } from "@/services/ai-service";
+import { showToast } from "@/utils/toast";
+import { useForm, useFieldArray, Controller } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+
+const variableSchema = z.object({
+ name: z.string().min(1, "Variable name is required").max(255),
+ value: z.string().optional(),
+ required: z.boolean().optional(),
+});
+
+const testCaseSchema = z.object({
+ name: z.string().min(1, "Name is required").max(255),
+ description: z.string().optional(),
+ expected_output: z.string().optional(),
+ tags: z.array(z.string()),
+ variables: z.array(variableSchema),
+}).superRefine((data, ctx) => {
+ (data.variables || []).forEach((v, index) => {
+ if (v.required && (!v.value || !v.value.trim())) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: `${v.name} is a required variable`,
+ path: ["variables", index, "value"],
+ });
+ }
+ });
+});
+
+type TestCaseFormData = z.infer
;
+
+export const PromptTestCaseCreate = (): ReactElement => {
+ const { id: promptId } = useParams<{ id: string }>();
+ const navigate = useNavigate();
+
+ const [isLoading, setIsLoading] = useState(true);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const {
+ control,
+ handleSubmit,
+ reset,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(testCaseSchema),
+ defaultValues: {
+ name: "",
+ description: "",
+ expected_output: "",
+ tags: [],
+ variables: [],
+ },
+ });
+
+ const { fields } = useFieldArray({
+ control,
+ name: "variables",
+ });
+
+ // Fetch prompt metadata to autofill initial variable keys
+ useEffect(() => {
+ const loadPromptDetails = async () => {
+ if (!promptId) return;
+ try {
+ setIsLoading(true);
+ const prompt = await aiService.getPrompt(promptId);
+ let initialVariables: Array<{ name: string; value: string; required?: boolean }> = [];
+
+ if (prompt && Array.isArray(prompt.variables)) {
+ initialVariables = prompt.variables.map((v: any) => ({
+ name: v.name || "",
+ value: v.default || "",
+ required: !!v.required,
+ }));
+ }
+
+ reset({
+ name: "",
+ description: "",
+ expected_output: "",
+ tags: prompt.tags || [],
+ variables: initialVariables,
+ });
+ } catch (err: any) {
+ console.warn("Failed to prefetch prompt variables", err);
+ reset({
+ name: "",
+ description: "",
+ expected_output: "",
+ tags: [],
+ variables: [],
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ void loadPromptDetails();
+ }, [promptId, reset]);
+
+ const onFormSubmit = async (data: TestCaseFormData) => {
+ if (!promptId) return;
+
+ // Convert array to Record
+ const variablesObject: Record = {};
+ (data.variables || []).forEach((v) => {
+ if (v.name.trim()) {
+ variablesObject[v.name.trim()] = v.value || "";
+ }
+ });
+
+ setIsSubmitting(true);
+ try {
+ await aiService.createPromptTestCase(promptId, {
+ name: data.name.trim(),
+ description: data.description?.trim() || undefined,
+ variables: variablesObject,
+ expected_output: data.expected_output?.trim() || undefined,
+ tags: data.tags && data.tags.length > 0 ? data.tags : undefined,
+ });
+
+ showToast.success("Test case created successfully!");
+ navigate(`/tenant/ai/prompts/${promptId}/test-cases`);
+ } catch (err: any) {
+ const msg =
+ err?.response?.data?.error?.message || "Failed to create test case.";
+ showToast.error(msg);
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ if (isLoading) {
+ return (
+
+
+
+
+
+ Loading prompt template details...
+
+
+
+
+ );
+ }
+
+ return (
+
+ navigate(`/tenant/ai/prompts/${promptId}/test-cases`)}
+ className="h-10 px-5 min-w-[120px]"
+ >
+ Cancel
+
+
+ {isSubmitting ? "Creating..." : "Save Test Case"}
+
+
+ ),
+ }}
+ >
+