"use client"; import { useMemo, useState } from "react"; import type { ToolCallMessagePartProps } from "@assistant-ui/react"; import { Alert, Badge, Box, Button, ColorInput, CopyButton, Group, InputWrapper, Select, SimpleGrid, Skeleton, Slider, Stack, Switch, Tabs, Text, Textarea, TextInput, ThemeIcon, Tooltip, } from "@mantine/core"; import { IconAlertTriangle, IconCheck, IconCopy, IconFileTypeCss, IconPalette, IconPhoto, IconRefresh, IconSettings, } from "@tabler/icons-react"; import type { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; import { backgroundImageAttachments, backgroundImageRepeats, backgroundImageSizes } from "@homarr/definitions"; import { useZodForm } from "@homarr/form"; import { useI18n, useScopedI18n } from "@homarr/translation/client"; import { boardSavePartialSettingsSchema } from "@homarr/validation/board"; import { getAssistantBoardSettingsResult, getAssistantBoardSettingsDefaultTab, getChangedBoardSettings, getCustomCssWarnings, } from "./assistant-board-settings"; import { useAssistantAutomaticAction } from "./assistant-auto-approval"; import { AssistantAutomaticActionProgress } from "./assistant-automatic-action-progress"; import { hasCompleteAssistantToolArguments, hasFailedAssistantToolArguments } from "./assistant-human-tool-status"; import { AssistantHumanToolError } from "./assistant-human-tools"; import classes from "./assistant-panel.module.css"; import type { AssistantBoardSettingsChanges, ConfigureBoardSettingsArgs, ConfigureBoardSettingsResult, } from "./assistant-tool-contracts"; type BoardSettings = RouterOutputs["board"]["getBoardSettings"]; const completeSettingsSchema = boardSavePartialSettingsSchema.required(); type BoardSettingsFormValues = z.infer; const normalizeFormValues = ( current: BoardSettings, proposed: AssistantBoardSettingsChanges, ): BoardSettingsFormValues => { const values = { ...current, ...proposed }; return { pageTitle: values.pageTitle ?? "", metaTitle: values.metaTitle ?? "", logoImageUrl: values.logoImageUrl ?? "", faviconImageUrl: values.faviconImageUrl ?? "", backgroundImageUrl: values.backgroundImageUrl ?? "", backgroundImageAttachment: values.backgroundImageAttachment, backgroundImageRepeat: values.backgroundImageRepeat, backgroundImageSize: values.backgroundImageSize, primaryColor: values.primaryColor, secondaryColor: values.secondaryColor, opacity: values.opacity, customCss: values.customCss, iconColor: values.iconColor ?? "", itemRadius: values.itemRadius, disableStatus: values.disableStatus, }; }; const toCurrentValues = (current: BoardSettings): BoardSettingsFormValues => normalizeFormValues(current, {}); export const AssistantConfigureBoardSettingsTool = ({ args, result, addResult, status, toolCallId, }: ToolCallMessagePartProps) => { const t = useScopedI18n("common.assistant.configureBoardSettings"); const hasCompleteArguments = hasCompleteAssistantToolArguments(status); const boardId = args?.boardId ?? ""; const settings = clientApi.board.getBoardSettings.useQuery( { id: boardId }, { enabled: result === undefined && hasCompleteArguments && boardId.length > 0, retry: false }, ); const autoConfirming = useAssistantAutomaticAction({ toolCallId, ready: result === undefined && settings.data !== undefined && hasCompleteArguments, completed: result !== undefined, confirm: () => { if (!settings.data || !args) return; const currentValues = toCurrentValues(settings.data); const proposedValues = normalizeFormValues(settings.data, args.changes); addResult(getAssistantBoardSettingsResult(settings.data.id, currentValues, proposedValues)); }, }); if (result) { const cancelled = "cancelled" in result && result.cancelled; const changedCount = cancelled ? 0 : Math.max(0, Object.keys(result).length - 1); return ( {cancelled ? t("cancelled") : t("ready")} {cancelled ? args?.boardName : t("changeCount", { count: changedCount })} ); } if (hasFailedAssistantToolArguments(status)) return ; if (!hasCompleteArguments) { return ; } if (!args?.boardId || !args.boardName) return ; if (settings.isError) { return ( }> {t("loadErrorDescription")} ); } if (!settings.data) return ; if (autoConfirming) { return ( ); } return ( addResult({ id: settings.data.id, ...changes })} onCancel={() => addResult({ id: settings.data.id, cancelled: true })} /> ); }; const BoardSettingsSkeleton = ({ label }: { label: string }) => ( ); const BoardSettingsForm = ({ current, args, onSubmit, onCancel, }: { current: BoardSettings; args: ConfigureBoardSettingsArgs; onSubmit: (changes: AssistantBoardSettingsChanges) => void; onCancel: () => void; }) => { const t = useScopedI18n("common.assistant.configureBoardSettings"); const boardT = useI18n(); const [submitting, setSubmitting] = useState(false); const currentValues = useMemo(() => toCurrentValues(current), [current]); const form = useZodForm(completeSettingsSchema, { initialValues: normalizeFormValues(current, args.changes), }); const changes = getChangedBoardSettings(currentValues, form.values); const changedCount = Object.keys(changes).length; const cssWarnings = getCustomCssWarnings(form.values.customCss); const proposedKeys = Object.keys(args.changes); const defaultTab = getAssistantBoardSettingsDefaultTab(args.changes); const optionData = ( values: readonly TValue[], key: "backgroundImageAttachment" | "backgroundImageRepeat" | "backgroundImageSize", ) => values.map((value) => ({ value, label: boardT(`board.field.${key}.option.${value}.label` as never), })); return ( {t("title")} {args.boardName} {args.summary} {proposedKeys.length > 0 && ( {t("proposedCount", { count: proposedKeys.length })} )}
{ if (submitting) return; const nextChanges = getChangedBoardSettings(currentValues, values); setSubmitting(true); if (Object.keys(nextChanges).length === 0) { onCancel(); return; } onSubmit(nextChanges); })} > }> {t("tabs.css")} }> {t("tabs.appearance")} }> {t("tabs.background")} }> {t("tabs.general")}