"use client"; import { useMemo } from "react"; import type { ToolCallMessagePartProps } from "@assistant-ui/react"; import { Alert, Box, Button, Group, Skeleton, Stack, Text, ThemeIcon } from "@mantine/core"; import { IconAlertTriangle, IconCheck, IconSettings } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; import { useSession } from "@homarr/auth/client"; import type { IntegrationKind } from "@homarr/definitions"; import { useModalAction } from "@homarr/modals"; import type { SettingsContextProps } from "@homarr/settings/creator"; import { useSettings } from "@homarr/settings"; import { useI18n, useScopedI18n } from "@homarr/translation/client"; import { reduceWidgetOptionsWithDefaultValues, widgetImports } from "@homarr/widgets"; import type { IntegrationSelectOption } from "@homarr/widgets/widget-integration-select"; import { WidgetEditModal } from "@homarr/widgets/modals"; import classes from "./assistant-panel.module.css"; import { useAssistantAutomaticAction } from "./assistant-auto-approval"; import { AssistantAutomaticActionProgress } from "./assistant-automatic-action-progress"; import { hasCompleteAssistantToolArguments, hasFailedAssistantToolArguments } from "./assistant-human-tool-status"; import type { ConfigureWidgetArgs, ConfigureWidgetResult } from "./assistant-tool-contracts"; import { IntegrationSelectModal } from "../integration/integration-select-modal"; interface AssistantIntegration extends IntegrationSelectOption { permissions: NonNullable; } export const getAssistantWidgetConfiguration = ( args: ConfigureWidgetArgs, settings: Pick, integrations: AssistantIntegration[], ) => { const definition = widgetImports[args.kind].definition; const integrationSupport = "supportedIntegrations" in definition; const supportedIntegrations = integrationSupport ? (definition.supportedIntegrations as readonly IntegrationKind[]) : []; const integrationData = integrations.filter( (integration) => integration.permissions.hasUseAccess && supportedIntegrations.includes(integration.kind), ); const requestedIds = new Set(args.integrationIds ?? []); const selectedIntegrations = requestedIds.size > 0 ? integrationData.filter((integration) => requestedIds.has(integration.id)) : integrationData; const maxIntegrations = "maxIntegrations" in definition ? (definition.maxIntegrations ?? Infinity) : Infinity; const integrationIds = selectedIntegrations.slice(0, maxIntegrations).map((integration) => integration.id); return { integrationData, integrationSupport, integrationsRequired: integrationSupport && (!("integrationsRequired" in definition) || definition.integrationsRequired !== false), value: { advancedOptions: { title: null, customCssClasses: [], borderColor: "" }, options: reduceWidgetOptionsWithDefaultValues(args.kind, settings, args.options), integrationIds, }, }; }; export const AssistantConfigureWidgetTool = ({ args, result, addResult, status, toolCallId, }: ToolCallMessagePartProps) => { const t = useScopedI18n("common.assistant.configureWidget"); const fullT = useI18n(); const { data: session } = useSession(); const settings = useSettings(); const hasCompleteArguments = hasCompleteAssistantToolArguments(status); const definition = args?.kind ? widgetImports[args.kind].definition : undefined; const needsIntegrations = definition !== undefined && "supportedIntegrations" in definition; const integrations = clientApi.integration.all.useQuery(undefined, { enabled: result === undefined && hasCompleteArguments && needsIntegrations, retry: false, }); const configuration = useMemo(() => { if (!args || !hasCompleteArguments || (needsIntegrations && !integrations.data)) return null; return getAssistantWidgetConfiguration(args, settings, integrations.data ?? []); }, [args, hasCompleteArguments, integrations.data, needsIntegrations, settings]); const missingRequiredIntegration = configuration?.integrationsRequired === true && configuration.value.integrationIds.length === 0; const { openModal: openWidgetModal } = useModalAction(WidgetEditModal); const { openModal: openIntegrationModal } = useModalAction(IntegrationSelectModal); const autoConfirming = useAssistantAutomaticAction({ toolCallId, ready: result === undefined && configuration !== null && !missingRequiredIntegration, completed: result !== undefined, confirm: () => { if (!args || !configuration) return; if (missingRequiredIntegration) { addResult({ boardId: args.boardId, kind: args.kind, cancelled: true, reason: "no-compatible-integration" }); return; } addResult({ boardId: args.boardId, kind: args.kind, options: configuration.value.options, integrationIds: configuration.value.integrationIds, }); }, }); if (result) { const cancelled = "cancelled" in result && result.cancelled; return ( {cancelled ? : } {cancelled ? t("cancelled") : t("ready")} {args?.kind ? fullT(`widget.${args.kind}.name`) : t("title")} ); } if (hasFailedAssistantToolArguments(status)) { return ( }> {t("errorDescription")} ); } if (!hasCompleteArguments || !args || (needsIntegrations && integrations.isPending)) { return ( ); } if (integrations.isError) { return ( }> {t("loadErrorDescription")} ); } if (!configuration) return null; const openConfigurationModal = ( value = configuration.value, integrationData: IntegrationSelectOption[] = configuration.integrationData, ) => openWidgetModal( { kind: args.kind, definition: widgetImports[args.kind].definition, value, integrationData, integrationSupport: configuration.integrationSupport, settings, onSuccessfulEdit: ({ options, integrationIds }) => addResult({ boardId: args.boardId, kind: args.kind, options, integrationIds }), }, { title: (titleT) => `${titleT("item.edit.title")} - ${titleT(`widget.${args.kind}.name`)}`, }, ); if (autoConfirming) { return ( ); } if (missingRequiredIntegration) { return ( }> {t("noIntegrationDescription")} {session?.user.permissions.includes("integration-create") ? ( ) : ( )} ); } const WidgetIcon = widgetImports[args.kind].definition.icon; return ( {fullT(`widget.${args.kind}.name`)} {args.summary} ); };