diff --git a/apps/desktop/src/components/main/body/sessions/index.tsx b/apps/desktop/src/components/main/body/sessions/index.tsx index ef4a3572c4..6f8b393d39 100644 --- a/apps/desktop/src/components/main/body/sessions/index.tsx +++ b/apps/desktop/src/components/main/body/sessions/index.tsx @@ -6,6 +6,7 @@ import { commands as miscCommands } from "@hypr/plugin-misc"; import AudioPlayer from "../../../../contexts/audio-player"; import { useListener } from "../../../../contexts/listener"; +import { useIsSessionEnhancing } from "../../../../hooks/useEnhancedNotes"; import * as main from "../../../../store/tinybase/main"; import { rowIdfromTab, type Tab } from "../../../../store/zustand/tabs"; import { StandardTabWrapper } from "../index"; @@ -36,7 +37,10 @@ export const TabItemNote: TabItem> = ({ main.STORE_ID, ); const sessionMode = useListener((state) => state.getSessionMode(tab.id)); + const isEnhancing = useIsSessionEnhancing(tab.id); const isActive = sessionMode === "active" || sessionMode === "finalizing"; + const isFinalizing = sessionMode === "finalizing"; + const showSpinner = isFinalizing || isEnhancing; return ( > = ({ title={title || "Untitled"} selected={tab.active} active={isActive} + finalizing={showSpinner} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} diff --git a/apps/desktop/src/components/main/body/shared.tsx b/apps/desktop/src/components/main/body/shared.tsx index 9e051bf7cd..de50bb474a 100644 --- a/apps/desktop/src/components/main/body/shared.tsx +++ b/apps/desktop/src/components/main/body/shared.tsx @@ -2,6 +2,7 @@ import { X } from "lucide-react"; import { useState } from "react"; import { Kbd } from "@hypr/ui/components/ui/kbd"; +import { Spinner } from "@hypr/ui/components/ui/spinner"; import { cn } from "@hypr/utils"; import { useCmdKeyPressed } from "../../../hooks/useCmdKeyPressed"; @@ -21,6 +22,7 @@ type TabItemBaseProps = { selected: boolean; active?: boolean; isEmptyTab?: boolean; + finalizing?: boolean; tabIndex?: number; } & { handleCloseThis: () => void; @@ -39,6 +41,7 @@ export function TabItemBase({ selected, active = false, isEmptyTab = false, + finalizing = false, tabIndex, handleCloseThis, handleSelectThis, @@ -110,7 +113,9 @@ export function TabItemBase({ isHovered ? "opacity-0" : "opacity-100", ])} > - {active ? ( + {finalizing ? ( + + ) : active ? (
diff --git a/apps/desktop/src/components/main/sidebar/timeline/item.tsx b/apps/desktop/src/components/main/sidebar/timeline/item.tsx index 466b16361a..7ad2ac732b 100644 --- a/apps/desktop/src/components/main/sidebar/timeline/item.tsx +++ b/apps/desktop/src/components/main/sidebar/timeline/item.tsx @@ -1,6 +1,7 @@ import { memo, useCallback, useMemo } from "react"; import { commands as analyticsCommands } from "@hypr/plugin-analytics"; +import { Spinner } from "@hypr/ui/components/ui/spinner"; import { Tooltip, TooltipContent, @@ -8,6 +9,8 @@ import { } from "@hypr/ui/components/ui/tooltip"; import { cn } from "@hypr/utils"; +import { useListener } from "../../../../contexts/listener"; +import { useIsSessionEnhancing } from "../../../../hooks/useEnhancedNotes"; import { deleteSessionCascade } from "../../../../store/tinybase/deleteSession"; import * as main from "../../../../store/tinybase/main"; import { type TabInput, useTabs } from "../../../../store/zustand/tabs"; @@ -37,6 +40,14 @@ export const TimelineItemComponent = memo( const timestamp = item.type === "event" ? item.data.started_at : item.data.created_at; + const sessionId = item.type === "session" ? item.id : null; + const sessionMode = useListener((state) => + sessionId ? state.getSessionMode(sessionId) : "inactive", + ); + const isEnhancing = useIsSessionEnhancing(sessionId ?? ""); + const isFinalizing = sessionMode === "finalizing"; + const showSpinner = isFinalizing || isEnhancing; + const calendarId = useMemo(() => { if (!store || !eventId) { return null; @@ -79,6 +90,11 @@ export const TimelineItemComponent = memo( ])} >
+ {showSpinner && ( +
+ +
+ )}
{title}
{displayTime && ( diff --git a/apps/desktop/src/hooks/useEnhancedNotes.ts b/apps/desktop/src/hooks/useEnhancedNotes.ts index 784ddd7050..a2a9108466 100644 --- a/apps/desktop/src/hooks/useEnhancedNotes.ts +++ b/apps/desktop/src/hooks/useEnhancedNotes.ts @@ -1,8 +1,10 @@ -import { useCallback, useEffect } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { useHasTranscript } from "../components/main/body/sessions/shared"; +import { useAITask } from "../contexts/ai-task"; import { useListener } from "../contexts/listener"; import * as main from "../store/tinybase/main"; +import { createTaskId } from "../store/zustand/ai-task/task-configs"; export function useCreateEnhancedNote() { const store = main.UI.useStore(main.STORE_ID) as main.Store | undefined; @@ -170,3 +172,24 @@ export function useEnsureDefaultSummary(sessionId: string) { createEnhancedNote, ]); } + +export function useIsSessionEnhancing(sessionId: string): boolean { + const enhancedNoteIds = main.UI.useSliceRowIds( + main.INDEXES.enhancedNotesBySession, + sessionId, + main.STORE_ID, + ); + + const taskIds = useMemo( + () => (enhancedNoteIds || []).map((id) => createTaskId(id, "enhance")), + [enhancedNoteIds], + ); + + const isEnhancing = useAITask((state) => { + return taskIds.some( + (taskId) => state.tasks[taskId]?.status === "generating", + ); + }); + + return isEnhancing; +}