From 5a73c4e291988bfa7efaa7736c30dcb2177912bf Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Thu, 12 Feb 2026 14:54:35 +0900 Subject: [PATCH] feat(batch): enhance batch process with phase tracking --- .../body/sessions/floating/options-menu.tsx | 23 +++++++++++++++---- .../note-input/transcript/progress.tsx | 5 +++- apps/desktop/src/hooks/useRunBatch.ts | 5 ++-- .../src/store/zustand/listener/batch.ts | 19 +++++++++++---- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/components/main/body/sessions/floating/options-menu.tsx b/apps/desktop/src/components/main/body/sessions/floating/options-menu.tsx index d3fac4d269..71afa13b27 100644 --- a/apps/desktop/src/components/main/body/sessions/floating/options-menu.tsx +++ b/apps/desktop/src/components/main/body/sessions/floating/options-menu.tsx @@ -54,7 +54,6 @@ export function OptionsMenu({ const queryClient = useQueryClient(); const handleBatchStarted = useListener((state) => state.handleBatchStarted); const handleBatchFailed = useListener((state) => state.handleBatchFailed); - const clearBatchSession = useListener((state) => state.clearBatchSession); const store = main.UI.useStore(main.STORE_ID) as main.Store | undefined; const indexes = main.UI.useIndexes(main.STORE_ID); @@ -63,6 +62,7 @@ export function OptionsMenu({ const createEnhancedNote = useCreateEnhancedNote(); const model = useLanguageModel(); const generate = useAITask((state) => state.generate); + const getAITaskState = useAITask((state) => state.getState); const sessionTab = useTabs((state) => { const found = state.tabs.find( (tab): tab is Extract => @@ -94,6 +94,14 @@ export function OptionsMenu({ } const enhanceTaskId = createTaskId(enhancedNoteId, "enhance"); + const existingTask = getAITaskState(enhanceTaskId); + if ( + existingTask?.status === "generating" || + existingTask?.status === "success" + ) { + return; + } + void generate(enhanceTaskId, { model, taskType: "enhance", @@ -142,6 +150,7 @@ export function OptionsMenu({ sessionTab, updateSessionTabState, generate, + getAITaskState, ]); const handleFilePath = useCallback( @@ -235,7 +244,7 @@ export function OptionsMenu({ view: { type: "transcript" }, }); } - handleBatchStarted(sessionId); + handleBatchStarted(sessionId, "importing"); }), Effect.flatMap(() => fromResult(fsSyncCommands.audioImport(sessionId, path)), @@ -254,8 +263,13 @@ export function OptionsMenu({ }); }), ), - Effect.tap(() => Effect.sync(() => clearBatchSession(sessionId))), - Effect.flatMap(() => Effect.promise(() => runBatch(path))), + Effect.flatMap(() => + Effect.tryPromise({ + try: () => runBatch(path), + catch: (error) => + error instanceof Error ? error : new Error(String(error)), + }), + ), Effect.tap(() => Effect.sync(() => triggerEnhance())), Effect.catchAll((error: unknown) => Effect.sync(() => { @@ -266,7 +280,6 @@ export function OptionsMenu({ ); }, [ - clearBatchSession, handleBatchFailed, handleBatchStarted, queryClient, diff --git a/apps/desktop/src/components/main/body/sessions/note-input/transcript/progress.tsx b/apps/desktop/src/components/main/body/sessions/note-input/transcript/progress.tsx index 11f0b843ab..97e3c1e4a5 100644 --- a/apps/desktop/src/components/main/body/sessions/note-input/transcript/progress.tsx +++ b/apps/desktop/src/components/main/body/sessions/note-input/transcript/progress.tsx @@ -16,7 +16,10 @@ export function TranscriptionProgress({ sessionId }: { sessionId: string }) { const statusLabel = useMemo(() => { if (!progressRaw || progressRaw.percentage === 0) { - return "Importing audio..."; + if (progressRaw?.phase === "importing") { + return "Importing audio..."; + } + return "Processing..."; } const percent = Math.round(progressRaw.percentage * 100); diff --git a/apps/desktop/src/hooks/useRunBatch.ts b/apps/desktop/src/hooks/useRunBatch.ts index 5e6dd0277d..159b2a7bd4 100644 --- a/apps/desktop/src/hooks/useRunBatch.ts +++ b/apps/desktop/src/hooks/useRunBatch.ts @@ -67,8 +67,9 @@ export const useRunBatch = (sessionId: string) => { return useCallback( async (filePath: string, options?: RunOptions) => { if (!store || !conn || !runBatch) { - console.error("no_batch_connection"); - return; + throw new Error( + "STT connection is not available. Please configure your speech-to-text provider.", + ); } const provider = getBatchProvider(conn.provider, conn.model); diff --git a/apps/desktop/src/store/zustand/listener/batch.ts b/apps/desktop/src/store/zustand/listener/batch.ts index 06b33cdacc..773c2a1494 100644 --- a/apps/desktop/src/store/zustand/listener/batch.ts +++ b/apps/desktop/src/store/zustand/listener/batch.ts @@ -10,6 +10,8 @@ import { import type { HandlePersistCallback } from "./transcript"; import { transformWordEntries } from "./utils"; +export type BatchPhase = "importing" | "transcribing"; + export type BatchState = { batch: Record< string, @@ -17,12 +19,13 @@ export type BatchState = { percentage: number; isComplete?: boolean; error?: string; + phase?: BatchPhase; } >; }; export type BatchActions = { - handleBatchStarted: (sessionId: string) => void; + handleBatchStarted: (sessionId: string, phase?: BatchPhase) => void; handleBatchResponse: (sessionId: string, response: BatchResponse) => void; handleBatchResponseStreamed: ( sessionId: string, @@ -44,12 +47,16 @@ export const createBatchSlice = < ): BatchState & BatchActions => ({ batch: {}, - handleBatchStarted: (sessionId) => { + handleBatchStarted: (sessionId, phase) => { set((state) => ({ ...state, batch: { ...state.batch, - [sessionId]: { percentage: 0, isComplete: false }, + [sessionId]: { + percentage: 0, + isComplete: false, + phase: phase ?? "transcribing", + }, }, })); }, @@ -88,7 +95,11 @@ export const createBatchSlice = < ...state, batch: { ...state.batch, - [sessionId]: { percentage, isComplete: isComplete || false }, + [sessionId]: { + percentage, + isComplete: isComplete || false, + phase: "transcribing", + }, }, })); },