diff --git a/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ToolbarActions.tsx b/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ToolbarActions.tsx index 7138ae55..fc8be109 100644 --- a/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ToolbarActions.tsx +++ b/frontend/src/app/pages/AgentChat/ChatInput/toolbar/ToolbarActions.tsx @@ -7,6 +7,8 @@ import StopIcon from '@mui/icons-material/Stop'; import AttachFileIcon from '@mui/icons-material/AttachFile'; import AdsClickIcon from '@mui/icons-material/AdsClick'; import LanguageIcon from '@mui/icons-material/Language'; +import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined'; +import { useVoice } from '@/shared/voice/VoiceDictationContext'; import { useElementSelection } from '@/app/components/editor/ElementSelectionContext'; import { ClaudeTokens } from '@/shared/styles/claudeTokens'; import { ComposerPlusMenu, ActiveTogglePills, PlusMenuItem } from './ComposerPlusMenu'; @@ -49,6 +51,7 @@ export const ToolbarActions: React.FC = ({ elementSelection.setSelectMode(true); } }; + const { state: voiceState, toggle: voiceToggle } = useVoice(); const plusItems: PlusMenuItem[] = []; plusItems.push({ key: 'attach', @@ -56,6 +59,15 @@ export const ToolbarActions: React.FC = ({ icon: , onSelect: () => generalFileInputRef.current?.click(), }); + // A menu click can't be held, so this entry always toggles regardless of the hold-to-talk setting. + plusItems.push({ + key: 'dictate', + label: voiceState === 'recording' ? 'Stop dictation' : 'Dictate', + icon: , + toggle: true, + active: voiceState === 'recording', + onSelect: voiceToggle, + }); if (onToggleWebSearch) { plusItems.push({ key: 'web', diff --git a/frontend/src/app/pages/AgentChat/shell/BrowserAgentInlineFeed.tsx b/frontend/src/app/pages/AgentChat/shell/BrowserAgentInlineFeed.tsx index 86d50750..afa393b0 100644 --- a/frontend/src/app/pages/AgentChat/shell/BrowserAgentInlineFeed.tsx +++ b/frontend/src/app/pages/AgentChat/shell/BrowserAgentInlineFeed.tsx @@ -219,7 +219,12 @@ const BrowserAgentInlineFeed: React.FC = ({ parentSessionId, browserId }) const entries: FeedEntry[] = []; for (const msg of session.messages) { const entry = formatMessage(msg); - if (entry) entries.push(entry); + if (!entry) continue; + // Retries emit the same status line back-to-back ("Picking up what I learned..." x4); one + // row carries the information, the repeats were pure noise in the transcript. + const prev = entries[entries.length - 1]; + if (prev && prev.type === entry.type && prev.text === entry.text) continue; + entries.push(entry); } return { session, entries }; }); diff --git a/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx b/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx index 0d5574e3..74918d16 100644 --- a/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx +++ b/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx @@ -80,7 +80,9 @@ export const DefaultToolBubble: React.FC = ({ bgcolor: mcpCompact ? 'transparent' : c.bg.elevated, border: mcpCompact ? 'none' : `1px solid ${ isPending || isStreaming - ? c.accent.primary + // Half-strength accent: still clearly "working", but a saturated user accent (greens + // especially) at full strength read as a loud alarm ring around a routine tool call. + ? c.accent.primary + '66' : isDenied ? c.status.error + '60' : c.border.subtle diff --git a/frontend/src/app/pages/Dashboard/cards/WindowControls.tsx b/frontend/src/app/pages/Dashboard/cards/WindowControls.tsx index b1caf31e..634e381e 100644 --- a/frontend/src/app/pages/Dashboard/cards/WindowControls.tsx +++ b/frontend/src/app/pages/Dashboard/cards/WindowControls.tsx @@ -76,10 +76,12 @@ function WindowControls({ onClose, onMinimize, onTile, tiled, fullscreen, noTile onPointerDown={stop} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', - width: 22, height: 22, p: 0, border: 'none', borderRadius: '6px', - background: 'transparent', color: 'inherit', opacity: 0.65, cursor: 'pointer', - transition: 'opacity 120ms, background 120ms', - '&:hover': { opacity: 1, background: 'rgba(136,136,136,0.18)' }, + width: 24, height: 24, p: 0, border: 'none', borderRadius: '7px', + // Neutral chip at rest so it reads on ANY ground (the fullscreen chat wash is dark, the + // browser chrome is light); hover goes explicit white-on-dark so it can never disappear. + background: 'rgba(128,128,128,0.28)', color: 'inherit', opacity: 0.9, cursor: 'pointer', + transition: 'opacity 120ms, background 120ms, color 120ms', + '&:hover': { opacity: 1, background: 'rgba(0,0,0,0.5)', color: '#fff' }, }} >