Files
AgentHub/client/src/components/Sagi/SpeechBubble.tsx
T
nntrivi2001 0cb5e41faf feat(pet): cleared alerts stay cleared, contextual bubbles, i18n, moments, chat window
- Fix: an errored session alerted on every update, and Clear alerts only
  dropped it in memory, so its next update (or a reload) brought the alert
  straight back. An error now alerts on the transition into error only;
  cleared sessions are remembered (agenthub.pet.dismissedErrors) and stay
  quiet until they recover and fail anew.
- Quips, greetings, notifications and Ask answers move to i18n (EN/VI);
  Ask understands Vietnamese keywords and names the sessions it counts.
- The pet goes by the worn pet's name; click it in the panel to rename
  (agenthub.pet.names, synced to the account).
- Bubbles name the session they are about, with an Open button (never a
  resume); a burst of the same event coalesces into one count.
- Moments: a new session waves, a finish hops, a subagent peeks, a wait taps,
  an error flinches — on Sagi, Codex sprites and still images. Idle fidgets,
  a once-per-session greeting, a "Needs you" list, an amber waiting dot.
- Pat (heart button, Ask, or stroking the pet) reacts on the pet itself, so
  it shows while the panel or chat is open.
- Panel drops Run Claude / Activity / Sessions / Errored.
- Ask hands off to a chat window (PetChat) on the default AI tool
  (Claude/Codex): transcript as bubbles, follow-ups and CLI menus through an
  off-screen attached terminal; no new server route.

Verified on HEAD + this diff in a clean worktree: tsc -b, the full client
suite (1334/1335; the one failure, ConversationView.follow, is flaky under
load and passes 3/3 alone) and server me-prefs.test.js.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:14:29 +07:00

104 lines
4.3 KiB
TypeScript

/**
* @file SpeechBubble.tsx
* @description Transient speech bubble rendered above the Sagi mascot.
* Pure presentation — visibility timing, coalescing and quip selection live in
* {@link useSagiBrain}; this component paints the line, the session(s) it is
* about, an Open button when it is about exactly one, and handles dismissal.
*
* ## Accessibility
* Uses `role="status"` with `aria-live="polite"` so screen readers announce
* new quips without interrupting current speech. Click the bubble to dismiss
* it early; its Open button opens the session instead.
*
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
/* =============================================================================
* MODULE_GUIDE — extended in-file reference (comments only; safe to read, never executed)
* =============================================================================
* **Purpose:** Sagi is the optional on-screen mascot assistant — quips, intents, and lightweight event reactions layered above the dashboard chrome.
*
* ## Design constraints
* - Local-first: no telemetry leaves the machine unless the user configures webhooks.
* - Fail-safe hooks path on the server must never block Claude Code; UI mirrors that
* philosophy by degrading gracefully (empty states, stale badges, reconnect loops).
* - Destructive flows stay behind explicit confirmation modals and server-side gates.
* - Internationalization: user-visible strings belong in i18n JSON, not literals here.
*
* ## Remote data & SSH
* Remote Data Sources let operators aggregate multiple machines. SSH entries describe
* how to reach a peer dashboard; the global data scope (`dataScope.ts`) narrows every
* scoped GET via `?sources=`. Health checks and import history surface in Settings.
*
* ## Public surface
* - `SpeechBubble` — exported API; see TSDoc on the symbol for behavior.
*
* ## Testing pointers
* - Prefer colocated `__tests__` with Vitest + Testing Library for UI.
* - Server contract changes require `npm run test:server` and OpenAPI sync.
*
* ## Related docs
* - `ARCHITECTURE.md` — hooks → API → SQLite → WebSocket → UI pipeline.
* - `docs/API.md` — REST reference.
* - `skills/file-headers/` — mandatory `@author` header policy.
* ============================================================================= */
/* -----------------------------------------------------------------------------
* EXPORT CATALOG — quick index of symbols defined below (documentation only).
* -----------------------------------------------------------------------------
* **SpeechBubble**
* Part of this module's public contract. Downstream imports should treat
* the signature and return type as stable unless release notes say otherwise.
* When behavior changes, update the `@file` overview and relevant tests.
*
* ----------------------------------------------------------------------------- */
import { useTranslation } from "react-i18next";
import type { SagiBubble } from "./useSagiBrain";
import type { SagiSubject } from "./brain";
/** Props for {@link SpeechBubble}. */
interface SpeechBubbleProps {
bubble: SagiBubble;
/** Called when the user clicks to dismiss. */
onDismiss: () => void;
/** Opens the session the bubble is about. */
onOpen?: (subject: SagiSubject) => void;
}
/**
* Animated speech bubble above Sagi.
* @param props See {@link SpeechBubbleProps}.
*/
export function SpeechBubble({ bubble, onDismiss, onOpen }: SpeechBubbleProps) {
const { t } = useTranslation("settings");
const { text, detail, subject, tone } = bubble;
return (
<div
className="sagi-bubble sagi-bubble-enter"
data-tone={tone}
role="status"
aria-live="polite"
onClick={onDismiss}
title={t("sagi.bubbleDismiss")}
>
<div>{text}</div>
{(detail || (subject && onOpen)) && (
<div className="sagi-bubble-detail">
{detail && <span className="sagi-bubble-subject">{detail}</span>}
{subject && onOpen && (
<button
type="button"
className="sagi-bubble-open"
onClick={(e) => {
e.stopPropagation();
onOpen(subject);
}}
>
{t("sagi.bubbleOpen")}
</button>
)}
</div>
)}
</div>
);
}