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

View file

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