diff --git a/apps/desktop/src/components/main/body/sessions/index.tsx b/apps/desktop/src/components/main/body/sessions/index.tsx index ead6d0d606..e2ced0f69c 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"; @@ -35,8 +36,11 @@ export const TabItemNote: TabItem> = ({ main.STORE_ID, ); const sessionMode = useListener((state) => state.getSessionMode(tab.id)); + const isEnhancing = useIsSessionEnhancing(tab.id); const isActive = sessionMode === "running_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 ce4b1a16ac..0e66ece6b4 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, KbdGroup } from "@hypr/ui/components/ui/kbd"; +import { Spinner } from "@hypr/ui/components/ui/spinner"; import { cn } from "@hypr/utils"; import { useCmdKeyPressed } from "../../../hooks/useCmdKeyPressed"; @@ -20,7 +21,7 @@ type TabItemBaseProps = { title: React.ReactNode; selected: boolean; active?: boolean; - isEmptyTab?: boolean; + finalizing?: boolean; tabIndex?: number; } & { handleCloseThis: () => void; @@ -38,7 +39,7 @@ export function TabItemBase({ title, selected, active = false, - isEmptyTab = false, + finalizing = false, tabIndex, handleCloseThis, handleSelectThis, @@ -110,7 +111,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 02d15a5f2d..c0dd29866e 100644 --- a/apps/desktop/src/components/main/sidebar/timeline/item.tsx +++ b/apps/desktop/src/components/main/sidebar/timeline/item.tsx @@ -1,8 +1,12 @@ import { memo, useCallback, useMemo } from "react"; import { commands as analyticsCommands } from "@hypr/plugin-analytics"; +import { ContextMenuItem } from "@hypr/ui/components/ui/context-menu"; +import { Spinner } from "@hypr/ui/components/ui/spinner"; import { cn } from "@hypr/utils"; +import { useListener } from "../../../../contexts/listener"; +import { useIsSessionEnhancing } from "../../../../hooks/useEnhancedNotes"; import * as main from "../../../../store/tinybase/main"; import { type TabInput, useTabs } from "../../../../store/zustand/tabs"; import { id } from "../../../../utils"; @@ -35,6 +39,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 handleClick = () => { if (item.type === "event") { handleEventClick(false); @@ -162,11 +174,18 @@ export const TimelineItemComponent = memo( !selected && "hover:bg-neutral-100", ])} > -
-
{title}
- {displayTime && ( -
{displayTime}
+
+ {showSpinner && ( +
+ +
)} +
+
{title}
+ {displayTime && ( +
{displayTime}
+ )} +
); diff --git a/apps/desktop/src/hooks/useEnhancedNotes.ts b/apps/desktop/src/hooks/useEnhancedNotes.ts index aed5cb1b37..4f4fe8bbbb 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; +}