"use client";
import type { ReactNode } from "react";
import { useCallback, useState } from "react";
import { useAui, useAuiState } from "@assistant-ui/react";
import { ActionIcon, Box, Button, Center, Group, Loader, Stack, Text, ThemeIcon, Tooltip } from "@mantine/core";
import { IconAlertTriangle, IconArrowsMaximize, IconMessage, IconRobot } from "@tabler/icons-react";
import { useI18n } from "@homarr/translation/client";
import type { WidgetComponentProps } from "@homarr/widgets";
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 { AssistantComposerSurfaceProvider } from "./assistant-runtime-provider";
export const AssistantBoardWidget = (props: WidgetComponentProps<"assistant">) => {
const assistant = useHomarrAssistant();
if (!assistant.enabled) {
return ;
}
return (
);
};
const EnabledAssistantBoardWidget = ({ options, width, height, isEditMode }: WidgetComponentProps<"assistant">) => {
const t = useI18n();
const assistant = useHomarrAssistant();
const preferences = useAssistantPreferences();
const aui = useAui();
const currentThreadId = useAuiState((state) => state.threadListItem.remoteId);
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 pinnedConversation = options.conversationMode === "pinned" ? options.conversation : null;
const pinnedConversationIsActive = pinnedConversation === null || pinnedConversation.value === currentThreadId;
const [switching, setSwitching] = useState(false);
const [switchErrorConversationId, setSwitchErrorConversationId] = useState(null);
const hasSwitchError = switchErrorConversationId === pinnedConversation?.value;
const selectModel = useCallback(
(modelId: string) => {
preferences.setModelId(modelId);
const threadListItem = aui.threadListItem();
if (!threadListItem.getState().remoteId) return;
threadListItem.updateCustom({ ...threadListItem.getState().custom, modelId });
},
[aui, preferences],
);
const openPinnedConversation = async () => {
if (!pinnedConversation) return;
setSwitchErrorConversationId(null);
setSwitching(true);
try {
await aui.threads().switchToThread(pinnedConversation.value);
} catch {
setSwitchErrorConversationId(pinnedConversation.value);
} finally {
setSwitching(false);
}
};
if (options.conversationMode === "pinned" && pinnedConversation === null) {
return (
);
}
if (!pinnedConversationIsActive && pinnedConversation) {
return (
);
}
if (width < 300 || height < 280) {
return ;
}
return (
);
};
const CompactAssistantWidget = ({ onOpen }: { onOpen: () => void }) => {
const t = useI18n();
return (
{t("widget.assistant.compact.title")}
{t("widget.assistant.compact.description")}
);
};
interface AssistantWidgetStateProps {
icon: typeof IconRobot;
title: string;
description: string;
color?: string;
children?: ReactNode;
}
const AssistantWidgetState = ({
icon: Icon,
title,
description,
color = "red",
children,
}: AssistantWidgetStateProps) => (
{title}
{description}
{children}
);
const UnavailableAssistantWidget = ({ description }: { description: string | null }) => {
const t = useI18n();
return (
);
};