From 12579622507e627bf283697715f192ed0c9ee240 Mon Sep 17 00:00:00 2001 From: Ben Scholtens Date: Fri, 10 Apr 2026 15:17:48 -0700 Subject: [PATCH 1/2] Add audio file path resolution for transcription checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented a new function to determine if a source notebook cell has a resolvable audio file for transcription. This ensures that the "Transcribing source audio…" message is only displayed when transcription can actually occur. Updated message handling to incorporate this check before processing audio transcription requests. --- .../codexCellEditorMessagehandling.ts | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/providers/codexCellEditorProvider/codexCellEditorMessagehandling.ts b/src/providers/codexCellEditorProvider/codexCellEditorMessagehandling.ts index 760083a4f..4e8ed7fc4 100644 --- a/src/providers/codexCellEditorProvider/codexCellEditorMessagehandling.ts +++ b/src/providers/codexCellEditorProvider/codexCellEditorMessagehandling.ts @@ -315,6 +315,43 @@ async function getAudioFilePathForCell( return null; } +/** + * True when the source notebook has a resolvable audio file for this cell (metadata or legacy paths). + * Used so we only show "Transcribing source audio…" when transcription can actually run — not when + * source text is missing from the index or empty with no audio. + */ +async function sourceCellHasResolvableAudioForTranscription( + sourceUri: vscode.Uri, + cellId: string, + workspaceFolder: vscode.WorkspaceFolder +): Promise { + const cancellationTokenSource = new vscode.CancellationTokenSource(); + try { + const sourceDoc = await CodexCellDocument.create( + sourceUri, + undefined, + cancellationTokenSource.token + ); + try { + const cell = sourceDoc.getCell(cellId); + const audioPath = await getAudioFilePathForCell( + cell ?? {}, + cellId, + workspaceFolder, + sourceUri + ); + return audioPath !== null; + } finally { + sourceDoc.dispose(); + } + } catch (e) { + debug("sourceCellHasResolvableAudioForTranscription: could not read source or check audio", e); + return false; + } finally { + cancellationTokenSource.dispose(); + } +} + // Individual message handlers const messageHandlers: Record Promise | void> = { webviewReady: () => { /* no-op */ }, @@ -816,6 +853,23 @@ const messageHandlers: Record Promise true); + if (!readyNoAudio) { + vscode.window.showWarningMessage( + "LLM is not configured. Set an API key or sign in to generate predictions." + ); + } + await provider.addCellToSingleCellQueue(cellId, document, webviewPanel, addContentToValue); + return; + } + await vscode.commands.executeCommand( "vscode.openWith", sourcePath, From bb5b4ef5ee7c4551c1f6d59fba9db93d65faa4aa Mon Sep 17 00:00:00 2001 From: Ben Scholtens Date: Fri, 10 Apr 2026 16:18:28 -0700 Subject: [PATCH 2/2] Add test for LLM completion skipping source transcription when source is empty Implemented a new test case to verify that the LLM completion process correctly skips source transcription when the source cell is empty and no source audio exists. This ensures that the system behaves as expected under these conditions, preventing unnecessary operations and improving efficiency. --- .../suite/codexCellEditorProvider.test.ts | 94 +++++++++++++++++++ .../startupFlowProvider_updateSync.test.ts | 6 ++ 2 files changed, 100 insertions(+) diff --git a/src/test/suite/codexCellEditorProvider.test.ts b/src/test/suite/codexCellEditorProvider.test.ts index 2f7530452..9ea61eed7 100644 --- a/src/test/suite/codexCellEditorProvider.test.ts +++ b/src/test/suite/codexCellEditorProvider.test.ts @@ -2922,6 +2922,100 @@ suite("CodexCellEditorProvider Test Suite", () => { } }); + test("llmCompletion skips source transcription when source is empty and no source audio exists", async function () { + this.timeout(15000); + + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + if (!workspaceFolder) { + this.skip(); + } + + const baseName = `llm-skip-transcribe-${Date.now()}`; + const codexName = `${baseName}.codex`; + const sourcePath = vscode.Uri.joinPath(workspaceFolder.uri, ".project", "sourceTexts", `${baseName}.source`); + const codexPath = vscode.Uri.joinPath(workspaceFolder.uri, "files", "target", codexName); + + fs.mkdirSync(path.dirname(sourcePath.fsPath), { recursive: true }); + fs.mkdirSync(path.dirname(codexPath.fsPath), { recursive: true }); + + const sourceNotebook = JSON.parse(JSON.stringify(codexSubtitleContent)) as CodexNotebookAsJSONData; + const firstCell = sourceNotebook.cells[0] as any; + firstCell.value = ""; + if (firstCell.metadata) { + delete firstCell.metadata.attachments; + delete firstCell.metadata.selectedAudioId; + } + + const encoder = new TextEncoder(); + await vscode.workspace.fs.writeFile( + sourcePath, + encoder.encode(JSON.stringify(sourceNotebook, null, 2)) + ); + await vscode.workspace.fs.writeFile( + codexPath, + encoder.encode(JSON.stringify(codexSubtitleContent, null, 2)) + ); + + const cellId = codexSubtitleContent.cells[0].metadata.id; + + const testProvider = new CodexCellEditorProvider(context); + const document = await testProvider.openCustomDocument( + codexPath, + { backupId: undefined }, + new vscode.CancellationTokenSource().token + ); + + const { panel: webviewPanel } = createMockWebviewPanel(); + await testProvider.resolveCustomEditor( + document, + webviewPanel, + new vscode.CancellationTokenSource().token + ); + + const addQueueStub = sinon.stub(testProvider as any, "addCellToSingleCellQueue").resolves(); + + const originalExecuteCommand = vscode.commands.executeCommand; + let openWithCallCount = 0; + (vscode.commands as any).executeCommand = async (command: string, ...args: any[]) => { + if (command === "vscode.openWith") { + openWithCallCount++; + return undefined; + } + if (command === "codex-editor-extension.getSourceCellByCellIdFromAllSourceCells") { + return { cellId: args[0] as string, content: "" }; + } + return originalExecuteCommand.apply(vscode.commands, [command, ...args]); + }; + + try { + await handleMessages( + { command: "llmCompletion", content: { currentLineId: cellId } } as any, + webviewPanel, + document, + () => { /* no-op */ }, + testProvider as any + ); + await sleep(300); + + assert.strictEqual( + openWithCallCount, + 0, + "Must not open the source editor for transcription when there is no source audio" + ); + assert.strictEqual( + addQueueStub.called, + true, + "Should enqueue LLM completion directly when transcription is not possible" + ); + } finally { + (vscode.commands as any).executeCommand = originalExecuteCommand; + addQueueStub.restore(); + document.dispose(); + await deleteIfExists(sourcePath); + await deleteIfExists(codexPath); + } + }); + test("validateCellContent persists validatedBy on latest edit", async () => { const provider = new CodexCellEditorProvider(context); const document = await provider.openCustomDocument( diff --git a/src/test/suite/startupFlowProvider_updateSync.test.ts b/src/test/suite/startupFlowProvider_updateSync.test.ts index eaa3f739b..f58e8c767 100644 --- a/src/test/suite/startupFlowProvider_updateSync.test.ts +++ b/src/test/suite/startupFlowProvider_updateSync.test.ts @@ -10,6 +10,7 @@ import { createMockExtensionContext, swallowDuplicateCommandRegistrations } from import * as directoryConflicts from "../../projectManager/utils/merge/directoryConflicts"; import * as mergeResolvers from "../../projectManager/utils/merge/resolvers"; +import * as connectivityChecker from "../../utils/connectivityChecker"; import * as projectLocationUtils from "../../utils/projectLocationUtils"; suite("StartupFlowProvider Update - triggers LFS-aware sync", () => { @@ -45,6 +46,10 @@ suite("StartupFlowProvider Update - triggers LFS-aware sync", () => { vscode.Uri.file(tempProjectsDir) ); + // Avoid indefinite wait: performProjectUpdate calls ensureConnectivity(), which polls until + // isOnline() succeeds — in tests fetch often fails, so the update never finishes. + const ensureConnectivityStub = sinon.stub(connectivityChecker, "ensureConnectivity").resolves(); + // Make merge steps no-op const buildConflictsStub = sinon.stub(directoryConflicts, "buildConflictsFromDirectories").resolves({ textConflicts: [], @@ -156,6 +161,7 @@ suite("StartupFlowProvider Update - triggers LFS-aware sync", () => { initStub.restore(); getExtensionStub.restore(); getCodexProjectsDirectoryStub.restore(); + ensureConnectivityStub.restore(); buildConflictsStub.restore(); resolveConflictFilesStub.restore(); if (fsStatStub) {