Skip to content
Open
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 @@ -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<boolean> {
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<string, (ctx: MessageHandlerContext) => Promise<void> | void> = {
webviewReady: () => { /* no-op */ },
Expand Down Expand Up @@ -816,6 +853,23 @@ const messageHandlers: Record<string, (ctx: MessageHandlerContext) => Promise<vo
sourceFileName
);

const hasAudioForTranscription =
await sourceCellHasResolvableAudioForTranscription(
sourcePath,
cellId,
workspaceFolder
);
if (!hasAudioForTranscription) {
const readyNoAudio = await provider.isLLMReady().catch(() => 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,
Expand Down
94 changes: 94 additions & 0 deletions src/test/suite/codexCellEditorProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
6 changes: 6 additions & 0 deletions src/test/suite/startupFlowProvider_updateSync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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: [],
Expand Down Expand Up @@ -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) {
Expand Down
Loading