Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/renderer/src/screens/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,32 @@ function Chat({
useEffect(() => {
onLoadingChange?.(runId, isLoading);
}, [runId, isLoading, onLoadingChange]);

// Play a notification sound when the agent finishes responding
const prevLoadingRef = useRef(isLoading);
useEffect(() => {
const wasLoading = prevLoadingRef.current;
prevLoadingRef.current = isLoading;
if (!wasLoading || isLoading) return;
Comment on lines +93 to +98

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Sound fires for every mounted Chat instance, including background sessions. Every other audio/keyboard side effect in this component guards on active (see the Cmd+N handler, context-menu handlers, etc.), but the notification sound doesn't. If a user has two background sessions finishing simultaneously they'll both beep, and a background session finishing while the user is reading the active chat will emit a misleading sound.

Suggested change
// Play a notification sound when the agent finishes responding
const prevLoadingRef = useRef(isLoading);
useEffect(() => {
const wasLoading = prevLoadingRef.current;
prevLoadingRef.current = isLoading;
if (!wasLoading || isLoading) return;
// Play a notification sound when the agent finishes responding
const prevLoadingRef = useRef(isLoading);
useEffect(() => {
const wasLoading = prevLoadingRef.current;
prevLoadingRef.current = isLoading;
if (!wasLoading || isLoading) return;
if (!active) return;

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

// Agent just finished — play a short notification beep
try {
const ctx = new AudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = "sine";
// Play two quick ascending tones
osc.frequency.setValueAtTime(880, ctx.currentTime);
osc.frequency.setValueAtTime(1100, ctx.currentTime + 0.08);
gain.gain.setValueAtTime(0.12, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.2);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.2);
} catch {
// AudioContext may fail in some environments — silently ignore
}
Comment on lines +100 to +116

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 AudioContext is never closed — stops working after a few responses. Browsers cap the number of simultaneously-live AudioContext instances (Chrome: ~6, Safari: lower). Each agent response allocates a new one that is never released, so after ~6 chats the constructor starts throwing and the sound silently stops working — exactly the regression users won't notice until it's been broken for a while.

Suggested change
try {
const ctx = new AudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = "sine";
// Play two quick ascending tones
osc.frequency.setValueAtTime(880, ctx.currentTime);
osc.frequency.setValueAtTime(1100, ctx.currentTime + 0.08);
gain.gain.setValueAtTime(0.12, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.2);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.2);
} catch {
// AudioContext may fail in some environments — silently ignore
}
try {
const ctx = new AudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = "sine";
// Play two quick ascending tones
osc.frequency.setValueAtTime(880, ctx.currentTime);
osc.frequency.setValueAtTime(1100, ctx.currentTime + 0.08);
gain.gain.setValueAtTime(0.12, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.2);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.2);
// Close the context once playback ends to avoid exhausting the browser limit
osc.onended = () => { void ctx.close(); };
} catch {
// AudioContext may fail in some environments — silently ignore
}

}, [isLoading]);
const [hermesSessionId, setHermesSessionId] = useState<string | null>(
initialSessionId ?? null,
);
Expand Down
Loading