Skip to content

feat(ui): 侧栏、底栏与状态指示器多项体验优化#20

Open
Rannichan wants to merge 1 commit into
Open-LLM-VTuber:mainfrom
Rannichan:feat/ui-optimization
Open

feat(ui): 侧栏、底栏与状态指示器多项体验优化#20
Rannichan wants to merge 1 commit into
Open-LLM-VTuber:mainfrom
Rannichan:feat/ui-optimization

Conversation

@Rannichan
Copy link
Copy Markdown

@Rannichan Rannichan commented Apr 12, 2026

  • 侧栏:摄像头/屏幕/浏览器 Tab 与预览区对齐;直播/会话标签移入预览框内
  • 底栏:压缩输入区高度;Footer 间距与折叠按钮布局;麦克风/举手按钮点击反馈
  • 字幕:毛玻璃背景
  • 状态:WebSocket 与 AI 状态文案更明确;AI 指示器与连接状态并排、颜色随状态变化
  • 聊天:主容器居中对称,为收起按钮保留完整宽度
  • 侧栏:Chakra Tooltip 与 Mode Menu 冲突,模式按钮改回 title
  • 国际化:tooltip、wsStatus、aiState 等中英文更新

Made-with: Cursor

Summary by CodeRabbit

  • New Features

    • Added tooltips throughout the interface for settings, controls, and interface actions
    • Updated AI state and server status messaging for clearer communication
  • Improvements

    • Refined UI layout with adjusted spacing and panel sizing across sidebars and footer
    • Enhanced visual styling with blur effects and refined opacity on overlay elements
    • Repositioned status indicators for improved visibility

- 侧栏:摄像头/屏幕/浏览器 Tab 与预览区对齐;直播/会话标签移入预览框内
- 底栏:压缩输入区高度;Footer 间距与折叠按钮布局;麦克风/举手按钮点击反馈
- 字幕:毛玻璃背景
- 状态:WebSocket 与 AI 状态文案更明确;AI 指示器与连接状态并排、颜色随状态变化
- 聊天:主容器居中对称,为收起按钮保留完整宽度
- 侧栏:Chakra Tooltip 与 Mode Menu 冲突,模式按钮改回 title
- 国际化:tooltip、wsStatus、aiState 等中英文更新

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

📝 Walkthrough

Walkthrough

This PR refactors the UI layout by repositioning indicators and controls from dedicated headers to absolutely-positioned overlays within containers, applies styling refinements for visual polish, and adds comprehensive internationalization support with translated tooltips throughout sidebar and footer controls.

Changes

Cohort / File(s) Summary
UI Layout Restructuring
src/renderer/src/App.tsx, src/renderer/src/components/sidebar/browser-panel.tsx, src/renderer/src/components/sidebar/camera-panel.tsx, src/renderer/src/components/sidebar/screen-panel.tsx
Repositioned UI indicators (AIStateIndicator, LiveIndicator, ScreenIndicator) from dedicated header containers to absolutely-positioned overlays (top="8px", left="10px", zIndex={1}) within main content containers; consolidated header rendering logic.
Styling Updates
src/renderer/src/components/canvas/canvas-styles.tsx, src/renderer/src/components/footer/footer-styles.tsx, src/renderer/src/components/sidebar/sidebar-styles.tsx
Applied visual refinements: reduced subtitle opacity (0.7→0.35) with backdrop blur effects and enhanced border styling, adjusted footer layout with transform offsets and button sizing (50px→44px), standardized panel container widths (97%→100%) and spacing calculations.
Component Refactoring
src/renderer/src/components/footer/ai-state-indicator.tsx, src/renderer/src/components/footer/footer.tsx, src/renderer/src/components/sidebar/sidebar.tsx
Refactored components to support i18n tooltips: relocated AIStateIndicator to app-level with dynamic color mapping via AiStateEnum, removed AIStateIndicator from footer, added Tooltip wrappers to footer and sidebar controls, converted memoized components to block-bodied implementations with computed tooltip content.
Localization
src/renderer/src/locales/en/translation.json, src/renderer/src/locales/zh/translation.json
Updated user-facing state labels with "AI" prefix (idleAI Idle, thinking/speakingAI Speaking), clarified server status messaging (ConnectedServer Connected), and added 13-key tooltip namespace for settings, sidebar/input controls, microphone, hand-raise, and file attachment actions in both English and Chinese.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

