mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
track meeting recoring and show active recording in the sidebar row
This commit is contained in:
parent
5eea5907e7
commit
1a3e5a4e2e
2 changed files with 84 additions and 28 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
})}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue