import { useEffect, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { FilePlus2, Loader2, RefreshCw, Trash2 } from "lucide-react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { ConfirmDialog } from "@/components/ConfirmDialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { promptsApi, type PiPromptFileKind, type PiPromptFileSnapshot, type PiPromptTemplate, } from "@/lib/api/prompts"; import { isValidPiPromptTemplateSlug } from "@/lib/piPromptSlug"; import { extractErrorMessage } from "@/utils/errorUtils"; const EDITABLE_FILES: Array<{ kind: Exclude; filename: string; titleKey: string; descriptionKey: string; }> = [ { kind: "system_override", filename: "SYSTEM.md", titleKey: "pi.prompts.systemOverride", descriptionKey: "pi.prompts.systemOverrideDescription", }, { kind: "system_append", filename: "APPEND_SYSTEM.md", titleKey: "pi.prompts.systemAppend", descriptionKey: "pi.prompts.systemAppendDescription", }, ]; function mutationError(error: unknown, fallback: string) { toast.error(extractErrorMessage(error) || fallback); } function PiInstructionFileEditor({ kind, filename, titleKey, descriptionKey, }: (typeof EDITABLE_FILES)[number]) { const { t } = useTranslation(); const queryClient = useQueryClient(); const [draft, setDraft] = useState(""); const [confirmCreate, setConfirmCreate] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const queryKey = ["pi", "promptFile", kind] as const; const query = useQuery({ queryKey, queryFn: () => promptsApi.getPiPromptFile(kind), }); useEffect(() => { if (query.data) setDraft(query.data.content); }, [query.data?.revision]); const save = useMutation({ mutationFn: () => { const snapshot = query.data; if (!snapshot) throw new Error(t("pi.prompts.loadFirst")); return promptsApi.replacePiPromptFile(kind, snapshot.revision, draft); }, onSuccess: (snapshot) => { queryClient.setQueryData(queryKey, snapshot); setConfirmCreate(false); toast.success(t("pi.prompts.fileSaved", { filename })); }, onError: (error) => { mutationError(error, t("pi.prompts.saveFailed")); void query.refetch(); }, }); const remove = useMutation({ mutationFn: async () => { const snapshot = query.data; if (!snapshot) throw new Error(t("pi.prompts.loadFirst")); await promptsApi.deletePiPromptFile(kind, snapshot.revision); return promptsApi.getPiPromptFile(kind); }, onSuccess: (snapshot) => { queryClient.setQueryData(queryKey, snapshot); setDraft(""); setConfirmDelete(false); toast.success(t("pi.prompts.fileDeactivated", { filename })); }, onError: (error) => { mutationError(error, t("pi.prompts.deleteFailed")); void query.refetch(); }, }); const busy = save.isPending || remove.isPending; const changed = Boolean(query.data && draft !== query.data.content); const blank = !draft.trim(); const requestSave = () => { if (kind === "system_override" && query.data && !query.data.exists) { setConfirmCreate(true); return; } save.mutate(); }; return (

{t(titleKey)}

{query.data?.exists ? t("pi.prompts.active") : t("pi.prompts.inactive")}

{t(descriptionKey)}

{query.data?.path && ( {query.data.path} )}
{query.isLoading ? (
{t("common.loading")}
) : ( <>