zerg-ztc 0.1.10 → 0.1.11

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.
Files changed (64) hide show
  1. package/dist/App.d.ts.map +1 -1
  2. package/dist/App.js +63 -2
  3. package/dist/App.js.map +1 -1
  4. package/dist/agent/commands/dictation.d.ts +3 -0
  5. package/dist/agent/commands/dictation.d.ts.map +1 -0
  6. package/dist/agent/commands/dictation.js +10 -0
  7. package/dist/agent/commands/dictation.js.map +1 -0
  8. package/dist/agent/commands/index.d.ts.map +1 -1
  9. package/dist/agent/commands/index.js +2 -1
  10. package/dist/agent/commands/index.js.map +1 -1
  11. package/dist/agent/commands/types.d.ts +7 -0
  12. package/dist/agent/commands/types.d.ts.map +1 -1
  13. package/dist/components/InputArea.d.ts +1 -0
  14. package/dist/components/InputArea.d.ts.map +1 -1
  15. package/dist/components/InputArea.js +591 -43
  16. package/dist/components/InputArea.js.map +1 -1
  17. package/dist/components/SingleMessage.d.ts.map +1 -1
  18. package/dist/components/SingleMessage.js +157 -7
  19. package/dist/components/SingleMessage.js.map +1 -1
  20. package/dist/config/types.d.ts +6 -0
  21. package/dist/config/types.d.ts.map +1 -1
  22. package/dist/ui/views/status_bar.js +2 -2
  23. package/dist/ui/views/status_bar.js.map +1 -1
  24. package/dist/utils/dictation.d.ts +46 -0
  25. package/dist/utils/dictation.d.ts.map +1 -0
  26. package/dist/utils/dictation.js +409 -0
  27. package/dist/utils/dictation.js.map +1 -0
  28. package/dist/utils/dictation_native.d.ts +51 -0
  29. package/dist/utils/dictation_native.d.ts.map +1 -0
  30. package/dist/utils/dictation_native.js +216 -0
  31. package/dist/utils/dictation_native.js.map +1 -0
  32. package/dist/utils/path_format.d.ts +20 -0
  33. package/dist/utils/path_format.d.ts.map +1 -0
  34. package/dist/utils/path_format.js +90 -0
  35. package/dist/utils/path_format.js.map +1 -0
  36. package/dist/utils/table.d.ts +38 -0
  37. package/dist/utils/table.d.ts.map +1 -0
  38. package/dist/utils/table.js +133 -0
  39. package/dist/utils/table.js.map +1 -0
  40. package/dist/utils/tool_trace.d.ts +7 -2
  41. package/dist/utils/tool_trace.d.ts.map +1 -1
  42. package/dist/utils/tool_trace.js +156 -51
  43. package/dist/utils/tool_trace.js.map +1 -1
  44. package/package.json +4 -1
  45. package/packages/ztc-dictation/Cargo.toml +43 -0
  46. package/packages/ztc-dictation/README.md +65 -0
  47. package/packages/ztc-dictation/bin/.gitkeep +0 -0
  48. package/packages/ztc-dictation/index.d.ts +16 -0
  49. package/packages/ztc-dictation/index.js +74 -0
  50. package/packages/ztc-dictation/package.json +41 -0
  51. package/packages/ztc-dictation/src/main.rs +430 -0
  52. package/src/App.tsx +98 -1
  53. package/src/agent/commands/dictation.ts +11 -0
  54. package/src/agent/commands/index.ts +2 -0
  55. package/src/agent/commands/types.ts +8 -0
  56. package/src/components/InputArea.tsx +606 -42
  57. package/src/components/SingleMessage.tsx +248 -9
  58. package/src/config/types.ts +7 -0
  59. package/src/ui/views/status_bar.ts +2 -2
  60. package/src/utils/dictation.ts +467 -0
  61. package/src/utils/dictation_native.ts +258 -0
  62. package/src/utils/path_format.ts +99 -0
  63. package/src/utils/table.ts +171 -0
  64. package/src/utils/tool_trace.ts +184 -54
@@ -0,0 +1,409 @@
1
+ /**
2
+ * Audio dictation with push-to-talk support
3
+ * Automatic provider fallback:
4
+ * 1. OpenAI Whisper API (if OPENAI_API_KEY available)
5
+ * 2. Local whisper (if installed)
6
+ * 3. macOS native (SFSpeechRecognizer)
7
+ */
8
+ import { spawn, execSync, spawnSync } from 'child_process';
9
+ import { writeFile, readFile, unlink } from 'fs/promises';
10
+ import { existsSync } from 'fs';
11
+ import { join } from 'path';
12
+ import { tmpdir, homedir, platform } from 'os';
13
+ const RECORDING_DIR = join(tmpdir(), 'ztc-dictation');
14
+ const RECORDING_FILE = join(RECORDING_DIR, 'recording.wav');
15
+ // Global state for push-to-talk
16
+ let activeRecording = null;
17
+ let cachedRecordingTool = null;
18
+ let cachedProvider = null;
19
+ /**
20
+ * Check for available recording tool (cached)
21
+ */
22
+ function getRecordingTool() {
23
+ if (cachedRecordingTool !== null)
24
+ return cachedRecordingTool;
25
+ const os = platform();
26
+ if (os === 'darwin' || os === 'linux') {
27
+ try {
28
+ execSync('which rec', { stdio: 'ignore' });
29
+ cachedRecordingTool = 'sox';
30
+ return 'sox';
31
+ }
32
+ catch { }
33
+ try {
34
+ execSync('which ffmpeg', { stdio: 'ignore' });
35
+ cachedRecordingTool = 'ffmpeg';
36
+ return 'ffmpeg';
37
+ }
38
+ catch { }
39
+ }
40
+ if (os === 'linux') {
41
+ try {
42
+ execSync('which arecord', { stdio: 'ignore' });
43
+ cachedRecordingTool = 'arecord';
44
+ return 'arecord';
45
+ }
46
+ catch { }
47
+ }
48
+ return null;
49
+ }
50
+ /**
51
+ * Get the best available transcription provider (cached)
52
+ */
53
+ export function getBestProvider() {
54
+ if (cachedProvider !== null)
55
+ return cachedProvider;
56
+ // Check OpenAI first (best quality)
57
+ if (process.env.OPENAI_API_KEY) {
58
+ cachedProvider = 'openai';
59
+ return 'openai';
60
+ }
61
+ // Check local whisper
62
+ try {
63
+ execSync('which whisper', { stdio: 'ignore' });
64
+ cachedProvider = 'local';
65
+ return 'local';
66
+ }
67
+ catch { }
68
+ try {
69
+ execSync('which whisper-cpp', { stdio: 'ignore' });
70
+ cachedProvider = 'local';
71
+ return 'local';
72
+ }
73
+ catch { }
74
+ // macOS native as fallback
75
+ if (platform() === 'darwin') {
76
+ cachedProvider = 'macos';
77
+ return 'macos';
78
+ }
79
+ return null;
80
+ }
81
+ /**
82
+ * Check if dictation is available
83
+ */
84
+ export function isDictationAvailable() {
85
+ return getRecordingTool() !== null && getBestProvider() !== null;
86
+ }
87
+ /**
88
+ * Check if currently recording
89
+ */
90
+ export function isRecording() {
91
+ return activeRecording !== null;
92
+ }
93
+ /**
94
+ * Get the name of the recording device/tool being used
95
+ */
96
+ export function getRecordingDeviceName() {
97
+ const tool = getRecordingTool();
98
+ if (!tool)
99
+ return null;
100
+ const os = platform();
101
+ if (tool === 'sox') {
102
+ // On macOS, sox uses coreaudio
103
+ if (os === 'darwin') {
104
+ return 'Default Input (sox)';
105
+ }
106
+ return 'Default Input (sox)';
107
+ }
108
+ if (tool === 'ffmpeg') {
109
+ if (os === 'darwin') {
110
+ return 'Default Input (avfoundation)';
111
+ }
112
+ return 'Default Input (alsa)';
113
+ }
114
+ if (tool === 'arecord') {
115
+ return 'Default Input (alsa)';
116
+ }
117
+ return tool;
118
+ }
119
+ /**
120
+ * Start recording (push-to-talk: press to start)
121
+ */
122
+ export function startRecording() {
123
+ if (activeRecording) {
124
+ return false; // Already recording
125
+ }
126
+ const tool = getRecordingTool();
127
+ if (!tool) {
128
+ throw new Error('No recording tool available. Install sox: brew install sox');
129
+ }
130
+ // Ensure directory exists
131
+ if (!existsSync(RECORDING_DIR)) {
132
+ execSync(`mkdir -p "${RECORDING_DIR}"`);
133
+ }
134
+ const os = platform();
135
+ let proc;
136
+ if (tool === 'sox') {
137
+ proc = spawn('rec', [
138
+ '-q', // Quiet
139
+ '-r', '16000', // 16kHz (Whisper optimal)
140
+ '-c', '1', // Mono
141
+ '-b', '16', // 16-bit
142
+ RECORDING_FILE
143
+ ], { stdio: 'ignore' });
144
+ }
145
+ else if (tool === 'ffmpeg') {
146
+ if (os === 'darwin') {
147
+ proc = spawn('ffmpeg', [
148
+ '-y', '-f', 'avfoundation', '-i', ':0',
149
+ '-ar', '16000', '-ac', '1', '-sample_fmt', 's16',
150
+ RECORDING_FILE
151
+ ], { stdio: 'ignore' });
152
+ }
153
+ else {
154
+ proc = spawn('ffmpeg', [
155
+ '-y', '-f', 'alsa', '-i', 'default',
156
+ '-ar', '16000', '-ac', '1', '-sample_fmt', 's16',
157
+ RECORDING_FILE
158
+ ], { stdio: 'ignore' });
159
+ }
160
+ }
161
+ else {
162
+ proc = spawn('arecord', [
163
+ '-f', 'S16_LE', '-r', '16000', '-c', '1',
164
+ RECORDING_FILE
165
+ ], { stdio: 'ignore' });
166
+ }
167
+ activeRecording = {
168
+ process: proc,
169
+ startTime: Date.now()
170
+ };
171
+ return true;
172
+ }
173
+ /**
174
+ * Stop recording and transcribe (push-to-talk: release to stop)
175
+ */
176
+ export async function stopRecordingAndTranscribe() {
177
+ if (!activeRecording) {
178
+ throw new Error('Not currently recording');
179
+ }
180
+ const { process: proc, startTime } = activeRecording;
181
+ activeRecording = null;
182
+ // Stop the recording process
183
+ await new Promise((resolve) => {
184
+ proc.on('close', () => resolve());
185
+ proc.on('error', () => resolve());
186
+ proc.kill('SIGINT');
187
+ // Fallback timeout
188
+ setTimeout(() => resolve(), 1000);
189
+ });
190
+ // Small delay to ensure file is written
191
+ await new Promise(resolve => setTimeout(resolve, 100));
192
+ if (!existsSync(RECORDING_FILE)) {
193
+ throw new Error('Recording file not created');
194
+ }
195
+ const recordingDuration = Date.now() - startTime;
196
+ const provider = getBestProvider();
197
+ if (!provider) {
198
+ throw new Error('No transcription provider available');
199
+ }
200
+ // Transcribe
201
+ const transcribeStart = Date.now();
202
+ let text;
203
+ try {
204
+ text = await transcribe(RECORDING_FILE, provider);
205
+ }
206
+ finally {
207
+ // Clean up recording file
208
+ try {
209
+ await unlink(RECORDING_FILE);
210
+ }
211
+ catch { }
212
+ }
213
+ return {
214
+ text: text.trim(),
215
+ duration: Date.now() - transcribeStart,
216
+ provider
217
+ };
218
+ }
219
+ /**
220
+ * Cancel active recording without transcribing
221
+ */
222
+ export function cancelRecording() {
223
+ if (activeRecording) {
224
+ activeRecording.process.kill('SIGKILL');
225
+ activeRecording = null;
226
+ // Clean up file
227
+ try {
228
+ if (existsSync(RECORDING_FILE)) {
229
+ execSync(`rm -f "${RECORDING_FILE}"`);
230
+ }
231
+ }
232
+ catch { }
233
+ }
234
+ }
235
+ /**
236
+ * Transcribe audio file
237
+ */
238
+ async function transcribe(audioPath, provider) {
239
+ switch (provider) {
240
+ case 'openai':
241
+ return transcribeOpenAI(audioPath);
242
+ case 'local':
243
+ return transcribeLocal(audioPath);
244
+ case 'macos':
245
+ return transcribeMacOS(audioPath);
246
+ default:
247
+ throw new Error(`Unknown provider: ${provider}`);
248
+ }
249
+ }
250
+ /**
251
+ * Transcribe using OpenAI Whisper API
252
+ */
253
+ async function transcribeOpenAI(audioPath) {
254
+ const apiKey = process.env.OPENAI_API_KEY;
255
+ if (!apiKey) {
256
+ throw new Error('OPENAI_API_KEY not set');
257
+ }
258
+ const audioData = await readFile(audioPath);
259
+ const formData = new FormData();
260
+ formData.append('file', new Blob([audioData], { type: 'audio/wav' }), 'audio.wav');
261
+ formData.append('model', 'whisper-1');
262
+ const response = await fetch('https://api.openai.com/v1/audio/transcriptions', {
263
+ method: 'POST',
264
+ headers: { 'Authorization': `Bearer ${apiKey}` },
265
+ body: formData
266
+ });
267
+ if (!response.ok) {
268
+ const error = await response.text();
269
+ throw new Error(`Whisper API error: ${error}`);
270
+ }
271
+ const result = await response.json();
272
+ return result.text;
273
+ }
274
+ /**
275
+ * Transcribe using local whisper
276
+ */
277
+ async function transcribeLocal(audioPath) {
278
+ // Try Python whisper first
279
+ try {
280
+ execSync('which whisper', { stdio: 'ignore' });
281
+ const result = spawnSync('whisper', [
282
+ audioPath,
283
+ '--model', 'base',
284
+ '--output_format', 'txt',
285
+ '--output_dir', RECORDING_DIR
286
+ ], { encoding: 'utf-8', timeout: 60000 });
287
+ if (result.status === 0) {
288
+ const outputFile = audioPath.replace(/\.wav$/, '.txt');
289
+ if (existsSync(outputFile)) {
290
+ const text = await readFile(outputFile, 'utf-8');
291
+ await unlink(outputFile).catch(() => { });
292
+ return text.trim();
293
+ }
294
+ }
295
+ }
296
+ catch { }
297
+ // Try whisper.cpp
298
+ try {
299
+ execSync('which whisper-cpp', { stdio: 'ignore' });
300
+ const modelPath = join(homedir(), '.local/share/whisper/ggml-base.bin');
301
+ const result = spawnSync('whisper-cpp', [
302
+ '-m', modelPath,
303
+ '-f', audioPath
304
+ ], { encoding: 'utf-8', timeout: 60000 });
305
+ if (result.status === 0) {
306
+ return result.stdout.trim();
307
+ }
308
+ }
309
+ catch { }
310
+ throw new Error('Local whisper transcription failed');
311
+ }
312
+ /**
313
+ * Transcribe using macOS native Speech Recognition
314
+ */
315
+ async function transcribeMacOS(audioPath) {
316
+ const swiftCode = `
317
+ import Foundation
318
+ import Speech
319
+
320
+ let semaphore = DispatchSemaphore(value: 0)
321
+ var transcription = ""
322
+ var errorMsg = ""
323
+
324
+ SFSpeechRecognizer.requestAuthorization { status in
325
+ guard status == .authorized else {
326
+ errorMsg = "Speech recognition not authorized"
327
+ semaphore.signal()
328
+ return
329
+ }
330
+
331
+ guard let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US")),
332
+ recognizer.isAvailable else {
333
+ errorMsg = "Speech recognizer not available"
334
+ semaphore.signal()
335
+ return
336
+ }
337
+
338
+ let url = URL(fileURLWithPath: CommandLine.arguments[1])
339
+ let request = SFSpeechURLRecognitionRequest(url: url)
340
+ request.shouldReportPartialResults = false
341
+
342
+ recognizer.recognitionTask(with: request) { result, error in
343
+ if let error = error {
344
+ errorMsg = error.localizedDescription
345
+ semaphore.signal()
346
+ return
347
+ }
348
+
349
+ if let result = result, result.isFinal {
350
+ transcription = result.bestTranscription.formattedString
351
+ semaphore.signal()
352
+ }
353
+ }
354
+ }
355
+
356
+ _ = semaphore.wait(timeout: .now() + 30)
357
+
358
+ if !errorMsg.isEmpty {
359
+ fputs(errorMsg + "\\n", stderr)
360
+ exit(1)
361
+ }
362
+ print(transcription)
363
+ `;
364
+ const swiftFile = join(RECORDING_DIR, 'transcribe.swift');
365
+ await writeFile(swiftFile, swiftCode);
366
+ try {
367
+ const result = spawnSync('swift', [swiftFile, audioPath], {
368
+ encoding: 'utf-8',
369
+ timeout: 35000
370
+ });
371
+ if (result.status !== 0) {
372
+ throw new Error(result.stderr || 'macOS transcription failed');
373
+ }
374
+ return result.stdout.trim();
375
+ }
376
+ finally {
377
+ await unlink(swiftFile).catch(() => { });
378
+ }
379
+ }
380
+ /**
381
+ * Get status message for dictation availability
382
+ */
383
+ export function getDictationStatus() {
384
+ const tool = getRecordingTool();
385
+ const provider = getBestProvider();
386
+ const parts = [];
387
+ if (tool) {
388
+ parts.push(`Recording: ${tool}`);
389
+ }
390
+ else {
391
+ parts.push('Recording: not available (install sox: brew install sox)');
392
+ }
393
+ if (provider) {
394
+ const providerNames = {
395
+ openai: 'OpenAI Whisper',
396
+ local: 'Local Whisper',
397
+ macos: 'macOS Native'
398
+ };
399
+ parts.push(`Transcription: ${providerNames[provider]}`);
400
+ }
401
+ else {
402
+ parts.push('Transcription: not available');
403
+ }
404
+ if (tool && provider) {
405
+ parts.push('\nPress Ctrl+T to start/stop recording');
406
+ }
407
+ return parts.join('\n');
408
+ }
409
+ //# sourceMappingURL=dictation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictation.js","sourceRoot":"","sources":["../../src/utils/dictation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Native real-time dictation using ztc-audio Rust binary
3
+ * Provides:
4
+ * - Real-time audio level metering (VU meter)
5
+ * - Real-time transcription via whisper
6
+ * - Cross-platform support
7
+ */
8
+ export interface DictationEvent {
9
+ type: 'ready' | 'level' | 'text' | 'error' | 'stopped';
10
+ device?: string;
11
+ model?: string;
12
+ db?: number;
13
+ rms?: number;
14
+ content?: string;
15
+ partial?: boolean;
16
+ message?: string;
17
+ }
18
+ export type DictationEventHandler = (event: DictationEvent) => void;
19
+ /**
20
+ * Check if native dictation is available
21
+ */
22
+ export declare function isNativeDictationAvailable(): boolean;
23
+ /**
24
+ * Check if currently recording
25
+ */
26
+ export declare function isNativeRecording(): boolean;
27
+ /**
28
+ * Subscribe to dictation events
29
+ */
30
+ export declare function onDictationEvent(handler: DictationEventHandler): () => void;
31
+ /**
32
+ * Start native recording with real-time transcription
33
+ */
34
+ export declare function startNativeRecording(options?: {
35
+ model?: string;
36
+ device?: string;
37
+ }): boolean;
38
+ /**
39
+ * Stop native recording
40
+ * Returns the final transcription
41
+ */
42
+ export declare function stopNativeRecording(): Promise<string>;
43
+ /**
44
+ * Cancel native recording without getting transcription
45
+ */
46
+ export declare function cancelNativeRecording(): void;
47
+ /**
48
+ * Get recording status info
49
+ */
50
+ export declare function getNativeDictationStatus(): string;
51
+ //# sourceMappingURL=dictation_native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictation_native.d.ts","sourceRoot":"","sources":["../../src/utils/dictation_native.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqBH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAoCpE;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,IAAI,CAK3E;AAYD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAuEf;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAgC3D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAmBjD"}
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Native real-time dictation using ztc-audio Rust binary
3
+ * Provides:
4
+ * - Real-time audio level metering (VU meter)
5
+ * - Real-time transcription via whisper
6
+ * - Cross-platform support
7
+ */
8
+ import { spawn } from 'child_process';
9
+ import { existsSync } from 'fs';
10
+ import { createInterface } from 'readline';
11
+ import { join, dirname } from 'path';
12
+ import { homedir } from 'os';
13
+ import { fileURLToPath } from 'url';
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+ // Try to import the @zergai/ztc-dictation package
17
+ let dictationPackage = null;
18
+ try {
19
+ // @ts-ignore - dynamic import of optional package
20
+ dictationPackage = await import('@zergai/ztc-dictation');
21
+ }
22
+ catch {
23
+ // Package not installed, will fall back to local binary
24
+ }
25
+ let activeProcess = null;
26
+ let eventHandlers = [];
27
+ /**
28
+ * Get the path to the ztc-audio binary
29
+ */
30
+ function getBinaryPath() {
31
+ // 1. Try the @zergai/ztc-dictation package first
32
+ if (dictationPackage) {
33
+ const pkgPath = dictationPackage.getBinaryPath();
34
+ if (pkgPath && existsSync(pkgPath)) {
35
+ return pkgPath;
36
+ }
37
+ }
38
+ // 2. Check local development/install paths
39
+ const possiblePaths = [
40
+ // Development (new location)
41
+ join(process.cwd(), 'packages', 'ztc-dictation', 'target', 'release', 'ztc-audio'),
42
+ // Development (old location)
43
+ join(process.cwd(), 'native', 'ztc-audio', 'target', 'release', 'ztc-audio'),
44
+ // Global install
45
+ join(homedir(), '.ztc', 'bin', 'ztc-audio'),
46
+ ];
47
+ for (const p of possiblePaths) {
48
+ if (existsSync(p)) {
49
+ return p;
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+ /**
55
+ * Check if native dictation is available
56
+ */
57
+ export function isNativeDictationAvailable() {
58
+ return getBinaryPath() !== null;
59
+ }
60
+ /**
61
+ * Check if currently recording
62
+ */
63
+ export function isNativeRecording() {
64
+ return activeProcess !== null;
65
+ }
66
+ /**
67
+ * Subscribe to dictation events
68
+ */
69
+ export function onDictationEvent(handler) {
70
+ eventHandlers.push(handler);
71
+ return () => {
72
+ eventHandlers = eventHandlers.filter(h => h !== handler);
73
+ };
74
+ }
75
+ function emit(event) {
76
+ for (const handler of eventHandlers) {
77
+ try {
78
+ handler(event);
79
+ }
80
+ catch (e) {
81
+ // Ignore handler errors
82
+ }
83
+ }
84
+ }
85
+ /**
86
+ * Start native recording with real-time transcription
87
+ */
88
+ export function startNativeRecording(options = {}) {
89
+ if (activeProcess) {
90
+ return false; // Already recording
91
+ }
92
+ const binaryPath = getBinaryPath();
93
+ if (!binaryPath) {
94
+ emit({
95
+ type: 'error',
96
+ message: 'ztc-audio binary not found. Run: cargo build --release in native/ztc-audio'
97
+ });
98
+ return false;
99
+ }
100
+ const args = [];
101
+ if (options.model) {
102
+ args.push('--model', options.model);
103
+ }
104
+ else {
105
+ args.push('--model', 'tiny'); // Default to tiny for speed
106
+ }
107
+ if (options.device) {
108
+ args.push('--device', options.device);
109
+ }
110
+ try {
111
+ activeProcess = spawn(binaryPath, args, {
112
+ stdio: ['pipe', 'pipe', 'pipe'],
113
+ });
114
+ // Parse JSON lines from stdout
115
+ const rl = createInterface({
116
+ input: activeProcess.stdout,
117
+ crlfDelay: Infinity,
118
+ });
119
+ rl.on('line', (line) => {
120
+ try {
121
+ const msg = JSON.parse(line);
122
+ emit(msg);
123
+ }
124
+ catch (e) {
125
+ // Ignore parse errors
126
+ }
127
+ });
128
+ // Capture stderr for debugging
129
+ activeProcess.stderr?.on('data', (data) => {
130
+ const text = data.toString().trim();
131
+ if (text && !text.includes('Downloading')) {
132
+ // Don't emit download progress as errors
133
+ // Could log this for debugging
134
+ }
135
+ });
136
+ activeProcess.on('close', (code) => {
137
+ activeProcess = null;
138
+ emit({ type: 'stopped' });
139
+ });
140
+ activeProcess.on('error', (err) => {
141
+ activeProcess = null;
142
+ emit({ type: 'error', message: err.message });
143
+ });
144
+ return true;
145
+ }
146
+ catch (err) {
147
+ emit({
148
+ type: 'error',
149
+ message: `Failed to start ztc-audio: ${err instanceof Error ? err.message : 'Unknown error'}`
150
+ });
151
+ return false;
152
+ }
153
+ }
154
+ /**
155
+ * Stop native recording
156
+ * Returns the final transcription
157
+ */
158
+ export async function stopNativeRecording() {
159
+ if (!activeProcess) {
160
+ return '';
161
+ }
162
+ return new Promise((resolve) => {
163
+ let finalText = '';
164
+ // Listen for the final transcription
165
+ const cleanup = onDictationEvent((event) => {
166
+ if (event.type === 'text' && event.partial === false) {
167
+ finalText = event.content || '';
168
+ }
169
+ if (event.type === 'stopped') {
170
+ cleanup();
171
+ resolve(finalText);
172
+ }
173
+ });
174
+ // Send SIGINT to trigger graceful shutdown
175
+ activeProcess?.kill('SIGINT');
176
+ // Fallback timeout
177
+ setTimeout(() => {
178
+ if (activeProcess) {
179
+ activeProcess.kill('SIGKILL');
180
+ activeProcess = null;
181
+ }
182
+ cleanup();
183
+ resolve(finalText);
184
+ }, 3000);
185
+ });
186
+ }
187
+ /**
188
+ * Cancel native recording without getting transcription
189
+ */
190
+ export function cancelNativeRecording() {
191
+ if (activeProcess) {
192
+ activeProcess.kill('SIGKILL');
193
+ activeProcess = null;
194
+ }
195
+ }
196
+ /**
197
+ * Get recording status info
198
+ */
199
+ export function getNativeDictationStatus() {
200
+ const binaryPath = getBinaryPath();
201
+ const parts = [];
202
+ if (binaryPath) {
203
+ parts.push(`Native dictation: available`);
204
+ parts.push(`Binary: ${binaryPath}`);
205
+ parts.push(`Model: ~/.ztc/models/ggml-*.bin`);
206
+ }
207
+ else {
208
+ parts.push('Native dictation: not available');
209
+ parts.push('Build with: cd native/ztc-audio && cargo build --release');
210
+ }
211
+ if (isNativeRecording()) {
212
+ parts.push('\nCurrently recording...');
213
+ }
214
+ return parts.join('\n');
215
+ }
216
+ //# sourceMappingURL=dictation_native.js.map