nixamp 0.28.43 → 0.28.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +8 -3
  2. package/dist/accounts.js +9 -1
  3. package/dist/live-interpreter.d.ts +1 -72
  4. package/dist/live-interpreter.js +1 -269
  5. package/dist/live-recovery.d.ts +1 -6
  6. package/dist/live-recovery.js +1 -11
  7. package/dist/live-voice.d.ts +6 -76
  8. package/dist/live-voice.js +6 -352
  9. package/dist/owner.js +4 -0
  10. package/dist/paywall.js +1 -1
  11. package/dist/pcm-wav.d.ts +1 -2
  12. package/dist/pcm-wav.js +1 -27
  13. package/dist/server.d.ts +16 -0
  14. package/dist/server.js +77 -6
  15. package/dist/shared-translation.d.ts +6 -53
  16. package/dist/shared-translation.js +7 -336
  17. package/dist/speaker-turns.d.ts +1 -32
  18. package/dist/speaker-turns.js +1 -30
  19. package/dist/speech.d.ts +3 -5
  20. package/dist/speech.js +4 -7
  21. package/dist/tickets.js +1 -1
  22. package/dist/translation-passes.d.ts +5 -96
  23. package/dist/translation-passes.js +5 -236
  24. package/dist/upgrade-allowance.d.ts +1 -23
  25. package/dist/upgrade-allowance.js +1 -73
  26. package/dist/voice-profile.d.ts +1 -7
  27. package/dist/voice-profile.js +1 -53
  28. package/dist/voice-provider.d.ts +1 -9
  29. package/dist/voice-provider.js +1 -27
  30. package/package.json +4 -2
  31. package/src/accounts.ts +9 -1
  32. package/src/live-interpreter.ts +1 -213
  33. package/src/live-recovery.ts +1 -10
  34. package/src/live-voice.ts +6 -304
  35. package/src/owner.ts +3 -0
  36. package/src/paywall.ts +1 -1
  37. package/src/pcm-wav.ts +1 -26
  38. package/src/server.ts +57 -6
  39. package/src/shared-translation.ts +7 -222
  40. package/src/speaker-turns.ts +1 -34
  41. package/src/speech.ts +4 -5
  42. package/src/tickets.ts +1 -1
  43. package/src/translation-passes.ts +6 -239
  44. package/src/types/auth-system.d.ts +12 -3
  45. package/src/upgrade-allowance.ts +1 -72
  46. package/src/voice-profile.ts +1 -46
  47. package/src/voice-provider.ts +1 -22
  48. package/web/dist/.well-known/openwebring.json +6 -0
  49. package/web/dist/assets/{ar-CR2NEW77.js → ar-8fDonUoB.js} +1 -1
  50. package/web/dist/assets/{cs-ivXHTYBK.js → cs-DCClolTy.js} +1 -1
  51. package/web/dist/assets/{da-DQEGAEdk.js → da-C3RPhXUd.js} +1 -1
  52. package/web/dist/assets/{de-B5VaSv3O.js → de-CYBoEomk.js} +1 -1
  53. package/web/dist/assets/{es-DUTtfGnk.js → es-DS3ls4xT.js} +1 -1
  54. package/web/dist/assets/{fi-CzQZtpO5.js → fi-DbCZpO89.js} +1 -1
  55. package/web/dist/assets/{fr-C2g4DUBr.js → fr-D0z3UpdL.js} +1 -1
  56. package/web/dist/assets/{hi-DKB7ZsZ8.js → hi-BTU3sRlQ.js} +1 -1
  57. package/web/dist/assets/{hls-3VKVEQE3-Bc75Z5iH.js → hls-3VKVEQE3-CQXTVHn_.js} +1 -1
  58. package/web/dist/assets/{hu--oH7WvAR.js → hu-B37Q8ldT.js} +1 -1
  59. package/web/dist/assets/{id-CjlpBIJc.js → id-4pdXNa4G.js} +1 -1
  60. package/web/dist/assets/index-BDBbN5Qy.js +22 -0
  61. package/web/dist/assets/{it-Dbctup0F.js → it-CTe69-UY.js} +1 -1
  62. package/web/dist/assets/{mpegts-D9VECdKk.js → mpegts-BBmkDppm.js} +1 -1
  63. package/web/dist/assets/{mpegts-LO6RVLD6-CSb7Fz4U.js → mpegts-LO6RVLD6-CUVdjYFi.js} +1 -1
  64. package/web/dist/assets/{nl-BYw8ZbV_.js → nl-CxrmHrMn.js} +1 -1
  65. package/web/dist/assets/{ru-DFGYCrj-.js → ru-zjVCfIKq.js} +1 -1
  66. package/web/dist/assets/{sv-DbO1Sc1v.js → sv-CEoiT2Lr.js} +1 -1
  67. package/web/dist/assets/{uk-BTXHwsjV.js → uk-ZC6JzQvD.js} +1 -1
  68. package/web/dist/assets/{vi-DJ-vIPLS.js → vi-D9anF-cl.js} +1 -1
  69. package/web/dist/assets/{zh-D3Gju_Y2.js → zh-BeLYdC0I.js} +1 -1
  70. package/web/dist/index.html +6 -5
  71. package/web/dist/sw.js +23 -22
  72. package/web/dist/assets/index-CF04ablL.js +0 -3
