Skip to main content
OrbitPilot

Widget de chat OrbitPilot — streaming, déplaçable, mobile & voix

Ce que c'est

OrbitPilot est maintenant un widget de chat flottant global pour les utilisateurs connectés : un  bouton dans le coin ouvre un panneau que vous pouvez utiliser depuis n'importe quelle page. Le panneau est non-bloquant (continuez à naviguer pendant qu'il est ouvert), déplaçable, réductible, et il se souvient de sa position et de votre conversation ouverte pendant que vous vous déplacez sur le site.

Nouvelles fonctionnalités

  • Réponses en streaming — la réponse apparaît mot par mot au lieu d'apparaître après une attente.
  • Arrêter — interrompez l'IA en pleine réponse. L'arrêt sauvegarde tout ce qui a été écrit jusqu'à présent et n'utilise pas une demande de votre quota mensuel.
  • Copier / Télécharger toute la discussion — boutons en haut et en bas du panneau (le téléchargement est une transcription .txt).
  • Copier un seul message — survolez n'importe quelle bulle pour un icône de copie.
  • Maximiser la fenêtre, parcourir votre historique de chat, et démarrer une Nouvelle conversation.
  • Entrée vocale — appuyez sur le microphone et parlez ; vos mots sont tapés pour vous.

Les invités (non connectés) conservent la fenêtre contextuelle de chat de la page d'accueil d'origine.

Sur votre téléphone (mobile)

  • Le panneau se fixe en bas de l'écran et ne déborde jamais des bords, dans n'importe quelle orientation — faire pivoter le téléphone le réajuste automatiquement.
  • Il respecte le notch et l'indicateur d'accueil (zones sécurisées), et utilise la hauteur de l'écran en direct pour que la barre d'adresse du navigateur ne puisse pas couper le bouton Envoyer.
  • Après avoir parlé et envoyé, la boîte est vidée pour votre prochaine question — appuyez à nouveau sur le microphone pour parler au tour suivant.

Pour les développeurs

Le streaming nécessite une vue async sous ASGI (un générateur synchrone met en mémoire tampon avant le premier octet). orbitpilot.views.orbitpilot_ask_stream est une async def retournant StreamingHttpResponse sur un générateur asynchrone qui émet des événements envoyés par le serveur (meta  token  done). Tous les ORM/gating fonctionnent dans deux aides SYNC (_prepare_ask_stream / _finalize_ask_stream) via sync_to_async(thread_sensitive=True). Arrêter = le client interrompt la récupération  le générateur asynchrone est annulé  la réponse partielle est sauvegardée et la ligne de quota n'est PAS écrite. Le frontend se trouve dans templates/_orbitpilot_widget.html + static/css/orbitpilot-widget.css + static/js/orbitpilot-widget.js, inclus dans base.html uniquement pour les utilisateurs authentifiés.

Réactivité mobile (Workstream E) : le widget entier est box-sizing: border-box donc la bordure de 1px compte à l'intérieur de la largeur (le débordement de l'ancien iPhone était width:100vw + border). La règle @media (max-width:575px) abandonne 100vw pour left/right: env(safe-area-inset-*) + width:auto, tailles avec dvh (avec un vh de secours), et espace le compositeur par env(safe-area-inset-bottom). En JS, une position de glissement de bureau sauvegardée n'est pas appliquée sur un viewport mobile, et un écouteur resize/orientationchange réajuste la position via applyPos(). Clair-vocal : le module vocal garde un saved tampon finalisé séparé de l'entrée ; send() appelle désormais un resetVoice() partagé (stop reconnaissance, effacer le minuteur de silence, vider saved) juste là où il efface input.value, donc le tour suivant commence vide.

Taille de police de chat + correction mobile Envoyer (2026-07-27)

  • A / A+ dans l'en-tête du widget réduit/augmente le texte du chat. Votre choix est mémorisé (sauvegardé dans le navigateur via localStorage clé op_widget_font) à travers les pages et les sessions. La taille est limitée à 12 22px.
  • Le bouton Envoyer sur les téléphones : la ligne du compositeur garde maintenant les boutons Envoyer (et micro) visibles à côté de la zone de texte sur des écrans étroits — auparavant, la zone de texte pouvait pousser Envoyer hors du bord droit.

