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>
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
/**
|
||||
* @file SpeechBubble.tsx
|
||||
* @description Transient speech bubble rendered above the Sagi mascot.
|
||||
* Pure presentation — visibility timing and quip selection live in
|
||||
* {@link useSagiBrain}; this component only paints the bubble and handles
|
||||
* user dismissal.
|
||||
* 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 anywhere on the bubble
|
||||
* to dismiss early.
|
||||
* 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>
|
||||
*/
|
||||
@@ -52,30 +52,52 @@
|
||||
* ----------------------------------------------------------------------------- */
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { SagiBubble } from "./useSagiBrain";
|
||||
import type { SagiSubject } from "./brain";
|
||||
|
||||
/** Props for {@link SpeechBubble}. */
|
||||
interface SpeechBubbleProps {
|
||||
/** Quip text to display inside the bubble. */
|
||||
text: string;
|
||||
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({ text, onDismiss }: 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")}
|
||||
>
|
||||
{text}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user