feat: add realtime practice voice selector

This commit is contained in:
2026-07-22 12:24:46 +08:00
parent 87772cc64c
commit 455267cf6f
3 changed files with 229 additions and 24 deletions
@@ -1,9 +1,32 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
import vm from 'node:vm';
import ts from 'typescript';
const source = (path) => readFile(new URL(path, import.meta.url), 'utf8');
const realtimeVoiceRuntime = async () => {
const service = await source('../src/services/realtime-practice.ts');
const compiled = ts.transpileModule(service, {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2020
}
});
const runtimeModule = { exports: {} };
vm.runInNewContext(compiled.outputText, {
Date,
exports: runtimeModule.exports,
module: runtimeModule,
require: (specifier) => {
if (specifier === './api') return { apiRequest: async () => ({}) };
throw new Error(`Unexpected module: ${specifier}`);
}
});
return runtimeModule.exports;
};
test('实时陪练通过受认证 SDP 代理接入,前端不携带供应商密钥', async () => {
const [service, page, backendService] = await Promise.all([
source('../src/services/realtime-practice.ts'),
@@ -17,10 +40,31 @@ test('实时陪练通过受认证 SDP 代理接入,前端不携带供应商密
assert.match(service, /response\.audio_transcript\.done/);
assert.match(service, /session\.created/);
assert.match(service, /session\.updated/);
assert.match(service, /REALTIME_PRACTICE_VOICES/);
assert.match(service, /createRealtimePracticeSessionUpdate/);
assert.match(service, /activeAttempt/);
assert.match(service, /resumeAudio/);
assert.doesNotMatch(service, /DASHSCOPE_API_KEY|Authorization:\s*Bearer/);
assert.match(backendService, /实时陪练限流暂时不可用/);
assert.match(page, /实时陪练 Beta/);
assert.match(page, /实时陪练音色/);
assert.match(page, /<picker :range="realtimeVoiceOptions" range-key="label"/);
assert.match(page, /aihr_realtime_practice_voice/);
assert.match(page, /selectRealtimeVoice/);
assert.match(page, /createRealtimePracticeController/);
});
test('实时陪练音色白名单会写入会话配置,非法值回退默认音色', async () => {
const runtime = await realtimeVoiceRuntime();
const voices = Array.from(runtime.REALTIME_PRACTICE_VOICES);
assert.deepEqual(voices, [
'Tina', 'Cindy', 'Liora Mira', 'Sunnybobi', 'Raymond', 'Ethan', 'Theo Calm', 'Serena', 'Harvey', 'Maia', 'Evan', 'Qiao', 'Momo', 'Wil', 'Angel', 'Li Cassian', 'Mia', 'Joyner', 'Gold', 'Katerina', 'Ryan', 'Jennifer', 'Aiden', 'Mione', 'Sunny', 'Dylan', 'Eric', 'Peter', 'Joseph Chen', 'Marcus', 'Li', 'Kiki', 'Rocky', 'Sohee', 'Lenn', 'Ono Anna', 'Sonrisa', 'Bodega', 'Emilien', 'Andre', 'Radio Gol', 'Alek', 'Rizky', 'Roya', 'Arda', 'Hana', 'Dolce', 'Jakub', 'Griet', 'Eliška', 'Marina', 'Siiri', 'Ingrid', 'Sigga', 'Bea', 'Chloe'
]);
for (const voice of voices) {
assert.equal(runtime.normalizeRealtimePracticeVoice(voice), voice);
assert.equal(runtime.createRealtimePracticeSessionUpdate('测试', voice).session.voice, voice);
}
assert.equal(runtime.normalizeRealtimePracticeVoice('untrusted-voice'), 'Ethan');
assert.equal(runtime.createRealtimePracticeSessionUpdate('测试', 'untrusted-voice').session.voice, 'Ethan');
});