Pour les développeurs : balisage templates/_orbitpilot_widget.html, comportement static/js/orbitpilot-widget.js (applyFont()/bumpFont(), restauré à l'initialisation), stylisme static/css/orbitpilot-widget.css. Les boutons poussent une variable CSS --op-chat-font lue par le corps du message + l'entrée ; le compositeur utilise min-width:0 sur le textarea et flex:0 0 auto sur les boutons afin qu'Envoyer ne soit jamais poussé hors écran.

Menu hamburger + mode plein écran (2026-07-27)

L'en-tête du widget de chat a maintenant un  menu et un bouton  plein écran :

  • Vos instructions — notes permanentes que l'IA considère toujours (comme les instructions personnalisées de ChatGPT). Sauvegardées dans OrbitPilotSettings.custom_user_prompt via orbitpilot_custom_instructions ; plan-gated par la fonctionnalité custom_prompt (un message verrouillé + un lien de mise à niveau apparaît si le plan ne le permet pas ; le serveur l'applique également avec un 403).
  • Téléchargements — maintenant clairement deux options : wCette conversation uniquement (orbitpilot_conversation_download) contre Toutes mes conversations de chat (nouveau orbitpilot_history_download). Le bouton de la barre d'outils est renommé en Cette discussion.
  • Mémoire  Reconstituer ma mémoire maintenant — une reconstruction complète (orbitpilot_rebuild_memory) derrière un avertissement de coût de confirmation (il relit tout et ré-intègre, ce qui utilise des crédits IA) ; il ne met pas en file d'attente une reconstruction tant qu'une est réellement déjà en cours. (BL-404, 2026-09-07) Si cette construction "déjà en cours" a réellement planté ou a été tuée par un redémarrage du serveur — bloquée en prévu ou en cours bien au-delà d'une fenêtre de sécurité (10 minutes avec le travailleur d'arrière-plan activé, 3 minutes sans) — appuyer sur le bouton efface désormais automatiquement et commence une nouvelle reconstruction, en utilisant le même moissonneur que l'Inspecteur de Booklet a toujours utilisé (orbitpilot.services.reap_stuck_builds). La réponse indique qu'une construction bloquée a été effacée plutôt que de prétendre que rien ne s'est passé. Avant ce correctif, une construction morte dans cet état bloquait le Rafraîchissement pour toujours, sans moyen de débloquer à moins qu'un administrateur n'ouvre l'Inspecteur.

