0cb5e41faf
- 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>
104 lines
4.3 KiB
TypeScript
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>
|
|
);
|
|
}
|