* Pick the voice from a carousel in the app language, local voice only Voice settings now list only the voices that speak the app language (German shows German voices, English shows English ones) as a horizontal carousel: one card per voice, a play button next to the name, tap to make it the active voice. Changing the app language moves the saved voice to that language. The hosted voice providers (ElevenLabs, OpenAI, Cartesia, Fish Audio) are removed; Kokoro on the server is the only voice. E2E runs against a stub Kokoro server so the carousel is covered. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxoSHAyzMXvdtwcDJZcn57 * test(e2e): measure the workspace panel width after its transition settles Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxoSHAyzMXvdtwcDJZcn57 --------- Co-authored-by: Claude <noreply@anthropic.com>
34 lines
1.3 KiB
TypeScript
34 lines
1.3 KiB
TypeScript
import { createServer } from "node:http";
|
|
import type { AddressInfo } from "node:net";
|
|
|
|
/**
|
|
* Stands in for the local Kokoro speech server so onboarding's voice step never depends on
|
|
* whatever happens to listen on the default port of the machine running the suite.
|
|
*/
|
|
export async function startFakeKokoro(): Promise<{ baseUrl: string; close: () => Promise<void> }> {
|
|
const server = createServer((req, res) => {
|
|
if (req.method === "GET" && req.url === "/v1/audio/voices") {
|
|
res.writeHead(200, { "content-type": "application/json" });
|
|
// An empty list makes the adapter fall back to its curated presets.
|
|
res.end(JSON.stringify({ voices: [] }));
|
|
return;
|
|
}
|
|
if (req.method === "POST" && req.url === "/v1/audio/speech") {
|
|
req.resume();
|
|
req.on("end", () => {
|
|
// An MPEG frame header is enough for callers that only check the content type.
|
|
res.writeHead(200, { "content-type": "audio/mpeg" });
|
|
res.end(Buffer.from([0xff, 0xfb, 0x90, 0x64, 0, 0, 0, 0]));
|
|
});
|
|
return;
|
|
}
|
|
res.writeHead(404).end();
|
|
});
|
|
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
const { port } = server.address() as AddressInfo;
|
|
return {
|
|
baseUrl: `http://127.0.0.1:${port}`,
|
|
close: () => new Promise((resolve) => server.close(() => resolve())),
|
|
};
|
|
}
|