track meeting recoring and show active recording in the sidebar row

This commit is contained in:
Arjun 2026-05-19 19:46:29 +05:30
parent 5eea5907e7
commit 1a3e5a4e2e
2 changed files with 84 additions and 28 deletions

View file

@ -4567,12 +4567,14 @@ function App() {
const pendingCalendarEventRef = useRef<CalendarEventMeta | undefined>(undefined)
const [meetingSummarizing, setMeetingSummarizing] = useState(false)
const [showMeetingPermissions, setShowMeetingPermissions] = useState(false)
const [recordingMeetingSource, setRecordingMeetingSource] = useState<string | null>(null)
const [checkingPermission, setCheckingPermission] = useState(false)
const startMeetingNow = useCallback(async () => {
const calEvent = pendingCalendarEventRef.current
pendingCalendarEventRef.current = undefined
setRecordingMeetingSource(calEvent?.source ?? null)
const notePath = await meetingTranscription.start(calEvent)
if (notePath) {
meetingNotePathRef.current = notePath
@ -4600,6 +4602,7 @@ function App() {
const handleToggleMeeting = useCallback(async () => {
if (meetingTranscription.state === 'recording') {
await meetingTranscription.stop()
setRecordingMeetingSource(null)
// Read the final transcript and generate meeting notes via LLM
const notePath = meetingNotePathRef.current
@ -5119,6 +5122,9 @@ function App() {
isSuggestedTopicsOpen={isSuggestedTopicsOpen}
onOpenSuggestedTopics={() => void navigateToView({ type: 'suggested-topics' })}
onOpenMeetings={openMeetingsView}
meetingRecordingState={meetingTranscription.state}
recordingMeetingSource={recordingMeetingSource}
onToggleMeetingRecording={() => { void handleToggleMeeting() }}
onOpenBgTasks={() => { setBgTaskInitialSlug(null); setBgTaskSlugVersion((v) => v + 1); openBgTasksView() }}
isEmailOpen={isEmailOpen}
onOpenEmail={openEmailView}

View file

@ -196,6 +196,9 @@ type SidebarContentPanelProps = {
isSuggestedTopicsOpen?: boolean
onOpenSuggestedTopics?: () => void
onOpenMeetings?: () => void
meetingRecordingState?: 'idle' | 'connecting' | 'recording' | 'stopping'
recordingMeetingSource?: string | null
onToggleMeetingRecording?: () => void
onOpenBgTasks?: () => void
isEmailOpen?: boolean
onOpenEmail?: () => void
@ -443,6 +446,9 @@ export function SidebarContentPanel({
isSuggestedTopicsOpen = false,
onOpenSuggestedTopics,
onOpenMeetings,
meetingRecordingState,
recordingMeetingSource,
onToggleMeetingRecording,
onOpenBgTasks,
isEmailOpen = false,
onOpenEmail,
@ -542,7 +548,12 @@ export function SidebarContentPanel({
</div>
</SidebarHeader>
<SidebarContent>
<MeetingsSidebarSection onOpenMeetingsView={onOpenMeetings} />
<MeetingsSidebarSection
onOpenMeetingsView={onOpenMeetings}
recordingState={meetingRecordingState ?? 'idle'}
recordingSource={recordingMeetingSource ?? null}
onToggleRecording={onToggleMeetingRecording}
/>
<TasksSidebarSection
tasks={bgTaskSummaries}
onOpenTask={onOpenBgTask}
@ -1183,8 +1194,14 @@ function formatMeetingTime(event: UpcomingMeeting): string {
function MeetingsSidebarSection({
onOpenMeetingsView,
recordingState,
recordingSource,
onToggleRecording,
}: {
onOpenMeetingsView?: () => void
recordingState: 'idle' | 'connecting' | 'recording' | 'stopping'
recordingSource: string | null
onToggleRecording?: () => void
}) {
const [meetings, setMeetings] = useState<UpcomingMeeting[]>([])
@ -1268,53 +1285,86 @@ function MeetingsSidebarSection({
<SidebarMenu>
{meetings.map((m) => {
const hasConference = Boolean(m.conferenceLink)
const isThisRecording = recordingSource === m.source && (recordingState === 'recording' || recordingState === 'connecting' || recordingState === 'stopping')
const isBusy = isThisRecording && (recordingState === 'connecting' || recordingState === 'stopping')
return (
<SidebarMenuItem key={m.id}>
<SidebarMenuButton onClick={onOpenMeetingsView} className="gap-2">
<Mic className="size-4 shrink-0" />
<span className="min-w-0 flex-1 truncate text-sm">{m.summary}</span>
<span className="shrink-0 text-[10px] text-muted-foreground group-hover/menu-item:hidden">
{formatMeetingTime(m)}
<span
className={`shrink-0 text-[10px] text-muted-foreground ${isThisRecording ? '' : 'group-hover/menu-item:hidden'}`}
>
{isThisRecording ? null : formatMeetingTime(m)}
</span>
</SidebarMenuButton>
<div className="absolute top-1.5 right-1 flex items-center gap-0.5 opacity-0 transition-opacity group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100">
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
aria-label="Take notes"
onClick={(e) => {
e.stopPropagation()
triggerMeetingCapture(m, false)
}}
onMouseDown={(e) => e.stopPropagation()}
className="flex aspect-square w-5 items-center justify-center rounded-md text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
>
<Mic className="size-4" />
</button>
</TooltipTrigger>
<TooltipContent side="bottom">Take notes</TooltipContent>
</Tooltip>
{hasConference && (
{isThisRecording ? (
<div className="absolute top-1.5 right-1 flex items-center gap-1.5">
<span className="relative flex size-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-red-500 opacity-75" />
<span className="relative inline-flex size-2 rounded-full bg-red-500" />
</span>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
aria-label="Join & take notes"
aria-label="Stop recording"
disabled={isBusy}
onClick={(e) => {
e.stopPropagation()
triggerMeetingCapture(m, true)
onToggleRecording?.()
}}
onMouseDown={(e) => e.stopPropagation()}
className="flex aspect-square w-5 items-center justify-center rounded-md text-destructive hover:bg-destructive/10 disabled:opacity-50"
>
{isBusy ? <LoaderIcon className="size-4 animate-spin" /> : <Square className="size-3.5 fill-current" />}
</button>
</TooltipTrigger>
<TooltipContent side="bottom">
{recordingState === 'connecting' ? 'Starting…' : recordingState === 'stopping' ? 'Stopping…' : 'Stop recording'}
</TooltipContent>
</Tooltip>
</div>
) : (
<div className="absolute top-1.5 right-1 flex items-center gap-0.5 opacity-0 transition-opacity group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100">
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
aria-label="Take notes"
onClick={(e) => {
e.stopPropagation()
triggerMeetingCapture(m, false)
}}
onMouseDown={(e) => e.stopPropagation()}
className="flex aspect-square w-5 items-center justify-center rounded-md text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
>
<Video className="size-4" />
<Mic className="size-4" />
</button>
</TooltipTrigger>
<TooltipContent side="bottom">Join & take notes</TooltipContent>
<TooltipContent side="bottom">Take notes</TooltipContent>
</Tooltip>
)}
</div>
{hasConference && (
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
aria-label="Join & take notes"
onClick={(e) => {
e.stopPropagation()
triggerMeetingCapture(m, true)
}}
onMouseDown={(e) => e.stopPropagation()}
className="flex aspect-square w-5 items-center justify-center rounded-md text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
>
<Video className="size-4" />
</button>
</TooltipTrigger>
<TooltipContent side="bottom">Join & take notes</TooltipContent>
</Tooltip>
)}
</div>
)}
</SidebarMenuItem>
)
})}