AttachmentSources: live streams support

This commit is contained in:
Enrico Ros
2026-03-06 15:04:02 -08:00
parent c1bdb1fc61
commit 5cdefc7b5e
@@ -2,12 +2,13 @@ import * as React from 'react';
import { keyframes } from '@emotion/react';
import type { FileWithHandle } from 'browser-fs-access';
import { Box, Button, Checkbox, ColorPaletteProp, Divider, Dropdown, IconButton, ListItem, ListItemDecorator, Menu, MenuButton, MenuItem } from '@mui/joy';
import { Box, Button, Checkbox, ColorPaletteProp, Dropdown, IconButton, ListDivider, ListItem, ListItemDecorator, Menu, MenuButton, MenuItem } from '@mui/joy';
import AddRoundedIcon from '@mui/icons-material/AddRounded';
import AddToDriveRoundedIcon from '@mui/icons-material/AddToDriveRounded';
import AttachFileRoundedIcon from '@mui/icons-material/AttachFileRounded';
import CameraAltOutlinedIcon from '@mui/icons-material/CameraAltOutlined';
import ContentPasteGoIcon from '@mui/icons-material/ContentPasteGo';
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
import LanguageRoundedIcon from '@mui/icons-material/LanguageRounded';
import ScreenshotMonitorIcon from '@mui/icons-material/ScreenshotMonitor';
@@ -35,10 +36,11 @@ const animationMenuItem = keyframes` from {opacity: 0;transform: translateY(-6px
const _style = {
menuItem: {
py: 1,
// pl: 3,
// pr: 2,
py: 0.5, // was 1
minHeight: 60,
// minHeight: '3.25rem', // now 52, was 60
} as const,
menuItemContent: {
display: 'flex',
@@ -58,6 +60,24 @@ const _style = {
};
// Live feed record button - returns null if onClick is undefined
function LiveFeedButton(props: { isActive: boolean, onClick: () => void }) {
return (
<IconButton
size='sm'
variant={props.isActive ? 'solid' : 'soft'}
color='danger'
onClick={(e) => {
e.stopPropagation();
props.onClick();
}}
>
<FiberManualRecordIcon sx={{ fontSize: 16 }} />
</IconButton>
);
}
// Rich menu item (used in menu-rich mode)
function RichMenuItem(props: {
name: React.ReactNode;
@@ -67,6 +87,7 @@ function RichMenuItem(props: {
delay?: number;
disabled?: boolean;
color?: ColorPaletteProp;
endAction?: React.ReactNode;
}) {
return (
<MenuItem
@@ -89,6 +110,11 @@ function RichMenuItem(props: {
{props.description}
</Box>
</Box>
{props.endAction && (
<Box sx={{ ml: 'auto', display: 'flex', alignItems: 'center' }}>
{props.endAction}
</Box>
)}
</MenuItem>
);
}
@@ -107,7 +133,7 @@ function AutoDownloadToggle(props: { delay?: number }) {
return <>
<Divider sx={{ my: 0.5 }} />
<ListDivider inset='gutter' sx={{ my: 1 }} />
<ListItem
sx={{
@@ -165,6 +191,11 @@ function AttachmentSources(props: {
hasScreenCapture: boolean,
// configuration
onlyImages?: boolean, // makes clipboard/drive/web unavailable
// live feeds - end action buttons (presence if the callback is set, active state if the boolean is true)
hasActiveCameraFeed?: boolean,
hasActiveScreenFeed?: boolean,
onStartLiveCameraFeed?: () => void,
onStartLiveScreenFeed?: () => void,
// callbacks
onAttachClipboard: () => void,
onAttachFiles: (files: FileWithHandle[], errorMessage: string | null) => void,
@@ -299,7 +330,15 @@ function AttachmentSources(props: {
// <ListItemDecorator><ScreenshotMonitorIcon /></ListItemDecorator>
// Screen
// </MenuItem>
<RichMenuItem name='Screen' description={screenCaptureError ? `Error: ${screenCaptureError}` : 'Capture windows, tabs, or screens'} color={screenCaptureError ? 'danger' : props.color} icon={<ScreenshotMonitorIcon />} onClick={handleTakeScreenCapture} disabled={capturingScreen} />
<RichMenuItem
name='Screen'
color={screenCaptureError ? 'danger' : props.color}
description={screenCaptureError ? `Error: ${screenCaptureError}` : 'Capture tabs, apps, and screens'}
icon={<ScreenshotMonitorIcon />}
disabled={capturingScreen}
onClick={handleTakeScreenCapture}
endAction={!isMessage && props.onStartLiveScreenFeed && <LiveFeedButton isActive={!!props.hasActiveScreenFeed} onClick={props.onStartLiveScreenFeed} />}
/>
)}
{/* Camera */}
@@ -308,7 +347,14 @@ function AttachmentSources(props: {
// <ListItemDecorator><CameraAltOutlinedIcon /></ListItemDecorator>
// Camera
// </MenuItem>
<RichMenuItem name='Camera' description='Capture photos and optional OCR' color={props.color} icon={<CameraAltOutlinedIcon />} onClick={props.onOpenCamera} />
<RichMenuItem
name='Camera'
color={props.color}
icon={<CameraAltOutlinedIcon />}
description='Capture photos with optional OCR'
onClick={props.onOpenCamera}
endAction={!isMessage && props.onStartLiveCameraFeed && <LiveFeedButton isActive={!!props.hasActiveCameraFeed} onClick={props.onStartLiveCameraFeed} />}
/>
)}
</Menu>
@@ -412,25 +458,51 @@ function AttachmentSources(props: {
<RichMenuItem
name='Clipboard'
icon={<ContentPasteGoIcon />}
description='Auto-converts images and text to the best format'
// description='Auto-converts images and text to the best format'
description='Auto-adapts images and text'
onClick={props.onAttachClipboard}
delay={0.06}
/>
)}
{/*{!props.onlyImages && props.canBrowse && (*/}
{/* <ListItem>*/}
{/* <ListItemDecorator />*/}
{/* <Checkbox*/}
{/* size='sm'*/}
{/* color='neutral'*/}
{/* // checked={enableComposerAttach}*/}
{/* // onChange={handleToggle}*/}
{/* onClick={(event) => event.stopPropagation()}*/}
{/* sx={{ ml: 0.375 }}*/}
{/* slotProps={{*/}
{/* label: {*/}
{/* sx: {*/}
{/* fontSize: 'sm',*/}
{/* fontWeight: 'md',*/}
{/* },*/}
{/* },*/}
{/* }}*/}
{/* label='Download and attach links'*/}
{/* />*/}
{/* </ListItem>*/}
{/*)}*/}
{/* Divider before labs features */}
{(props.hasScreenCapture || props.hasCamera) && <Divider sx={{ my: 0.5 }} />}
{(props.hasScreenCapture || props.hasCamera) && <ListDivider inset='gutter' sx={{ my: 1 }} />}
{/* Screen Capture */}
{props.hasScreenCapture && (
<RichMenuItem
name='Screen'
icon={<ScreenshotMonitorIcon />}
description={screenCaptureError ? `Error: ${screenCaptureError}` : 'Capture windows, tabs, or screens'}
description={screenCaptureError ? `Error: ${screenCaptureError}` : 'Capture tabs, apps, and screens'}
onClick={handleTakeScreenCapture}
disabled={capturingScreen}
color={screenCaptureError ? 'danger' : undefined}
delay={0.08}
endAction={props.onStartLiveScreenFeed && <LiveFeedButton isActive={!!props.hasActiveScreenFeed} onClick={props.onStartLiveScreenFeed} />}
/>
)}
@@ -439,9 +511,10 @@ function AttachmentSources(props: {
<RichMenuItem
name='Camera'
icon={<CameraAltOutlinedIcon />}
description='Capture photos with optional text recognition'
description='Capture photos with optional OCR'
onClick={props.onOpenCamera}
delay={0.1}
endAction={props.onStartLiveCameraFeed && <LiveFeedButton isActive={!!props.hasActiveCameraFeed} onClick={props.onStartLiveCameraFeed} />}
/>
)}