"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { useEffect, useMemo, useState } from "react"; import { Controller, type Resolver, useForm } from "react-hook-form"; import { z } from "zod/v4"; import { ContentEditor } from "@/components/content-editor"; import { OptionCombobox } from "@/components/option-combobox"; import { ProjectDialog } from "@/components/project-dialog"; import { Button } from "@/components/ui/button"; import { Field, FieldContent, FieldError, FieldLabel, } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { fetchMCPCatalog, fetchSkillDefinition, type CreateSkillDefinitionPayload, type MCPToolCatalogItem, type SkillDefinition, } from "@/lib/api/admin"; import { useI18n } from "@/i18n/provider"; type TFunction = (key: string, values?: Record) => string; type SkillEditDialogProps = { open: boolean; saving: boolean; itemId: number | null; onOpenChange: (open: boolean) => void; onSubmit: (payload: CreateSkillDefinitionPayload) => Promise; }; const emptyForm: EditForm = { code: "", name: "", description: "", instruction: "", examplesText: "", remark: "", }; function createSkillFormSchema(t: TFunction) { return z.object({ code: z .string() .trim() .min(1, t("skillDefinition.codeRequired")) .regex(/^[a-zA-Z0-9_-]+$/, t("skillDefinition.codeInvalid")), name: z.string().trim().min(1, t("skillDefinition.nameRequired")), description: z.string().trim(), instruction: z.string().trim().min(1, t("skillDefinition.instructionRequired")), examplesText: z.string().trim(), remark: z.string().trim(), }); } type EditForm = { code: string; name: string; description: string; instruction: string; examplesText: string; remark: string; }; function buildForm(item: SkillDefinition | null): EditForm { if (!item) { return emptyForm; } return { code: item.code, name: item.name, description: item.description ?? "", instruction: item.instruction ?? "", examplesText: (item.examples ?? []).join("\n"), remark: item.remark ?? "", }; } function buildPayload( form: EditForm, toolWhitelist: string[], ): CreateSkillDefinitionPayload { return { code: form.code.trim(), name: form.name.trim(), description: form.description.trim(), instruction: form.instruction.trim(), examples: form.examplesText .split("\n") .map((item) => item.trim()) .filter(Boolean), toolWhitelist, remark: form.remark.trim(), }; } export function EditDialog({ open, saving, itemId, onOpenChange, onSubmit, }: SkillEditDialogProps) { if (!open) { return null; } return ( ); } type SkillEditDialogBodyProps = SkillEditDialogProps; function SkillEditDialogBody({ open, saving, itemId, onOpenChange, onSubmit, }: SkillEditDialogBodyProps) { const t = useI18n(); const formId = "skill-definition-edit-form"; const [loading, setLoading] = useState(false); const [toolCatalog, setToolCatalog] = useState([]); const [selectedToolWhitelist, setSelectedToolWhitelist] = useState< string[] >([]); const [toolCodeToAdd, setToolCodeToAdd] = useState(""); const skillFormSchema = useMemo(() => createSkillFormSchema(t), [t]); const editFormResolver = useMemo( () => zodResolver(skillFormSchema) as Resolver, [skillFormSchema], ); const form = useForm({ resolver: editFormResolver, defaultValues: emptyForm, }); const { handleSubmit, reset, register, control, formState: { errors }, } = form; useEffect(() => { async function loadDetail() { if (!itemId) { reset(emptyForm); setSelectedToolWhitelist([]); setToolCodeToAdd(""); return; } setLoading(true); try { const data = await fetchSkillDefinition(itemId); reset(buildForm(data)); setSelectedToolWhitelist(data.toolWhitelist ?? []); setToolCodeToAdd(""); } catch (error) { console.error("Failed to load skill definition:", error); } finally { setLoading(false); } } void loadDetail(); }, [itemId, reset]); useEffect(() => { async function loadToolCatalog() { try { const data = await fetchMCPCatalog(); setToolCatalog(data); } catch (error) { console.error("Failed to load MCP tool catalog:", error); } } void loadToolCatalog(); }, []); async function onFormSubmit(values: EditForm) { await onSubmit(buildPayload(values, selectedToolWhitelist)); } const toolOptions = useMemo( () => toolCatalog.map((item) => ({ value: item.toolCode, label: `${item.title || item.toolName} ยท ${item.toolCode}`, })), [toolCatalog], ); const addableToolOptions = useMemo( () => toolOptions.filter( (option) => !selectedToolWhitelist.includes(option.value), ), [selectedToolWhitelist, toolOptions], ); const selectedToolOptions = useMemo( () => selectedToolWhitelist .map((toolCode) => toolOptions.find((option) => option.value === toolCode)) .filter( (option): option is { value: string; label: string } => !!option, ), [selectedToolWhitelist, toolOptions], ); function handleAddToolWhitelist(toolCode: string) { if (!toolCode || selectedToolWhitelist.includes(toolCode)) { return; } setSelectedToolWhitelist((prev) => [...prev, toolCode]); setToolCodeToAdd(""); } function handleRemoveToolWhitelist(toolCode: string) { setSelectedToolWhitelist((prev) => prev.filter((item) => item !== toolCode), ); } return ( } > {loading ? (
{t("skillDefinition.loading")}
) : (
{t("skillDefinition.code")} {t("skillDefinition.name")}
{t("skillDefinition.description")}