feat(server): refactor record schema (#14729)

#### PR Dependency Tree


* **PR #14729** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Transcriptions now produce structured meeting summaries (strict JSON)
and a normalized, speaker‑tagged, non‑overlapping transcript with legacy
projection support.

* **API**
* Submission accepts richer transcription input; results return
source‑audio metadata, slice manifest, quality indicators, normalized
segments/transcript, and structured summary JSON.

* **Frontend**
* Recording flow stores transcription metadata and uploads preprocessed
audio slices with slice/quality info; UI-side result normalization
applied.

* **Tests**
* Expanded unit, contract, and e2e coverage for normalization, payload
parsing, persistence/retry, and end‑to‑end transcription flows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-03-26 21:32:36 +08:00
committed by GitHub
parent a3379c8979
commit 5b05c5a1b2
29 changed files with 2977 additions and 463 deletions
+24 -16
View File
@@ -1,6 +1,5 @@
import * as dns from 'node:dns/promises';
import { BlockList, isIP } from 'node:net';
import { Readable } from 'node:stream';
import { ResponseTooLargeError, SsrfBlockedError } from '../error/errors.gen';
import { OneMinute } from './unit';
@@ -298,36 +297,45 @@ export async function readResponseBufferWithLimit(
return Buffer.alloc(0);
}
// Convert Web ReadableStream -> Node Readable for consistent limit handling.
const nodeStream = Readable.fromWeb(response.body);
const chunks: Buffer[] = [];
const reader = response.body.getReader();
const chunks: Uint8Array[] = [];
let total = 0;
try {
for await (const chunk of nodeStream) {
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
total += buf.length;
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = value ?? new Uint8Array();
total += chunk.byteLength;
if (total > limitBytes) {
try {
nodeStream.destroy();
await reader.cancel();
} catch {
// ignore
}
throw new ResponseTooLargeError({ limitBytes, receivedBytes: total });
}
chunks.push(buf);
chunks.push(chunk);
}
} catch (error) {
try {
await reader.cancel(error);
} catch {
// ignore
}
throw error;
} finally {
if (total > limitBytes) {
try {
await response.body?.cancel();
} catch {
// ignore
}
try {
reader.releaseLock();
} catch {
// ignore
}
}
return Buffer.concat(chunks, total);
return Buffer.concat(
chunks.map(chunk => Buffer.from(chunk)),
total
);
}
type FetchBufferResult = { buffer: Buffer; type: string };