@hyperdreamer/pi-webui 1.13.0 → 1.14.0
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.
- package/dist/cli.js +19 -4
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/CodeViewer-BEbclxBf.js +4 -0
- package/dist/client/assets/{UnifiedDiffViewer--UWVARrK.js → UnifiedDiffViewer-DXWhrIST.js} +1 -1
- package/dist/client/assets/{index-DXa2f-o6.js → index-2EIg2vwJ.js} +574 -487
- package/dist/client/assets/{vendor-editor-core-vUi74DnD.js → vendor-editor-core-vyMPzMNA.js} +1 -1
- package/dist/client/assets/vendor-editor-languages-DlEQaRIv.js +46 -0
- package/dist/client/index.html +3 -3
- package/dist/config.js +288 -4
- package/dist/config.js.map +1 -1
- package/dist/configMutationCoordinator.js +357 -0
- package/dist/configMutationCoordinator.js.map +1 -0
- package/dist/server/app.js +64 -1
- package/dist/server/app.js.map +1 -1
- package/dist/server/configRoutes.js +84 -18
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/index.js +15 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +6 -9
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/sessiond/configMutationWriters.js +18 -0
- package/dist/server/sessiond/configMutationWriters.js.map +1 -0
- package/dist/server/sessiond.js +7 -7
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/modelTierSettingsRoutes.js +5 -1
- package/dist/server/sessions/modelTierSettingsRoutes.js.map +1 -1
- package/dist/server/sessions/utilityModelSettingsRoutes.js +5 -1
- package/dist/server/sessions/utilityModelSettingsRoutes.js.map +1 -1
- package/dist/server/speechInput/openAiCompatibleTranscriptionProvider.js +357 -0
- package/dist/server/speechInput/openAiCompatibleTranscriptionProvider.js.map +1 -0
- package/dist/server/speechInput/piCompatibleCredentialResolver.js +348 -0
- package/dist/server/speechInput/piCompatibleCredentialResolver.js.map +1 -0
- package/dist/server/speechInput/speechInputSettingsRoutes.js +39 -0
- package/dist/server/speechInput/speechInputSettingsRoutes.js.map +1 -0
- package/dist/server/speechInput/speechInputSettingsService.js +247 -0
- package/dist/server/speechInput/speechInputSettingsService.js.map +1 -0
- package/dist/server/speechInput/speechInputTranscriptionRoutes.js +226 -0
- package/dist/server/speechInput/speechInputTranscriptionRoutes.js.map +1 -0
- package/dist/server/speechInput/speechTranscriptionService.js +116 -0
- package/dist/server/speechInput/speechTranscriptionService.js.map +1 -0
- package/dist/shared/apiTypes.d.ts +54 -0
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/speechInput.js +75 -0
- package/dist/shared/speechInput.js.map +1 -0
- package/dist/shared/speechInputAudio.js +42 -0
- package/dist/shared/speechInputAudio.js.map +1 -0
- package/docs/config.md +86 -1
- package/package.json +1 -1
- package/dist/client/assets/CodeViewer-B1Rl2AYu.js +0 -4
- package/dist/client/assets/vendor-editor-languages-DoaXUodB.js +0 -46
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speechInputSettingsService.js","sourceRoot":"","sources":["../../../src/server/speechInput/speechInputSettingsService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { PiWebUiConfigMutationBusyError } from "../../configMutationCoordinator.js";
|
|
2
|
+
import { SPEECH_INPUT_MAX_AUDIO_BYTES, SPEECH_INPUT_MAX_TRANSCRIPT_BYTES, SPEECH_INPUT_UPLOAD_TIMEOUT_MS, parseSpeechInputAudioMimeType, } from "../../shared/speechInputAudio.js";
|
|
3
|
+
import { SpeechInputProviderError, SpeechInputProviderTimeoutError, } from "./openAiCompatibleTranscriptionProvider.js";
|
|
4
|
+
import { SpeechInputAudioValidationError, SpeechInputCredentialUnavailableError, } from "./speechTranscriptionService.js";
|
|
5
|
+
const TRANSCRIBE_PATH = "/api/speech-input/transcribe";
|
|
6
|
+
const ADMISSION_REJECTED_MESSAGE = "Speech transcription is busy. Try again.";
|
|
7
|
+
const INVALID_AUDIO_MESSAGE = "Invalid speech audio.";
|
|
8
|
+
const CONFIG_BUSY_MESSAGE = "PI WEBUI config is busy. Try again.";
|
|
9
|
+
const CREDENTIAL_UNAVAILABLE_MESSAGE = "Speech transcription is unavailable.";
|
|
10
|
+
const PROVIDER_FAILURE_MESSAGE = "Speech transcription provider request failed.";
|
|
11
|
+
const PROVIDER_TIMEOUT_MESSAGE = "Speech transcription provider timed out.";
|
|
12
|
+
const UNEXPECTED_FAILURE_MESSAGE = "Speech transcription failed.";
|
|
13
|
+
/**
|
|
14
|
+
* Registers a gateway-only raw-audio endpoint in an encapsulated plugin. Its
|
|
15
|
+
* parsers intentionally do not escape into general API route ownership.
|
|
16
|
+
*/
|
|
17
|
+
export function registerSpeechInputTranscriptionRoutes(app, service, options = {}) {
|
|
18
|
+
const bodyLimit = options.bodyLimit ?? SPEECH_INPUT_MAX_AUDIO_BYTES;
|
|
19
|
+
const admission = createAdmission(options.admissionLimit ?? 2);
|
|
20
|
+
const scheduleDeadline = options.scheduleDeadline ?? defaultScheduleDeadline;
|
|
21
|
+
const active = new WeakMap();
|
|
22
|
+
const activeRequests = new Set();
|
|
23
|
+
app.register((scope, _pluginOptions, done) => {
|
|
24
|
+
registerAudioBufferParsers(scope);
|
|
25
|
+
const cleanup = (request) => {
|
|
26
|
+
const state = active.get(request);
|
|
27
|
+
if (state === undefined || state.cleaned)
|
|
28
|
+
return;
|
|
29
|
+
state.cleaned = true;
|
|
30
|
+
active.delete(request);
|
|
31
|
+
activeRequests.delete(request);
|
|
32
|
+
try {
|
|
33
|
+
state.cancelUploadDeadline();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Cleanup has already taken ownership of this request.
|
|
37
|
+
}
|
|
38
|
+
state.rawRequest.removeListener("end", state.onEnd);
|
|
39
|
+
state.rawRequest.removeListener("aborted", state.onAborted);
|
|
40
|
+
state.reply.raw.removeListener("close", state.onClose);
|
|
41
|
+
try {
|
|
42
|
+
state.controller.abort();
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Abort is best effort after this request's terminal state is fixed.
|
|
46
|
+
}
|
|
47
|
+
state.releaseAdmission();
|
|
48
|
+
};
|
|
49
|
+
scope.addHook("onRequest", (request, reply, hookDone) => {
|
|
50
|
+
const releaseAdmission = admission.acquire();
|
|
51
|
+
if (releaseAdmission === undefined) {
|
|
52
|
+
reply.code(429).send({ error: ADMISSION_REJECTED_MESSAGE });
|
|
53
|
+
hookDone();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
const scheduledDeadline = { cancel: () => undefined };
|
|
58
|
+
const state = {
|
|
59
|
+
controller,
|
|
60
|
+
releaseAdmission,
|
|
61
|
+
cancelUploadDeadline: () => undefined,
|
|
62
|
+
rawRequest: request.raw,
|
|
63
|
+
reply,
|
|
64
|
+
onEnd: () => undefined,
|
|
65
|
+
onAborted: () => undefined,
|
|
66
|
+
onClose: () => undefined,
|
|
67
|
+
cleaned: false,
|
|
68
|
+
};
|
|
69
|
+
let uploadDeadlineCancelled = false;
|
|
70
|
+
state.cancelUploadDeadline = () => {
|
|
71
|
+
if (uploadDeadlineCancelled)
|
|
72
|
+
return;
|
|
73
|
+
uploadDeadlineCancelled = true;
|
|
74
|
+
scheduledDeadline.cancel();
|
|
75
|
+
};
|
|
76
|
+
state.onEnd = () => {
|
|
77
|
+
// This timer covers admission through body completion only. Provider
|
|
78
|
+
// work remains admitted after the upload finishes.
|
|
79
|
+
try {
|
|
80
|
+
state.cancelUploadDeadline();
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// A callback already owns terminal cleanup.
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
state.onAborted = () => {
|
|
87
|
+
cleanup(request);
|
|
88
|
+
};
|
|
89
|
+
state.onClose = () => {
|
|
90
|
+
// ServerResponse also closes after a successful response. Only a
|
|
91
|
+
// still-writable response means the client actually disconnected.
|
|
92
|
+
if (reply.raw.writableEnded)
|
|
93
|
+
return;
|
|
94
|
+
cleanup(request);
|
|
95
|
+
};
|
|
96
|
+
active.set(request, state);
|
|
97
|
+
activeRequests.add(request);
|
|
98
|
+
request.raw.once("end", state.onEnd);
|
|
99
|
+
request.raw.once("aborted", state.onAborted);
|
|
100
|
+
reply.raw.once("close", state.onClose);
|
|
101
|
+
scheduledDeadline.cancel = scheduleDeadline(() => {
|
|
102
|
+
if (uploadDeadlineCancelled)
|
|
103
|
+
return;
|
|
104
|
+
cleanup(request);
|
|
105
|
+
if (!request.raw.destroyed) {
|
|
106
|
+
try {
|
|
107
|
+
request.raw.destroy();
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// A concurrently closed socket has the same terminal result.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}, SPEECH_INPUT_UPLOAD_TIMEOUT_MS);
|
|
114
|
+
if (request.raw.destroyed)
|
|
115
|
+
cleanup(request);
|
|
116
|
+
hookDone();
|
|
117
|
+
});
|
|
118
|
+
scope.addHook("onError", (request, _reply, _error, hookDone) => {
|
|
119
|
+
cleanup(request);
|
|
120
|
+
hookDone();
|
|
121
|
+
});
|
|
122
|
+
scope.addHook("onResponse", (request, _reply, hookDone) => {
|
|
123
|
+
cleanup(request);
|
|
124
|
+
hookDone();
|
|
125
|
+
});
|
|
126
|
+
scope.addHook("onClose", (_instance, hookDone) => {
|
|
127
|
+
for (const request of activeRequests)
|
|
128
|
+
cleanup(request);
|
|
129
|
+
hookDone();
|
|
130
|
+
});
|
|
131
|
+
scope.post(TRANSCRIBE_PATH, { bodyLimit }, async (request, reply) => {
|
|
132
|
+
const state = active.get(request);
|
|
133
|
+
if (state === undefined || isAborted(state.controller.signal))
|
|
134
|
+
return undefined;
|
|
135
|
+
const mimeType = parseSpeechInputAudioMimeType(contentTypeHeader(request));
|
|
136
|
+
if (mimeType === undefined || !Buffer.isBuffer(request.body) || request.body.byteLength === 0) {
|
|
137
|
+
return reply.code(400).send({ error: INVALID_AUDIO_MESSAGE });
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const text = await service.transcribe({
|
|
141
|
+
audio: request.body,
|
|
142
|
+
mimeType,
|
|
143
|
+
signal: state.controller.signal,
|
|
144
|
+
});
|
|
145
|
+
if (isAborted(state.controller.signal))
|
|
146
|
+
return undefined;
|
|
147
|
+
if (!isValidTranscript(text))
|
|
148
|
+
return await reply.code(500).send({ error: UNEXPECTED_FAILURE_MESSAGE });
|
|
149
|
+
return { text };
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (isAborted(state.controller.signal))
|
|
153
|
+
return undefined;
|
|
154
|
+
return await mapTranscriptionError(reply, error);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
done();
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function registerAudioBufferParsers(app) {
|
|
161
|
+
const parser = (_request, body, done) => {
|
|
162
|
+
done(null, body);
|
|
163
|
+
};
|
|
164
|
+
// Exact codecs retain Fastify's normal exact-match precedence. Base media
|
|
165
|
+
// parsers then admit header spelling variants so the strict shared parser can
|
|
166
|
+
// make the allowlist decision from the original header value.
|
|
167
|
+
app.addContentTypeParser("audio/webm;codecs=opus", { parseAs: "buffer" }, parser);
|
|
168
|
+
app.addContentTypeParser("audio/ogg;codecs=opus", { parseAs: "buffer" }, parser);
|
|
169
|
+
app.addContentTypeParser("audio/mp4;codecs=mp4a.40.2", { parseAs: "buffer" }, parser);
|
|
170
|
+
app.addContentTypeParser("audio/webm", { parseAs: "buffer" }, parser);
|
|
171
|
+
app.addContentTypeParser("audio/ogg", { parseAs: "buffer" }, parser);
|
|
172
|
+
app.addContentTypeParser("audio/mp4", { parseAs: "buffer" }, parser);
|
|
173
|
+
app.addContentTypeParser("*", { parseAs: "buffer" }, parser);
|
|
174
|
+
}
|
|
175
|
+
function contentTypeHeader(request) {
|
|
176
|
+
const value = request.headers["content-type"];
|
|
177
|
+
return typeof value === "string" ? value : undefined;
|
|
178
|
+
}
|
|
179
|
+
function isValidTranscript(value) {
|
|
180
|
+
return typeof value === "string"
|
|
181
|
+
&& value.trim() !== ""
|
|
182
|
+
&& Buffer.byteLength(value, "utf8") <= SPEECH_INPUT_MAX_TRANSCRIPT_BYTES;
|
|
183
|
+
}
|
|
184
|
+
function isAborted(signal) {
|
|
185
|
+
return signal.aborted;
|
|
186
|
+
}
|
|
187
|
+
function mapTranscriptionError(reply, error) {
|
|
188
|
+
if (error instanceof SpeechInputAudioValidationError) {
|
|
189
|
+
return reply.code(400).send({ error: INVALID_AUDIO_MESSAGE });
|
|
190
|
+
}
|
|
191
|
+
if (error instanceof PiWebUiConfigMutationBusyError) {
|
|
192
|
+
return reply.code(503).send({ error: CONFIG_BUSY_MESSAGE });
|
|
193
|
+
}
|
|
194
|
+
if (error instanceof SpeechInputCredentialUnavailableError) {
|
|
195
|
+
return reply.code(503).send({ error: CREDENTIAL_UNAVAILABLE_MESSAGE });
|
|
196
|
+
}
|
|
197
|
+
if (error instanceof SpeechInputProviderTimeoutError) {
|
|
198
|
+
return reply.code(504).send({ error: PROVIDER_TIMEOUT_MESSAGE });
|
|
199
|
+
}
|
|
200
|
+
if (error instanceof SpeechInputProviderError) {
|
|
201
|
+
return reply.code(502).send({ error: PROVIDER_FAILURE_MESSAGE });
|
|
202
|
+
}
|
|
203
|
+
return reply.code(500).send({ error: UNEXPECTED_FAILURE_MESSAGE });
|
|
204
|
+
}
|
|
205
|
+
function createAdmission(limit) {
|
|
206
|
+
let active = 0;
|
|
207
|
+
return {
|
|
208
|
+
acquire() {
|
|
209
|
+
if (active >= limit)
|
|
210
|
+
return undefined;
|
|
211
|
+
active += 1;
|
|
212
|
+
let released = false;
|
|
213
|
+
return () => {
|
|
214
|
+
if (released)
|
|
215
|
+
return;
|
|
216
|
+
released = true;
|
|
217
|
+
active -= 1;
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function defaultScheduleDeadline(callback, delayMs) {
|
|
223
|
+
const timer = setTimeout(callback, delayMs);
|
|
224
|
+
return () => { clearTimeout(timer); };
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=speechInputTranscriptionRoutes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speechInputTranscriptionRoutes.js","sourceRoot":"","sources":["../../../src/server/speechInput/speechInputTranscriptionRoutes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EACL,4BAA4B,EAC5B,iCAAiC,EACjC,8BAA8B,EAC9B,6BAA6B,GAC9B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,wBAAwB,EACxB,+BAA+B,GAChC,MAAM,4CAA4C,CAAC;AACpD,OAAO,EACL,+BAA+B,EAC/B,qCAAqC,GAEtC,MAAM,iCAAiC,CAAC;AA4BzC,MAAM,eAAe,GAAG,8BAA8B,CAAC;AACvD,MAAM,0BAA0B,GAAG,0CAA0C,CAAC;AAC9E,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AACtD,MAAM,mBAAmB,GAAG,qCAAqC,CAAC;AAClE,MAAM,8BAA8B,GAAG,sCAAsC,CAAC;AAC9E,MAAM,wBAAwB,GAAG,+CAA+C,CAAC;AACjF,MAAM,wBAAwB,GAAG,0CAA0C,CAAC;AAC5E,MAAM,0BAA0B,GAAG,8BAA8B,CAAC;AAElE;;;GAGG;AACH,MAAM,UAAU,sCAAsC,CACpD,GAAoB,EACpB,OAA6C,EAC7C,UAAgD,EAAE;IAElD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,4BAA4B,CAAC;IACpE,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,cAAc,IAAI,CAAC,CAAC,CAAC;IAC/D,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,uBAAuB,CAAC;IAC7E,MAAM,MAAM,GAAG,IAAI,OAAO,EAAiC,CAAC;IAC5D,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEjD,GAAG,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE;QAC3C,0BAA0B,CAAC,KAAK,CAAC,CAAC;QAElC,MAAM,OAAO,GAAG,CAAC,OAAuB,EAAQ,EAAE;YAChD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAClC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO;gBAAE,OAAO;YACjD,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;YACrB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACvB,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC/B,IAAI,CAAC;gBACH,KAAK,CAAC,oBAAoB,EAAE,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,uDAAuD;YACzD,CAAC;YACD,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YACpD,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;YAC5D,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACvD,IAAI,CAAC;gBACH,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,qEAAqE;YACvE,CAAC;YACD,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAC3B,CAAC,CAAC;QAEF,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;YACtD,MAAM,gBAAgB,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC;YAC7C,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;gBACnC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;gBAC5D,QAAQ,EAAE,CAAC;gBACX,OAAO;YACT,CAAC;YAED,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,iBAAiB,GAA2B,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC;YAC9E,MAAM,KAAK,GAAkB;gBAC3B,UAAU;gBACV,gBAAgB;gBAChB,oBAAoB,EAAE,GAAS,EAAE,CAAC,SAAS;gBAC3C,UAAU,EAAE,OAAO,CAAC,GAAG;gBACvB,KAAK;gBACL,KAAK,EAAE,GAAG,EAAE,CAAC,SAAS;gBACtB,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS;gBAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS;gBACxB,OAAO,EAAE,KAAK;aACf,CAAC;YACF,IAAI,uBAAuB,GAAG,KAAK,CAAC;YACpC,KAAK,CAAC,oBAAoB,GAAG,GAAG,EAAE;gBAChC,IAAI,uBAAuB;oBAAE,OAAO;gBACpC,uBAAuB,GAAG,IAAI,CAAC;gBAC/B,iBAAiB,CAAC,MAAM,EAAE,CAAC;YAC7B,CAAC,CAAC;YACF,KAAK,CAAC,KAAK,GAAG,GAAG,EAAE;gBACjB,qEAAqE;gBACrE,mDAAmD;gBACnD,IAAI,CAAC;oBACH,KAAK,CAAC,oBAAoB,EAAE,CAAC;gBAC/B,CAAC;gBAAC,MAAM,CAAC;oBACP,4CAA4C;gBAC9C,CAAC;YACH,CAAC,CAAC;YACF,KAAK,CAAC,SAAS,GAAG,GAAG,EAAE;gBACrB,OAAO,CAAC,OAAO,CAAC,CAAC;YACnB,CAAC,CAAC;YACF,KAAK,CAAC,OAAO,GAAG,GAAG,EAAE;gBACnB,iEAAiE;gBACjE,kEAAkE;gBAClE,IAAI,KAAK,CAAC,GAAG,CAAC,aAAa;oBAAE,OAAO;gBACpC,OAAO,CAAC,OAAO,CAAC,CAAC;YACnB,CAAC,CAAC;YACF,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC3B,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;YAC7C,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACvC,iBAAiB,CAAC,MAAM,GAAG,gBAAgB,CAAC,GAAG,EAAE;gBAC/C,IAAI,uBAAuB;oBAAE,OAAO;gBACpC,OAAO,CAAC,OAAO,CAAC,CAAC;gBACjB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;oBAC3B,IAAI,CAAC;wBACH,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;oBACxB,CAAC;oBAAC,MAAM,CAAC;wBACP,6DAA6D;oBAC/D,CAAC;gBACH,CAAC;YACH,CAAC,EAAE,8BAA8B,CAAC,CAAC;YACnC,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS;gBAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YAC5C,QAAQ,EAAE,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;YAC7D,OAAO,CAAC,OAAO,CAAC,CAAC;YACjB,QAAQ,EAAE,CAAC;QACb,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;YACxD,OAAO,CAAC,OAAO,CAAC,CAAC;YACjB,QAAQ,EAAE,CAAC;QACb,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE;YAC/C,KAAK,MAAM,OAAO,IAAI,cAAc;gBAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YACvD,QAAQ,EAAE,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,IAAI,CAAoB,eAAe,EAAE,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACrF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAClC,IAAI,KAAK,KAAK,SAAS,IAAI,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;gBAAE,OAAO,SAAS,CAAC;YAChF,MAAM,QAAQ,GAAG,6BAA6B,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC;YAC3E,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;gBAC9F,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;YAChE,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC;oBACpC,KAAK,EAAE,OAAO,CAAC,IAAI;oBACnB,QAAQ;oBACR,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,MAAM;iBAChC,CAAC,CAAC;gBACH,IAAI,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;oBAAE,OAAO,SAAS,CAAC;gBACzD,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC;oBAAE,OAAO,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;gBACvG,OAAO,EAAE,IAAI,EAAE,CAAC;YAClB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;oBAAE,OAAO,SAAS,CAAC;gBACzD,OAAO,MAAM,qBAAqB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACnD,CAAC;QACH,CAAC,CAAC,CAAC;QACH,IAAI,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,0BAA0B,CAAC,GAAoB;IACtD,MAAM,MAAM,GAAG,CAAC,QAAwB,EAAE,IAAY,EAAE,IAAmD,EAAE,EAAE;QAC7G,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnB,CAAC,CAAC;IACF,0EAA0E;IAC1E,8EAA8E;IAC9E,8DAA8D;IAC9D,GAAG,CAAC,oBAAoB,CAAC,wBAAwB,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;IAClF,GAAG,CAAC,oBAAoB,CAAC,uBAAuB,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;IACjF,GAAG,CAAC,oBAAoB,CAAC,4BAA4B,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;IACtF,GAAG,CAAC,oBAAoB,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;IACtE,GAAG,CAAC,oBAAoB,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;IACrE,GAAG,CAAC,oBAAoB,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;IACrE,GAAG,CAAC,oBAAoB,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAuB;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC9C,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc;IACvC,OAAO,OAAO,KAAK,KAAK,QAAQ;WAC3B,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;WACnB,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,iCAAiC,CAAC;AAC7E,CAAC;AAED,SAAS,SAAS,CAAC,MAAmB;IACpC,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAmB,EAAE,KAAc;IAChE,IAAI,KAAK,YAAY,+BAA+B,EAAE,CAAC;QACrD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,KAAK,YAAY,8BAA8B,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,KAAK,YAAY,qCAAqC,EAAE,CAAC;QAC3D,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,KAAK,YAAY,+BAA+B,EAAE,CAAC;QACrD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,KAAK,YAAY,wBAAwB,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,eAAe,CAAC,KAAa;IACpC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO;QACL,OAAO;YACL,IAAI,MAAM,IAAI,KAAK;gBAAE,OAAO,SAAS,CAAC;YACtC,MAAM,IAAI,CAAC,CAAC;YACZ,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,OAAO,GAAG,EAAE;gBACV,IAAI,QAAQ;oBAAE,OAAO;gBACrB,QAAQ,GAAG,IAAI,CAAC;gBAChB,MAAM,IAAI,CAAC,CAAC;YACd,CAAC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,uBAAuB,CAAC,QAAoB,EAAE,OAAe;IACpE,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5C,OAAO,GAAG,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { effectiveSpeechInputSettings, speechInputCloudLanguage } from "../../shared/speechInput.js";
|
|
2
|
+
import { SPEECH_INPUT_MAX_AUDIO_BYTES, parseSpeechInputAudioMimeType, } from "../../shared/speechInputAudio.js";
|
|
3
|
+
import { resolvePiCompatibleCredentialSource } from "./piCompatibleCredentialResolver.js";
|
|
4
|
+
import { OpenAiCompatibleTranscriptionProvider, SpeechInputTranscriptionAbortedError, } from "./openAiCompatibleTranscriptionProvider.js";
|
|
5
|
+
/** A malformed direct service call; routes validate this before service work. */
|
|
6
|
+
export class SpeechInputAudioValidationError extends Error {
|
|
7
|
+
constructor() {
|
|
8
|
+
super("Speech audio is invalid.");
|
|
9
|
+
this.code = "SPEECH_INPUT_AUDIO_VALIDATION";
|
|
10
|
+
this.name = "SpeechInputAudioValidationError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** A missing, unresolved, empty, or failed credential source. */
|
|
14
|
+
export class SpeechInputCredentialUnavailableError extends Error {
|
|
15
|
+
constructor() {
|
|
16
|
+
super("Speech transcription is unavailable.");
|
|
17
|
+
this.code = "SPEECH_INPUT_CREDENTIAL_UNAVAILABLE";
|
|
18
|
+
this.name = "SpeechInputCredentialUnavailableError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class SpeechTranscriptionService {
|
|
22
|
+
constructor(dependencies) {
|
|
23
|
+
this.dependencies = dependencies;
|
|
24
|
+
this.provider = dependencies.provider ?? new OpenAiCompatibleTranscriptionProvider();
|
|
25
|
+
this.resolveCredential = dependencies.resolveCredential ?? resolvePiCompatibleCredentialSource;
|
|
26
|
+
}
|
|
27
|
+
async transcribe(request) {
|
|
28
|
+
throwIfAborted(request.signal);
|
|
29
|
+
// `read` opens and closes its own coordinator transaction before resolving.
|
|
30
|
+
// Copy every persisted value needed below before any potentially slow work.
|
|
31
|
+
const snapshot = await raceWithAbort(this.dependencies.coordinator.read(), request.signal);
|
|
32
|
+
const rawSource = snapshot.loaded.config.speechInput?.cloud?.apiKey;
|
|
33
|
+
const effective = effectiveSpeechInputSettings(snapshot.loaded.config.speechInput);
|
|
34
|
+
const captured = {
|
|
35
|
+
source: rawSource,
|
|
36
|
+
baseUrl: effective.cloud.baseUrl,
|
|
37
|
+
model: effective.cloud.model,
|
|
38
|
+
language: speechInputCloudLanguage(effective.language),
|
|
39
|
+
};
|
|
40
|
+
validateAudio(request);
|
|
41
|
+
if (captured.source === undefined || captured.source.trim() === "") {
|
|
42
|
+
throw new SpeechInputCredentialUnavailableError();
|
|
43
|
+
}
|
|
44
|
+
let apiKey;
|
|
45
|
+
try {
|
|
46
|
+
apiKey = await raceWithAbort(this.resolveCredential(captured.source, {
|
|
47
|
+
...(this.dependencies.env === undefined ? {} : { env: this.dependencies.env }),
|
|
48
|
+
signal: request.signal,
|
|
49
|
+
}), request.signal);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof SpeechInputTranscriptionAbortedError)
|
|
53
|
+
throw error;
|
|
54
|
+
if (request.signal.aborted)
|
|
55
|
+
throw new SpeechInputTranscriptionAbortedError();
|
|
56
|
+
throw new SpeechInputCredentialUnavailableError();
|
|
57
|
+
}
|
|
58
|
+
if (apiKey.trim() === "")
|
|
59
|
+
throw new SpeechInputCredentialUnavailableError();
|
|
60
|
+
throwIfAborted(request.signal);
|
|
61
|
+
const providerRequest = {
|
|
62
|
+
audio: request.audio,
|
|
63
|
+
mimeType: request.mimeType,
|
|
64
|
+
baseUrl: captured.baseUrl,
|
|
65
|
+
model: captured.model,
|
|
66
|
+
apiKey,
|
|
67
|
+
...(captured.language === undefined ? {} : { language: captured.language }),
|
|
68
|
+
signal: request.signal,
|
|
69
|
+
};
|
|
70
|
+
return await raceWithAbort(this.provider.transcribe(providerRequest), request.signal);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function createSpeechTranscriptionService(dependencies) {
|
|
74
|
+
return new SpeechTranscriptionService(dependencies);
|
|
75
|
+
}
|
|
76
|
+
function validateAudio(request) {
|
|
77
|
+
if (!Buffer.isBuffer(request.audio)
|
|
78
|
+
|| request.audio.byteLength === 0
|
|
79
|
+
|| request.audio.byteLength > SPEECH_INPUT_MAX_AUDIO_BYTES
|
|
80
|
+
|| parseSpeechInputAudioMimeType(request.mimeType) !== request.mimeType) {
|
|
81
|
+
throw new SpeechInputAudioValidationError();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function throwIfAborted(signal) {
|
|
85
|
+
if (signal.aborted)
|
|
86
|
+
throw new SpeechInputTranscriptionAbortedError();
|
|
87
|
+
}
|
|
88
|
+
function raceWithAbort(promise, signal) {
|
|
89
|
+
if (signal.aborted)
|
|
90
|
+
return Promise.reject(new SpeechInputTranscriptionAbortedError());
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
let settled = false;
|
|
93
|
+
const settle = (callback, value) => {
|
|
94
|
+
if (settled)
|
|
95
|
+
return;
|
|
96
|
+
settled = true;
|
|
97
|
+
signal.removeEventListener("abort", onAbort);
|
|
98
|
+
callback(value);
|
|
99
|
+
};
|
|
100
|
+
const fail = (error) => {
|
|
101
|
+
if (settled)
|
|
102
|
+
return;
|
|
103
|
+
settled = true;
|
|
104
|
+
signal.removeEventListener("abort", onAbort);
|
|
105
|
+
reject(error instanceof Error ? error : new Error("Speech transcription failed."));
|
|
106
|
+
};
|
|
107
|
+
const onAbort = () => {
|
|
108
|
+
fail(new SpeechInputTranscriptionAbortedError());
|
|
109
|
+
};
|
|
110
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
111
|
+
Promise.resolve(promise).then((value) => { settle(resolve, value); }, (error) => { fail(error); });
|
|
112
|
+
if (signal.aborted)
|
|
113
|
+
onAbort();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=speechTranscriptionService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speechTranscriptionService.js","sourceRoot":"","sources":["../../../src/server/speechInput/speechTranscriptionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -202,6 +202,58 @@ export interface PiWebUiTtsConfig {
|
|
|
202
202
|
voice?: string;
|
|
203
203
|
rate?: number;
|
|
204
204
|
}
|
|
205
|
+
export type SpeechInputProviderPreference = "auto" | "browser" | "cloud";
|
|
206
|
+
export interface PiWebUiSpeechInputCloudConfig {
|
|
207
|
+
/** HTTPS OpenAI-compatible transcription base URL. */
|
|
208
|
+
baseUrl?: string;
|
|
209
|
+
/** Transcription model name. */
|
|
210
|
+
model?: string;
|
|
211
|
+
/** Pi-compatible literal, environment template, or command credential source. */
|
|
212
|
+
apiKey?: string;
|
|
213
|
+
}
|
|
214
|
+
export interface PiWebUiSpeechInputConfig {
|
|
215
|
+
/** Default: "auto". */
|
|
216
|
+
provider?: SpeechInputProviderPreference;
|
|
217
|
+
/** Omitted means Auto. Stored as a canonical BCP 47 tag. */
|
|
218
|
+
language?: string;
|
|
219
|
+
cloud?: PiWebUiSpeechInputCloudConfig;
|
|
220
|
+
}
|
|
221
|
+
export interface SpeechInputCredentialStatus {
|
|
222
|
+
configured: boolean;
|
|
223
|
+
source?: "literal" | "environment" | "command";
|
|
224
|
+
resolution: "missing" | "resolved" | "unresolved" | "unchecked";
|
|
225
|
+
}
|
|
226
|
+
export interface SpeechInputSettings {
|
|
227
|
+
provider: SpeechInputProviderPreference;
|
|
228
|
+
language?: string;
|
|
229
|
+
cloud: {
|
|
230
|
+
baseUrl: string;
|
|
231
|
+
model: string;
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
export interface SpeechInputSettingsResponse {
|
|
235
|
+
contractVersion: 1;
|
|
236
|
+
/** Canonical lowercase UUID; opaque to clients. */
|
|
237
|
+
revision: string;
|
|
238
|
+
settings: SpeechInputSettings;
|
|
239
|
+
credential: SpeechInputCredentialStatus;
|
|
240
|
+
}
|
|
241
|
+
export type SpeechInputCredentialMutation = {
|
|
242
|
+
action: "preserve";
|
|
243
|
+
} | {
|
|
244
|
+
action: "replace";
|
|
245
|
+
value: string;
|
|
246
|
+
} | {
|
|
247
|
+
action: "clear";
|
|
248
|
+
};
|
|
249
|
+
export interface SpeechInputSettingsUpdate {
|
|
250
|
+
expectedRevision: string;
|
|
251
|
+
settings: SpeechInputSettings;
|
|
252
|
+
credential: SpeechInputCredentialMutation;
|
|
253
|
+
}
|
|
254
|
+
export interface SpeechInputTranscribeResponse {
|
|
255
|
+
text: string;
|
|
256
|
+
}
|
|
205
257
|
export interface HostSpeechVoice {
|
|
206
258
|
name: string;
|
|
207
259
|
language: string;
|
|
@@ -255,6 +307,8 @@ export interface PiWebUiConfigValues {
|
|
|
255
307
|
agent?: PiWebUiAgentConfig;
|
|
256
308
|
/** Host speech configuration for manual text-to-speech playback. */
|
|
257
309
|
tts?: PiWebUiTtsConfig;
|
|
310
|
+
/** Gateway speech-input settings; the credential source is write-only at the browser boundary. */
|
|
311
|
+
speechInput?: PiWebUiSpeechInputConfig;
|
|
258
312
|
}
|
|
259
313
|
export type PiWebUiPluginScope = "bundled" | "local" | "user" | "project";
|
|
260
314
|
export interface PiWebUiPluginInfo {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiTypes.js","sourceRoot":"","sources":["../../src/shared/apiTypes.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,sBAAsB,EAAE,yBAAyB;IACjD,qBAAqB,EAAE,wBAAwB;IAC/C,eAAe,EAAE,kBAAkB;IACnC,cAAc,EAAE,iBAAiB;IACjC,kBAAkB,EAAE,qBAAqB;IACzC,sBAAsB,EAAE,yBAAyB;IACjD,oBAAoB,EAAE,uBAAuB;IAC7C,sBAAsB,EAAE,yBAAyB;IACjD,qBAAqB,EAAE,wBAAwB;IAC/C,cAAc,EAAE,iBAAiB;IACjC,iBAAiB,EAAE,oBAAoB;IACvC,wBAAwB,EAAE,2BAA2B;IACrD,gBAAgB,EAAE,mBAAmB;IACrC,uBAAuB,EAAE,0BAA0B;IACnD,kBAAkB,EAAE,uBAAuB;IAC3C,iBAAiB,EAAE,qBAAqB;IACxC,oBAAoB,EAAE,wBAAwB;IAC9C,mBAAmB,EAAE,sBAAsB;IAC3C,2BAA2B,EAAE,8BAA8B;IAC3D,mCAAmC,EAAE,sCAAsC;IAC3E,eAAe,EAAE,kBAAkB;IACnC,sBAAsB,EAAE,yBAAyB;CACzC,CAAC;AAsDX,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,CAAU,CAAC;AAQvG,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,aAAa,EAAE,SAAS,CAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"apiTypes.js","sourceRoot":"","sources":["../../src/shared/apiTypes.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,sBAAsB,EAAE,yBAAyB;IACjD,qBAAqB,EAAE,wBAAwB;IAC/C,eAAe,EAAE,kBAAkB;IACnC,cAAc,EAAE,iBAAiB;IACjC,kBAAkB,EAAE,qBAAqB;IACzC,sBAAsB,EAAE,yBAAyB;IACjD,oBAAoB,EAAE,uBAAuB;IAC7C,sBAAsB,EAAE,yBAAyB;IACjD,qBAAqB,EAAE,wBAAwB;IAC/C,cAAc,EAAE,iBAAiB;IACjC,iBAAiB,EAAE,oBAAoB;IACvC,wBAAwB,EAAE,2BAA2B;IACrD,gBAAgB,EAAE,mBAAmB;IACrC,uBAAuB,EAAE,0BAA0B;IACnD,kBAAkB,EAAE,uBAAuB;IAC3C,iBAAiB,EAAE,qBAAqB;IACxC,oBAAoB,EAAE,wBAAwB;IAC9C,mBAAmB,EAAE,sBAAsB;IAC3C,2BAA2B,EAAE,8BAA8B;IAC3D,mCAAmC,EAAE,sCAAsC;IAC3E,eAAe,EAAE,kBAAkB;IACnC,sBAAsB,EAAE,yBAAyB;CACzC,CAAC;AAsDX,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,CAAU,CAAC;AAQvG,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,aAAa,EAAE,SAAS,CAAU,CAAC;AAogBvE,gEAAgE;AAChE,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAwEvC,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC;AAC3C,MAAM,CAAC,MAAM,qCAAqC,GAAG,GAAG,CAAC;AACzD,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,GAAG,IAAI,CAAC;AACxD,MAAM,CAAC,MAAM,sCAAsC,GAAG,EAAE,GAAG,IAAI,CAAC;AAkBhE,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAC1C,MAAM,CAAC,MAAM,oCAAoC,GAAG,GAAG,CAAC;AACxD,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,GAAG,IAAI,CAAC;AACvD,MAAM,CAAC,MAAM,oCAAoC,GAAG,GAAG,CAAC;AACxD,MAAM,CAAC,MAAM,sCAAsC,GAAG,EAAE,CAAC;AAqCzD,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAC9C,MAAM,CAAC,MAAM,kCAAkC,GAAG,CAAC,GAAG,IAAI,CAAC;AAiwB3D,MAAM,CAAC,MAAM,2CAA2C,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export const SPEECH_INPUT_DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
2
|
+
export const SPEECH_INPUT_DEFAULT_MODEL = "gpt-4o-mini-transcribe";
|
|
3
|
+
const CANONICAL_LOWERCASE_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
4
|
+
/**
|
|
5
|
+
* Canonical lowercase v4 UUID spelling, as produced by the config mutation
|
|
6
|
+
* coordinator's default revision source. Opaque to clients; used to enforce
|
|
7
|
+
* the wire spelling at both the server and browser parser edges.
|
|
8
|
+
*/
|
|
9
|
+
export function isCanonicalLowercaseUuid(value) {
|
|
10
|
+
return CANONICAL_LOWERCASE_UUID_PATTERN.test(value);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Syntactic-only BCP 47 canonicalization. Returns undefined for malformed
|
|
14
|
+
* tags; "auto" is not a valid BCP 47 tag, so omission remains the only Auto
|
|
15
|
+
* wire representation.
|
|
16
|
+
*/
|
|
17
|
+
export function canonicalBcp47LanguageTag(value) {
|
|
18
|
+
try {
|
|
19
|
+
const canonical = Intl.getCanonicalLocales(value);
|
|
20
|
+
return canonical.length === 1 ? canonical[0] : undefined;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Effective nonsecret speech settings with the persisted defaults applied. */
|
|
27
|
+
export function effectiveSpeechInputSettings(config) {
|
|
28
|
+
return {
|
|
29
|
+
provider: config?.provider ?? "auto",
|
|
30
|
+
...(config?.language === undefined ? {} : { language: config.language }),
|
|
31
|
+
cloud: {
|
|
32
|
+
baseUrl: config?.cloud?.baseUrl ?? SPEECH_INPUT_DEFAULT_BASE_URL,
|
|
33
|
+
model: config?.cloud?.model ?? SPEECH_INPUT_DEFAULT_MODEL,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Canonical primary language subtag for the cloud `language` field; Auto stays omitted. */
|
|
38
|
+
export function speechInputCloudLanguage(language) {
|
|
39
|
+
if (language === undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
try {
|
|
42
|
+
return new Intl.Locale(language).language;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Reparses an already validated HTTPS base URL, canonicalizes URL syntax
|
|
50
|
+
* (host case, default port, trailing path slashes), and appends exactly one
|
|
51
|
+
* `/audio/transcriptions` segment while preserving non-root base paths.
|
|
52
|
+
* Shared by the preserved-credential endpoint comparison and the cloud
|
|
53
|
+
* provider so both always agree on the effective endpoint.
|
|
54
|
+
*/
|
|
55
|
+
export function speechInputTranscriptionEndpoint(baseUrl) {
|
|
56
|
+
let url;
|
|
57
|
+
try {
|
|
58
|
+
url = new URL(baseUrl);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new Error("Speech input cloud base URL must be a valid HTTPS URL");
|
|
62
|
+
}
|
|
63
|
+
if (url.protocol !== "https:")
|
|
64
|
+
throw new Error("Speech input cloud base URL must use HTTPS");
|
|
65
|
+
if (url.username !== "" || url.password !== "")
|
|
66
|
+
throw new Error("Speech input cloud base URL must not contain credentials");
|
|
67
|
+
if (url.search !== "")
|
|
68
|
+
throw new Error("Speech input cloud base URL must not contain a query string");
|
|
69
|
+
if (url.hash !== "")
|
|
70
|
+
throw new Error("Speech input cloud base URL must not contain a fragment");
|
|
71
|
+
const path = url.pathname.replace(/\/+$/u, "");
|
|
72
|
+
url.pathname = `${path}/audio/transcriptions`;
|
|
73
|
+
return url.href;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=speechInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speechInput.js","sourceRoot":"","sources":["../../src/shared/speechInput.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,6BAA6B,GAAG,2BAA2B,CAAC;AACzE,MAAM,CAAC,MAAM,0BAA0B,GAAG,wBAAwB,CAAC;AAEnE,MAAM,gCAAgC,GAAG,wEAAwE,CAAC;AAElH;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAa;IACpD,OAAO,gCAAgC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAAa;IACrD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAClD,OAAO,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,4BAA4B,CAAC,MAA4C;IACvF,OAAO;QACL,QAAQ,EAAE,MAAM,EAAE,QAAQ,IAAI,MAAM;QACpC,GAAG,CAAC,MAAM,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxE,KAAK,EAAE;YACL,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,IAAI,6BAA6B;YAChE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,0BAA0B;SAC1D;KACF,CAAC;AACJ,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,wBAAwB,CAAC,QAA4B;IACnE,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC7C,IAAI,CAAC;QACH,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gCAAgC,CAAC,OAAe;IAC9D,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAC7F,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC5H,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACtG,IAAI,GAAG,CAAC,IAAI,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAChG,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC/C,GAAG,CAAC,QAAQ,GAAG,GAAG,IAAI,uBAAuB,CAAC;IAC9C,OAAO,GAAG,CAAC,IAAI,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const SPEECH_INPUT_MAX_AUDIO_BYTES = 20 * 1024 * 1024;
|
|
2
|
+
export const SPEECH_INPUT_MAX_TRANSCRIPT_BYTES = 1024 * 1024;
|
|
3
|
+
export const SPEECH_INPUT_PROVIDER_TIMEOUT_MS = 120_000;
|
|
4
|
+
export const SPEECH_INPUT_UPLOAD_TIMEOUT_MS = 130_000;
|
|
5
|
+
const MIME_TYPE_PATTERN = /^\s*([!#$%&'*+.^_`|~0-9A-Za-z-]+)\/([!#$%&'*+.^_`|~0-9A-Za-z-]+)(?:\s*;\s*([!#$%&'*+.^_`|~0-9A-Za-z-]+)\s*=\s*([!#$%&'*+.^_`|~0-9A-Za-z.-]+))?\s*$/u;
|
|
6
|
+
/**
|
|
7
|
+
* Strictly recognizes the recorder formats PI WEBUI can forward without
|
|
8
|
+
* changing the encoded container. Header field names and token values are
|
|
9
|
+
* case-insensitive here because browsers vary their Content-Type spelling.
|
|
10
|
+
*/
|
|
11
|
+
export function parseSpeechInputAudioMimeType(value) {
|
|
12
|
+
if (value === undefined)
|
|
13
|
+
return undefined;
|
|
14
|
+
const match = MIME_TYPE_PATTERN.exec(value);
|
|
15
|
+
if (match === null)
|
|
16
|
+
return undefined;
|
|
17
|
+
const type = match[1]?.toLowerCase();
|
|
18
|
+
const subtype = match[2]?.toLowerCase();
|
|
19
|
+
const parameter = match[3]?.toLowerCase();
|
|
20
|
+
const parameterValue = match[4]?.toLowerCase();
|
|
21
|
+
const mediaType = type === undefined || subtype === undefined ? undefined : `${type}/${subtype}`;
|
|
22
|
+
if (mediaType === "audio/webm" && parameter === "codecs" && parameterValue === "opus") {
|
|
23
|
+
return "audio/webm;codecs=opus";
|
|
24
|
+
}
|
|
25
|
+
if (mediaType === "audio/ogg" && parameter === "codecs" && parameterValue === "opus") {
|
|
26
|
+
return "audio/ogg;codecs=opus";
|
|
27
|
+
}
|
|
28
|
+
if (mediaType === "audio/mp4" && parameter === "codecs" && parameterValue === "mp4a.40.2") {
|
|
29
|
+
return "audio/mp4;codecs=mp4a.40.2";
|
|
30
|
+
}
|
|
31
|
+
if (mediaType === "audio/mp4" && parameter === undefined)
|
|
32
|
+
return "audio/mp4";
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
export function speechInputAudioFilename(value) {
|
|
36
|
+
if (value === "audio/webm;codecs=opus")
|
|
37
|
+
return "speech.webm";
|
|
38
|
+
if (value === "audio/ogg;codecs=opus")
|
|
39
|
+
return "speech.ogg";
|
|
40
|
+
return "speech.m4a";
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=speechInputAudio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speechInputAudio.js","sourceRoot":"","sources":["../../src/shared/speechInputAudio.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAC7D,MAAM,CAAC,MAAM,iCAAiC,GAAG,IAAI,GAAG,IAAI,CAAC;AAC7D,MAAM,CAAC,MAAM,gCAAgC,GAAG,OAAO,CAAC;AACxD,MAAM,CAAC,MAAM,8BAA8B,GAAG,OAAO,CAAC;AAQtD,MAAM,iBAAiB,GAAG,qJAAqJ,CAAC;AAEhL;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,KAAyB;IACrE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;IACxC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;IAC1C,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;IAC/C,MAAM,SAAS,GAAG,IAAI,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC;IAEjG,IAAI,SAAS,KAAK,YAAY,IAAI,SAAS,KAAK,QAAQ,IAAI,cAAc,KAAK,MAAM,EAAE,CAAC;QACtF,OAAO,wBAAwB,CAAC;IAClC,CAAC;IACD,IAAI,SAAS,KAAK,WAAW,IAAI,SAAS,KAAK,QAAQ,IAAI,cAAc,KAAK,MAAM,EAAE,CAAC;QACrF,OAAO,uBAAuB,CAAC;IACjC,CAAC;IACD,IAAI,SAAS,KAAK,WAAW,IAAI,SAAS,KAAK,QAAQ,IAAI,cAAc,KAAK,WAAW,EAAE,CAAC;QAC1F,OAAO,4BAA4B,CAAC;IACtC,CAAC;IACD,IAAI,SAAS,KAAK,WAAW,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAC7E,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,KAA+B;IACtE,IAAI,KAAK,KAAK,wBAAwB;QAAE,OAAO,aAAa,CAAC;IAC7D,IAAI,KAAK,KAAK,uBAAuB;QAAE,OAAO,YAAY,CAAC;IAC3D,OAAO,YAAY,CAAC;AACtB,CAAC"}
|