FloatAssistant
A button that opens an assistant panel over the page.
Harbour rebrand
Offline sync for tasks
Eng
JWHarbour homepage
Design
MCOpen the assistant and ask about the project.
In context
Offline sync for tasks
Jonas · Eng
Harbour homepage
Maya · Design
Ask about Harbour rebrand
Props
| Prop | Type | Required | Default |
|---|---|---|---|
name | string | undefined | No | Assistant |
greeting | string | undefined | No | Hi! How can I help you today? |
voiceGreetings | string[] | undefined | No | — |
position | "bottom-right" | "bottom-left" | "top-right" | "top-left" | undefined | No | bottom-right |
accentColor | string | undefined | No | var(--rebar-color-primary, #0066cc) |
persona | OrbPersonaId | undefined | No | — |
personaLabel | string | undefined | No | — |
onSendMessage | ((message: string, screenshot?: string | undefined, pageContext?: string[] | undefined) => void) | undefined | No | — |
onVoiceRecord | ((recording: boolean) => void) | undefined | No | — |
onAttachmentPress | (() => void) | undefined | No | — |
screenshotEnabled | boolean | undefined | No | true |
onCaptureScreenshot | (() => Promise<string>) | undefined | No | — |
screenshotAcknowledgment | string | undefined | No | Yep, I'm looking at the screen — what would you like me to analyze? |
contextAware | boolean | undefined | No | false |
voiceEnabled | boolean | undefined | No | true |
mode | "text" | "voice" | undefined | No | — |
onModeChange | ((mode: "text" | "voice") => void) | undefined | No | — |
draggable | boolean | undefined | No | true |
minimizable | boolean | undefined | No | true |
sidebarDockable | boolean | undefined | No | false |
sidebarWidth | number | undefined | No | 380 |
dockMode | "floating" | "sidebar" | undefined | No | — |
onDockModeChange | ((dockMode: "floating" | "sidebar") => void) | undefined | No | — |
onSlashCommand | (() => void) | undefined | No | — |
onHistoryPress | (() => void) | undefined | No | — |
modelLabel | string | undefined | No | — |
onModelPress | (() => void) | undefined | No | — |
apiEndpoint | string | undefined | No | — |
apiAuthToken | string | undefined | No | — |
getHostContext | (() => string | Promise<string | undefined> | undefined) | undefined | No | — |
voices | FloatAssistantVoiceOption[] | undefined | No | — |
voiceId | string | undefined | No | — |
onVoiceChange | ((voiceId: string) => void) | undefined | No | — |
theme | "light" | "dark" | undefined | No | light |
markdown | boolean | undefined | No | true |
open | boolean | undefined | No | — |
defaultOpen | boolean | undefined | No | — |
onOpenChange | ((open: boolean) => void) | undefined | No | — |
className | string | undefined | No | — |
bionic | boolean | undefined | No | — |
bionicOptions | BionicOptions | undefined | No | — |
API integration
The construct never sees an API key: apiEndpoint is a server-side proxy you implement. It receives { message, history, screenshot?, pageContext?, hostContext? } and returns { reply }. getHostContext is called fresh before every send to describe the real on-screen state (the open project, an active filter).
POST /api/assistant/chat
{ "message": "What is due this week?", "history": [...], "hostContext": "Project: Harbour rebrand, 5 open tasks" }
{ "reply": "Seven tasks, three today. Offline sync is in review." }Features
Drag the button anywhere (draggable), minimise it to a dot (minimizable), dock the panel to a full-height sidebar (sidebarDockable), switch to voice (voiceEnabled, voices), render replies as Markdown (markdown), and pick a WebGL orb persona (persona: Spark, Strato or Chorus). The footer always says it is an AI.
Accessibility
Real role="dialog" when expanded, aria-label and aria-expanded on the trigger, keyboard-navigable controls and visible focus. The floating button is always reachable with Tab.
data-rebar-* attributes
data-rebar-component="float-assistant", data-rebar-part="trigger" on the button, "panel" on the expanded panel, "messages" on the message list.