crewly 1.20.213 → 1.20.214
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/backend/backend/src/constants.d.ts +67 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +77 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/talk/talk-transcribe.controller.d.ts +37 -0
- package/dist/backend/backend/src/controllers/talk/talk-transcribe.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/talk/talk-transcribe.controller.js +97 -0
- package/dist/backend/backend/src/controllers/talk/talk-transcribe.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/talk/talk-transcribe.routes.d.ts +18 -0
- package/dist/backend/backend/src/controllers/talk/talk-transcribe.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/talk/talk-transcribe.routes.js +25 -0
- package/dist/backend/backend/src/controllers/talk/talk-transcribe.routes.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +6 -4
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +4 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +6 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +2 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/talk/talk-transcribe.service.d.ts +344 -0
- package/dist/backend/backend/src/services/talk/talk-transcribe.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/talk/talk-transcribe.service.js +642 -0
- package/dist/backend/backend/src/services/talk/talk-transcribe.service.js.map +1 -0
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +67 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +77 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Talk transcription — speech-to-text for the portal Talk page on the
|
|
3
|
+
* owner's own machine (specs/2026-10-04-talk-whisper-transcribe.md, #1074).
|
|
4
|
+
*
|
|
5
|
+
* The phone records a short clip and sends it over the relay to the machine
|
|
6
|
+
* of the agent being talked to; this service turns it into text with
|
|
7
|
+
* whisper.cpp and hands the text back. It never sends anything to an agent:
|
|
8
|
+
* the owner reads, edits and confirms the text on the phone first.
|
|
9
|
+
*
|
|
10
|
+
* Engine: the `transcribe-audio` skill's — same `whisper-cli` lookup order,
|
|
11
|
+
* same large-v3-turbo model file, same ffmpeg normalisation to 16 kHz mono
|
|
12
|
+
* WAV. Its setup block is what installs the pieces ({@link startSetup}).
|
|
13
|
+
*
|
|
14
|
+
* Properties:
|
|
15
|
+
* - **One at a time.** whisper uses most of the CPU; requests queue behind
|
|
16
|
+
* the running one (at most {@link TALK_TRANSCRIBE_CONSTANTS.MAX_QUEUE}
|
|
17
|
+
* waiting) and share one deadline that includes the wait.
|
|
18
|
+
* - **Nothing kept.** Audio is written to a private temp dir that is removed
|
|
19
|
+
* in `finally`; neither audio nor transcript is ever logged.
|
|
20
|
+
* - **Clear failures.** Every refusal carries a code the phone acts on by
|
|
21
|
+
* falling back to on-device recognition: `whisper_unavailable`,
|
|
22
|
+
* `too_long`, `timeout`, `failed` (and `busy`, `invalid_audio`).
|
|
23
|
+
*
|
|
24
|
+
* @module services/talk/talk-transcribe.service
|
|
25
|
+
*/
|
|
26
|
+
import * as fs from 'fs';
|
|
27
|
+
import * as os from 'os';
|
|
28
|
+
import * as path from 'path';
|
|
29
|
+
import { SKILL_SETUP_CONSTANTS, TALK_TRANSCRIBE_CONSTANTS } from '../../constants.js';
|
|
30
|
+
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
31
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
32
|
+
import { resolveExecutable, runCommand } from '../harness/harness-exec.utils.js';
|
|
33
|
+
const C = TALK_TRANSCRIBE_CONSTANTS;
|
|
34
|
+
/** HTTP status for each refusal. */
|
|
35
|
+
export const TALK_TRANSCRIBE_HTTP_STATUS = {
|
|
36
|
+
[C.CODES.WHISPER_UNAVAILABLE]: 503,
|
|
37
|
+
[C.CODES.TOO_LONG]: 413,
|
|
38
|
+
[C.CODES.TIMEOUT]: 504,
|
|
39
|
+
[C.CODES.FAILED]: 500,
|
|
40
|
+
[C.CODES.BUSY]: 429,
|
|
41
|
+
[C.CODES.INVALID_AUDIO]: 400,
|
|
42
|
+
[C.CODES.OWNER_ONLY]: 403,
|
|
43
|
+
};
|
|
44
|
+
/** A refusal with a machine-readable code. Its message never contains audio or transcript. */
|
|
45
|
+
export class TalkTranscribeError extends Error {
|
|
46
|
+
code;
|
|
47
|
+
/**
|
|
48
|
+
* @param code - What went wrong (the phone falls back on any of them)
|
|
49
|
+
* @param message - Short English explanation
|
|
50
|
+
*/
|
|
51
|
+
constructor(code, message) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.code = code;
|
|
54
|
+
this.name = 'TalkTranscribeError';
|
|
55
|
+
}
|
|
56
|
+
/** HTTP status for this refusal. */
|
|
57
|
+
get httpStatus() {
|
|
58
|
+
return TALK_TRANSCRIBE_HTTP_STATUS[this.code];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Whether a file exists and is executable.
|
|
63
|
+
*
|
|
64
|
+
* @param file - Absolute path
|
|
65
|
+
* @returns True when executable
|
|
66
|
+
*/
|
|
67
|
+
function defaultIsExecutable(file) {
|
|
68
|
+
try {
|
|
69
|
+
fs.accessSync(file, fs.constants.X_OK);
|
|
70
|
+
return fs.statSync(file).isFile();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Whether a regular file exists.
|
|
78
|
+
*
|
|
79
|
+
* @param file - Absolute path
|
|
80
|
+
* @returns True when it is a file
|
|
81
|
+
*/
|
|
82
|
+
function defaultIsFile(file) {
|
|
83
|
+
try {
|
|
84
|
+
return fs.statSync(file).isFile();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Temp-file extension for a MediaRecorder type (`audio/webm;codecs=opus` → `webm`).
|
|
92
|
+
*
|
|
93
|
+
* @param mimeType - Container type
|
|
94
|
+
* @returns Extension, or null when the type is not audio we take
|
|
95
|
+
*/
|
|
96
|
+
export function extensionForMime(mimeType) {
|
|
97
|
+
const base = mimeType.split(';')[0].trim().toLowerCase();
|
|
98
|
+
return C.MIME_EXTENSIONS[base] ?? null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Decode the base64 body, refusing anything malformed or over the size cap
|
|
102
|
+
* before decoding it.
|
|
103
|
+
*
|
|
104
|
+
* @param audio - Base64 string, optionally a `data:` URL
|
|
105
|
+
* @param maxBytes - Size cap of the decoded audio
|
|
106
|
+
* @returns The audio bytes
|
|
107
|
+
* @throws TalkTranscribeError `invalid_audio` or `too_long`
|
|
108
|
+
*/
|
|
109
|
+
export function decodeAudio(audio, maxBytes) {
|
|
110
|
+
if (typeof audio !== 'string' || audio.length === 0) {
|
|
111
|
+
throw new TalkTranscribeError(C.CODES.INVALID_AUDIO, 'audio (base64) is required');
|
|
112
|
+
}
|
|
113
|
+
const comma = audio.startsWith('data:') ? audio.indexOf(',') : -1;
|
|
114
|
+
const b64 = (comma >= 0 ? audio.slice(comma + 1) : audio).replace(/\s+/g, '');
|
|
115
|
+
if (b64.length > Math.ceil(maxBytes / 3) * 4) {
|
|
116
|
+
throw new TalkTranscribeError(C.CODES.TOO_LONG, `The clip is larger than ${Math.round(maxBytes / 1024)} KB`);
|
|
117
|
+
}
|
|
118
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(b64)) {
|
|
119
|
+
throw new TalkTranscribeError(C.CODES.INVALID_AUDIO, 'audio is not valid base64');
|
|
120
|
+
}
|
|
121
|
+
const bytes = Buffer.from(b64, 'base64');
|
|
122
|
+
if (bytes.length === 0)
|
|
123
|
+
throw new TalkTranscribeError(C.CODES.INVALID_AUDIO, 'audio is empty');
|
|
124
|
+
if (bytes.length > maxBytes) {
|
|
125
|
+
throw new TalkTranscribeError(C.CODES.TOO_LONG, `The clip is larger than ${Math.round(maxBytes / 1024)} KB`);
|
|
126
|
+
}
|
|
127
|
+
return bytes;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The initial prompt: the Simplified-Chinese bias sentence, then the owner
|
|
131
|
+
* glossary and this machine's team / agent / project names, so names like
|
|
132
|
+
* "CE" are not heard as "侧". Bounded so whisper keeps all of it.
|
|
133
|
+
*
|
|
134
|
+
* @param names - Vocabulary from this machine
|
|
135
|
+
* @param maxChars - Bound on the vocabulary part
|
|
136
|
+
* @returns Prompt text
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* buildVocabularyPrompt(['Marketing', 'Ella']);
|
|
141
|
+
* // '以下是普通话的句子。Crewly、CE、Crewly Cloud、Slack、Marketing、Ella。'
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export function buildVocabularyPrompt(names, maxChars = C.MAX_VOCAB_CHARS) {
|
|
145
|
+
const seen = new Set();
|
|
146
|
+
const words = [];
|
|
147
|
+
let used = 0;
|
|
148
|
+
for (const raw of [...C.GLOSSARY, ...names]) {
|
|
149
|
+
const word = typeof raw === 'string' ? raw.replace(/\s+/g, ' ').trim() : '';
|
|
150
|
+
if (!word || word.length > 40)
|
|
151
|
+
continue;
|
|
152
|
+
const key = word.toLowerCase();
|
|
153
|
+
if (seen.has(key))
|
|
154
|
+
continue;
|
|
155
|
+
if (used + word.length + 1 > maxChars)
|
|
156
|
+
break;
|
|
157
|
+
seen.add(key);
|
|
158
|
+
words.push(word);
|
|
159
|
+
used += word.length + 1;
|
|
160
|
+
}
|
|
161
|
+
return words.length > 0 ? `${C.PROMPT_PREFIX}${words.join('、')}。` : C.PROMPT_PREFIX;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Join whisper segments: a space only between two Latin-script edges, so
|
|
165
|
+
* Chinese stays unspaced and English words stay apart.
|
|
166
|
+
*
|
|
167
|
+
* @param segments - Segment texts
|
|
168
|
+
* @returns The transcript
|
|
169
|
+
*/
|
|
170
|
+
export function joinSegments(segments) {
|
|
171
|
+
let out = '';
|
|
172
|
+
for (const raw of segments) {
|
|
173
|
+
const seg = raw.trim();
|
|
174
|
+
if (!seg || /^\[.*\]$/.test(seg) || /^\(.*\)$/.test(seg))
|
|
175
|
+
continue; // [BLANK_AUDIO], (music)
|
|
176
|
+
if (out && /[A-Za-z0-9.,!?;:]$/.test(out) && /^[A-Za-z0-9]/.test(seg))
|
|
177
|
+
out += ' ';
|
|
178
|
+
out += seg;
|
|
179
|
+
}
|
|
180
|
+
return out.trim();
|
|
181
|
+
}
|
|
182
|
+
/** Talk speech-to-text with whisper.cpp. */
|
|
183
|
+
export class TalkTranscribeService {
|
|
184
|
+
run;
|
|
185
|
+
env;
|
|
186
|
+
homeDir;
|
|
187
|
+
crewlyHome;
|
|
188
|
+
isExecutable;
|
|
189
|
+
isFile;
|
|
190
|
+
vocabulary;
|
|
191
|
+
deviceNameFn;
|
|
192
|
+
startSetupFn;
|
|
193
|
+
setupJobFn;
|
|
194
|
+
tmpDir;
|
|
195
|
+
cpuCount;
|
|
196
|
+
now;
|
|
197
|
+
logger;
|
|
198
|
+
maxAudioBytes;
|
|
199
|
+
maxDurationSec;
|
|
200
|
+
requestTimeoutMs;
|
|
201
|
+
maxQueue;
|
|
202
|
+
running = false;
|
|
203
|
+
waiters = [];
|
|
204
|
+
lastSetup = null;
|
|
205
|
+
/**
|
|
206
|
+
* @param deps - Injectable dependencies
|
|
207
|
+
*/
|
|
208
|
+
constructor(deps = {}) {
|
|
209
|
+
this.run = deps.run ?? runCommand;
|
|
210
|
+
this.env = deps.env ?? process.env;
|
|
211
|
+
this.homeDir = deps.homeDir ?? os.homedir;
|
|
212
|
+
this.crewlyHome = deps.crewlyHome ?? getCrewlyHomePath;
|
|
213
|
+
this.isExecutable = deps.isExecutable ?? defaultIsExecutable;
|
|
214
|
+
this.isFile = deps.isFile ?? defaultIsFile;
|
|
215
|
+
this.vocabulary = deps.vocabulary ?? (async () => []);
|
|
216
|
+
this.deviceNameFn = deps.deviceName ?? (async () => os.hostname());
|
|
217
|
+
this.startSetupFn = deps.startSetup ?? null;
|
|
218
|
+
this.setupJobFn = deps.setupJob ?? null;
|
|
219
|
+
this.tmpDir = deps.tmpDir ?? os.tmpdir;
|
|
220
|
+
this.cpuCount = deps.cpuCount ?? (() => os.cpus().length);
|
|
221
|
+
this.now = deps.now ?? Date.now;
|
|
222
|
+
this.logger = deps.logger ?? LoggerService.getInstance().createComponentLogger('TalkTranscribe');
|
|
223
|
+
this.maxAudioBytes = deps.limits?.maxAudioBytes ?? C.MAX_AUDIO_BYTES;
|
|
224
|
+
this.maxDurationSec = deps.limits?.maxDurationSec ?? C.MAX_DURATION_SEC;
|
|
225
|
+
this.requestTimeoutMs = deps.limits?.requestTimeoutMs ?? C.REQUEST_TIMEOUT_MS;
|
|
226
|
+
this.maxQueue = deps.limits?.maxQueue ?? C.MAX_QUEUE;
|
|
227
|
+
}
|
|
228
|
+
// -------------------------------------------------------------------------
|
|
229
|
+
// Engine discovery (the transcribe-audio skill's order)
|
|
230
|
+
// -------------------------------------------------------------------------
|
|
231
|
+
/**
|
|
232
|
+
* PATH searched for commands: `$CREWLY_HOME/bin`, the process PATH, then
|
|
233
|
+
* the usual system and Homebrew dirs (a backend started by launchd often
|
|
234
|
+
* lacks /opt/homebrew/bin) — the skill setup runner's search path.
|
|
235
|
+
*
|
|
236
|
+
* @returns PATH string
|
|
237
|
+
*/
|
|
238
|
+
searchPath() {
|
|
239
|
+
const entries = [
|
|
240
|
+
path.join(this.crewlyHome(), SKILL_SETUP_CONSTANTS.BIN_DIR),
|
|
241
|
+
...(this.env.PATH ?? '').split(path.delimiter),
|
|
242
|
+
...SKILL_SETUP_CONSTANTS.EXTRA_COMMAND_DIRS,
|
|
243
|
+
].filter((e) => e.length > 0);
|
|
244
|
+
return [...new Set(entries)].join(path.delimiter);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Find whisper-cli, the model and ffmpeg. Checked on every call (cheap
|
|
248
|
+
* file checks), so a finished setup is seen at once.
|
|
249
|
+
*
|
|
250
|
+
* Order, as in `transcribe-audio/execute.sh`: `FLOPOST_WHISPER_BIN`,
|
|
251
|
+
* `~/.flopost/whisper/whisper-cli`, `$CREWLY_HOME/bin`, PATH, Homebrew;
|
|
252
|
+
* model: `FLOPOST_WHISPER_MODEL`, `~/.flopost/whisper/`, `~/.cache/whisper-models/`.
|
|
253
|
+
*
|
|
254
|
+
* @returns Paths (null = missing)
|
|
255
|
+
*/
|
|
256
|
+
resolveEngine() {
|
|
257
|
+
const home = this.homeDir();
|
|
258
|
+
let whisperBin = null;
|
|
259
|
+
const envBin = this.env.FLOPOST_WHISPER_BIN;
|
|
260
|
+
if (envBin && this.isExecutable(envBin))
|
|
261
|
+
whisperBin = envBin;
|
|
262
|
+
for (const rel of C.HOME_BIN_CANDIDATES) {
|
|
263
|
+
if (whisperBin)
|
|
264
|
+
break;
|
|
265
|
+
const p = path.join(home, rel);
|
|
266
|
+
if (this.isExecutable(p))
|
|
267
|
+
whisperBin = p;
|
|
268
|
+
}
|
|
269
|
+
const searchPath = this.searchPath();
|
|
270
|
+
whisperBin ??= resolveExecutable(C.WHISPER_BINARY, searchPath, this.isExecutable);
|
|
271
|
+
let model = null;
|
|
272
|
+
const envModel = this.env.FLOPOST_WHISPER_MODEL;
|
|
273
|
+
if (envModel && this.isFile(envModel))
|
|
274
|
+
model = envModel;
|
|
275
|
+
for (const dir of C.MODEL_DIRS) {
|
|
276
|
+
if (model)
|
|
277
|
+
break;
|
|
278
|
+
const p = path.join(home, dir, C.MODEL_FILENAME);
|
|
279
|
+
if (this.isFile(p))
|
|
280
|
+
model = p;
|
|
281
|
+
}
|
|
282
|
+
const ffmpeg = resolveExecutable(C.FFMPEG_BINARY, searchPath, this.isExecutable);
|
|
283
|
+
return { whisperBin, model, ffmpeg };
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Missing pieces, named by the skill's setup step ids.
|
|
287
|
+
*
|
|
288
|
+
* @param engine - Resolved engine
|
|
289
|
+
* @returns e.g. `['whisper-model']`
|
|
290
|
+
*/
|
|
291
|
+
missingParts(engine) {
|
|
292
|
+
const missing = [];
|
|
293
|
+
if (!engine.ffmpeg)
|
|
294
|
+
missing.push('ffmpeg');
|
|
295
|
+
if (!engine.whisperBin)
|
|
296
|
+
missing.push('whisper-cli');
|
|
297
|
+
if (!engine.model)
|
|
298
|
+
missing.push('whisper-model');
|
|
299
|
+
return missing;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Whether a transcription can run here now.
|
|
303
|
+
*
|
|
304
|
+
* @returns True when whisper-cli, the model and ffmpeg are present
|
|
305
|
+
*/
|
|
306
|
+
isReady() {
|
|
307
|
+
return this.missingParts(this.resolveEngine()).length === 0;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Capabilities for the Cloud heartbeat: `talk_whisper` while ready.
|
|
311
|
+
*
|
|
312
|
+
* @returns Capability list
|
|
313
|
+
*/
|
|
314
|
+
capabilities() {
|
|
315
|
+
return this.isReady() ? [C.CAPABILITY] : [];
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* This machine's name, never failing.
|
|
319
|
+
*
|
|
320
|
+
* @returns Device name
|
|
321
|
+
*/
|
|
322
|
+
async deviceName() {
|
|
323
|
+
try {
|
|
324
|
+
return (await this.deviceNameFn()) || os.hostname();
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return os.hostname();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Status for the phone: ready or not, what is missing, queue, setup.
|
|
332
|
+
*
|
|
333
|
+
* @returns Status
|
|
334
|
+
*/
|
|
335
|
+
async getStatus() {
|
|
336
|
+
const missing = this.missingParts(this.resolveEngine());
|
|
337
|
+
const setup = await this.currentSetup();
|
|
338
|
+
return {
|
|
339
|
+
whisperReady: missing.length === 0,
|
|
340
|
+
missing,
|
|
341
|
+
deviceName: await this.deviceName(),
|
|
342
|
+
busy: this.running,
|
|
343
|
+
queued: this.waiters.length,
|
|
344
|
+
maxDurationSec: this.maxDurationSec,
|
|
345
|
+
maxAudioBytes: this.maxAudioBytes,
|
|
346
|
+
...(setup ? { setup } : {}),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* The last setup job, refreshed from the job service.
|
|
351
|
+
*
|
|
352
|
+
* @returns State, or null when none was started here
|
|
353
|
+
*/
|
|
354
|
+
async currentSetup() {
|
|
355
|
+
if (!this.lastSetup)
|
|
356
|
+
return null;
|
|
357
|
+
if (this.lastSetup.jobId && this.setupJobFn) {
|
|
358
|
+
const fresh = await this.setupJobFn(this.lastSetup.jobId).catch(() => null);
|
|
359
|
+
if (fresh)
|
|
360
|
+
this.lastSetup = fresh;
|
|
361
|
+
}
|
|
362
|
+
return this.lastSetup;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Install ffmpeg, whisper-cli and the model with the transcribe-audio
|
|
366
|
+
* skill's setup block, in the background (unattended; the skill is
|
|
367
|
+
* official). Returns at once.
|
|
368
|
+
*
|
|
369
|
+
* @returns The setup state (`succeeded` when everything was already there)
|
|
370
|
+
* @throws TalkTranscribeError `failed` when setup cannot be started
|
|
371
|
+
*/
|
|
372
|
+
async startSetup() {
|
|
373
|
+
if (this.isReady()) {
|
|
374
|
+
this.lastSetup = { state: 'succeeded', message: 'Whisper is ready' };
|
|
375
|
+
return this.lastSetup;
|
|
376
|
+
}
|
|
377
|
+
if (!this.startSetupFn)
|
|
378
|
+
throw new TalkTranscribeError(C.CODES.FAILED, 'Setup is not available on this machine');
|
|
379
|
+
try {
|
|
380
|
+
this.lastSetup = await this.startSetupFn();
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
384
|
+
this.lastSetup = { state: 'failed', message };
|
|
385
|
+
throw new TalkTranscribeError(C.CODES.FAILED, message);
|
|
386
|
+
}
|
|
387
|
+
this.logger.info('Whisper setup for Talk started', { state: this.lastSetup.state, jobId: this.lastSetup.jobId });
|
|
388
|
+
return this.lastSetup;
|
|
389
|
+
}
|
|
390
|
+
// -------------------------------------------------------------------------
|
|
391
|
+
// Transcription
|
|
392
|
+
// -------------------------------------------------------------------------
|
|
393
|
+
/**
|
|
394
|
+
* Take the one-at-a-time slot, waiting until `deadline` at most.
|
|
395
|
+
*
|
|
396
|
+
* @param deadline - Absolute ms
|
|
397
|
+
* @throws TalkTranscribeError `busy` (queue full) or `timeout`
|
|
398
|
+
*/
|
|
399
|
+
acquire(deadline) {
|
|
400
|
+
if (!this.running) {
|
|
401
|
+
this.running = true;
|
|
402
|
+
return Promise.resolve();
|
|
403
|
+
}
|
|
404
|
+
if (this.waiters.length >= this.maxQueue) {
|
|
405
|
+
return Promise.reject(new TalkTranscribeError(C.CODES.BUSY, 'Another transcription is running; try again in a moment'));
|
|
406
|
+
}
|
|
407
|
+
return new Promise((resolve, reject) => {
|
|
408
|
+
const grant = () => {
|
|
409
|
+
clearTimeout(timer);
|
|
410
|
+
resolve();
|
|
411
|
+
};
|
|
412
|
+
const timer = setTimeout(() => {
|
|
413
|
+
const i = this.waiters.indexOf(grant);
|
|
414
|
+
if (i >= 0)
|
|
415
|
+
this.waiters.splice(i, 1);
|
|
416
|
+
reject(new TalkTranscribeError(C.CODES.TIMEOUT, 'Timed out waiting for the previous transcription'));
|
|
417
|
+
}, Math.max(0, deadline - this.now()));
|
|
418
|
+
this.waiters.push(grant);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
/** Hand the slot to the next waiter, or free it. */
|
|
422
|
+
release() {
|
|
423
|
+
const next = this.waiters.shift();
|
|
424
|
+
if (next)
|
|
425
|
+
next();
|
|
426
|
+
else
|
|
427
|
+
this.running = false;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Transcribe one clip.
|
|
431
|
+
*
|
|
432
|
+
* Validation (type, size, engine present) happens before queueing; the
|
|
433
|
+
* deadline covers the wait, ffmpeg and whisper. Temp files are removed in
|
|
434
|
+
* `finally`. Logs carry only sizes, durations and codes.
|
|
435
|
+
*
|
|
436
|
+
* @param input - Base64 audio, container type, language hint
|
|
437
|
+
* @returns The transcript
|
|
438
|
+
* @throws TalkTranscribeError with a code the phone falls back on
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* ```ts
|
|
442
|
+
* const r = await svc.transcribe({ audio: b64, mimeType: 'audio/mp4' });
|
|
443
|
+
* // { text: '帮我看一下 CE 的网站', language: 'zh', durationSec: 3.2, ... }
|
|
444
|
+
* ```
|
|
445
|
+
*/
|
|
446
|
+
async transcribe(input) {
|
|
447
|
+
const started = this.now();
|
|
448
|
+
const deadline = started + this.requestTimeoutMs;
|
|
449
|
+
const mimeType = typeof input.mimeType === 'string' ? input.mimeType : '';
|
|
450
|
+
const ext = extensionForMime(mimeType);
|
|
451
|
+
if (!ext)
|
|
452
|
+
throw new TalkTranscribeError(C.CODES.INVALID_AUDIO, `Unsupported audio type: ${mimeType.slice(0, 60) || '(none)'}`);
|
|
453
|
+
const language = input.language === undefined || input.language === null || input.language === '' ? 'auto' : input.language;
|
|
454
|
+
if (typeof language !== 'string' || !C.LANGUAGES.includes(language)) {
|
|
455
|
+
throw new TalkTranscribeError(C.CODES.INVALID_AUDIO, `language must be one of ${C.LANGUAGES.join(', ')}`);
|
|
456
|
+
}
|
|
457
|
+
const bytes = decodeAudio(input.audio, this.maxAudioBytes);
|
|
458
|
+
const engine = this.resolveEngine();
|
|
459
|
+
const missing = this.missingParts(engine);
|
|
460
|
+
if (missing.length > 0) {
|
|
461
|
+
throw new TalkTranscribeError(C.CODES.WHISPER_UNAVAILABLE, `Whisper is not set up on this machine (missing: ${missing.join(', ')})`);
|
|
462
|
+
}
|
|
463
|
+
await this.acquire(deadline);
|
|
464
|
+
let workDir = null;
|
|
465
|
+
try {
|
|
466
|
+
workDir = fs.mkdtempSync(path.join(this.tmpDir(), C.TEMP_PREFIX));
|
|
467
|
+
const paths = { whisperBin: engine.whisperBin, model: engine.model, ffmpeg: engine.ffmpeg };
|
|
468
|
+
const result = await this.runEngine(paths, workDir, bytes, ext, language, deadline);
|
|
469
|
+
const elapsedMs = this.now() - started;
|
|
470
|
+
this.logger.info('Talk clip transcribed', {
|
|
471
|
+
bytes: bytes.length,
|
|
472
|
+
durationSec: result.durationSec,
|
|
473
|
+
language: result.language,
|
|
474
|
+
chars: result.text.length,
|
|
475
|
+
elapsedMs,
|
|
476
|
+
});
|
|
477
|
+
return { ...result, engine: 'whisper.cpp', deviceName: await this.deviceName(), elapsedMs };
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
const code = error instanceof TalkTranscribeError ? error.code : C.CODES.FAILED;
|
|
481
|
+
this.logger.warn('Talk clip not transcribed', { code, bytes: bytes.length, elapsedMs: this.now() - started });
|
|
482
|
+
if (error instanceof TalkTranscribeError)
|
|
483
|
+
throw error;
|
|
484
|
+
throw new TalkTranscribeError(C.CODES.FAILED, 'Transcription failed');
|
|
485
|
+
}
|
|
486
|
+
finally {
|
|
487
|
+
if (workDir) {
|
|
488
|
+
try {
|
|
489
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
/* best effort; the OS temp dir is cleaned anyway */
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
this.release();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* ffmpeg → WAV, duration check, whisper-cli → JSON → text.
|
|
500
|
+
*
|
|
501
|
+
* @param engine - Resolved paths (all present)
|
|
502
|
+
* @param workDir - Private temp dir
|
|
503
|
+
* @param bytes - Encoded audio
|
|
504
|
+
* @param ext - Input extension
|
|
505
|
+
* @param language - `auto` / `zh` / `en`
|
|
506
|
+
* @param deadline - Absolute ms
|
|
507
|
+
* @returns Text, language, duration
|
|
508
|
+
*/
|
|
509
|
+
async runEngine(engine, workDir, bytes, ext, language, deadline) {
|
|
510
|
+
const inFile = path.join(workDir, `clip.${ext}`);
|
|
511
|
+
const wav = path.join(workDir, 'clip.wav');
|
|
512
|
+
fs.writeFileSync(inFile, bytes, { mode: 0o600 });
|
|
513
|
+
const env = { ...this.env, PATH: this.searchPath() };
|
|
514
|
+
const remaining = () => {
|
|
515
|
+
const ms = deadline - this.now();
|
|
516
|
+
if (ms <= 0)
|
|
517
|
+
throw new TalkTranscribeError(C.CODES.TIMEOUT, 'Transcription timed out');
|
|
518
|
+
return ms;
|
|
519
|
+
};
|
|
520
|
+
// One second past the cap is decoded so an over-long clip is detected, not silently cut.
|
|
521
|
+
const conv = await this.run(engine.ffmpeg, ['-hide_banner', '-loglevel', 'error', '-nostdin', '-y', '-i', inFile, '-t', String(this.maxDurationSec + 1), '-vn', '-ac', '1', '-ar', String(C.SAMPLE_RATE), '-c:a', 'pcm_s16le', wav], { env, timeoutMs: remaining() });
|
|
522
|
+
if (conv.code !== 0) {
|
|
523
|
+
if (conv.code === null && /timed out/.test(conv.error ?? ''))
|
|
524
|
+
throw new TalkTranscribeError(C.CODES.TIMEOUT, 'Transcription timed out');
|
|
525
|
+
throw new TalkTranscribeError(C.CODES.INVALID_AUDIO, 'Could not decode the audio');
|
|
526
|
+
}
|
|
527
|
+
let wavBytes = 0;
|
|
528
|
+
try {
|
|
529
|
+
wavBytes = fs.statSync(wav).size;
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
throw new TalkTranscribeError(C.CODES.INVALID_AUDIO, 'Could not decode the audio');
|
|
533
|
+
}
|
|
534
|
+
const durationSec = Math.max(0, (wavBytes - C.WAV_HEADER_BYTES) / C.WAV_BYTES_PER_SEC);
|
|
535
|
+
if (durationSec > this.maxDurationSec) {
|
|
536
|
+
throw new TalkTranscribeError(C.CODES.TOO_LONG, `The clip is longer than ${this.maxDurationSec} s`);
|
|
537
|
+
}
|
|
538
|
+
const rounded = Math.round(durationSec * 100) / 100;
|
|
539
|
+
const prompt = buildVocabularyPrompt(await this.vocabulary().catch(() => []));
|
|
540
|
+
const prefix = path.join(workDir, 'out');
|
|
541
|
+
const threads = Math.max(C.MIN_THREADS, this.cpuCount() - C.THREADS_RESERVED);
|
|
542
|
+
const res = await this.run(engine.whisperBin, ['-m', engine.model, '-f', wav, '-l', language, '--prompt', prompt, '-oj', '-of', prefix, '-t', String(threads), '-np'], { env, timeoutMs: remaining() });
|
|
543
|
+
if (res.code !== 0) {
|
|
544
|
+
if (res.code === null && /timed out/.test(res.error ?? ''))
|
|
545
|
+
throw new TalkTranscribeError(C.CODES.TIMEOUT, 'Transcription timed out');
|
|
546
|
+
throw new TalkTranscribeError(C.CODES.FAILED, `whisper-cli exited with ${res.code ?? 'an error'}`);
|
|
547
|
+
}
|
|
548
|
+
let parsed;
|
|
549
|
+
try {
|
|
550
|
+
parsed = JSON.parse(fs.readFileSync(`${prefix}.json`, 'utf8'));
|
|
551
|
+
}
|
|
552
|
+
catch {
|
|
553
|
+
throw new TalkTranscribeError(C.CODES.FAILED, 'whisper-cli produced no readable output');
|
|
554
|
+
}
|
|
555
|
+
let text = joinSegments((parsed.transcription ?? []).map((s) => (typeof s.text === 'string' ? s.text : '')));
|
|
556
|
+
// On silence whisper can echo its prompt back; that is not what was said.
|
|
557
|
+
if (text === C.PROMPT_PREFIX || text === prompt)
|
|
558
|
+
text = '';
|
|
559
|
+
const detected = parsed.result?.language || (language === 'auto' ? 'auto' : language);
|
|
560
|
+
return { text, language: detected, durationSec: rounded };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// ---------------------------------------------------------------------------
|
|
564
|
+
// Singleton
|
|
565
|
+
// ---------------------------------------------------------------------------
|
|
566
|
+
let instance = null;
|
|
567
|
+
/**
|
|
568
|
+
* The process-wide service, created with the default dependencies on first use.
|
|
569
|
+
*
|
|
570
|
+
* @returns The service
|
|
571
|
+
*/
|
|
572
|
+
export function getTalkTranscribeService() {
|
|
573
|
+
instance ??= new TalkTranscribeService(defaultDeps());
|
|
574
|
+
return instance;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Replace the process-wide service (tests; null resets).
|
|
578
|
+
*
|
|
579
|
+
* @param service - Service or null
|
|
580
|
+
*/
|
|
581
|
+
export function setTalkTranscribeService(service) {
|
|
582
|
+
instance = service;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Capabilities for the Cloud heartbeat (`talk_whisper` while Whisper is ready).
|
|
586
|
+
*
|
|
587
|
+
* @returns Capability list
|
|
588
|
+
*/
|
|
589
|
+
export function talkTranscribeCapabilities() {
|
|
590
|
+
try {
|
|
591
|
+
return getTalkTranscribeService().capabilities();
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
return [];
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Production wiring: vocabulary from storage, the device name, and the skill
|
|
599
|
+
* install job service for setup. Imports are lazy so the module stays light.
|
|
600
|
+
*
|
|
601
|
+
* @returns Dependencies
|
|
602
|
+
*/
|
|
603
|
+
function defaultDeps() {
|
|
604
|
+
return {
|
|
605
|
+
vocabulary: async () => {
|
|
606
|
+
const { StorageService } = await import('../core/storage.service.js');
|
|
607
|
+
const storage = StorageService.getInstance();
|
|
608
|
+
const [teams, projects] = await Promise.all([storage.getTeams().catch(() => []), storage.getProjects().catch(() => [])]);
|
|
609
|
+
const names = [];
|
|
610
|
+
for (const team of teams) {
|
|
611
|
+
names.push(team.name);
|
|
612
|
+
for (const member of team.members ?? [])
|
|
613
|
+
names.push(member.name);
|
|
614
|
+
}
|
|
615
|
+
for (const project of projects)
|
|
616
|
+
names.push(project.name);
|
|
617
|
+
return names.filter((n) => typeof n === 'string');
|
|
618
|
+
},
|
|
619
|
+
deviceName: async () => {
|
|
620
|
+
const { DeviceIdentityService } = await import('../cloud/device-identity.service.js');
|
|
621
|
+
return (await DeviceIdentityService.getInstance().getOrCreateIdentity()).deviceName;
|
|
622
|
+
},
|
|
623
|
+
startSetup: async () => {
|
|
624
|
+
const { getSkillInstallJobService } = await import('../skill-setup/skill-install-job.service.js');
|
|
625
|
+
const started = await getSkillInstallJobService().startInstall({ skillId: C.SKILL_ID, ownerDashboard: true });
|
|
626
|
+
if (started.kind === 'already-ready')
|
|
627
|
+
return { state: 'succeeded', message: 'Whisper is ready' };
|
|
628
|
+
return { state: started.job.state, jobId: started.job.jobId, ...(started.job.message ? { message: started.job.message } : {}) };
|
|
629
|
+
},
|
|
630
|
+
setupJob: async (jobId) => {
|
|
631
|
+
const { getSkillInstallJobService } = await import('../skill-setup/skill-install-job.service.js');
|
|
632
|
+
try {
|
|
633
|
+
const job = getSkillInstallJobService().getJob(jobId);
|
|
634
|
+
return { state: job.state, jobId: job.jobId, ...(job.message ? { message: job.message } : {}) };
|
|
635
|
+
}
|
|
636
|
+
catch {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
//# sourceMappingURL=talk-transcribe.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"talk-transcribe.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/talk/talk-transcribe.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,aAAa,EAAwB,MAAM,2BAA2B,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAGjF,MAAM,CAAC,GAAG,yBAAyB,CAAC;AAKpC,oCAAoC;AACpC,MAAM,CAAC,MAAM,2BAA2B,GAAiD;IACxF,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC,EAAE,GAAG;IAClC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,GAAG;IACvB,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,GAAG;IACtB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG;IACrB,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG;IACnB,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,GAAG;IAC5B,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,GAAG;CACzB,CAAC;AAEF,8FAA8F;AAC9F,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAM5B;IALjB;;;OAGG;IACH,YACiB,IAAwB,EACxC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAoB;QAIxC,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACnC,CAAC;IAED,oCAAoC;IACpC,IAAI,UAAU;QACb,OAAO,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;CACD;AA+ED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,IAAY;IACxC,IAAI,CAAC;QACJ,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACvC,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,IAAY;IAClC,IAAI,CAAC;QACJ,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAChD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACzD,OAAO,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AACxC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,QAAgB;IAC3D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,4BAA4B,CAAC,CAAC;IACpF,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9E,IAAI,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,2BAA2B,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9G,CAAC;IACD,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,2BAA2B,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACzC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAC;IAC/F,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;QAC7B,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,2BAA2B,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9G,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAwB,EAAE,WAAmB,CAAC,CAAC,eAAe;IACnG,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,EAAE;YAAE,SAAS;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,QAAQ;YAAE,MAAM;QAC7C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;AACrF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAA2B;IACvD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,SAAS,CAAC,yBAAyB;QAC7F,IAAI,GAAG,IAAI,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,GAAG,IAAI,GAAG,CAAC;QAClF,GAAG,IAAI,GAAG,CAAC;IACZ,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;AACnB,CAAC;AAQD,4CAA4C;AAC5C,MAAM,OAAO,qBAAqB;IAChB,GAAG,CAAa;IAChB,GAAG,CAAoB;IACvB,OAAO,CAAe;IACtB,UAAU,CAAe;IACzB,YAAY,CAA4B;IACxC,MAAM,CAA4B;IAClC,UAAU,CAA0B;IACpC,YAAY,CAAwB;IACpC,YAAY,CAAyC;IACrD,UAAU,CAA6D;IACvE,MAAM,CAAe;IACrB,QAAQ,CAAe;IACvB,GAAG,CAAe;IAClB,MAAM,CAAkB;IACxB,aAAa,CAAS;IACtB,cAAc,CAAS;IACvB,gBAAgB,CAAS;IACzB,QAAQ,CAAS;IAE1B,OAAO,GAAG,KAAK,CAAC;IACP,OAAO,GAAsB,EAAE,CAAC;IACzC,SAAS,GAA0B,IAAI,CAAC;IAEhD;;OAEG;IACH,YAAY,OAA2B,EAAE;QACxC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,UAAU,CAAC;QAClC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,OAAO,CAAC;QAC1C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,iBAAiB,CAAC;QACvD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,mBAAmB,CAAC;QAC7D,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC;QAC3C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;QACnE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;QAC5C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC;QACxC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,MAAM,CAAC;QACvC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;QAC1D,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC,WAAW,EAAE,CAAC,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;QACjG,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,MAAM,EAAE,aAAa,IAAI,CAAC,CAAC,eAAe,CAAC;QACrE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,MAAM,EAAE,cAAc,IAAI,CAAC,CAAC,gBAAgB,CAAC;QACxE,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,MAAM,EAAE,gBAAgB,IAAI,CAAC,CAAC,kBAAkB,CAAC;QAC9E,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC;IACtD,CAAC;IAED,4EAA4E;IAC5E,wDAAwD;IACxD,4EAA4E;IAE5E;;;;;;OAMG;IACK,UAAU;QACjB,MAAM,OAAO,GAAG;YACf,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,qBAAqB,CAAC,OAAO,CAAC;YAC3D,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;YAC9C,GAAG,qBAAqB,CAAC,kBAAkB;SAC3C,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC9B,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnD,CAAC;IAED;;;;;;;;;OASG;IACH,aAAa;QACZ,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5B,IAAI,UAAU,GAAkB,IAAI,CAAC;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC;QAC5C,IAAI,MAAM,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;YAAE,UAAU,GAAG,MAAM,CAAC;QAC7D,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;YACzC,IAAI,UAAU;gBAAE,MAAM;YACtB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC/B,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;gBAAE,UAAU,GAAG,CAAC,CAAC;QAC1C,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QACrC,UAAU,KAAK,iBAAiB,CAAC,CAAC,CAAC,cAAc,EAAE,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAElF,IAAI,KAAK,GAAkB,IAAI,CAAC;QAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,qBAAqB,CAAC;QAChD,IAAI,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;YAAE,KAAK,GAAG,QAAQ,CAAC;QACxD,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;YAChC,IAAI,KAAK;gBAAE,MAAM;YACjB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC;YACjD,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;gBAAE,KAAK,GAAG,CAAC,CAAC;QAC/B,CAAC;QAED,MAAM,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,aAAa,EAAE,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QACjF,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IACtC,CAAC;IAED;;;;;OAKG;IACK,YAAY,CAAC,MAAqB;QACzC,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,UAAU;YAAE,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM,CAAC,KAAK;YAAE,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACjD,OAAO,OAAO,CAAC;IAChB,CAAC;IAED;;;;OAIG;IACH,OAAO;QACN,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IAC7D,CAAC;IAED;;;;OAIG;IACH,YAAY;QACX,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7C,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,UAAU;QACvB,IAAI,CAAC;YACJ,OAAO,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC;QACtB,CAAC;IACF,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,SAAS;QACd,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QACxC,OAAO;YACN,YAAY,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;YAClC,OAAO;YACP,UAAU,EAAE,MAAM,IAAI,CAAC,UAAU,EAAE;YACnC,IAAI,EAAE,IAAI,CAAC,OAAO;YAClB,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;YAC3B,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3B,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,YAAY;QACzB,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;QACjC,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YAC5E,IAAI,KAAK;gBAAE,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACnC,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,UAAU;QACf,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACpB,IAAI,CAAC,SAAS,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;YACrE,OAAO,IAAI,CAAC,SAAS,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,wCAAwC,CAAC,CAAC;QAChH,IAAI,CAAC;YACJ,IAAI,CAAC,SAAS,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,IAAI,CAAC,SAAS,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;YAC9C,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gCAAgC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;QACjH,OAAO,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;IAED,4EAA4E;IAC5E,gBAAgB;IAChB,4EAA4E;IAE5E;;;;;OAKG;IACK,OAAO,CAAC,QAAgB;QAC/B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACnB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC1B,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1C,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,yDAAyD,CAAC,CAAC,CAAC;QACzH,CAAC;QACD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC5C,MAAM,KAAK,GAAG,GAAS,EAAE;gBACxB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;YACX,CAAC,CAAC;YACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACtC,IAAI,CAAC,IAAI,CAAC;oBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACtC,MAAM,CAAC,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,kDAAkD,CAAC,CAAC,CAAC;YACtG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACvC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,oDAAoD;IAC5C,OAAO;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,IAAI;YAAE,IAAI,EAAE,CAAC;;YACZ,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IAC3B,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,UAAU,CAAC,KAAsB;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC;QACjD,MAAM,QAAQ,GAAG,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,MAAM,GAAG,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,2BAA2B,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC;QAC/H,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;QAC5H,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAE,CAAC,CAAC,SAA+B,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5F,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,2BAA2B,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3G,CAAC;QACD,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,EAAE,mDAAmD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtI,CAAC;QAED,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC7B,IAAI,OAAO,GAAkB,IAAI,CAAC;QAClC,IAAI,CAAC;YACJ,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;YAClE,MAAM,KAAK,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,UAAoB,EAAE,KAAK,EAAE,MAAM,CAAC,KAAe,EAAE,MAAM,EAAE,MAAM,CAAC,MAAgB,EAAE,CAAC;YAC1H,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACpF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE;gBACzC,KAAK,EAAE,KAAK,CAAC,MAAM;gBACnB,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM;gBACzB,SAAS;aACT,CAAC,CAAC;YACH,OAAO,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,CAAC;QAC7F,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,GAAG,KAAK,YAAY,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;YAChF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;YAC9G,IAAI,KAAK,YAAY,mBAAmB;gBAAE,MAAM,KAAK,CAAC;YACtD,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;QACvE,CAAC;gBAAS,CAAC;YACV,IAAI,OAAO,EAAE,CAAC;gBACb,IAAI,CAAC;oBACJ,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACtD,CAAC;gBAAC,MAAM,CAAC;oBACR,oDAAoD;gBACrD,CAAC;YACF,CAAC;YACD,IAAI,CAAC,OAAO,EAAE,CAAC;QAChB,CAAC;IACF,CAAC;IAED;;;;;;;;;;OAUG;IACK,KAAK,CAAC,SAAS,CACtB,MAA6D,EAC7D,OAAe,EACf,KAAa,EACb,GAAW,EACX,QAAgB,EAChB,QAAgB;QAEhB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,EAAE,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QAC3C,EAAE,CAAC,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QAErD,MAAM,SAAS,GAAG,GAAW,EAAE;YAC9B,MAAM,EAAE,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACjC,IAAI,EAAE,IAAI,CAAC;gBAAE,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC;YACvF,OAAO,EAAE,CAAC;QACX,CAAC,CAAC;QAEF,yFAAyF;QACzF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAC1B,MAAM,CAAC,MAAM,EACb,CAAC,cAAc,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,cAAc,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC,EACxL,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,CAC/B,CAAC;QACF,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACrB,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;gBAAE,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC;YACxI,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,4BAA4B,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,IAAI,CAAC;YACJ,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACR,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,4BAA4B,CAAC,CAAC;QACpF,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,QAAQ,GAAG,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC;QACvF,IAAI,WAAW,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;YACvC,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,2BAA2B,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC;QACrG,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;QAEpD,MAAM,MAAM,GAAG,qBAAqB,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9E,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,gBAAgB,CAAC,CAAC;QAC9E,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CACzB,MAAM,CAAC,UAAU,EACjB,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,EACvH,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,CAC/B,CAAC;QACF,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACpB,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;gBAAE,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC;YACtI,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,2BAA2B,GAAG,CAAC,IAAI,IAAI,UAAU,EAAE,CAAC,CAAC;QACpG,CAAC;QACD,IAAI,MAAmB,CAAC;QACxB,IAAI,CAAC;YACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,MAAM,OAAO,EAAE,MAAM,CAAC,CAAgB,CAAC;QAC/E,CAAC;QAAC,MAAM,CAAC;YACR,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,yCAAyC,CAAC,CAAC;QAC1F,CAAC;QACD,IAAI,IAAI,GAAG,YAAY,CAAC,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7G,0EAA0E;QAC1E,IAAI,IAAI,KAAK,CAAC,CAAC,aAAa,IAAI,IAAI,KAAK,MAAM;YAAE,IAAI,GAAG,EAAE,CAAC;QAC3D,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,IAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACtF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;IAC3D,CAAC;CACD;AAED,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,IAAI,QAAQ,GAAiC,IAAI,CAAC;AAElD;;;;GAIG;AACH,MAAM,UAAU,wBAAwB;IACvC,QAAQ,KAAK,IAAI,qBAAqB,CAAC,WAAW,EAAE,CAAC,CAAC;IACtD,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAqC;IAC7E,QAAQ,GAAG,OAAO,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B;IACzC,IAAI,CAAC;QACJ,OAAO,wBAAwB,EAAE,CAAC,YAAY,EAAE,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC;AAED;;;;;GAKG;AACH,SAAS,WAAW;IACnB,OAAO;QACN,UAAU,EAAE,KAAK,IAAI,EAAE;YACtB,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,4BAA4B,CAAC,CAAC;YACtE,MAAM,OAAO,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC;YAC7C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACzH,MAAM,KAAK,GAAa,EAAE,CAAC;YAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBAC1B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACtB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE;oBAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAClE,CAAC;YACD,KAAK,MAAM,OAAO,IAAI,QAAQ;gBAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACzD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;QAChE,CAAC;QACD,UAAU,EAAE,KAAK,IAAI,EAAE;YACtB,MAAM,EAAE,qBAAqB,EAAE,GAAG,MAAM,MAAM,CAAC,qCAAqC,CAAC,CAAC;YACtF,OAAO,CAAC,MAAM,qBAAqB,CAAC,WAAW,EAAE,CAAC,mBAAmB,EAAE,CAAC,CAAC,UAAU,CAAC;QACrF,CAAC;QACD,UAAU,EAAE,KAAK,IAAI,EAAE;YACtB,MAAM,EAAE,yBAAyB,EAAE,GAAG,MAAM,MAAM,CAAC,6CAA6C,CAAC,CAAC;YAClG,MAAM,OAAO,GAAG,MAAM,yBAAyB,EAAE,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9G,IAAI,OAAO,CAAC,IAAI,KAAK,eAAe;gBAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;YACjG,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QACjI,CAAC;QACD,QAAQ,EAAE,KAAK,EAAE,KAAa,EAAE,EAAE;YACjC,MAAM,EAAE,yBAAyB,EAAE,GAAG,MAAM,MAAM,CAAC,6CAA6C,CAAC,CAAC;YAClG,IAAI,CAAC;gBACJ,MAAM,GAAG,GAAG,yBAAyB,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACtD,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;YACjG,CAAC;YAAC,MAAM,CAAC;gBACR,OAAO,IAAI,CAAC;YACb,CAAC;QACF,CAAC;KACD,CAAC;AACH,CAAC"}
|