Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<Tab, { type: "sessions" }> =>
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -142,6 +150,7 @@ export function OptionsMenu({
sessionTab,
updateSessionTabState,
generate,
getAITaskState,
]);

const handleFilePath = useCallback(
Expand Down Expand Up @@ -235,7 +244,7 @@ export function OptionsMenu({
view: { type: "transcript" },
});
}
handleBatchStarted(sessionId);
handleBatchStarted(sessionId, "importing");
}),
Effect.flatMap(() =>
fromResult(fsSyncCommands.audioImport(sessionId, path)),
Expand All @@ -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)),
}),
),
Comment on lines 263 to +272
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Removal of clearBatchSession before runBatch causes batch transcription to silently abort

The audio upload + batch transcription flow is broken. When uploading an audio file, handleBatchStarted(sessionId, "importing") is called at options-menu.tsx:247, which sets batch[sessionId] in the store. Then after the audio import succeeds, runBatch(path) is called at line 268. This eventually invokes general.ts:runBatch, which at line 461 checks getSessionMode(sessionId). Because batch[sessionId] already exists (set by the earlier handleBatchStarted), getSessionMode returns "running_batch" (see general.ts:595). The guard at general.ts:469 then triggers: if (mode === "running_batch") { return; }, silently aborting the transcription.

Root Cause

The old code had a clearBatchSession(sessionId) call between the audio import and runBatch that cleared the batch state, allowing runBatch to proceed:

Effect.tap(() => Effect.sync(() => clearBatchSession(sessionId))),
Effect.flatMap(() => Effect.promise(() => runBatch(path))),

This PR removed the clearBatchSession call and clearBatchSession from the component entirely. The new code calls handleBatchStarted(sessionId, "importing") to show the importing phase, but never clears it before calling runBatch. Inside general.ts:runBatch at line 469, the running_batch mode guard causes an immediate return, so the actual batch transcription command (listener2Commands.runBatch) is never executed.

Impact: Every audio file upload will import the file but never transcribe it. The user sees the "importing" progress indicator, then the flow silently fails without any error message shown to the user (only a console.warn).

(Refers to lines 246-272)

Prompt for agents
The batch state set by handleBatchStarted(sessionId, "importing") at line 247 needs to be cleared before runBatch is called, so the running_batch guard in general.ts:469 does not abort the transcription. There are two possible approaches:

1. Re-add clearBatchSession: Add back the clearBatchSession selector from useListener at the top of the component (it was removed in this PR), and insert an Effect.tap step that calls clearBatchSession(sessionId) between the audio import step (around line 264) and the Effect.flatMap that calls runBatch (line 266). This restores the old behavior while keeping the new importing phase.

2. Modify general.ts:runBatch: Change the running_batch guard in general.ts around line 469 to allow re-entry when the batch is in the importing phase, e.g. check state.batch[sessionId]?.phase === "importing" and allow proceeding in that case. This is more nuanced and changes the semantics of the guard.

Approach 1 is simpler and safer. Add clearBatchSession back and insert it before runBatch in the Effect pipeline.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Effect.tap(() => Effect.sync(() => triggerEnhance())),
Effect.catchAll((error: unknown) =>
Effect.sync(() => {
Expand All @@ -266,7 +280,6 @@ export function OptionsMenu({
);
},
[
clearBatchSession,
handleBatchFailed,
handleBatchStarted,
queryClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions apps/desktop/src/hooks/useRunBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 15 additions & 4 deletions apps/desktop/src/store/zustand/listener/batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@ import {
import type { HandlePersistCallback } from "./transcript";
import { transformWordEntries } from "./utils";

export type BatchPhase = "importing" | "transcribing";

export type BatchState = {
batch: Record<
string,
{
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,
Expand All @@ -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",
},
},
}));
},
Expand Down Expand Up @@ -88,7 +95,11 @@ export const createBatchSlice = <
...state,
batch: {
...state.batch,
[sessionId]: { percentage, isComplete: isComplete || false },
[sessionId]: {
percentage,
isComplete: isComplete || false,
phase: "transcribing",
},
},
}));
},
Expand Down
Loading