Replace the previous whisper.cpp-based word alignment with a fully ONNX-based implementation using onnxruntime-node and @huggingface/tokenizers. Add new Whisper ONNX model management, alignment mapping, and spectral analysis modules. Remove all code and documentation referencing whisper.cpp, update environment variables, Dockerfile, and docs to reflect ONNX-only alignment. Add unit tests for alignment and ONNX model logic.
71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { createHash } from 'crypto';
|
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'fs/promises';
|
|
import { tmpdir } from 'os';
|
|
import path from 'path';
|
|
import {
|
|
createSingleflightRunner,
|
|
ensureWhisperArtifacts,
|
|
} from '../../src/lib/server/whisper/ensureModel';
|
|
|
|
function sha256(bytes: Uint8Array): string {
|
|
return createHash('sha256').update(bytes).digest('hex');
|
|
}
|
|
|
|
test.describe('whisper ensure model helpers', () => {
|
|
test('downloads and verifies artifacts, and repairs checksum mismatch', async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), 'openreader-whisper-model-test-'));
|
|
const artifactBytes = new TextEncoder().encode('artifact-content-v1');
|
|
const artifactHash = sha256(artifactBytes);
|
|
const artifactPath = 'onnx/encoder_model_int8.onnx';
|
|
const target = path.join(root, artifactPath);
|
|
|
|
try {
|
|
// Seed a corrupted file to verify repair behavior.
|
|
await mkdir(path.dirname(target), { recursive: true });
|
|
await writeFile(target, new Uint8Array([0, 1, 2, 3]));
|
|
|
|
let fetchCount = 0;
|
|
await ensureWhisperArtifacts({
|
|
modelDir: root,
|
|
artifacts: [
|
|
{
|
|
path: artifactPath,
|
|
sha256: artifactHash,
|
|
size: artifactBytes.byteLength,
|
|
url: 'https://example.local/fake-artifact',
|
|
},
|
|
],
|
|
fetchImpl: async () => {
|
|
fetchCount += 1;
|
|
return new Response(artifactBytes, { status: 200 });
|
|
},
|
|
});
|
|
|
|
const repaired = await readFile(target);
|
|
expect(repaired.byteLength).toBe(artifactBytes.byteLength);
|
|
expect(sha256(repaired)).toBe(artifactHash);
|
|
expect(fetchCount).toBe(1);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('singleflight runner deduplicates concurrent work', async () => {
|
|
let runs = 0;
|
|
const run = createSingleflightRunner(async () => {
|
|
runs += 1;
|
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
return 'ok';
|
|
});
|
|
|
|
const [a, b, c] = await Promise.all([run(), run(), run()]);
|
|
expect(a).toBe('ok');
|
|
expect(b).toBe('ok');
|
|
expect(c).toBe('ok');
|
|
expect(runs).toBe(1);
|
|
|
|
await run();
|
|
expect(runs).toBe(2);
|
|
});
|
|
});
|