Le mode plein écran () ouvre /orbitpilot/chat/ dans un nouvel onglet, un chat plein écran de style ChatGPT/Gemini qui réutilise le MÊME widget (via la classe body op-fullpage) — donc changer entre le petit widget et la page pleine garde la même conversation (partagé op_widget_conv localStorage + un écouteur d'événements storage). Aussi : A / A+ change la police de chat (mémorisée), et sur un téléphone le bouton Envoyer reste visible à côté du micro.

Pour les développeurs : balisage templates/_orbitpilot_widget.html, comportement static/js/orbitpilot-widget.js, stylisme static/css/orbitpilot-widget.css ; la vue plein écran orbitpilot.views.orbitpilot_chat  orbitpilot/templates/orbitpilot/chat_full.html.

Mise à jour rapide vs Reconstruction complète (mémoire douce/dure, 2026-07-27)

Le menu  Mémoire du widget offre maintenant deux reconstructions :

  •  Mise à jour rapide (douce) — relit et ré-intègre uniquement les éléments que vous avez changés depuis la dernière construction ; les éléments inchangés conservent leurs intégrations existantes, les éléments supprimés sont abandonnés. Beaucoup moins cher, car le coût d'intégration OpenAI évolue avec le delta, pas avec l'ensemble du corpus.
  • Reconstruction complète (dure) — relit et ré-intègre tout depuis le début (utilise plus de crédits IA). Utilisez-le uniquement si quelque chose semble faux.

Pour les développeurs : run_memory_build(user, settings, build, incremental=True) différencie les nouveaux morceaux par rapport à ceux stockés selon la clé (source_key, source_id, chunk_index, content_hash) avant d'appeler embed_chunks, donc seuls les morceaux nouveaux/changed sont intégrés ; les lignes périmées sont supprimées dans le même bloc atomique ; les lignes inchangées restent intactes. Le mode est porté sur OrbitPilotMemoryBuild.incremental (migration orbitpilot/0025) et défini par orbitpilot_rebuild_memory de mode=soft|hard. Si incremental est omis, run_memory_build le lit à partir de l'enregistrement de construction. Le chemin complet/dur conserve la même priorité exacte du comportement d'effacement atomique de tout + ré-intégration.

Corrections basées sur le chat (2026-07-28)

Analyser de vraies conversations OrbitPilot (redactées PII) a révélé quatre corrections :

  • Liens brisés de l'IA (your-app-link) : le modèle a inventé un domaine lorsqu'il a reçu des chemins relatifs d'un livret périmé. Maintenant (a) un contrôle de santé du système avertit si SITE_URL est localhost en production, et (b) orbitpilot.services.heal_links() réécrit toute base de lien placeholder/localhost vers l'hôte réel du site dans chaque réponse (stream/blocking/guest) — donc les liens fonctionnent même avant une reconstruction. La solution permanente (définir SITE_URL + reconstruire) se trouve sur la Liste de vérification de configuration de l'administrateur.
  •  Liste mes Blinks maintenant fonctionne : pipeline/retrieve.py add_list_intent_sources() injecte TOUS les morceaux d'une source lorsque la requête est une demande d'énumération ("list/show/all my blinks/tasks/events"), et l'instantané de l'espace de travail échantillonne les titres de Blink — donc l'IA liste des éléments réels, pas juste le compte.
  • Actions suivantes cliquables : les actions sont maintenant {label, url}. Un lien de navigation réel s'affiche sous forme de bouton cliquable (s'ouvre dans un nouvel onglet) ; sinon, c'est une puce de pré-remplissage. Corrigé dans tout le widget, le modal d'accueil des invités (qui jetait auparavant des actions), et le résultat plein écran. Les liens de réponse en ligne s'ouvrent également dans un nouvel onglet maintenant.
  • Polissage des invites : l'invite de réponse principale ne répète plus excessivement l'objectif principal de l'utilisateur et gère "qu'est-ce qui a changé depuis hier" de manière élégante (elle fonctionne à partir de l'instantané actuel, pas l'historique jour après jour).

Pour les développeurs : orbitpilot/services.py (heal_links, request_base, split_answer_actions maintenant retournant dict, couche next_actions des invités), pipeline/retrieve.py (add_list_intent_sources), adminapp/diagnostics.py (_check_site_url), widget JS + home.html (rendu d'action + DOMPurify target=_blank hook), aihub/prompt_registry.py (noyau + actions suivantes par défaut ; rafraîchi sur les Defaults ensemencés non touchés via aihub/0007).

Une barre d'outils, pas deux (2026-08-28)

Ce qui a changé. Le widget dessinait auparavant la même rangée de boutons  Nouveau  Historique  Copier  Cette discussion — deux fois : une fois directement sous l'en-tête violet, et à nouveau en dessous de la boîte de message. Il y a maintenant une seule, en dessous de la boîte de message.

Pourquoi. Rapport #136, d'un téléphone. Deux rangées de chrome à l'intérieur d'une fenêtre déjà courte laissaient la conversation avec ce qui dépassait. Le rapport concernait l'espace très limité de la conversation.

Pourquoi la rangée inférieure est celle qui est restée. C'est celle qu'un pouce atteint sur un téléphone, elle se trouve à côté de la boîte dans laquelle vous tapez — qui est là où "commencer une nouvelle discussion" et "copier cette discussion" appartiennent — et elle portait déjà tous les boutons que l'autre avait. Les info-bulles de la rangée supérieure étaient la formulation la plus claire, donc elles sont descendues avec elle.

Rien n'est devenu inaccessible. L'en-tête garde son propre , donc un widget replié se ferme toujours ; et Fermer reste en dehors de la passerelle de connexion, donc un invité peut toujours fermer le widget. Protégé par orbitpilot/test_widget_toolbar.py.

Le microphone : une intention, une session (BL-447, 2026-09-09)

Le propriétaire a signalé le microphone de chat comme "la première fois ça fonctionne, puis la deuxième fois ça ne fonctionne pas, et parfois ça reste bloqué". La production avait déjà le correctif précédent lorsqu'il l'a dit, donc c'était ce qui restait en dessous, pas un déploiement raté. Trois choses étaient fausses, et elles expliquent les trois symptômes différents.

1. Le bouton pouvait signifier le contraire de ce qu'il montrait

Le gestionnaire de clics décidait quoi faire en lisant une variable (wantListening, la demande de l'utilisateur) tandis que l'état rouge pulsant était peint à partir d'une autre (listening, l'état réel du moteur). L'appel stop() du navigateur est asynchrone, donc après le délai de silence de six secondes et après chaque Envoi, il y a une fenêtre où la demande est déjà désactivée tandis que le moteur fonctionne toujours — et le bouton semble toujours actif. Une pression censée être arrêter était donc lue comme commencer, et le correctif précédent (correctement, pour son propre cas) gardait cette pression active pour que le moteur redémarre. Appuyer sur arrête a réactivé le microphone. L'image miroir de la même lacune a fait qu'une seconde pression annulait la propre première pression de l'utilisateur, de sorte que le microphone n'a jamais démarré du tout — c'est ce qui explique "la deuxième fois ça ne fonctionne pas".

Maintenant, il y a une variable. micOn est ce que l'utilisateur a demandé ; le bouton est dessiné à partir de cela et le clic le lit, donc ils ne peuvent pas être en désaccord.

2. Un objet de reconnaissance servait toute la page

Un seul SpeechRecognition a été créé lorsque la page s'est chargée et a été réutilisé pour toujours. C'est la seule raison pour laquelle le démarrage pouvait échouer avec une erreur "état invalide" à tout jamais, et un objet coincé par une perturbation réseau ou un microphone tenu par une autre application restait coincé jusqu'à ce que la page soit rechargée — "fonctionne une fois, puis jamais plus". Chaque autre microphone sur le site (la boîte de capture d'accueil, le rapporteur de bogues, la dictation admin) construit déjà un nouvel objet par pression. Le widget de chat fait maintenant la même chose, donc chaque pression reçoit un moteur propre.

