Skip to main content
OrbitPilot

OrbitPilot chat widget — streaming, movable, mobile & voice

What it is

OrbitPilot is now a global floating chat widget for logged-in users: a 🪐 button in the corner opens a panel you can use from any page. The panel is non-blocking (keep browsing while it's open), draggable, collapsible, and it remembers its place and your open conversation as you move around the site.

New abilities

  • Streaming answers — the reply appears word-by-word instead of after a wait.
  • Stop — halt the AI mid-answer. Stopping saves whatever was written so far and does not use up an ask from your monthly allowance.
  • Copy / Download the whole chat — buttons at the top and bottom of the panel (download is a .txt transcript).
  • Copy a single message — hover any bubble for a copy icon.
  • Maximize the window, browse your chat history, and start a New conversation.
  • Voice input — tap the microphone and talk; your words are typed for you.

Guests (not logged in) keep the original home-page chat pop-up.

On your phone (mobile)

  • The panel docks to the bottom of the screen and never spills past the edges, in any orientation — rotating the phone re-fits it automatically.
  • It respects the notch and the home-indicator (safe areas), and uses the live screen height so the browser's address bar can't clip the Send button.
  • After you speak and send, the box is emptied for your next question — tap the microphone again to talk for the next turn.

For developers

Streaming requires an async view under ASGI (a sync generator buffers before the first byte). orbitpilot.views.orbitpilot_ask_stream is an async def returning StreamingHttpResponse over an async generator that emits Server-Sent Events (metatoken… → done). All ORM/gating runs in two SYNC helpers (_prepare_ask_stream / _finalize_ask_stream) via sync_to_async(thread_sensitive=True). Stop = the client aborts the fetch → the async generator is cancelled → the partial answer is saved and the quota row is NOT written. Frontend lives in templates/_orbitpilot_widget.html + static/css/orbitpilot-widget.css + static/js/orbitpilot-widget.js, included in base.html for authed users only.

Mobile responsiveness (Workstream E): the whole widget is box-sizing: border-box so the 1px border counts inside the width (the old iPhone overflow was width:100vw + border). The @media (max-width:575px) rule drops 100vw for left/right: env(safe-area-inset-*) + width:auto, sizes with dvh (with a vh fallback), and pads the composer by env(safe-area-inset-bottom). In JS, a saved desktop drag position is not applied on a mobile viewport, and a resize/orientationchange listener re-clamps the position via applyPos(). Voice-clear: the speech module keeps a finalized saved buffer separate from the input; send() now calls a shared resetVoice() (stop recognition, clear the silence timer, blank saved) right where it clears input.value, so the next turn starts empty.

Chat font size + mobile Send fix (2026-07-27)

  • A− / A+ in the widget header shrink/grow the chat text. Your choice is remembered (saved in the browser via localStorage key op_widget_font) across pages and sessions. Size is clamped to 12–22px.
  • Send button on phones: the composer row now keeps the Send (and mic) buttons visible next to the text box on narrow screens — previously the text box could push Send off the right edge.

For developers: markup templates/_orbitpilot_widget.html, behavior static/js/orbitpilot-widget.js (applyFont()/bumpFont(), restored on init), styling static/css/orbitpilot-widget.css. The buttons bump a --op-chat-font CSS var read by the message body + input; the composer uses min-width:0 on the textarea and flex:0 0 auto on the buttons so Send never gets shoved off-screen.

Hamburger menu + full-page mode (2026-07-27)

The chat widget header now has a ☰ menu and a ⤢ full-page button:

  • Your instructions — standing notes the AI always considers (like ChatGPT custom instructions). Saved to OrbitPilotSettings.custom_user_prompt via orbitpilot_custom_instructions; plan-gated by the custom_prompt feature (a locked message + upgrade link appears if the plan doesn't allow it; the server also enforces it with a 403).
  • Downloads — now clearly two options: “This conversation only” (orbitpilot_conversation_download) vs “All my chat history” (new orbitpilot_history_download). The toolbar button is relabelled “⭳ This chat”.
  • Memory → Rebuild my memory now — a full rebuild (orbitpilot_rebuild_memory) behind a confirm cost-warning (it re-reads everything and re-embeds, which uses AI credits); it won't stack a rebuild while one is already running.

Full-page mode (⤢) opens /orbitpilot/chat/ in a new tab, a ChatGPT/Gemini-style full-screen chat that reuses the SAME widget (via body class op-fullpage) — so switching between the small widget and the full page keeps the same conversation (shared op_widget_conv localStorage + a storage event listener). Also: A− / A+ change the chat font (remembered), and on a phone the Send button stays visible next to the mic.

For developers: markup templates/_orbitpilot_widget.html, behavior static/js/orbitpilot-widget.js, styling static/css/orbitpilot-widget.css; full-page view orbitpilot.views.orbitpilot_chatorbitpilot/templates/orbitpilot/chat_full.html.

Quick update vs Full rebuild (soft/hard memory, 2026-07-27)

The widget's 🧠 Memory menu now offers two rebuilds:

  • ⚡ Quick update (soft) — re-reads and re-embeds only the items you've changed since the last build; unchanged items keep their existing embeddings, removed items are dropped. Much cheaper, because the OpenAI embedding cost scales with the delta, not the whole corpus.
  • 🔄 Full rebuild (hard) — re-reads and re-embeds everything from scratch (uses more AI credits). Use it only if something looks wrong.

For developers: run_memory_build(user, settings, build, incremental=True) diffs the new chunks against the stored ones by the (source_key, source_id, chunk_index, content_hash) key before calling embed_chunks, so only new/changed chunks are embedded; stale rows are deleted in the same atomic block; unchanged rows are left untouched. The mode is carried on OrbitPilotMemoryBuild.incremental (migration orbitpilot/0025) and set by orbitpilot_rebuild_memory from mode=soft|hard. If incremental is omitted, run_memory_build reads it from the build record. The full/hard path keeps the exact prior atomic delete-all + re-embed behaviour.

Chat-driven fixes (2026-07-28)

Analysing real (PII-redacted) OrbitPilot conversations surfaced four fixes:

  • Broken AI links (your-app-link): the model invented a domain when handed relative paths from a stale booklet. Now (a) a System Health check warns if SITE_URL is localhost in production, and (b) orbitpilot.services.heal_links() rewrites any placeholder/localhost link base to the real site host in every answer (stream/blocking/guest) — so links work even before a rebuild. The permanent fix (set SITE_URL + rebuild) is on the Admin Setup Checklist.
  • “List my Winks” now works: pipeline/retrieve.py add_list_intent_sources() injects ALL of a source's chunks when the query is an enumerate request ("list/show/all my winks/tasks/events"), and the workspace snapshot samples Wink titles — so the AI lists actual items, not just the count.
  • Clickable next-actions: actions are now {label, url}. A real navigation link renders as a clickable button (opens in a new tab); otherwise it's a pre-fill chip. Fixed across the widget, the guest home modal (which previously dropped actions), and the full-page result. Inline answer links also open in a new tab now.
  • Prompt polish: the core answer prompt stops over-repeating the user's headline goal and handles "what changed since yesterday" gracefully (it works from the current snapshot, not day-over-day history).

For developers: orbitpilot/services.py (heal_links, request_base, split_answer_actions now dict-returning, guest next_actions layer), pipeline/retrieve.py (add_list_intent_sources), adminapp/diagnostics.py (_check_site_url), widget JS + home.html (action render + DOMPurify target=_blank hook), aihub/prompt_registry.py (core + next_actions defaults; refreshed on untouched seeded Defaults via aihub/0007).

🪐 OrbitPilot