"use client"; import type { CSSProperties, ReactNode } from "react"; import { useEffect, useId, useRef } from "react"; import { ActionIcon, Box, Center, Group, Stack, Text, ThemeIcon, Tooltip } from "@mantine/core"; import { IconArrowsMaximize, IconArrowsMinimize, IconRobot } from "@tabler/icons-react"; import { useAui, useAuiState } from "@assistant-ui/react"; import { useI18n } from "@homarr/translation/client"; import type { WidgetComponentProps } from "@homarr/widgets"; import { useRequiredBoard } from "@homarr/boards/context"; import classes from "./assistant-panel.module.css"; import { AssistantConversationSurface } from "./assistant-panel"; import { getPendingAssistantAction } from "./assistant-pending-action"; import { useAssistantPreferences, useHomarrAssistant } from "./assistant-context"; import { AssistantComposerSurfaceBoundary } from "./assistant-runtime-provider"; export const AssistantBoardWidget = (props: WidgetComponentProps<"assistant">) => { const assistant = useHomarrAssistant(); const { enabled, setWidgetVisible } = assistant; const generatedWidgetId = useId(); const widgetId = props.itemId ?? generatedWidgetId; const widgetRef = useRef(null); useEffect(() => { const widget = widgetRef.current; if (!enabled || !widget) return; const observer = new IntersectionObserver(([entry]) => setWidgetVisible(widgetId, entry?.isIntersecting === true), { threshold: 0.15, }); observer.observe(widget); return () => { observer.disconnect(); setWidgetVisible(widgetId, false); }; }, [enabled, setWidgetVisible, widgetId]); if (!assistant.enabled) { return ; } return ( ); }; const EnabledAssistantBoardWidget = ({ width, height, isEditMode, assistantWidgetId, }: WidgetComponentProps<"assistant"> & { assistantWidgetId: string }) => { const board = useRequiredBoard(); const assistant = useHomarrAssistant(); const preferences = useAssistantPreferences(); const aui = useAui(); const messages = useAuiState((state) => state.thread.messages); const isLoading = useAuiState((state) => state.thread.isLoading); const latestAssistantMessage = messages.findLast((message) => message.role === "assistant"); const pendingAction = getPendingAssistantAction(latestAssistantMessage); const selectModel = (modelId: string) => { preferences.setModelId(modelId); const threadListItem = aui.threadListItem(); if (!threadListItem.getState().remoteId) return; threadListItem.updateCustom({ ...threadListItem.getState().custom, modelId }); }; if (assistant.opened) { return ; } if (assistant.activeWidgetId !== null && assistant.activeWidgetId !== assistantWidgetId) { return assistant.activateWidget(assistantWidgetId)} location="widget" />; } if (width < 300 || height < 280) { return ; } return ( ); }; const CompactAssistantWidget = ({ onOpen, location = "compact", }: { onOpen: () => void; location?: "compact" | "panel" | "widget"; }) => { const t = useI18n(); let title = t("widget.assistant.compact.title"); let description = t("widget.assistant.compact.description"); let actionLabel = t("widget.assistant.compact.open"); let actionIcon = ; if (location === "panel") { title = t("widget.assistant.compact.openTitle"); description = t("widget.assistant.compact.openDescription"); actionLabel = t("widget.assistant.compact.returnToWidget"); actionIcon = ; } if (location === "widget") { title = t("widget.assistant.compact.otherWidgetTitle"); description = t("widget.assistant.compact.otherWidgetDescription"); actionLabel = t("widget.assistant.compact.useHere"); } return ( {title} {description} {actionIcon} ); }; interface AssistantWidgetStateProps { icon: typeof IconRobot; title: string; description: string; color?: string; children?: ReactNode; } const AssistantWidgetState = ({ icon: Icon, title, description, color, children }: AssistantWidgetStateProps) => (
{title} {description} {children}
); const UnavailableAssistantWidget = ({ description }: { description: string | null }) => { const t = useI18n(); return ( ); };