techfreakworm commited on
Commit
192ae87
·
unverified ·
1 Parent(s): edf3bf7

feat(web): generateDialog client with per-speaker multipart

Browse files
Files changed (2) hide show
  1. web/src/lib/api.ts +40 -0
  2. web/src/test/api.test.ts +43 -0
web/src/lib/api.ts CHANGED
@@ -95,3 +95,43 @@ export function streamActiveEvents(
95
  };
96
  return () => es.close();
97
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  };
96
  return () => es.close();
97
  }
98
+
99
+ export type DialogSpeakerInput = {
100
+ letter: "A" | "B" | "C" | "D";
101
+ reference: Blob;
102
+ };
103
+
104
+ export type DialogInput = {
105
+ engineId: string;
106
+ text: string;
107
+ language?: string;
108
+ params: Record<string, unknown>;
109
+ speakers: DialogSpeakerInput[];
110
+ };
111
+
112
+ export type DialogResult = {
113
+ blob: Blob;
114
+ seedUsed: number | null;
115
+ };
116
+
117
+ export async function generateDialog(input: DialogInput): Promise<DialogResult> {
118
+ const fd = new FormData();
119
+ fd.set("text", input.text);
120
+ fd.set("engine_id", input.engineId);
121
+ fd.set("params", JSON.stringify(input.params ?? {}));
122
+ if (input.language) fd.set("language", input.language);
123
+ for (const s of input.speakers) {
124
+ fd.set(`reference_wav_${s.letter.toLowerCase()}`, s.reference, `${s.letter}.wav`);
125
+ }
126
+ const r = await fetch("/api/generate/dialog", { method: "POST", body: fd });
127
+ if (!r.ok) {
128
+ const err = await r.json().catch(() => ({}));
129
+ const code = err?.error?.code ?? `dialog: ${r.status}`;
130
+ const msg = err?.error?.message;
131
+ throw new Error(msg ? `${code}: ${msg}` : code);
132
+ }
133
+ const seedHeader = r.headers.get("x-seed-used");
134
+ const seedUsed = seedHeader != null ? Number(seedHeader) : null;
135
+ const blob = await r.blob();
136
+ return { blob, seedUsed };
137
+ }
web/src/test/api.test.ts CHANGED
@@ -1,5 +1,6 @@
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
  import { activateModel, generate, getActiveModel, listModels } from "@/lib/api";
 
3
 
4
  const fetchMock = vi.fn();
5
 
@@ -65,3 +66,45 @@ describe("api", () => {
65
  ).rejects.toThrow(/model_not_found/);
66
  });
67
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
  import { activateModel, generate, getActiveModel, listModels } from "@/lib/api";
3
+ import { generateDialog } from "@/lib/api";
4
 
5
  const fetchMock = vi.fn();
6
 
 
66
  ).rejects.toThrow(/model_not_found/);
67
  });
68
  });
69
+
70
+ describe("generateDialog", () => {
71
+ it("posts multipart with engine_id and per-speaker clips", async () => {
72
+ fetchMock.mockResolvedValue(
73
+ new Response("RIFFOK", {
74
+ status: 200,
75
+ headers: { "X-Seed-Used": "33" },
76
+ }),
77
+ );
78
+ const out = await generateDialog({
79
+ engineId: "x",
80
+ text: "SPEAKER A: hi\nSPEAKER B: hi",
81
+ params: { temperature: 0.8 },
82
+ speakers: [
83
+ { letter: "A", reference: new Blob(["a"], { type: "audio/wav" }) },
84
+ { letter: "B", reference: new Blob(["b"], { type: "audio/wav" }) },
85
+ ],
86
+ });
87
+ expect(out.seedUsed).toBe(33);
88
+ expect(typeof out.blob.size).toBe("number");
89
+ const call = fetchMock.mock.calls[0];
90
+ expect(call[0]).toBe("/api/generate/dialog");
91
+ const body = call[1].body as FormData;
92
+ expect(body.get("engine_id")).toBe("x");
93
+ expect(body.get("text")).toContain("SPEAKER A:");
94
+ expect(body.get("reference_wav_a")).toBeInstanceOf(Blob);
95
+ expect(body.get("reference_wav_b")).toBeInstanceOf(Blob);
96
+ });
97
+
98
+ it("forwards language only when provided", async () => {
99
+ fetchMock.mockResolvedValue(new Response("RIFF", { status: 200 }));
100
+ await generateDialog({
101
+ engineId: "x",
102
+ text: "SPEAKER A: hi",
103
+ language: "fr",
104
+ params: {},
105
+ speakers: [{ letter: "A", reference: new Blob(["a"]) }],
106
+ });
107
+ const body = fetchMock.mock.calls[0][1].body as FormData;
108
+ expect(body.get("language")).toBe("fr");
109
+ });
110
+ });