Skip to content
Open
Show file tree
Hide file tree
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { BrowserProvider } from "./context/browser-context";
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
import Background from "./components/canvas/background";
import WebSocketStatus from "./components/canvas/ws-status";
import AIStateIndicator from "./components/footer/ai-state-indicator";
import Subtitle from "./components/canvas/subtitle";
import { ModeProvider, useMode } from "./context/mode-context";

Expand Down Expand Up @@ -121,9 +122,10 @@ function AppContent(): JSX.Element {
</Box>
<Box {...layoutStyles.mainContent}>
<Background />
<Box position="absolute" top="20px" left="20px" zIndex={10}>
<Flex position="absolute" top="20px" left="20px" zIndex={10} gap={2} alignItems="center">
<WebSocketStatus />
</Box>
<AIStateIndicator />
</Flex>
<Box
position="absolute"
bottom={isFooterCollapsed ? "39px" : "135px"}
Expand Down
8 changes: 6 additions & 2 deletions src/renderer/src/components/canvas/canvas-styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,15 @@ export const canvasStyles = {
},
subtitle: {
container: {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
backgroundColor: 'rgba(0, 0, 0, 0.35)',
backdropFilter: 'blur(16px) saturate(180%)',
WebkitBackdropFilter: 'blur(16px) saturate(180%)',
border: '1px solid rgba(255, 255, 255, 0.12)',
padding: '15px 30px',
borderRadius: '12px',
borderRadius: '16px',
minWidth: '60%',
maxWidth: '95%',
boxShadow: '0 4px 30px rgba(0, 0, 0, 0.15)',
},
text: {
color: 'white',
Expand Down
28 changes: 23 additions & 5 deletions src/renderer/src/components/footer/ai-state-indicator.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
import { Box, Text } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { useAiState } from '@/context/ai-state-context';
import { footerStyles } from './footer-styles';
import { useAiState, AiStateEnum } from '@/context/ai-state-context';

const stateColors: Record<string, 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',
};

function AIStateIndicator(): JSX.Element {
const { t } = useTranslation();
const { aiState } = useAiState();
const styles = footerStyles.aiIndicator;
const color = stateColors[aiState] || 'gray.500';

return (
<Box {...styles.container}>
<Text {...styles.text}>{t(`aiState.${aiState}`)}</Text>
<Box
padding="8px 16px"
borderRadius="20px"
fontSize="14px"
fontWeight="medium"
color="white"
backgroundColor={color}
transition="all 0.3s"
userSelect="none"
whiteSpace="nowrap"
>
<Text fontSize="14px">{t(`aiState.${aiState}`)}</Text>
</Box>
);
}
Expand Down
26 changes: 15 additions & 11 deletions src/renderer/src/components/footer/footer-styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@ export const footerStyles: {
container: (isCollapsed) => ({
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.

transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
height: '100%',
position: 'relative',
overflow: isCollapsed ? 'visible' : 'hidden',
pb: '4',
}),
toggleButton: {
height: '24px',
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '20px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
Expand All @@ -38,19 +41,20 @@ export const footerStyles: {
_hover: { color: 'white' },
bg: 'transparent',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
zIndex: 1,
},
actionButton: {
borderRadius: '12px',
width: '50px',
height: '50px',
minW: '50px',
width: '44px',
height: '44px',
minW: '44px',
},
input: {
bg: 'gray.700',
border: 'none',
height: '80px',
height: '50px',
borderRadius: '12px',
fontSize: '18px',
fontSize: '16px',
pl: '12',
pr: '4',
color: 'whiteAlpha.900',
Expand All @@ -62,12 +66,12 @@ export const footerStyles: {
bg: 'gray.700',
},
resize: 'none',
minHeight: '80px',
maxHeight: '80px',
minHeight: '50px',
maxHeight: '50px',
py: '0',
display: 'flex',
alignItems: 'center',
paddingTop: '28px',
paddingTop: '14px',
lineHeight: '1.4',
},
attachButton: {
Expand Down
105 changes: 61 additions & 44 deletions src/renderer/src/components/footer/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { IoHandRightSharp } from 'react-icons/io5';
import { FiChevronDown } from 'react-icons/fi';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Tooltip } from '@/components/ui/tooltip';
import { InputGroup } from '@/components/ui/input-group';
import { footerStyles } from './footer-styles';
import AIStateIndicator from './ai-state-indicator';
import { useFooter } from '@/hooks/footer/use-footer';

// Type definitions
Expand Down Expand Up @@ -38,40 +38,58 @@ interface MessageInputProps {
}

// Reusable components
const ToggleButton = memo(({ isCollapsed, onToggle }: ToggleButtonProps) => (
<Box
{...footerStyles.footer.toggleButton}
onClick={onToggle}
color="whiteAlpha.500"
style={{
transform: isCollapsed ? 'rotate(180deg)' : 'rotate(0deg)',
}}
>
<FiChevronDown />
</Box>
));
const ToggleButton = memo(({ isCollapsed, onToggle }: ToggleButtonProps) => {
const { t } = useTranslation();
return (
<Tooltip content={isCollapsed ? t('tooltip.expandInput') : t('tooltip.collapseInput')} showArrow openDelay={300}>
<Box
{...footerStyles.footer.toggleButton}
onClick={onToggle}
color="whiteAlpha.500"
style={{
transform: isCollapsed ? 'rotate(180deg)' : 'rotate(0deg)',
}}
>
<FiChevronDown />
</Box>
Comment on lines +45 to +54
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.

</Tooltip>
);
});

ToggleButton.displayName = 'ToggleButton';

const ActionButtons = memo(({ micOn, onMicToggle, onInterrupt }: ActionButtonsProps) => (
<HStack gap={2}>
<IconButton
bg={micOn ? 'green.500' : 'red.500'}
{...footerStyles.footer.actionButton}
onClick={onMicToggle}
>
{micOn ? <BsMicFill /> : <BsMicMuteFill />}
</IconButton>
<IconButton
aria-label="Raise hand"
bg="yellow.500"
{...footerStyles.footer.actionButton}
onClick={onInterrupt}
>
<IoHandRightSharp size="24" />
</IconButton>
</HStack>
));
const ActionButtons = memo(({ micOn, onMicToggle, onInterrupt }: ActionButtonsProps) => {
const { t } = useTranslation();
return (
<HStack gap={2}>
<Tooltip content={micOn ? t('tooltip.micOn') : t('tooltip.micOff')} showArrow openDelay={300}>
<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>
Comment on lines +66 to +75
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.

</Tooltip>
<Tooltip content={t('tooltip.raiseHand')} showArrow openDelay={300}>
<IconButton
aria-label="Raise hand"
bg="yellow.500"
Comment on lines +79 to +80
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.

{...footerStyles.footer.actionButton}
onClick={onInterrupt}
transition="all 0.15s ease"
_hover={{ bg: 'yellow.400', transform: 'scale(1.05)' }}
_active={{ bg: 'yellow.600', transform: 'scale(0.88)', boxShadow: '0 0 12px rgba(234, 179, 8, 0.6)' }}
>
<IoHandRightSharp size="24" />
</IconButton>
</Tooltip>
</HStack>
);
});

ActionButtons.displayName = 'ActionButtons';

Expand All @@ -87,13 +105,15 @@ const MessageInput = memo(({
return (
<InputGroup flex={1}>
<Box position="relative" width="100%">
<IconButton
aria-label="Attach file"
variant="ghost"
{...footerStyles.footer.attachButton}
>
<BsPaperclip size="24" />
</IconButton>
<Tooltip content={t('tooltip.attachFile')} showArrow openDelay={300}>
<IconButton
aria-label="Attach file"
variant="ghost"
{...footerStyles.footer.attachButton}
>
<BsPaperclip size="24" />
</IconButton>
</Tooltip>
<Textarea
value={value}
onChange={onChange}
Expand Down Expand Up @@ -127,12 +147,9 @@ function Footer({ isCollapsed = false, onToggle }: FooterProps): JSX.Element {
<Box {...footerStyles.footer.container(isCollapsed)}>
<ToggleButton isCollapsed={isCollapsed} onToggle={onToggle} />

<Box pt="0" px="4">
<HStack width="100%" gap={4}>
<Box pt="24px" pb="24px" px="4">
<HStack width="100%" gap={3} alignItems="center">
<Box>
<Box mb="1.5">
<AIStateIndicator />
</Box>
<ActionButtons
micOn={micOn}
onMicToggle={handleMicToggle}
Expand Down
11 changes: 5 additions & 6 deletions src/renderer/src/components/sidebar/browser-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ function BrowserPanel(): JSX.Element {

return (
<Box {...sidebarStyles.browserPanel.container}>
<Box {...sidebarStyles.browserPanel.header}>
{browserViewData && (
<Text fontSize="sm" color="blue.300">{t('sidebar.browserSession')}</Text>
)}
</Box>

<Tooltip
showArrow
content={
Expand All @@ -57,6 +51,11 @@ function BrowserPanel(): JSX.Element {
onMouseLeave={handleMouseLeave}
position="relative"
>
{browserViewData && (
<Box position="absolute" top="8px" left="10px" zIndex={1}>
<Text fontSize="sm" color="blue.300">{t('sidebar.browserSession')}</Text>
</Box>
Comment on lines +55 to +57
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.

)}
{browserViewData ? (
<iframe
src={browserViewData.debuggerFullscreenUrl}
Expand Down
9 changes: 5 additions & 4 deletions src/renderer/src/components/sidebar/camera-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,6 @@ function CameraPanel(): JSX.Element {

return (
<Box {...sidebarStyles.cameraPanel.container}>
<Box {...sidebarStyles.cameraPanel.header}>
{isStreaming && <LiveIndicator />}
</Box>

<Tooltip
showArrow
content={isStreaming ? t('footer.cameraStopping') : t('footer.cameraControl')}
Expand All @@ -98,6 +94,11 @@ function CameraPanel(): JSX.Element {
bg: 'whiteAlpha.100',
}}
>
{isStreaming && (
<Box position="absolute" top="8px" left="10px" zIndex={1}>
<LiveIndicator />
</Box>
)}
{error ? (
<Text color="red.300" fontSize="sm" textAlign="center">
{error}
Expand Down
9 changes: 5 additions & 4 deletions src/renderer/src/components/sidebar/screen-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,6 @@ function ScreenPanel(): JSX.Element {

return (
<Box {...sidebarStyles.screenPanel.container}>
<Box {...sidebarStyles.screenPanel.header}>
{isStreaming && <ScreenIndicator />}
</Box>

<Tooltip
showArrow
content={
Expand All @@ -100,6 +96,11 @@ function ScreenPanel(): JSX.Element {
bg: "whiteAlpha.100",
}}
>
{isStreaming && (
<Box position="absolute" top="8px" left="10px" zIndex={1}>
<ScreenIndicator />
</Box>
)}
{error ? (
<Text color="red.300" fontSize="sm" textAlign="center">
{error}
Expand Down
Loading