"use client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Sparkles } from "lucide-react"; import { toast } from "sonner"; import { SettingsCard } from "@/components/settings/settings-card"; import { SettingsCardHeader } from "@/components/settings/_card-header"; import { useTranslations } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; import { SettingsToggle } from "./_shared"; import { apiGet, apiPut } from "@/lib/api/api-fetch"; /** * v1.4.31 — operator-side panel for the six assistant feature * flags. The master toggle gates the whole assistant; five * sub-toggles carve specific surfaces (Coach, Daily Briefing, * per-metric status cards, correlation narration, Health-Score * delta explainer). * * UX: * - Master toggle at the top. When off, the sub-toggles are * visually greyed out (kept rendered so the operator can see * which sub-flags are individually flipped before they unmute * the master) but cannot be flipped via the disabled ``. * - Optimistic flip + server confirm via `useMutation`; failure * surfaces a toast and reverts the in-flight toggle by * re-invalidating the read query. */ interface AssistantFlagsResponse { raw: { assistantEnabled: boolean; assistantCoachEnabled: boolean; assistantBriefingEnabled: boolean; assistantInsightStatusEnabled: boolean; assistantCorrelationsEnabled: boolean; assistantHealthScoreExplainerEnabled: boolean; }; resolved: { enabled: boolean; coach: boolean; briefing: boolean; insightStatus: boolean; correlations: boolean; healthScoreExplainer: boolean; }; } function useAssistantFlags() { return useQuery({ queryKey: queryKeys.adminAssistantFlags(), queryFn: async () => { return apiGet( "/api/admin/settings/assistant-flags", ); }, }); } function useUpdateAssistantFlags() { const client = useQueryClient(); const { t } = useTranslations(); return useMutation({ mutationFn: async ( patch: Partial, ): Promise => { return apiPut( "/api/admin/settings/assistant-flags", patch, ); }, onSuccess: (data) => { client.setQueryData(queryKeys.adminAssistantFlags(), data); // Bust the runtime `/api/feature-flags` cache so the operator // sees the toggled surface react within the same session. client.invalidateQueries({ queryKey: queryKeys.featureFlags() }); toast.success(t("common.saved")); }, onError: (err) => { toast.error( err instanceof Error && err.message ? err.message : t("admin.settingsSaveError"), ); client.invalidateQueries({ queryKey: queryKeys.adminAssistantFlags(), }); }, }); } export function AssistantSection() { const { t } = useTranslations(); const { data } = useAssistantFlags(); const mutation = useUpdateAssistantFlags(); const raw = data?.raw; const masterOn = raw?.assistantEnabled ?? true; const disabledSubs = !masterOn || mutation.isPending; return (
mutation.mutate({ assistantEnabled: checked }) } disabled={mutation.isPending} />
mutation.mutate({ assistantCoachEnabled: checked }) } disabled={disabledSubs} /> mutation.mutate({ assistantBriefingEnabled: checked }) } disabled={disabledSubs} /> mutation.mutate({ assistantInsightStatusEnabled: checked }) } disabled={disabledSubs} /> mutation.mutate({ assistantCorrelationsEnabled: checked }) } disabled={disabledSubs} /> mutation.mutate({ assistantHealthScoreExplainerEnabled: checked, }) } disabled={disabledSubs} />
); }