3. Fermer la fenêtre ne libérait pas le microphone

close() n'a jamais arrêté la reconnaissance et rien ne l'a arrêté lorsque la page a été quittée, donc fermer le chat en plein discours laissait le moteur fonctionner derrière un panneau caché : l'indicateur d'enregistrement du navigateur restait allumé, la boucle de redémarrage continuait de se réarmer, et des mots continuaient à atterrir dans une boîte que personne ne pouvait voir. C'est ce qu'on appelle "le blocage". Fermer la fenêtre et quitter la page libèrent désormais tous les deux le microphone.

Quoi d'autre a changé que vous pouvez voir

  • Les échecs disent ce qui s'est passé. Permission bloquée, aucun microphone trouvé, et "ce clic n'a démarré rien" avaient le même aspect — un bouton gris et l'espace réservé ordinaire. Chacun écrit maintenant son propre court message dans la boîte de message.
  • Un moteur défectueux abandonne au lieu de tourner. La dictation se redémarre toujours elle-même par le biais d'une pause de réflexion, qui est le comportement que chaque microphone ici partage, mais une session qui continue de tomber sans jamais entendre la parole s'arrête après quelques essais et le dit. La parole réelle remplit ce budget, donc la dictation ordinaire ne se tarit jamais.
  • La boîte de chat ne prend le clavier que lorsqu'elle est à l'écran. La fin de la dictation précédemment faisait se concentrer sur l'entrée sans condition, ce qui pouvait faire apparaître le clavier sur une fenêtre qui venait juste d'être fermée.

Pour les développeurs. La session mourante est orpheline — la référence de session actuelle est définie sur null et chaque gestionnaire vérifie son identité avant de toucher quoi que ce soit — donc un dernier résultat tardif après Envoi est ignoré car il appartient à une session que personne ne possède plus. Cela maintient la garantie que les deux correctifs précédents cherchaient à atteindre sans un drapeau qui peut rester armé. La logique se trouve dans static/js/orbitpilot-widget.js (le bloc initVoice) et la forme est fixée par adminapp/test_widget_mic.py et adminapp/test_voice_parity.py. La parole du navigateur ne peut pas s'exécuter dans la suite de tests ; un vrai test de fumée Chromium est un élément proposé séparé.

This guide was translated automatically. Read the English original.

🪐 OrbitPilot

Create a free account and I'll remember your conversations — sign up

🧠 Memory status