🐰 Buttons hop to their new nest,
Overlays dance, positioned best,
Tooltips whisper every quest,
AI states wear their vest!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title in Chinese describes the main changes: sidebar, footer, and status indicator UI/UX optimizations, which accurately reflects the changeset focusing on layout adjustments, status display improvements, and internationalization updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (1)
src/renderer/src/components/footer/ai-state-indicator.tsx (1)

5-12: Use Record<AiStateEnum, string> to enforce exhaustive state-color mapping.

Record<string, string> permits arbitrary string keys and allows missing enum mappings to pass type-checking. Change to Record<AiStateEnum, string> to make the mapping exhaustive and eliminate the defensive fallback.

Refactor
-const stateColors: Record<string, string> = {
+const stateColors: Record<AiStateEnum, string> = {
   [AiStateEnum.IDLE]: 'gray.500',
   [AiStateEnum.THINKING_SPEAKING]: '#7C5CFF',
   [AiStateEnum.INTERRUPTED]: 'orange.500',
   [AiStateEnum.LOADING]: 'yellow.500',
   [AiStateEnum.LISTENING]: 'blue.400',
   [AiStateEnum.WAITING]: 'gray.500',
 };
 
-const color = stateColors[aiState] || 'gray.500';
+const color = stateColors[aiState];

Also applies to: 17-17

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/footer/ai-state-indicator.tsx` around lines 5 -
12, Change the stateColors declaration from Record<string, string> to
Record<AiStateEnum, string> and ensure every AiStateEnum member is present in
the mapping (add any missing enum keys such as X if applicable) so the compiler
enforces exhaustiveness; update the symbol stateColors and validate usages of
AiStateEnum (e.g., in ai-state-indicator.tsx) to remove the need for a defensive
fallback since the mapping is now exhaustive.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/src/components/footer/footer-styles.tsx`:
- Line 24: The footer collapsed translateY uses a 20px handle (transform:
isCollapsed ? 'translateY(calc(100% - 20px))'...), but App.tsx still uses
hardcoded subtitle offsets (39px / 135px) causing vertical drift; change App.tsx
to compute subtitle anchor offsets from a single source of truth instead of
magic numbers—either export a FOOTER_HANDLE_HEIGHT (or FOOTER_COLLAPSED_HANDLE)
from src/renderer/src/components/footer/footer-styles.tsx and use it to compute
the offsets in the subtitle positioning logic in App.tsx, or derive the offsets
at runtime from the footer element (query by its component ID/class and read its
clientHeight) so isCollapsed, translateY and subtitle offsets stay in sync.

In `@src/renderer/src/components/footer/footer.tsx`:
- Around line 79-80: Replace hard-coded aria-label strings in the Footer
component with localized translation keys: import and call the i18n hook (e.g.,
useTranslation) at the top of the Footer component, then replace
aria-label="Raise hand" and the other static aria-labels around lines referenced
with aria-label={t('footer.raiseHand')} (and corresponding keys like
'footer.someOtherAction') so the Button/Action components use t('...') instead
of literal English; update translation files with the new keys.
- Around line 66-75: The IconButton used for the mic toggle (IconButton with
props bg, onClick={onMicToggle}, using micOn and icons BsMicFill/BsMicMuteFill)
is icon-only and needs an accessible name; add an explicit accessible label and
toggle state by supplying props like aria-label (e.g., aria-label={micOn ? "Mute
microphone" : "Unmute microphone"}) and aria-pressed={micOn} (or aria-checked if
using role="switch") to the IconButton so screen readers can announce the button
and its state.
- Around line 45-54: Replace the non-semantic clickable Box with a real button
element (e.g., Chakra UI Box with as="button" or an IconButton) so it becomes
keyboard-accessible and behaves like a native control; keep the existing props
footerStyles.footer.toggleButton and the onToggle handler, add type="button",
include aria-expanded={isCollapsed} and an accessible name via
aria-label="Toggle footer collapse" (or visually-hidden text) and preserve the
transform style based on isCollapsed; update the element rendering the
FiChevronDown accordingly so the visual appearance stays the same.

In `@src/renderer/src/components/sidebar/browser-panel.tsx`:
- Around line 55-57: The absolute positioned session label Box (the Box
rendering t('sidebar.browserSession') in browser-panel.tsx) is intercepting
iframe clicks; make that Box non-interactive by disabling pointer events and
accessibility interception (e.g., set CSS pointer-events to 'none' and mark it
aria-hidden) so it no longer captures mouse events or screen-reader focus while
keeping the visual label intact.

In `@src/renderer/src/components/sidebar/sidebar.tsx`:
- Around line 71-85: The hardcoded English strings for the mode options bypass
localization; update the Menu.RadioItem labels and the disabled title to use the
app's i18n/messages (e.g., call t('liveMode'), t('petMode'), and
t('petModeDesktopOnlyHint')) instead of literal "Live Mode", "Pet Mode" and the
inline title; locate the two Menu.RadioItem components (the one with value "pet"
and its sibling for Live mode) and replace the visible text and the
title={!isElectron ? "Pet mode is only available in desktop app" : undefined}
with localized lookups, leaving the isElectron guard and setMode('pet') logic
unchanged.
- Around line 99-129: The sidebar icon-only Buttons (the Button inside the
Settings area with onSettingsOpen, the Button wrapped by GroupDrawer, the Button
wrapped by HistoryDrawer, and the New Chat Button with onNewHistory) currently
rely on tooltips for identification; add explicit localized aria-label
attributes to each Button using the same translation keys (e.g.,
t('tooltip.settings'), t('tooltip.group'), t('tooltip.chatHistory'),
t('tooltip.newChat')) so screen readers receive accessible names independent of
the Tooltip component.
- Around line 40-46: The sidebar toggle is currently a clickable Box
(sidebarStyles.sidebar.toggleButton) with onClick and transform but lacks
keyboard and ARIA semantics; make it keyboard-accessible by giving that Box
role="button", tabIndex={0}, and an accessible name (e.g., aria-label="Toggle
sidebar" or aria-labelledby pointing to a label), add a keyDown handler on the
same element that calls onToggle when Enter or Space is pressed, and consider
using aria-pressed or aria-expanded (tied to isCollapsed) to expose state to
assistive tech.

---

Nitpick comments:
In `@src/renderer/src/components/footer/ai-state-indicator.tsx`:
- Around line 5-12: Change the stateColors declaration from Record<string,
string> to Record<AiStateEnum, string> and ensure every AiStateEnum member is
present in the mapping (add any missing enum keys such as X if applicable) so
the compiler enforces exhaustiveness; update the symbol stateColors and validate
usages of AiStateEnum (e.g., in ai-state-indicator.tsx) to remove the need for a
defensive fallback since the mapping is now exhaustive.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fbab6dbc-0581-4564-94f4-0503d385566a

📥 Commits

Reviewing files that changed from the base of the PR and between d176e7d and 4c2dddb.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • src/renderer/src/App.tsx
  • src/renderer/src/components/canvas/canvas-styles.tsx
  • src/renderer/src/components/footer/ai-state-indicator.tsx
  • src/renderer/src/components/footer/footer-styles.tsx
  • src/renderer/src/components/footer/footer.tsx
  • src/renderer/src/components/sidebar/browser-panel.tsx
  • src/renderer/src/components/sidebar/camera-panel.tsx
  • src/renderer/src/components/sidebar/screen-panel.tsx
  • src/renderer/src/components/sidebar/sidebar-styles.tsx
  • src/renderer/src/components/sidebar/sidebar.tsx
  • src/renderer/src/locales/en/translation.json
  • src/renderer/src/locales/zh/translation.json

bg: isCollapsed ? 'transparent' : 'gray.800',
borderTopRadius: isCollapsed ? 'none' : 'lg',
transform: isCollapsed ? 'translateY(calc(100% - 24px))' : 'translateY(0)',
transform: isCollapsed ? 'translateY(calc(100% - 20px))' : 'translateY(0)',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Sync footer handle height changes with subtitle anchor offsets.

Line 24 reduces the collapsed reveal height to 20px, but src/renderer/src/App.tsx Line 131 still uses hardcoded subtitle offsets (39px / 135px) tuned to the old footer geometry. This can cause subtitle vertical drift between collapsed/expanded states.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/footer/footer-styles.tsx` at line 24, The footer
collapsed translateY uses a 20px handle (transform: isCollapsed ?
'translateY(calc(100% - 20px))'...), but App.tsx still uses hardcoded subtitle
offsets (39px / 135px) causing vertical drift; change App.tsx to compute
subtitle anchor offsets from a single source of truth instead of magic
numbers—either export a FOOTER_HANDLE_HEIGHT (or FOOTER_COLLAPSED_HANDLE) from
src/renderer/src/components/footer/footer-styles.tsx and use it to compute the
offsets in the subtitle positioning logic in App.tsx, or derive the offsets at
runtime from the footer element (query by its component ID/class and read its
clientHeight) so isCollapsed, translateY and subtitle offsets stay in sync.

Comment on lines +45 to +54
<Box
{...footerStyles.footer.toggleButton}
onClick={onToggle}
color="whiteAlpha.500"
style={{
transform: isCollapsed ? 'rotate(180deg)' : 'rotate(0deg)',
}}
>
<FiChevronDown />
</Box>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use a semantic button for the footer collapse toggle.

The current clickable Box is not keyboard-friendly by default and has no explicit accessible name.

♿ Suggested fix
-      <Box
+      <Box
+        as="button"
+        type="button"
+        aria-label={isCollapsed ? t('tooltip.expandInput') : t('tooltip.collapseInput')}
         {...footerStyles.footer.toggleButton}
         onClick={onToggle}
         color="whiteAlpha.500"
         style={{
           transform: isCollapsed ? 'rotate(180deg)' : 'rotate(0deg)',
         }}
+        onKeyDown={(e) => {
+          if (e.key === 'Enter' || e.key === ' ') {
+            e.preventDefault();
+            onToggle?.();
+          }
+        }}
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/footer/footer.tsx` around lines 45 - 54, Replace
the non-semantic clickable Box with a real button element (e.g., Chakra UI Box
with as="button" or an IconButton) so it becomes keyboard-accessible and behaves
like a native control; keep the existing props footerStyles.footer.toggleButton
and the onToggle handler, add type="button", include aria-expanded={isCollapsed}
and an accessible name via aria-label="Toggle footer collapse" (or
visually-hidden text) and preserve the transform style based on isCollapsed;
update the element rendering the FiChevronDown accordingly so the visual
appearance stays the same.

Comment on lines +66 to +75
<IconButton
bg={micOn ? 'green.500' : 'red.500'}
{...footerStyles.footer.actionButton}
onClick={onMicToggle}
transition="all 0.15s ease"
_hover={{ bg: micOn ? 'green.400' : 'red.400', transform: 'scale(1.05)' }}
_active={{ bg: micOn ? 'green.600' : 'red.600', transform: 'scale(0.88)', boxShadow: micOn ? '0 0 12px rgba(34, 197, 94, 0.6)' : '0 0 12px rgba(239, 68, 68, 0.6)' }}
>
{micOn ? <BsMicFill /> : <BsMicMuteFill />}
</IconButton>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add an accessible label to the mic toggle button.

The mic button is icon-only and currently lacks an explicit accessible name.

♿ Suggested fix
         <IconButton
+          aria-label={micOn ? t('tooltip.micOn') : t('tooltip.micOff')}
           bg={micOn ? 'green.500' : 'red.500'}
           {...footerStyles.footer.actionButton}
           onClick={onMicToggle}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<IconButton
bg={micOn ? 'green.500' : 'red.500'}
{...footerStyles.footer.actionButton}
onClick={onMicToggle}
transition="all 0.15s ease"
_hover={{ bg: micOn ? 'green.400' : 'red.400', transform: 'scale(1.05)' }}
_active={{ bg: micOn ? 'green.600' : 'red.600', transform: 'scale(0.88)', boxShadow: micOn ? '0 0 12px rgba(34, 197, 94, 0.6)' : '0 0 12px rgba(239, 68, 68, 0.6)' }}
>
{micOn ? <BsMicFill /> : <BsMicMuteFill />}
</IconButton>
<IconButton
aria-label={micOn ? t('tooltip.micOn') : t('tooltip.micOff')}
bg={micOn ? 'green.500' : 'red.500'}
{...footerStyles.footer.actionButton}
onClick={onMicToggle}
transition="all 0.15s ease"
_hover={{ bg: micOn ? 'green.400' : 'red.400', transform: 'scale(1.05)' }}
_active={{ bg: micOn ? 'green.600' : 'red.600', transform: 'scale(0.88)', boxShadow: micOn ? '0 0 12px rgba(34, 197, 94, 0.6)' : '0 0 12px rgba(239, 68, 68, 0.6)' }}
>
{micOn ? <BsMicFill /> : <BsMicMuteFill />}
</IconButton>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/footer/footer.tsx` around lines 66 - 75, The
IconButton used for the mic toggle (IconButton with props bg,
onClick={onMicToggle}, using micOn and icons BsMicFill/BsMicMuteFill) is
icon-only and needs an accessible name; add an explicit accessible label and
toggle state by supplying props like aria-label (e.g., aria-label={micOn ? "Mute
microphone" : "Unmute microphone"}) and aria-pressed={micOn} (or aria-checked if
using role="switch") to the IconButton so screen readers can announce the button
and its state.

Comment on lines +79 to +80
aria-label="Raise hand"
bg="yellow.500"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize static aria-label strings for action buttons.

These labels stay English regardless of language selection; use translation keys for consistency.

🌐 Suggested fix
-          aria-label="Raise hand"
+          aria-label={t('tooltip.raiseHand')}
...
-            aria-label="Attach file"
+            aria-label={t('tooltip.attachFile')}

Also applies to: 110-111

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/footer/footer.tsx` around lines 79 - 80, Replace
hard-coded aria-label strings in the Footer component with localized translation
keys: import and call the i18n hook (e.g., useTranslation) at the top of the
Footer component, then replace aria-label="Raise hand" and the other static
aria-labels around lines referenced with aria-label={t('footer.raiseHand')} (and
corresponding keys like 'footer.someOtherAction') so the Button/Action
components use t('...') instead of literal English; update translation files
with the new keys.

Comment on lines +55 to +57
<Box position="absolute" top="8px" left="10px" zIndex={1}>
<Text fontSize="sm" color="blue.300">{t('sidebar.browserSession')}</Text>
</Box>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Prevent session label overlay from intercepting iframe clicks.

The absolute label on Line 55 sits above the interactive browser view and can steal clicks in that region. Make it non-interactive.

💡 Proposed fix
-            <Box position="absolute" top="8px" left="10px" zIndex={1}>
+            <Box position="absolute" top="8px" left="10px" zIndex={1} pointerEvents="none">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Box position="absolute" top="8px" left="10px" zIndex={1}>
<Text fontSize="sm" color="blue.300">{t('sidebar.browserSession')}</Text>
</Box>
<Box position="absolute" top="8px" left="10px" zIndex={1} pointerEvents="none">
<Text fontSize="sm" color="blue.300">{t('sidebar.browserSession')}</Text>
</Box>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/sidebar/browser-panel.tsx` around lines 55 - 57,
The absolute positioned session label Box (the Box rendering
t('sidebar.browserSession') in browser-panel.tsx) is intercepting iframe clicks;
make that Box non-interactive by disabling pointer events and accessibility
interception (e.g., set CSS pointer-events to 'none' and mark it aria-hidden) so
it no longer captures mouse events or screen-reader focus while keeping the
visual label intact.

Comment on lines +40 to +46
<Box
{...sidebarStyles.sidebar.toggleButton}
style={{
transform: isCollapsed ? 'rotate(180deg)' : 'rotate(0deg)',
}}
onClick={onToggle}
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the sidebar toggle keyboard-accessible.

This control is implemented as a clickable Box, so it lacks native button semantics and an explicit accessible name.

♿ Suggested fix
-      <Box
+      <Box
+        as="button"
+        type="button"
+        aria-label={isCollapsed ? t('tooltip.expandSidebar') : t('tooltip.collapseSidebar')}
         {...sidebarStyles.sidebar.toggleButton}
         style={{
           transform: isCollapsed ? 'rotate(180deg)' : 'rotate(0deg)',
         }}
         onClick={onToggle}
+        onKeyDown={(e) => {
+          if (e.key === 'Enter' || e.key === ' ') {
+            e.preventDefault();
+            onToggle();
+          }
+        }}
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/sidebar/sidebar.tsx` around lines 40 - 46, The
sidebar toggle is currently a clickable Box (sidebarStyles.sidebar.toggleButton)
with onClick and transform but lacks keyboard and ARIA semantics; make it
keyboard-accessible by giving that Box role="button", tabIndex={0}, and an
accessible name (e.g., aria-label="Toggle sidebar" or aria-labelledby pointing
to a label), add a keyDown handler on the same element that calls onToggle when
Enter or Space is pressed, and consider using aria-pressed or aria-expanded
(tied to isCollapsed) to expose state to assistive tech.

Comment on lines +71 to +85
Live Mode
</Menu.RadioItem>
<Menu.RadioItem
value="pet"
onClick={() => {
if (isElectron) {
setMode('pet');
}
}}
disabled={!isElectron}
title={!isElectron ? "Pet mode is only available in desktop app" : undefined}
>
<Menu.ItemIndicator />
Pet Mode
</Menu.RadioItem>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize mode option labels and the desktop-only hint.

Live Mode, Pet Mode, and the disabled title are hardcoded English and will bypass locale switching.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/sidebar/sidebar.tsx` around lines 71 - 85, The
hardcoded English strings for the mode options bypass localization; update the
Menu.RadioItem labels and the disabled title to use the app's i18n/messages
(e.g., call t('liveMode'), t('petMode'), and t('petModeDesktopOnlyHint'))
instead of literal "Live Mode", "Pet Mode" and the inline title; locate the two
Menu.RadioItem components (the one with value "pet" and its sibling for Live
mode) and replace the visible text and the title={!isElectron ? "Pet mode is
only available in desktop app" : undefined} with localized lookups, leaving the
isElectron guard and setMode('pet') logic unchanged.

Comment on lines +99 to +129
<Tooltip content={t('tooltip.settings')} showArrow openDelay={300}>
<Button onClick={onSettingsOpen}>
<FiSettings />
</Button>
</Tooltip>

<GroupDrawer>
<Box as="span" display="inline-flex">
<Tooltip content={t('tooltip.group')} showArrow openDelay={300}>
<Button>
<FiUsers />
</Button>
</Tooltip>
</Box>
</GroupDrawer>

<HistoryDrawer>
<Box as="span" display="inline-flex">
<Tooltip content={t('tooltip.chatHistory')} showArrow openDelay={300}>
<Button>
<FiClock />
</Button>
</Tooltip>
</Box>
</HistoryDrawer>

<Tooltip content={t('tooltip.newChat')} showArrow openDelay={300}>
<Button onClick={onNewHistory}>
<FiPlus />
</Button>
</Tooltip>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add accessible names to sidebar icon-only buttons.

Settings, Group, History, and New Chat buttons should expose explicit localized aria-labels instead of relying on tooltips.

♿ Suggested fix
-        <Button onClick={onSettingsOpen}>
+        <Button onClick={onSettingsOpen} aria-label={t('tooltip.settings')}>
           <FiSettings />
         </Button>
...
-            <Button>
+            <Button aria-label={t('tooltip.group')}>
               <FiUsers />
             </Button>
...
-            <Button>
+            <Button aria-label={t('tooltip.chatHistory')}>
               <FiClock />
             </Button>
...
-        <Button onClick={onNewHistory}>
+        <Button onClick={onNewHistory} aria-label={t('tooltip.newChat')}>
           <FiPlus />
         </Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Tooltip content={t('tooltip.settings')} showArrow openDelay={300}>
<Button onClick={onSettingsOpen}>
<FiSettings />
</Button>
</Tooltip>
<GroupDrawer>
<Box as="span" display="inline-flex">
<Tooltip content={t('tooltip.group')} showArrow openDelay={300}>
<Button>
<FiUsers />
</Button>
</Tooltip>
</Box>
</GroupDrawer>
<HistoryDrawer>
<Box as="span" display="inline-flex">
<Tooltip content={t('tooltip.chatHistory')} showArrow openDelay={300}>
<Button>
<FiClock />
</Button>
</Tooltip>
</Box>
</HistoryDrawer>
<Tooltip content={t('tooltip.newChat')} showArrow openDelay={300}>
<Button onClick={onNewHistory}>
<FiPlus />
</Button>
</Tooltip>
<Tooltip content={t('tooltip.settings')} showArrow openDelay={300}>
<Button onClick={onSettingsOpen} aria-label={t('tooltip.settings')}>
<FiSettings />
</Button>
</Tooltip>
<GroupDrawer>
<Box as="span" display="inline-flex">
<Tooltip content={t('tooltip.group')} showArrow openDelay={300}>
<Button aria-label={t('tooltip.group')}>
<FiUsers />
</Button>
</Tooltip>
</Box>
</GroupDrawer>
<HistoryDrawer>
<Box as="span" display="inline-flex">
<Tooltip content={t('tooltip.chatHistory')} showArrow openDelay={300}>
<Button aria-label={t('tooltip.chatHistory')}>
<FiClock />
</Button>
</Tooltip>
</Box>
</HistoryDrawer>
<Tooltip content={t('tooltip.newChat')} showArrow openDelay={300}>
<Button onClick={onNewHistory} aria-label={t('tooltip.newChat')}>
<FiPlus />
</Button>
</Tooltip>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/components/sidebar/sidebar.tsx` around lines 99 - 129, The
sidebar icon-only Buttons (the Button inside the Settings area with
onSettingsOpen, the Button wrapped by GroupDrawer, the Button wrapped by
HistoryDrawer, and the New Chat Button with onNewHistory) currently rely on
tooltips for identification; add explicit localized aria-label attributes to
each Button using the same translation keys (e.g., t('tooltip.settings'),
t('tooltip.group'), t('tooltip.chatHistory'), t('tooltip.newChat')) so screen
readers receive accessible names independent of the Tooltip component.

thr3a pushed a commit to thr3a/Open-LLM-VTuber-Web that referenced this pull request May 10, 2026
…outh_form support (Open-LLM-VTuber#20)

Co-authored-by: MrXnneHang <XnneHang@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant