@gotcos/glasses-server 6.36.28 → 6.37.1
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/.env.example +22 -0
- package/CHANGELOG.md +133 -0
- package/package.json +1 -1
- package/server/lib/adaptive-playback-audio.ts +4 -1
- package/server/lib/even-hub-speaker-role.ts +116 -0
- package/server/lib/meeting-store.ts +13 -4
- package/server/lib/meeting-summary-budget.ts +90 -0
- package/server/lib/meeting-summary-persistence.ts +179 -0
- package/server/lib/meeting-summary.ts +429 -0
- package/server/lib/speaker-name.ts +89 -0
- package/server/lib/video-upload-v2.ts +5 -1
- package/server/routes/agent-session-bindings.ts +3 -1
- package/server/routes/health.ts +7 -0
- package/server/routes/meeting.ts +22 -1
- package/server/routes/transcribe-stream.ts +25 -0
- package/server/routes/voice.ts +15 -0
|
@@ -61,6 +61,14 @@ import {
|
|
|
61
61
|
appendChunkEmbedding,
|
|
62
62
|
sweepExpiredChunkEmbeddings,
|
|
63
63
|
} from '../lib/chunk-embedding-store.js'
|
|
64
|
+
import {
|
|
65
|
+
evenSpeakerRoleMode,
|
|
66
|
+
formatEvenRoleAgreement,
|
|
67
|
+
parseEvenHubSpeakerRoleBody,
|
|
68
|
+
parseEvenHubSpeakerRoleQuery,
|
|
69
|
+
warnEvenSpeakerRoleApplyNotImplemented,
|
|
70
|
+
type EvenSpeakerRoleHistogram,
|
|
71
|
+
} from '../lib/even-hub-speaker-role.js'
|
|
64
72
|
import { archiveSessionAudio, runMeetingAudioRetention } from '../lib/meeting-audio-archive.js'
|
|
65
73
|
import {
|
|
66
74
|
countChunkWavs,
|
|
@@ -276,6 +284,7 @@ export interface TranscriptChunk {
|
|
|
276
284
|
latencyMs?: number
|
|
277
285
|
audioSha256?: string
|
|
278
286
|
canonical?: boolean
|
|
287
|
+
evenHubSpeakerRole?: EvenSpeakerRoleHistogram
|
|
279
288
|
}
|
|
280
289
|
|
|
281
290
|
export interface ProviderCandidateRecord {
|
|
@@ -1945,8 +1954,11 @@ async function processStreamChunk(opts: {
|
|
|
1945
1954
|
clientElapsed?: number
|
|
1946
1955
|
/** Original client recording start, applied only before canonical chunks. */
|
|
1947
1956
|
startTimeOverride?: number
|
|
1957
|
+
evenHubSpeakerRole?: EvenSpeakerRoleHistogram
|
|
1948
1958
|
}): Promise<StreamChunkCompletionResponse> {
|
|
1949
1959
|
const { sessionId, chunkIndex, clientSpeaker, audioBuffer, candidate } = opts
|
|
1960
|
+
const evenHubSpeakerRole = evenSpeakerRoleMode() === 'off' ? undefined : opts.evenHubSpeakerRole
|
|
1961
|
+
if (evenHubSpeakerRole) warnEvenSpeakerRoleApplyNotImplemented()
|
|
1950
1962
|
const tReq = performance.now()
|
|
1951
1963
|
validateSessionId(sessionId)
|
|
1952
1964
|
validateChunkIndex(chunkIndex)
|
|
@@ -2066,6 +2078,15 @@ async function processStreamChunk(opts: {
|
|
|
2066
2078
|
}
|
|
2067
2079
|
|
|
2068
2080
|
const { speaker, similarity } = await speakerPromise
|
|
2081
|
+
if (evenHubSpeakerRole) {
|
|
2082
|
+
console.log(formatEvenRoleAgreement({
|
|
2083
|
+
chunkIndex,
|
|
2084
|
+
even: evenHubSpeakerRole,
|
|
2085
|
+
amp: clientSpeaker,
|
|
2086
|
+
emb: speaker,
|
|
2087
|
+
similarity,
|
|
2088
|
+
}))
|
|
2089
|
+
}
|
|
2069
2090
|
// Client time is authoritative for live network jitter and deferred replay.
|
|
2070
2091
|
const elapsed = Number.isFinite(opts.clientElapsed) && (opts.clientElapsed as number) >= 0
|
|
2071
2092
|
? Math.round(opts.clientElapsed as number)
|
|
@@ -2110,6 +2131,7 @@ async function processStreamChunk(opts: {
|
|
|
2110
2131
|
latencyMs,
|
|
2111
2132
|
audioSha256,
|
|
2112
2133
|
canonical: true,
|
|
2134
|
+
evenHubSpeakerRole,
|
|
2113
2135
|
}
|
|
2114
2136
|
const finalExisting = session.chunks[chunkIndex]
|
|
2115
2137
|
if (finalExisting?.text) {
|
|
@@ -2263,6 +2285,7 @@ transcribeStreamRouter.post('/transcribe-stream', async (req, res) => {
|
|
|
2263
2285
|
audioBuffer,
|
|
2264
2286
|
clientElapsed,
|
|
2265
2287
|
startTimeOverride,
|
|
2288
|
+
evenHubSpeakerRole: parseEvenHubSpeakerRoleQuery(req.query.eh),
|
|
2266
2289
|
}))
|
|
2267
2290
|
} catch (err: unknown) {
|
|
2268
2291
|
sendStreamError(res, err)
|
|
@@ -2327,6 +2350,7 @@ transcribeStreamRouter.post('/transcribe-stream/offline-sessions/:sessionId/chun
|
|
|
2327
2350
|
clientSpeaker: String(body.clientSpeaker ?? 'Unknown'),
|
|
2328
2351
|
audioBuffer,
|
|
2329
2352
|
clientElapsed: typeof body.elapsed === 'number' ? body.elapsed : Number(body.elapsed ?? 0),
|
|
2353
|
+
evenHubSpeakerRole: parseEvenHubSpeakerRoleBody(body.evenHubSpeakerRole),
|
|
2330
2354
|
candidate: {
|
|
2331
2355
|
provider: 'iphone-whisperkit-beta',
|
|
2332
2356
|
text: normalizeCandidateText(candidate.text),
|
|
@@ -2405,6 +2429,7 @@ transcribeStreamRouter.post('/transcribe-stream/candidates', async (req, res) =>
|
|
|
2405
2429
|
clientSpeaker: String(body.clientSpeaker ?? 'Unknown'),
|
|
2406
2430
|
audioBuffer,
|
|
2407
2431
|
clientElapsed: typeof body.elapsed === 'number' ? body.elapsed : Number(body.elapsed ?? 0),
|
|
2432
|
+
evenHubSpeakerRole: parseEvenHubSpeakerRoleBody(body.evenHubSpeakerRole),
|
|
2408
2433
|
candidate: {
|
|
2409
2434
|
provider: 'iphone-whisperkit-beta',
|
|
2410
2435
|
text: normalizeCandidateText(candidate.text),
|
package/server/routes/voice.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Router } from 'express'
|
|
|
4
4
|
import { errMsg } from '../lib/utils.js'
|
|
5
5
|
import { readdirSync, readFileSync, unlinkSync, existsSync, rmdirSync, rmSync } from 'node:fs'
|
|
6
6
|
import { resolve } from 'node:path'
|
|
7
|
+
import { checkSpeakerName } from '../lib/speaker-name.js'
|
|
7
8
|
import { enrollSpeaker, isEnrolled, getAllSpeakerNames, identifySpeaker, extractEmbedding, enrollEmbedding, getEmbeddingCount, removeSpeakerProfile, readVoiceProfiles, mergeSpeakerProfiles } from '../lib/speaker-embeddings.js'
|
|
8
9
|
import { statSync } from 'node:fs'
|
|
9
10
|
import { trainFromFireflies, getTrainingStatus } from '../lib/speaker-trainer.js'
|
|
@@ -48,6 +49,20 @@ voiceRouter.post('/voice/enroll', async (req, res) => {
|
|
|
48
49
|
// under a stranger's name.
|
|
49
50
|
const name = (req.query.name as string) || getOwnerSpeakerLabel()
|
|
50
51
|
|
|
52
|
+
// An old client can still send a whole spoken sentence as the name (the
|
|
53
|
+
// "enroll my voice" fall-through). Refuse it here: a junk profile can
|
|
54
|
+
// never match owner_speaker_label, so /voice/status would report
|
|
55
|
+
// enrolled:false forever, and the store cannot be repaired by editing
|
|
56
|
+
// voice-profiles.json — the server rewrites it from memory.
|
|
57
|
+
const nameCheck = checkSpeakerName(name, { ownerLabel: getOwnerSpeakerLabel() })
|
|
58
|
+
if (!nameCheck.ok) {
|
|
59
|
+
return res.status(400).json({
|
|
60
|
+
success: false,
|
|
61
|
+
error: nameCheck.message,
|
|
62
|
+
reason: nameCheck.reason,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
51
66
|
// Collect raw audio body
|
|
52
67
|
const buffers: Buffer[] = []
|
|
53
68
|
for await (const chunk of req) {
|