package/README.md CHANGED
@@ -619,9 +619,14 @@ multiplied by five. Credentials and balances never travel in checkout URLs.
619
619
  New checkout creation is capped at five per account and fifty per account server
620
620
  per UTC day, plus IP and request throttles; retries reuse the original invoice.
621
621
 
622
- After the ten free sessions, new sessions require purchased credit. Configure `COINPAY_X402_KEY`
623
- with `payments:create` permission and at least one business wallet; the scoped
624
- key supplies the merchant identity. Free sessions and existing credit still work during a
622
+ After the ten free sessions, new sessions require purchased credit. Configure `COINPAY_API_KEY`
623
+ (the older name `COINPAY_X402_KEY` still works; `COINPAY_API_KEY` wins when both are set)
624
+ with `payments:create` and `payments:read` permission and at least one business wallet. The scoped
625
+ key supplies the merchant identity, so it must belong to nixamp's own CoinPay business. Set
626
+ `COINPAY_WEBHOOK_SECRET` to that business's webhook secret and its webhook URL to
627
+ `https://<site>/api/v1/webhooks/coinpay`, and a paid order is credited even if the buyer never
628
+ comes back to the page. The webhook body is only a nudge: the order is re-checked with CoinPay
629
+ before anything is credited, and it is credited once however many times it is delivered. Free sessions and existing credit still work during a
625
630
  checkout outage. A self-hosted operator explicitly sponsoring API usage may set
626
631
  `NIXAMP_TRANSLATION_BILLING=off`.
627
632
 
