import { Sparkles } from 'lucide-react'; import { useEffect, useRef } from 'react'; import { useTranslation } from '../../i18n'; import { assistantScopeOf } from '../../store/assistantChatKey'; import { useAssistantStore } from '../../store/assistantStore'; import Tooltip from '../shared/Tooltip'; import { useAssistantHints } from './useAssistantHints'; import { useAssistantShortcutKeys } from './useAssistantHotkey'; /** Where the floating button sits, from the window's end and bottom edges. The window grows out of it. */ export const LAUNCHER_INSET = 24; export const LAUNCHER_SIZE = 48; /** * The way into the assistant: a button in the navbar, and in the planner a * round one floating at the bottom end of the screen. Both open and close the * window; outside a trip it opens on the trip picker. Nothing shows until the * assistant has a connection this user can use: no setup prompt in its place. * The floating one carries a dot while the trip has a warning the user has * not been shown; opening the chat counts them as seen. */ export default function AssistantLauncher({ variant, tripId = null, focusOnMount = false, }: { variant: 'navbar' | 'floating'; /** The trip the floating button stands in, for its hint dot. */ tripId?: number | null; /** Takes the focus when it appears, as when the window it stands for was just closed. */ focusOnMount?: boolean; }) { const { t } = useTranslation(); const available = useAssistantStore((s) => s.status?.available === true); const open = useAssistantStore((s) => s.window.open && !s.window.minimized); const toggle = useAssistantStore((s) => s.toggle); // An answer still being written in a closed window: the floating button breathes until it is opened. Only a // trip's chat counts: an answer still running in the Studio is not one this window opens on. const busy = useAssistantStore((s) => !s.window.open || s.window.minimized ? Object.entries(s.byTrip).some(([key, c]) => c.isRunning && assistantScopeOf(Number(key)).kind === 'trip') : false ); const keys = useAssistantShortcutKeys(); const hints = useAssistantHints(variant === 'floating' ? tripId : null, { withStays: false }); const floating = useRef(null); useEffect(() => { if (focusOnMount) floating.current?.focus(); }, [focusOnMount]); if (!available) return null; const label = t('assistant.launcher.tooltip'); if (variant === 'navbar') { return ( ); } const pointed = hints.unseenWarn > 0 ? t('assistant.launcher.withHints', { count: hints.unseenWarn }) : label; const openChat = () => { if (!open) hints.markWarnsSeen(); toggle(); }; return ( ); }