"use client"; import { Suspense, use, 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 { ErrorBoundary } from "react-error-boundary"; import type { FallbackProps } from "react-error-boundary"; import { clientApi } from "@homarr/api/client"; import { useSession } from "@homarr/auth/client"; import { getWidgetName } from "@homarr/definitions"; 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 } from "@homarr/translation/client"; import type { WidgetDefinition } from "@homarr/widgets/definition"; import { loadWidgetDefinition, reduceWidgetOptionsWithDefinition } from "@homarr/widgets/manifest"; 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 = ( definition: WidgetDefinition, args: ConfigureWidgetArgs, settings: Pick, integrations: AssistantIntegration[], ) => { 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: reduceWidgetOptionsWithDefinition(definition, settings, args.options), integrationIds, }, }; }; type AssistantConfigureWidgetToolProps = ToolCallMessagePartProps; export const AssistantConfigureWidgetTool = (props: AssistantConfigureWidgetToolProps) => { const hasCompleteArguments = hasCompleteAssistantToolArguments(props.status); const completeArgs = props.result === undefined && !hasFailedAssistantToolArguments(props.status) && hasCompleteArguments ? props.args : undefined; if (!completeArgs) return ; return ( }> ); }; const AssistantWidgetDefinitionErrorFallback = ({ resetErrorBoundary }: FallbackProps) => { const t = useI18n("assistant.configureWidget"); const actionT = useI18n("common.action"); return ( }> {t("definitionLoadErrorDescription")} ); }; const LoadedAssistantConfigureWidgetTool = ( props: AssistantConfigureWidgetToolProps & { args: ConfigureWidgetArgs }, ) => { const definition = use(loadWidgetDefinition(props.args.kind)); return ; }; const AssistantConfigureWidgetSkeleton = () => { const t = useI18n("assistant.configureWidget"); return ( ); }; const AssistantConfigureWidgetToolContent = ({ args, result, addResult, status, toolCallId, definition, }: AssistantConfigureWidgetToolProps & { definition: WidgetDefinition | undefined }) => { const t = useI18n("assistant.configureWidget"); const actionT = useI18n("common.action"); const fullT = useI18n(); const { data: session } = useSession(); const settings = useSettings(); const hasCompleteArguments = hasCompleteAssistantToolArguments(status); 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 || !definition || !hasCompleteArguments || (needsIntegrations && !integrations.data)) return null; return getAssistantWidgetConfiguration(definition, args, settings, integrations.data ?? []); }, [args, definition, 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 ? getWidgetName(args.kind, fullT) : t("title")} ); } if (hasFailedAssistantToolArguments(status)) { return ( }> {t("errorDescription")} ); } if (!hasCompleteArguments || !args || (needsIntegrations && integrations.isPending)) { return ; } if (integrations.isError) { return ( }> {t("loadErrorDescription")} ); } if (!configuration || !definition) return null; const openConfigurationModal = ( value = configuration.value, integrationData: IntegrationSelectOption[] = configuration.integrationData, ) => openWidgetModal( { kind: 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")} - ${getWidgetName(args.kind, titleT)}`, }, ); if (autoConfirming) { return ( ); } if (missingRequiredIntegration) { return ( }> {t("noIntegrationDescription")} {session?.user.permissions.includes("integration-create") ? ( ) : ( )} ); } const WidgetIcon = definition.icon; return ( {getWidgetName(args.kind, fullT)} {args.summary} ); };