package/dist/accounts.js CHANGED
@@ -96,11 +96,19 @@ export class Accounts {
96
96
  (options.system
97
97
  ? null
98
98
  : new PostgresAdapter({ connectionString: options.connectionString }));
99
+ // Without a secret the module signs with a public default, and anyone can
100
+ // mint a session for any account. Refuse rather than run that way.
101
+ if (!options.system && !options.secret) {
102
+ throw new Error("NIXAMP_JWT_SECRET is required for accounts");
103
+ }
99
104
  this.system =
100
105
  options.system ??
101
106
  createAuthSystem({
102
107
  adapter,
103
- jwtSecret: options.secret,
108
+ // tokenOptions.secret, not jwtSecret: the module has no top-level
109
+ // jwtSecret, and until 2026-10-09 this passed one, so every JWT was
110
+ // signed with the module's public default secret.
111
+ tokenOptions: { secret: options.secret },
104
112
  // The module defaults to requiring an uppercase and a lowercase
105
113
  // letter. Its rules have to match the ones checked above, or a password
106
114
  // this accepts is refused a layer down in a different sentence.
@@ -1,72 +1 @@
1
- import type { SpeakerTurn } from "./speaker-turns.ts";
2
- export interface Caption {
3
- channel: string;
4
- at: number;
5
- until: number;
6
- text: string;
7
- language?: string;
8
- original?: string;
9
- sourceLanguage?: string;
10
- speaker?: string;
11
- voiceProfile?: "lower" | "higher" | "unknown";
12
- }
13
- export interface VoiceChoice {
14
- id: string;
15
- name: string;
16
- gender: string;
17
- language: string;
18
- }
19
- export interface AudioWindow {
20
- samples: Float32Array;
21
- at: number;
22
- until: number;
23
- freshAt: number;
24
- }
25
- export interface Speaker {
26
- id: string;
27
- profile: string;
28
- voice: string;
29
- }
30
- /** Match provider labels across overlapping timestamps. A speaker absent from
31
- * the rolling context gets a new label; pitch alone never identifies someone. */
32
- export declare class SpeakerTracker {
33
- private readonly random;
34
- private previous;
35
- private sequence;
36
- readonly speakers: Map<string, Speaker>;
37
- constructor(random?: () => number);
38
- reset(): void;
39
- reconcile(turns: SpeakerTurn[], at: number, voices: VoiceChoice[]): Map<string, Speaker>;
40
- }
41
- /** Listener-local interpretation for the playing media. One request in flight and only the latest pending audio window. */
42
- export declare class Interpreter {
43
- private readonly options;
44
- readonly tracker: SpeakerTracker;
45
- private generation;
46
- private pending;
47
- private committedUntil;
48
- private running;
49
- private controller;
50
- private translating;
51
- private translationController;
52
- private recognitionFailures;
53
- private translationFailures;
54
- private translations;
55
- constructor(options: {
56
- language: () => string;
57
- speakers: () => boolean;
58
- voices: () => VoiceChoice[];
59
- channel: () => string;
60
- lines: (lines: Caption[]) => void;
61
- status: (text: string) => void;
62
- failed: (message: string) => void;
63
- fetcher?: typeof fetch;
64
- });
65
- reset(): void;
66
- push(window: AudioWindow): void;
67
- private json;
68
- private run;
69
- private translate;
70
- }
71
- /** Preserve all words while respecting the speech API's character limit. */
72
- export declare function splitCaption(line: Caption): Caption[];
1
+ export { Interpreter, SpeakerTracker, splitCaption, type Caption, type VoiceChoice, type AudioWindow, type Speaker } from "@profullstack/live-translate/core";
@@ -1,269 +1 @@
1
- import { audioError, transientAudioError } from "./live-recovery.js";
2
- import { encodeWav } from "./pcm-wav.js";
3
- /** Match provider labels across overlapping timestamps. A speaker absent from
4
- * the rolling context gets a new label; pitch alone never identifies someone. */
5
- export class SpeakerTracker {
6
- random;
7
- previous = [];
8
- sequence = 0;
9
- speakers = new Map();
10
- constructor(random = Math.random) {
11
- this.random = random;
12
- }
13
- reset() { this.previous = []; this.sequence = 0; this.speakers.clear(); }
14
- reconcile(turns, at, voices) {
15
- const links = new Map();
16
- const used = new Set();
17
- const candidates = [];
18
- const totals = new Map();
19
- for (const turn of turns)
20
- for (const old of this.previous) {
21
- const overlap = Math.min(at + turn.end * 1000, old.end) - Math.max(at + turn.start * 1000, old.start);
22
- if (overlap > 120) {
23
- const key = `${turn.speaker}|${old.speaker}`;
24
- totals.set(key, (totals.get(key) ?? 0) + overlap);
25
- }
26
- }
27
- for (const [key, overlap] of totals) {
28
- const [local, global] = key.split("|");
29
- candidates.push({ local: local, global: global, overlap });
30
- }
31
- for (const one of candidates.sort((a, b) => b.overlap - a.overlap)) {
32
- const speaker = this.speakers.get(one.global);
33
- if (speaker && !links.has(one.local) && !used.has(one.global)) {
34
- links.set(one.local, speaker);
35
- used.add(one.global);
36
- }
37
- }
38
- for (const turn of turns) {
39
- let speaker = links.get(turn.speaker);
40
- if (!speaker) {
41
- // Assign contrasting stock voices, without guessing a person's
42
- // gender from pitch or a noisy, short opening phrase.
43
- const taken = new Set([...this.speakers.values()].map(one => one.voice));
44
- const available = voices.filter(voice => !taken.has(voice.id));
45
- const pool = available.length ? available : voices;
46
- const voice = pool[Math.floor(this.random() * pool.length)];
47
- speaker = { id: `speaker-${++this.sequence}`, profile: turn.profile, voice: voice?.id ?? "auto" };
48
- this.speakers.set(speaker.id, speaker);
49
- links.set(turn.speaker, speaker);
50
- }
51
- }
52
- this.previous = turns.map(turn => ({ start: at + turn.start * 1000, end: at + turn.end * 1000, speaker: links.get(turn.speaker).id }));
53
- const present = new Set(this.previous.map(turn => turn.speaker));
54
- for (const key of this.speakers.keys())
55
- if (this.speakers.size > 64 && !present.has(key))
56
- this.speakers.delete(key);
57
- return links;
58
- }
59
- }
60
- /** Listener-local interpretation for the playing media. One request in flight and only the latest pending audio window. */
61
- export class Interpreter {
62
- options;
63
- tracker = new SpeakerTracker();
64
- generation = 0;
65
- pending = null;
66
- committedUntil = null;
67
- running = null;
68
- controller = null;
69
- translating = null;
70
- translationController = null;
71
- recognitionFailures = 0;
72
- translationFailures = 0;
73
- translations = [];
74
- constructor(options) {
75
- this.options = options;
76
- }
77
- reset() {
78
- this.generation++;
79
- this.controller?.abort();
80
- this.translationController?.abort();
81
- this.pending = null;
82
- this.running = null;
83
- this.translating = null;
84
- this.recognitionFailures = this.translationFailures = 0;
85
- this.translations = [];
86
- this.committedUntil = null;
87
- this.tracker.reset();
88
- }
89
- push(window) {
90
- this.committedUntil ??= window.freshAt;
91
- this.pending = window;
92
- if (this.running === null)
93
- void this.run(this.generation);
94
- }
95
- async json(path, init, signal) {
96
- const response = await (this.options.fetcher ?? fetch)(path, { ...init, signal });
97
- const body = await response.json();
98
- if (!response.ok)
99
- throw audioError(body.error || "Audio translation is unavailable.", response.status);
100
- return body;
101
- }
102
- async run(generation) {
103
- this.running = generation;
104
- try {
105
- while (this.pending && generation === this.generation) {
106
- const window = this.pending;
107
- this.pending = null;
108
- try {
109
- if (Date.now() - window.until > 12_000)
110
- continue;
111
- const controller = new AbortController();
112
- this.controller = controller;
113
- const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)]);
114
- const speakers = this.options.speakers(), target = this.options.language();
115
- const heard = await this.json(speakers ? "/api/v1/speech/speakers" : "/api/v1/speech/transcribe?live=1", {
116
- method: "POST", headers: { "content-type": "audio/wav" }, body: new Uint8Array(encodeWav(speakers ? window.samples : window.samples.slice(-80_000))),
117
- }, signal);
118
- if (generation !== this.generation)
119
- return;
120
- this.recognitionFailures = 0;
121
- const language = heard.language;
122
- let lines = [];
123
- if (speakers) {
124
- const links = this.tracker.reconcile(heard.turns ?? [], window.at, this.options.voices());
125
- // The watermark follows emitted words, not the newest capture
126
- // interval: an overlapping window can recover audio that arrived
127
- // while recognition/translation was busy. Keep unfinished phrases
128
- // in that overlap so short capture intervals do not split every
129
- // sentence (especially damaging when German changes word order).
130
- const cutoff = this.committedUntil ?? window.freshAt;
131
- const turns = heard.turns ?? [];
132
- for (const [index, turn] of turns.entries()) {
133
- const speaker = links.get(turn.speaker);
134
- const words = turn.words.filter(word => window.at + (word.start + word.end) * 500 >= cutoff &&
135
- (window.at + word.end * 1000 <= window.until - 100 || /[.!?。!?]$/.test(word.text)));
136
- let phrase = [];
137
- const emit = () => {
138
- if (!phrase.length)
139
- return;
140
- lines.push({ channel: this.options.channel(), at: window.at + phrase[0].start * 1000,
141
- until: window.at + phrase.at(-1).end * 1000, text: phrase.map(word => word.text).join(" ").trim(),
142
- language, speaker: speaker.id, voiceProfile: turn.profile });
143
- phrase = [];
144
- };
145
- for (const word of words) {
146
- if (phrase.length && word.start - phrase.at(-1).end >= 0.45)
147
- emit();
148
- phrase.push(word);
149
- if (/[.!?。!?]$/.test(word.text) || phrase.map(word => word.text).join(" ").length >= 400)
150
- emit();
151
- }
152
- if (phrase.length && (index < turns.length - 1 ||
153
- window.until - (window.at + phrase.at(-1).end * 1000) >= 350 ||
154
- window.until - (window.at + phrase[0].start * 1000) >= 3000))
155
- emit();
156
- }
157
- }
158
- else if (heard.text) {
159
- lines = [{ channel: this.options.channel(), at: window.freshAt, until: window.until, text: heard.text, language }];
160
- }
161
- if (!lines.length)
162
- continue;
163
- this.committedUntil = Math.max(this.committedUntil ?? window.freshAt, ...lines.map(line => line.until));
164
- // Drop expired work instead of turning off an otherwise healthy stream.
165
- this.translations = this.translations.filter(item => Date.now() - item.until <= 12_000).slice(-11);
166
- this.translations.push({ lines, language, target, until: window.until });
167
- // Recognition of the next audio window can proceed while the text
168
- // model translates this phrase. Voice synthesis is a third stage.
169
- if (this.translating === null)
170
- void this.translate(this.generation);
171
- }
172
- catch (error) {
173
- if (generation !== this.generation)
174
- return;
175
- if (!transientAudioError(error) || ++this.recognitionFailures >= 3)
176
- throw error;
177
- this.options.status("Speech recognition interrupted · listening for the next phrase…");
178
- }
179
- }
180
- }
181
- catch (error) {
182
- if (generation === this.generation) {
183
- const message = error instanceof Error ? error.message : "Live translation stopped.";
184
- this.reset();
185
- this.options.failed(message);
186
- this.options.status(message);
187
- }
188
- }
189
- finally {
190
- if (this.running === generation)
191
- this.running = null;
192
- }
193
- }
194
- async translate(generation) {
195
- this.translating = generation;
196
- try {
197
- while (this.translations.length && generation === this.generation) {
198
- const item = this.translations.shift();
199
- try {
200
- let { lines } = item;
201
- const { language, target, until } = item;
202
- if (Date.now() - until > 12_000)
203
- continue;
204
- const controller = new AbortController();
205
- this.translationController = controller;
206
- if (target && language !== target) {
207
- if (!language)
208
- throw audioError("The audio language could not be detected. Waiting for clearer speech.", 502);
209
- const translated = await this.json("/api/v1/translate?live=1", {
210
- method: "POST", headers: { "content-type": "application/json" },
211
- body: JSON.stringify({ texts: lines.map(line => line.text), from: language, to: target }),
212
- }, AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)]));
213
- if (translated.texts.length !== lines.length || translated.texts.some(text => !text.trim()))
214
- throw audioError("Translation omitted a phrase. Waiting for the next phrase.", 502);
215
- lines = lines.map((line, i) => ({ ...line, original: line.text, sourceLanguage: language, language: target, text: translated.texts[i] }));
216
- }
217
- if (generation !== this.generation)
218
- return;
219
- if (Date.now() - until > 12_000)
220
- continue;
221
- this.translationFailures = 0;
222
- this.options.lines(lines.flatMap(line => splitCaption(line)));
223
- this.options.status(target ? `${language} → ${target} · live translation` : `Original audio language: ${language || "detecting…"}`);
224
- }
225
- catch (error) {
226
- if (generation !== this.generation)
227
- return;
228
- if (!transientAudioError(error) || ++this.translationFailures >= 3)
229
- throw error;
230
- this.options.status("Translation interrupted · catching up with live speech…");
231
- }
232
- }
233
- }
234
- catch (error) {
235
- if (generation === this.generation) {
236
- const message = error instanceof Error ? error.message : "Live translation stopped.";
237
- this.reset();
238
- this.options.failed(message);
239
- this.options.status(message);
240
- }
241
- }
242
- finally {
243
- if (this.translating === generation)
244
- this.translating = null;
245
- }
246
- }
247
- }
248
- /** Preserve all words while respecting the speech API's character limit. */
249
- export function splitCaption(line) {
250
- const text = line.text.trim();
251
- if (!text)
252
- return [];
253
- const parts = [];
254
- let rest = text;
255
- while (rest.length > 600) {
256
- const space = rest.lastIndexOf(" ", 600);
257
- const end = space > 300 ? space : 600;
258
- parts.push(rest.slice(0, end));
259
- rest = rest.slice(end).trimStart();
260
- }
261
- if (rest)
262
- parts.push(rest);
263
- let offset = 0;
264
- return parts.map(part => {
265
- const at = line.at + (line.until - line.at) * offset / text.length;
266
- offset += part.length + 1;
267
- return { ...line, text: part, at, until: Math.min(line.until, line.at + (line.until - line.at) * offset / text.length) };
268
- });
269
- }
1
+ export { Interpreter, SpeakerTracker, splitCaption } from "@profullstack/live-translate/core";
@@ -1,6 +1 @@
1
- /** Access, billing and throttling failures are terminal. Only transient service
2
- * and transport failures may recover, with a bounded consecutive failure count. */
3
- export declare function transientAudioError(error: unknown): boolean;
4
- export declare function audioError(message: string, status: number): Error & {
5
- status: number;
6
- };
1
+ export { audioError, transientAudioError } from "@profullstack/live-translate/core";
@@ -1,11 +1 @@
1
- /** Access, billing and throttling failures are terminal. Only transient service
2
- * and transport failures may recover, with a bounded consecutive failure count. */
3
- export function transientAudioError(error) {
4
- const status = error?.status;
5
- if (typeof status === "number")
6
- return [408, 502, 503, 504].includes(status);
7
- return error instanceof TypeError || (error instanceof Error && ["AbortError", "TimeoutError"].includes(error.name));
8
- }
9
- export function audioError(message, status) {
10
- return Object.assign(new Error(message), { status });
11
- }
1
+ export { audioError, transientAudioError } from "@profullstack/live-translate/core";
@@ -1,77 +1,7 @@
1
- import { type SpeakerTranscript } from "./speaker-turns.ts";
2
- import type { VoiceProfile } from "./voice-profile.ts";
3
- import type { TranslationMeter } from "./translation-passes.ts";
4
- import type { Queryable } from "./follows.ts";
5
- export declare const LIVE_VOICE_MODEL = "eleven_flash_v2_5";
6
- export declare const LIVE_VOICE_RATE = 16000;
7
- export declare const LIVE_VOICE_LANGUAGES: Set<string>;
8
- export interface LiveVoiceChoice {
9
- id: string;
10
- name: string;
11
- gender: string;
12
- language: string;
13
- }
14
- export interface VoiceRequest {
15
- text: string;
16
- language: string;
17
- voice?: string;
18
- profile?: VoiceProfile;
19
- channel?: string;
20
- speaker?: string;
21
- }
22
- export declare class LiveVoice {
23
- private readonly key;
24
- private readonly fetcher;
25
- private readonly now;
26
- private catalog;
27
- private catalogUntil;
28
- private readonly cache;
29
- private readonly pending;
30
- private readonly usage;
31
- private readonly charsPerMinute;
32
- private readonly requests;
33
- private readonly grants;
34
- private readonly activeBy;
35
- private readonly db?;
36
- private readonly billing?;
37
- private schema;
38
- private readonly dailyChars;
39
- private cleanupAt;
40
- private readonly hearing;
41
- private readonly dailyAudioSeconds;
42
- private readonly userDailyChars;
43
- private readonly userDailyAudioSeconds;
44
- private providerPause;
45
- private checkProvider;
46
- private providerFailed;
47
- constructor(options?: {
48
- apiKey?: string;
49
- fetcher?: typeof fetch;
50
- now?: () => number;
51
- charsPerMinute?: number;
52
- dailyChars?: number;
53
- dailyAudioSeconds?: number;
54
- userDailyChars?: number;
55
- userDailyAudioSeconds?: number;
56
- db?: Queryable;
57
- billing?: TranslationMeter;
58
- });
59
- available(): boolean;
60
- /** Optional diarization, billed only while a signed-in listener requests it.
61
- * Rolling audio is bounded to 15 seconds, including overlap. Every second
62
- * submitted (also repeated context) consumes the persistent provider budget. */
63
- hear(bytes: Uint8Array, by: string, signal?: AbortSignal, meter?: TranslationMeter | undefined, resource?: string): Promise<SpeakerTranscript>;
64
- voices(): Promise<LiveVoiceChoice[]>;
65
- /** A 90-second capability for one channel, never the viewer's account credential. */
66
- grant(by: string, channel: string): Promise<{
67
- token: string;
68
- expires: number;
69
- }>;
70
- authorize(token: string, channel: string, chars: number): Promise<string>;
71
- private ensure;
72
- private reserve;
73
- private charge;
74
- private checkRequest;
75
- /** Stream the first request immediately; concurrent listeners share its cached result. */
76
- stream(ask: VoiceRequest, by: string, signal?: AbortSignal, meter?: TranslationMeter | undefined): Promise<Response>;
1
+ /** ElevenLabs Flash/Scribe for live captions, from @profullstack/live-translate.
2
+ * nixamp keeps its `nxd_` grant tokens: captions.ts and older pages expect them. */
3
+ import { LiveVoice as Base } from "@profullstack/live-translate";
4
+ export { LIVE_VOICE_MODEL, LIVE_VOICE_RATE, LIVE_VOICE_LANGUAGES, type LiveVoiceChoice, type VoiceRequest } from "@profullstack/live-translate";
5
+ export declare class LiveVoice extends Base {
6
+ constructor(options?: ConstructorParameters<typeof Base>[0]);
77
7
  }