# Alchemist: Embedded Agent — Floating Widget Add a persistent, floating chat widget that users can toggle on any page. --- The **floating widget** is a persistent, toggleable chat interface that floats over your application content. It appears as a button in the bottom-right corner (or custom position) and opens a chat panel when clicked. ## When to use the floating widget Use the floating widget when: - The agent is **supplementary** to your main application (support, help, search assistant) - You want a **low-intrusion** interface that users can hide when not needed - You want the agent available on **every page** with minimal code - You want **persistent state** across page navigation For a prominent agent interface that is part of your main navigation, use the [inline panel](/docs/guides/alchemist-embedded-chipp-agent-inline-panel/) instead. ## Basic setup ### 1. Install the feature pack Follow the [installation guide](/docs/guides/alchemist-embedded-chipp-agent-install/) first. Once installed, your project has a `FloatingWidget` component ready to import. ### 2. Import the component In your root layout or main app file: ```tsx import { FloatingWidget } from '@/components/embedded-agent/FloatingWidget'; ``` ### 3. Render the widget Place the component once in your root layout (it renders globally and floats over all content): ```tsx export default function RootLayout({ children }) { return ( <>
{/* Your app header */}
{children}
); } ``` The widget is now available on every page. Users see a chat button in the bottom-right corner. ## Component props ```typescript interface FloatingWidgetProps { // Required projectId: string; // Your Alchemist project ID sessionEndpoint: string; // Your backend session endpoint // Optional: Identity externalUserId?: string; // Authenticated user ID (email) metadata?: Record; // Additional user context // Optional: Appearance position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; buttonLabel?: string; // Label on the toggle button (default: "Chat") buttonIcon?: React.ReactNode; // Custom icon for the button buttonColor?: string; // CSS color for button background theme?: 'light' | 'dark'; // Default: inherits from page // Optional: Behavior initiallyOpen?: boolean; // Open by default (default: false) hideOnMobile?: boolean; // Don't show on mobile devices (default: false) allowMinimize?: boolean; // Show minimize button (default: true) persistState?: boolean; // Remember open/closed state (default: true) // Optional: Callbacks onOpen?: () => void; // Fired when widget opens onClose?: () => void; // Fired when widget closes onError?: (error: EmbedError) => void; // Fired on chat errors } ``` ## Positioning ### Custom position Place the widget in a different corner: ```tsx ``` ### Custom offset Adjust the distance from the edge via CSS: ```tsx
``` ```css .custom-widget-wrapper :global(.embed-widget-button) { bottom: 2rem; /* Default: 1rem */ right: 2rem; /* Default: 1rem */ } ``` ## Authentication & identity ### Authenticated users Pass the logged-in user's email: ```tsx const user = useAuthContext(); ``` The user's conversations are retained across sessions and pages. ### Anonymous/visitor mode For public pages or unauthenticated visitors: ```tsx ``` ### Upgrading sessions When a visitor logs in, upgrade their anonymous session to authenticated: ```tsx // In your login handler const newSessionId = await fetch('/api/chat/session/upgrade', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ currentSessionId: anonSessionId, externalUserId: user.email, }), }); ``` Their previous conversation is merged into the authenticated session. ## Styling & customization ### Custom button appearance ```tsx ``` ### Custom icon ```tsx import { HelpCircle } from 'lucide-react'; } /> ``` ### Dark mode override ```tsx ``` ### Mobile behavior Hide the widget on mobile devices: ```tsx ``` Or show a different component instead: ```tsx {isMobile ? ( ) : ( )} ``` ## State persistence By default, the widget remembers whether the user opened or closed it (stored in localStorage). On each page load, it restores that state. ### Disable persistence ```tsx ``` ### Reset state Clear the persisted state programmatically: ```tsx localStorage.removeItem('chipp-embed-widget-state'); ``` ## Opening/closing programmatically You can control the widget state via a ref: ```tsx import { useRef } from 'react'; const widgetRef = useRef(null); const handleOpenChat = () => { widgetRef.current?.open(); }; const handleCloseChat = () => { widgetRef.current?.close(); }; <> ``` ## Callbacks & events ### Widget lifecycle ```tsx { console.log('User opened chat'); analytics.track('chat_opened'); }} onClose={() => { console.log('User closed chat'); analytics.track('chat_closed'); }} /> ``` ### Error handling ```tsx const handleChatError = (error: EmbedError) => { if (error.code === 'AUTH_FAILED') { // Prompt to re-authenticate showModal('Login Required', 'Please sign in to use chat.'); } else { showToast(`Chat unavailable: ${error.message}`); } }; ``` ## Responsive layout The widget is responsive by default. On narrow screens: - The chat panel shrinks to fit the available width - The button position adjusts to avoid keyboard on mobile (if the user taps the input) ### Disable on specific breakpoints ```tsx const isMobile = useMediaQuery('(max-width: 768px)'); const isSmallScreen = useMediaQuery('(max-height: 600px)'); return !isMobile && !isSmallScreen ? ( ) : null; ``` ## Z-index & layering The widget has a default z-index of `999` (below modals, above most content). Adjust if needed: ```css :global(.embed-widget-container) { z-index: 50 !important; /* Higher or lower than default */ } ``` ## Conversation history The widget maintains the current conversation across page navigations. Reloading the page or starting a new session (different `externalUserId`) creates a new conversation. ### Clearing the conversation Users can clear the conversation by clicking the **Clear** button in the widget header. You can also programmatically clear it: ```tsx localStorage.removeItem('chipp-embed-widget-messages'); ``` ## Troubleshooting ### "Widget button not visible" - Verify the component is rendered at the root level (not conditionally hidden). - Check z-index: other elements may be covering it. Use browser DevTools to inspect z-index values. - Ensure CSS is loaded (check for styling overrides). ### "Widget opens but chat doesn't load" - Verify `projectId` and `sessionEndpoint` are correct. - Check browser console for network errors (401, 404, 5xx). - Ensure your backend session endpoint is responding correctly. ### "Session expires quickly or chat resets" - Verify token TTL is reasonable (default: 8 hours for authenticated, 1 hour for anonymous). - If using anonymous mode, clearing the browser's cookies will reset the session. - For authenticated users, check that `externalUserId` is consistent across page loads. ### "Widget not persisting state across page reloads" - Ensure `persistState={true}` (default). - Check if browser localStorage is available and not full (quota exceeded). - Inspect localStorage: `localStorage.getItem('chipp-embed-widget-state')` should return JSON. ## Next steps - **[Inline panel](/docs/guides/alchemist-embedded-chipp-agent-inline-panel/)** — Embed a full panel as part of your layout - **[Backend client](/docs/guides/alchemist-embedded-chipp-agent-backend/)** — Integrate agent calls in your server routes