@leemour/cli-messaging 0.40.0 → 0.41.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/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/messenger/chats-command.d.ts +8 -1
- package/dist/cli/messenger/chats-command.d.ts.map +1 -1
- package/dist/cli/messenger/chats-command.js +39 -0
- package/dist/cli/messenger/chats-command.js.map +1 -1
- package/dist/cli/messenger/context.d.ts +2 -0
- package/dist/cli/messenger/context.d.ts.map +1 -1
- package/dist/cli/messenger/context.js.map +1 -1
- package/dist/cli/messenger/models-command.d.ts +9 -0
- package/dist/cli/messenger/models-command.d.ts.map +1 -0
- package/dist/cli/messenger/models-command.js +69 -0
- package/dist/cli/messenger/models-command.js.map +1 -0
- package/dist/cli/messenger/port.d.ts +5 -1
- package/dist/cli/messenger/port.d.ts.map +1 -1
- package/dist/cli/messenger/port.js.map +1 -1
- package/dist/cli/messenger/transcribe-command.d.ts +1 -1
- package/dist/cli/messenger/transcribe-command.d.ts.map +1 -1
- package/dist/cli/messenger/transcribe-command.js +16 -7
- package/dist/cli/messenger/transcribe-command.js.map +1 -1
- package/dist/cli/settings.d.ts +4 -0
- package/dist/cli/settings.d.ts.map +1 -1
- package/dist/cli/settings.js +2 -0
- package/dist/cli/settings.js.map +1 -1
- package/dist/domain/models.d.ts +26 -0
- package/dist/domain/models.d.ts.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool.d.ts +4 -0
- package/dist/mcp/tool.d.ts.map +1 -1
- package/dist/mcp/tool.js.map +1 -1
- package/dist/mcp/tools/chats.d.ts.map +1 -1
- package/dist/mcp/tools/chats.js +14 -1
- package/dist/mcp/tools/chats.js.map +1 -1
- package/dist/mcp/tools/messages-transcribe.d.ts.map +1 -1
- package/dist/mcp/tools/messages-transcribe.js +15 -8
- package/dist/mcp/tools/messages-transcribe.js.map +1 -1
- package/dist/speech/install.d.ts +23 -0
- package/dist/speech/install.d.ts.map +1 -0
- package/dist/speech/install.js +106 -0
- package/dist/speech/install.js.map +1 -0
- package/dist/speech/models.d.ts +38 -0
- package/dist/speech/models.d.ts.map +1 -0
- package/dist/speech/models.js +130 -0
- package/dist/speech/models.js.map +1 -0
- package/dist/speech/recognize.d.ts +24 -0
- package/dist/speech/recognize.d.ts.map +1 -0
- package/dist/speech/recognize.js +106 -0
- package/dist/speech/recognize.js.map +1 -0
- package/dist/speech/transcribe.d.ts +36 -0
- package/dist/speech/transcribe.d.ts.map +1 -0
- package/dist/speech/transcribe.js +80 -0
- package/dist/speech/transcribe.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
export const SAMPLE_RATE = 16_000;
|
|
3
|
+
/**
|
|
4
|
+
* Half a second of silence ahead of the speech. Without it the voice detector clipped a recording
|
|
5
|
+
* that starts talking at once: "Ничьих" came back as "чьих" (measured 2026-09-24).
|
|
6
|
+
*/
|
|
7
|
+
const LEAD = SAMPLE_RATE / 2;
|
|
8
|
+
/**
|
|
9
|
+
* Voice messages are Ogg Opus at 48 kHz (measured on a MAX one), and the models want 16 kHz. Averaging each three samples is ours rather than the engine's resampler, which announces
|
|
10
|
+
* itself on stderr on every call.
|
|
11
|
+
*/
|
|
12
|
+
export const toModelRate = (samples, rate) => {
|
|
13
|
+
const step = rate / SAMPLE_RATE;
|
|
14
|
+
const length = Math.floor(samples.length / step);
|
|
15
|
+
const pcm = new Float32Array(LEAD + length);
|
|
16
|
+
for (let i = 0; i < length; i++) {
|
|
17
|
+
const from = Math.floor(i * step);
|
|
18
|
+
const to = Math.max(from + 1, Math.floor((i + 1) * step));
|
|
19
|
+
let sum = 0;
|
|
20
|
+
for (let j = from; j < to; j++)
|
|
21
|
+
sum += samples[j] ?? 0;
|
|
22
|
+
pcm[LEAD + i] = sum / (to - from);
|
|
23
|
+
}
|
|
24
|
+
return pcm;
|
|
25
|
+
};
|
|
26
|
+
export const decodeOgg = async (bytes) => {
|
|
27
|
+
const { OggOpusDecoder } = await import("ogg-opus-decoder");
|
|
28
|
+
const decoder = new OggOpusDecoder();
|
|
29
|
+
await decoder.ready;
|
|
30
|
+
try {
|
|
31
|
+
const { channelData, sampleRate, samplesDecoded } = await decoder.decodeFile(bytes);
|
|
32
|
+
const samples = channelData[0];
|
|
33
|
+
if (!samples || samplesDecoded === 0)
|
|
34
|
+
throw new Error("no Ogg Opus audio in it");
|
|
35
|
+
return { samples, rate: sampleRate };
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
decoder.free();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const WINDOW = 512;
|
|
42
|
+
/**
|
|
43
|
+
* The model, and a voice detector that cuts the audio at pauses into pieces of at most 20 seconds.
|
|
44
|
+
* The cutting is not optional: five minutes in one piece crash the WebAssembly build (measured).
|
|
45
|
+
*
|
|
46
|
+
* `sherpa-onnx` is loaded here, on first use, so no other command pays for its 15 MB of WebAssembly.
|
|
47
|
+
*/
|
|
48
|
+
export const openRecognizer = (model, path, vadModel) => {
|
|
49
|
+
const sherpa = createRequire(import.meta.url)("sherpa-onnx");
|
|
50
|
+
const engine = sherpa.createOfflineRecognizer({
|
|
51
|
+
featConfig: { sampleRate: SAMPLE_RATE, featureDim: model.featureDim },
|
|
52
|
+
modelConfig: { ...model.config(path), numThreads: 1, provider: "cpu", debug: 0 },
|
|
53
|
+
decodingMethod: "greedy_search",
|
|
54
|
+
});
|
|
55
|
+
const vad = sherpa.createVad({
|
|
56
|
+
sileroVad: {
|
|
57
|
+
model: vadModel,
|
|
58
|
+
threshold: 0.5,
|
|
59
|
+
minSilenceDuration: 0.5,
|
|
60
|
+
minSpeechDuration: 0.25,
|
|
61
|
+
maxSpeechDuration: 20,
|
|
62
|
+
windowSize: WINDOW,
|
|
63
|
+
},
|
|
64
|
+
sampleRate: SAMPLE_RATE,
|
|
65
|
+
numThreads: 1,
|
|
66
|
+
provider: "cpu",
|
|
67
|
+
debug: 0,
|
|
68
|
+
bufferSizeInSeconds: 30,
|
|
69
|
+
});
|
|
70
|
+
const piece = (samples) => {
|
|
71
|
+
const stream = engine.createStream();
|
|
72
|
+
try {
|
|
73
|
+
stream.acceptWaveform(SAMPLE_RATE, samples);
|
|
74
|
+
engine.decode(stream);
|
|
75
|
+
return engine.getResult(stream).text.trim();
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
stream.free();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
recognize: (pcm) => {
|
|
83
|
+
// One recognizer can hear several recordings in a row; nothing of the last may leak into this one.
|
|
84
|
+
vad.reset();
|
|
85
|
+
const parts = [];
|
|
86
|
+
const drain = () => {
|
|
87
|
+
while (!vad.isEmpty()) {
|
|
88
|
+
parts.push(piece(vad.front().samples));
|
|
89
|
+
vad.pop();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
for (let i = 0; i + WINDOW <= pcm.length; i += WINDOW) {
|
|
93
|
+
vad.acceptWaveform(pcm.subarray(i, i + WINDOW));
|
|
94
|
+
drain();
|
|
95
|
+
}
|
|
96
|
+
vad.flush();
|
|
97
|
+
drain();
|
|
98
|
+
return parts.filter(Boolean).join(" ");
|
|
99
|
+
},
|
|
100
|
+
free: () => {
|
|
101
|
+
vad.free();
|
|
102
|
+
engine.free();
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=recognize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recognize.js","sourceRoot":"","sources":["../../src/speech/recognize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAS3C,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAA;AAEjC;;;GAGG;AACH,MAAM,IAAI,GAAG,WAAW,GAAG,CAAC,CAAA;AAE5B;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAqB,EAAE,IAAY,EAAgB,EAAE;IAC/E,MAAM,IAAI,GAAG,IAAI,GAAG,WAAW,CAAA;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAChD,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,IAAI,GAAG,MAAM,CAAC,CAAA;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;QACjC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAA;QACzD,IAAI,GAAG,GAAG,CAAC,CAAA;QACX,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE;YAAE,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QACtD,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAA;IACnC,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,KAAiB,EAAoD,EAAE;IACrG,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;IAC3D,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAA;IACpC,MAAM,OAAO,CAAC,KAAK,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;QACnF,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;QAC9B,IAAI,CAAC,OAAO,IAAI,cAAc,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;QAChF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA;IACtC,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,IAAI,EAAE,CAAA;IAChB,CAAC;AACH,CAAC,CAAA;AAwBD,MAAM,MAAM,GAAG,GAAG,CAAA;AAElB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAkB,EAAE,IAA8B,EAAE,QAAgB,EAAc,EAAE;IACjH,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,aAAa,CAAW,CAAA;IACtE,MAAM,MAAM,GAAG,MAAM,CAAC,uBAAuB,CAAC;QAC5C,UAAU,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE;QACrE,WAAW,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE;QAChF,cAAc,EAAE,eAAe;KAChC,CAAC,CAAA;IACF,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC;QAC3B,SAAS,EAAE;YACT,KAAK,EAAE,QAAQ;YACf,SAAS,EAAE,GAAG;YACd,kBAAkB,EAAE,GAAG;YACvB,iBAAiB,EAAE,IAAI;YACvB,iBAAiB,EAAE,EAAE;YACrB,UAAU,EAAE,MAAM;SACnB;QACD,UAAU,EAAE,WAAW;QACvB,UAAU,EAAE,CAAC;QACb,QAAQ,EAAE,KAAK;QACf,KAAK,EAAE,CAAC;QACR,mBAAmB,EAAE,EAAE;KACxB,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,CAAC,OAAqB,EAAU,EAAE;QAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,EAAE,CAAA;QACpC,IAAI,CAAC;YACH,MAAM,CAAC,cAAc,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;YAC3C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACrB,OAAO,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;QAC7C,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,IAAI,EAAE,CAAA;QACf,CAAC;IACH,CAAC,CAAA;IAED,OAAO;QACL,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;YACjB,mGAAmG;YACnG,GAAG,CAAC,KAAK,EAAE,CAAA;YACX,MAAM,KAAK,GAAa,EAAE,CAAA;YAC1B,MAAM,KAAK,GAAG,GAAG,EAAE;gBACjB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;oBACtB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;oBACtC,GAAG,CAAC,GAAG,EAAE,CAAA;gBACX,CAAC;YACH,CAAC,CAAA;YACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC;gBACtD,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAA;gBAC/C,KAAK,EAAE,CAAA;YACT,CAAC;YACD,GAAG,CAAC,KAAK,EAAE,CAAA;YACX,KAAK,EAAE,CAAA;YACP,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACxC,CAAC;QACD,IAAI,EAAE,GAAG,EAAE;YACT,GAAG,CAAC,IAAI,EAAE,CAAA;YACV,MAAM,CAAC,IAAI,EAAE,CAAA;QACf,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CliError } from "@leemour/cli-core";
|
|
2
|
+
import type { Messenger } from "../cli/messenger/context.js";
|
|
3
|
+
import { type MessengerAdapter, type Transcript } from "../cli/messenger/port.js";
|
|
4
|
+
import { type Settings } from "../cli/settings.js";
|
|
5
|
+
import { type SpeechModel } from "./models.js";
|
|
6
|
+
/** `messenger`: its own recognition (Telegram Premium). `local`: a model on this machine. `auto`: the first, then the second. */
|
|
7
|
+
export declare const TRANSCRIBE_WITH: readonly ["auto", "messenger", "local"];
|
|
8
|
+
export type TranscribeWith = (typeof TRANSCRIBE_WITH)[number];
|
|
9
|
+
export interface Heard extends Transcript {
|
|
10
|
+
messageId: string;
|
|
11
|
+
/** The messenger's provider, or `local`. */
|
|
12
|
+
via: string;
|
|
13
|
+
/** The local model, when it was one. */
|
|
14
|
+
model?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface Choice {
|
|
17
|
+
with: TranscribeWith;
|
|
18
|
+
model: SpeechModel;
|
|
19
|
+
/** Where the models are, `modelsDirectory` for the command's environment. */
|
|
20
|
+
directory: string;
|
|
21
|
+
}
|
|
22
|
+
/** The flag, then the profile's `transcribeWith` and `speechModel`, then `auto` and the CLI's first model. */
|
|
23
|
+
export declare const choose: (messenger: Messenger, settings: Pick<Settings, "configured" | "shared">, flags: {
|
|
24
|
+
local?: boolean;
|
|
25
|
+
model?: string;
|
|
26
|
+
}, env?: NodeJS.ProcessEnv) => Choice;
|
|
27
|
+
/** Never a download: a model is hundreds of MB, and whether to fetch one is the owner's call. */
|
|
28
|
+
export declare const notDownloaded: (messenger: Messenger, model: SpeechModel) => CliError;
|
|
29
|
+
/**
|
|
30
|
+
* Over an open connection: the messenger's own transcript, or the recording's bytes for a local model.
|
|
31
|
+
* The model runs after the connection closes — a minute of speech should not hold a socket open.
|
|
32
|
+
*/
|
|
33
|
+
export declare const hearOnline: (messenger: Messenger, connection: MessengerAdapter, chat: string, messageId: string, choice: Choice) => Promise<Heard | Uint8Array>;
|
|
34
|
+
/** The local model, on the bytes `hearOnline` fetched. */
|
|
35
|
+
export declare const hearLocally: (bytes: Uint8Array, messageId: string, { model, directory }: Choice) => Promise<Heard>;
|
|
36
|
+
//# sourceMappingURL=transcribe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcribe.d.ts","sourceRoot":"","sources":["../../src/speech/transcribe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAc,MAAM,mBAAmB,CAAA;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAc,KAAK,gBAAgB,EAAmB,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAC9G,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAE5D,OAAO,EAAiB,KAAK,WAAW,EAAe,MAAM,aAAa,CAAA;AAE1E,iIAAiI;AACjI,eAAO,MAAM,eAAe,YAAI,MAAM,EAAE,WAAW,EAAE,OAAO,CAAU,CAAA;AACtE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7D,MAAM,WAAW,KAAM,SAAQ,UAAU;IACvC,SAAS,EAAE,MAAM,CAAA;IACjB,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAA;IACX,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAKD,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,cAAc,CAAA;IACpB,KAAK,EAAE,WAAW,CAAA;IAClB,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,8GAA8G;AAC9G,eAAO,MAAM,MAAM,cACN,SAAS,YACV,IAAI,CAAC,QAAQ,EAAE,YAAY,GAAG,QAAQ,CAAC,SAC1C;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,QACrC,MAAM,CAAC,UAAU,KACrB,MAUF,CAAA;AAED,iGAAiG;AACjG,eAAO,MAAM,aAAa,cAAe,SAAS,SAAS,WAAW,KAAG,QAKtE,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,UAAU,cACV,SAAS,cACR,gBAAgB,QACtB,MAAM,aACD,MAAM,UACT,MAAM,KACb,OAAO,CAAC,KAAK,GAAG,UAAU,CAkB5B,CAAA;AAED,0DAA0D;AAC1D,eAAO,MAAM,WAAW,UACf,UAAU,aACN,MAAM,wBACK,MAAM,KAC3B,OAAO,CAAC,KAAK,CAef,CAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { CliError, isCliError } from "@leemour/cli-core";
|
|
2
|
+
import { capability } from "../cli/messenger/port.js";
|
|
3
|
+
import { fromFile } from "../cli/settings.js";
|
|
4
|
+
import { installedBytes, isInstalled, megabytes, modelPath, modelsDirectory, vadPath } from "./install.js";
|
|
5
|
+
import { orderedModels, speechModel } from "./models.js";
|
|
6
|
+
/** `messenger`: its own recognition (Telegram Premium). `local`: a model on this machine. `auto`: the first, then the second. */
|
|
7
|
+
export const TRANSCRIBE_WITH = ["auto", "messenger", "local"];
|
|
8
|
+
/** A voice message is read into memory whole; minutes of Opus are a few megabytes. */
|
|
9
|
+
const LARGEST_VOICE = 32 * 1024 * 1024;
|
|
10
|
+
/** The flag, then the profile's `transcribeWith` and `speechModel`, then `auto` and the CLI's first model. */
|
|
11
|
+
export const choose = (messenger, settings, flags, env = process.env) => {
|
|
12
|
+
const model = flags.model ?? fromFile(settings, "speechModel", undefined).value;
|
|
13
|
+
return {
|
|
14
|
+
directory: modelsDirectory(env),
|
|
15
|
+
with: flags.local || flags.model !== undefined
|
|
16
|
+
? "local"
|
|
17
|
+
: fromFile(settings, "transcribeWith", "auto").value,
|
|
18
|
+
model: model === undefined ? orderedModels(messenger.speechModels)[0] : speechModel(model),
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/** Never a download: a model is hundreds of MB, and whether to fetch one is the owner's call. */
|
|
22
|
+
export const notDownloaded = (messenger, model) => new CliError("not_found", `the speech model ${model.id} is not on this machine — the owner downloads it once with ` +
|
|
23
|
+
`\`${messenger.app.command} models audio download ${model.id}\` (${megabytes(installedBytes(model))})`);
|
|
24
|
+
/**
|
|
25
|
+
* Over an open connection: the messenger's own transcript, or the recording's bytes for a local model.
|
|
26
|
+
* The model runs after the connection closes — a minute of speech should not hold a socket open.
|
|
27
|
+
*/
|
|
28
|
+
export const hearOnline = async (messenger, connection, chat, messageId, choice) => {
|
|
29
|
+
if (choice.with !== "local") {
|
|
30
|
+
const canAsk = typeof connection.transcribe === "function";
|
|
31
|
+
if (choice.with === "messenger" || canAsk) {
|
|
32
|
+
try {
|
|
33
|
+
const heard = await capability(connection, "transcribe", "transcribe voice messages")(chat, messageId);
|
|
34
|
+
return { messageId, ...heard, via: messenger.provider };
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
// `auto` falls back only when the messenger refuses this account; any other failure is real.
|
|
38
|
+
if (choice.with === "messenger" || !(isCliError(error) && error.code === "permission_error"))
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!isInstalled(choice.model, choice.directory))
|
|
44
|
+
throw notDownloaded(messenger, choice.model);
|
|
45
|
+
const { files } = await capability(connection, "download", "download attachments")(chat, messageId);
|
|
46
|
+
const voice = files.find((file) => file.kind === "voice" || file.mime === "audio/ogg");
|
|
47
|
+
if (!voice)
|
|
48
|
+
throw new CliError("validation_error", "that message is not a voice message");
|
|
49
|
+
return readAll(voice);
|
|
50
|
+
};
|
|
51
|
+
/** The local model, on the bytes `hearOnline` fetched. */
|
|
52
|
+
export const hearLocally = async (bytes, messageId, { model, directory }) => {
|
|
53
|
+
const { decodeOgg, openRecognizer, toModelRate } = await import("./recognize.js");
|
|
54
|
+
const { samples, rate } = await decodeOgg(bytes);
|
|
55
|
+
const recognizer = openRecognizer(model, modelPath(directory, model), vadPath(directory));
|
|
56
|
+
try {
|
|
57
|
+
return {
|
|
58
|
+
messageId,
|
|
59
|
+
text: recognizer.recognize(toModelRate(samples, rate)),
|
|
60
|
+
pending: false,
|
|
61
|
+
via: "local",
|
|
62
|
+
model: model.id,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
recognizer.free();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const readAll = async (file) => {
|
|
70
|
+
const chunks = [];
|
|
71
|
+
let total = 0;
|
|
72
|
+
for await (const chunk of file.bytes()) {
|
|
73
|
+
total += chunk.length;
|
|
74
|
+
if (total > LARGEST_VOICE)
|
|
75
|
+
throw new CliError("validation_error", "the voice message is larger than 32 MB");
|
|
76
|
+
chunks.push(chunk);
|
|
77
|
+
}
|
|
78
|
+
return new Uint8Array(Buffer.concat(chunks));
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=transcribe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcribe.js","sourceRoot":"","sources":["../../src/speech/transcribe.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leemour/cli-messaging",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
|
@@ -56,7 +56,9 @@
|
|
|
56
56
|
"@leemour/cli-core": "0.8.0",
|
|
57
57
|
"@modelcontextprotocol/server": "2.1.0",
|
|
58
58
|
"@valibot/to-json-schema": "1.8.0",
|
|
59
|
+
"ogg-opus-decoder": "1.7.5",
|
|
59
60
|
"qrcode-generator": "2.0.4",
|
|
61
|
+
"sherpa-onnx": "1.13.8",
|
|
60
62
|
"string-width": "8.3.0",
|
|
61
63
|
"valibot": "1.5.0",
|
|
62
64
|
"wrap-ansi": "10.0.2"
|