zerg-ztc 0.1.11 → 0.1.13
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/bin/ztc-audio-darwin-arm64 +0 -0
- package/dist/utils/dictation_native.d.ts.map +1 -1
- package/dist/utils/dictation_native.js +43 -23
- package/dist/utils/dictation_native.js.map +1 -1
- package/package.json +5 -4
- package/packages/ztc-dictation/Cargo.toml +0 -43
- package/packages/ztc-dictation/README.md +0 -65
- package/packages/ztc-dictation/index.d.ts +0 -16
- package/packages/ztc-dictation/index.js +0 -74
- package/packages/ztc-dictation/package.json +0 -41
- package/packages/ztc-dictation/src/main.rs +0 -430
- package/src/App.tsx +0 -910
- package/src/agent/agent.ts +0 -534
- package/src/agent/backends/anthropic.ts +0 -86
- package/src/agent/backends/gemini.ts +0 -119
- package/src/agent/backends/inception.ts +0 -23
- package/src/agent/backends/index.ts +0 -17
- package/src/agent/backends/openai.ts +0 -23
- package/src/agent/backends/openai_compatible.ts +0 -143
- package/src/agent/backends/types.ts +0 -83
- package/src/agent/commands/clipboard.ts +0 -77
- package/src/agent/commands/config.ts +0 -204
- package/src/agent/commands/debug.ts +0 -23
- package/src/agent/commands/dictation.ts +0 -11
- package/src/agent/commands/emulation.ts +0 -80
- package/src/agent/commands/execution.ts +0 -9
- package/src/agent/commands/help.ts +0 -20
- package/src/agent/commands/history.ts +0 -13
- package/src/agent/commands/index.ts +0 -48
- package/src/agent/commands/input_mode.ts +0 -22
- package/src/agent/commands/keybindings.ts +0 -40
- package/src/agent/commands/model.ts +0 -11
- package/src/agent/commands/models.ts +0 -116
- package/src/agent/commands/permissions.ts +0 -64
- package/src/agent/commands/retry.ts +0 -9
- package/src/agent/commands/shell.ts +0 -68
- package/src/agent/commands/skills.ts +0 -54
- package/src/agent/commands/status.ts +0 -19
- package/src/agent/commands/types.ts +0 -88
- package/src/agent/commands/update.ts +0 -32
- package/src/agent/factory.ts +0 -60
- package/src/agent/index.ts +0 -20
- package/src/agent/runtime/capabilities.ts +0 -7
- package/src/agent/runtime/memory.ts +0 -23
- package/src/agent/runtime/policy.ts +0 -48
- package/src/agent/runtime/session.ts +0 -18
- package/src/agent/runtime/tracing.ts +0 -23
- package/src/agent/tools/file.ts +0 -178
- package/src/agent/tools/index.ts +0 -52
- package/src/agent/tools/screenshot.ts +0 -821
- package/src/agent/tools/search.ts +0 -138
- package/src/agent/tools/shell.ts +0 -69
- package/src/agent/tools/skills.ts +0 -28
- package/src/agent/tools/types.ts +0 -14
- package/src/agent/tools/zerg.ts +0 -50
- package/src/cli.tsx +0 -163
- package/src/components/ActivityLine.tsx +0 -23
- package/src/components/FullScreen.tsx +0 -79
- package/src/components/Header.tsx +0 -27
- package/src/components/InputArea.tsx +0 -1660
- package/src/components/MessageList.tsx +0 -71
- package/src/components/SingleMessage.tsx +0 -298
- package/src/components/StatusBar.tsx +0 -55
- package/src/components/index.tsx +0 -8
- package/src/config/types.ts +0 -19
- package/src/config.ts +0 -186
- package/src/debug/logger.ts +0 -14
- package/src/emulation/README.md +0 -24
- package/src/emulation/catalog.ts +0 -82
- package/src/emulation/trace_style.ts +0 -8
- package/src/emulation/types.ts +0 -7
- package/src/skills/index.ts +0 -36
- package/src/skills/loader.ts +0 -135
- package/src/skills/registry.ts +0 -6
- package/src/skills/types.ts +0 -10
- package/src/types.ts +0 -84
- package/src/ui/README.md +0 -44
- package/src/ui/core/factory.ts +0 -9
- package/src/ui/core/index.ts +0 -4
- package/src/ui/core/input.ts +0 -38
- package/src/ui/core/input_segments.ts +0 -410
- package/src/ui/core/input_state.ts +0 -17
- package/src/ui/core/layout_yoga.ts +0 -122
- package/src/ui/core/style.ts +0 -38
- package/src/ui/core/types.ts +0 -54
- package/src/ui/ink/index.tsx +0 -1
- package/src/ui/ink/render.tsx +0 -60
- package/src/ui/views/activity_line.ts +0 -33
- package/src/ui/views/app.ts +0 -111
- package/src/ui/views/header.ts +0 -44
- package/src/ui/views/input_area.ts +0 -255
- package/src/ui/views/message_list.ts +0 -443
- package/src/ui/views/status_bar.ts +0 -114
- package/src/ui/vue/index.ts +0 -53
- package/src/ui/web/frame_render.tsx +0 -148
- package/src/ui/web/index.tsx +0 -1
- package/src/ui/web/render.tsx +0 -41
- package/src/utils/clipboard.ts +0 -39
- package/src/utils/clipboard_image.ts +0 -40
- package/src/utils/dictation.ts +0 -467
- package/src/utils/dictation_native.ts +0 -258
- package/src/utils/diff.ts +0 -52
- package/src/utils/image_preview.ts +0 -36
- package/src/utils/models.ts +0 -98
- package/src/utils/path_complete.ts +0 -173
- package/src/utils/path_format.ts +0 -99
- package/src/utils/shell.ts +0 -72
- package/src/utils/spinner_frames.ts +0 -1
- package/src/utils/spinner_verbs.ts +0 -23
- package/src/utils/table.ts +0 -171
- package/src/utils/tool_summary.ts +0 -56
- package/src/utils/tool_trace.ts +0 -346
- package/src/utils/update.ts +0 -44
- package/src/utils/version.ts +0 -15
- package/src/web/index.html +0 -352
- package/src/web/mirror-favicon.svg +0 -4
- package/src/web/mirror.html +0 -641
- package/src/web/mirror_hook.ts +0 -25
- package/src/web/mirror_server.ts +0 -204
- package/tsconfig.json +0 -22
- package/vite.config.ts +0 -363
- /package/{packages/ztc-dictation/bin → bin}/.gitkeep +0 -0
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
use clap::Parser;
|
|
2
|
-
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
|
3
|
-
use ringbuf::{HeapRb, traits::{Consumer, Producer, Split}};
|
|
4
|
-
use serde::Serialize;
|
|
5
|
-
use std::io::{self, Write};
|
|
6
|
-
use std::path::PathBuf;
|
|
7
|
-
use std::sync::atomic::{AtomicBool, Ordering};
|
|
8
|
-
use std::sync::{Arc, Mutex};
|
|
9
|
-
use std::time::{Duration, Instant};
|
|
10
|
-
use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters};
|
|
11
|
-
|
|
12
|
-
const SAMPLE_RATE: u32 = 16000;
|
|
13
|
-
const CHANNELS: u16 = 1;
|
|
14
|
-
|
|
15
|
-
#[derive(Parser, Debug)]
|
|
16
|
-
#[command(name = "ztc-audio")]
|
|
17
|
-
#[command(about = "Real-time audio capture and transcription for ZTC")]
|
|
18
|
-
struct Args {
|
|
19
|
-
/// Whisper model to use: tiny, base, small, medium, large
|
|
20
|
-
#[arg(short, long, default_value = "base")]
|
|
21
|
-
model: String,
|
|
22
|
-
|
|
23
|
-
/// Just list audio devices and exit
|
|
24
|
-
#[arg(long)]
|
|
25
|
-
list_devices: bool,
|
|
26
|
-
|
|
27
|
-
/// Download model if not present and exit
|
|
28
|
-
#[arg(long)]
|
|
29
|
-
download_model: bool,
|
|
30
|
-
|
|
31
|
-
/// Audio input device name (uses default if not specified)
|
|
32
|
-
#[arg(short, long)]
|
|
33
|
-
device: Option<String>,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
#[derive(Serialize)]
|
|
37
|
-
#[serde(tag = "type")]
|
|
38
|
-
enum OutputMessage {
|
|
39
|
-
#[serde(rename = "ready")]
|
|
40
|
-
Ready { device: String, model: String },
|
|
41
|
-
|
|
42
|
-
#[serde(rename = "level")]
|
|
43
|
-
Level { db: f32, rms: f32 },
|
|
44
|
-
|
|
45
|
-
#[serde(rename = "text")]
|
|
46
|
-
Text { content: String, partial: bool },
|
|
47
|
-
|
|
48
|
-
#[serde(rename = "error")]
|
|
49
|
-
Error { message: String },
|
|
50
|
-
|
|
51
|
-
#[serde(rename = "device")]
|
|
52
|
-
Device { name: String, is_default: bool },
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
fn emit(msg: &OutputMessage) {
|
|
56
|
-
if let Ok(json) = serde_json::to_string(msg) {
|
|
57
|
-
println!("{}", json);
|
|
58
|
-
let _ = io::stdout().flush();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
fn get_model_path(model_name: &str) -> PathBuf {
|
|
63
|
-
let models_dir = dirs::home_dir()
|
|
64
|
-
.unwrap_or_else(|| PathBuf::from("."))
|
|
65
|
-
.join(".ztc")
|
|
66
|
-
.join("models");
|
|
67
|
-
|
|
68
|
-
std::fs::create_dir_all(&models_dir).ok();
|
|
69
|
-
models_dir.join(format!("ggml-{}.bin", model_name))
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
fn download_model(model_name: &str) -> Result<PathBuf, String> {
|
|
73
|
-
let path = get_model_path(model_name);
|
|
74
|
-
|
|
75
|
-
if path.exists() {
|
|
76
|
-
return Ok(path);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let url = format!(
|
|
80
|
-
"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-{}.bin",
|
|
81
|
-
model_name
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
eprintln!("Downloading whisper model '{}' from {}", model_name, url);
|
|
85
|
-
|
|
86
|
-
let response = ureq::get(&url)
|
|
87
|
-
.call()
|
|
88
|
-
.map_err(|e| format!("Failed to download model: {}", e))?;
|
|
89
|
-
|
|
90
|
-
let total_size = response
|
|
91
|
-
.header("Content-Length")
|
|
92
|
-
.and_then(|s| s.parse::<u64>().ok())
|
|
93
|
-
.unwrap_or(0);
|
|
94
|
-
|
|
95
|
-
let pb = indicatif::ProgressBar::new(total_size);
|
|
96
|
-
pb.set_style(
|
|
97
|
-
indicatif::ProgressStyle::default_bar()
|
|
98
|
-
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")
|
|
99
|
-
.unwrap()
|
|
100
|
-
.progress_chars("#>-"),
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
let mut reader = response.into_reader();
|
|
104
|
-
let mut file = std::fs::File::create(&path)
|
|
105
|
-
.map_err(|e| format!("Failed to create model file: {}", e))?;
|
|
106
|
-
|
|
107
|
-
let mut buffer = [0u8; 8192];
|
|
108
|
-
let mut downloaded = 0u64;
|
|
109
|
-
|
|
110
|
-
loop {
|
|
111
|
-
let bytes_read = reader.read(&mut buffer)
|
|
112
|
-
.map_err(|e| format!("Failed to read from network: {}", e))?;
|
|
113
|
-
|
|
114
|
-
if bytes_read == 0 {
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
file.write_all(&buffer[..bytes_read])
|
|
119
|
-
.map_err(|e| format!("Failed to write to file: {}", e))?;
|
|
120
|
-
|
|
121
|
-
downloaded += bytes_read as u64;
|
|
122
|
-
pb.set_position(downloaded);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
pb.finish_with_message("Download complete");
|
|
126
|
-
eprintln!("Model saved to {:?}", path);
|
|
127
|
-
|
|
128
|
-
Ok(path)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
fn list_devices() {
|
|
132
|
-
let host = cpal::default_host();
|
|
133
|
-
|
|
134
|
-
if let Some(device) = host.default_input_device() {
|
|
135
|
-
if let Ok(name) = device.name() {
|
|
136
|
-
emit(&OutputMessage::Device {
|
|
137
|
-
name,
|
|
138
|
-
is_default: true,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if let Ok(devices) = host.input_devices() {
|
|
144
|
-
for device in devices {
|
|
145
|
-
if let Ok(name) = device.name() {
|
|
146
|
-
emit(&OutputMessage::Device {
|
|
147
|
-
name,
|
|
148
|
-
is_default: false,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
fn calculate_db(samples: &[f32]) -> (f32, f32) {
|
|
156
|
-
if samples.is_empty() {
|
|
157
|
-
return (-60.0, 0.0);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
let sum_squares: f32 = samples.iter().map(|s| s * s).sum();
|
|
161
|
-
let rms = (sum_squares / samples.len() as f32).sqrt();
|
|
162
|
-
|
|
163
|
-
// Convert to dB (with floor at -60dB)
|
|
164
|
-
let db = if rms > 0.0 {
|
|
165
|
-
20.0 * rms.log10()
|
|
166
|
-
} else {
|
|
167
|
-
-60.0
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
(db.max(-60.0), rms)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
fn run_audio_capture(
|
|
174
|
-
device_name: Option<&str>,
|
|
175
|
-
model_path: PathBuf,
|
|
176
|
-
) -> Result<(), String> {
|
|
177
|
-
let host = cpal::default_host();
|
|
178
|
-
|
|
179
|
-
// Find the audio device
|
|
180
|
-
let device = if let Some(name) = device_name {
|
|
181
|
-
host.input_devices()
|
|
182
|
-
.map_err(|e| format!("Failed to enumerate devices: {}", e))?
|
|
183
|
-
.find(|d| d.name().map(|n| n.contains(name)).unwrap_or(false))
|
|
184
|
-
.ok_or_else(|| format!("Device '{}' not found", name))?
|
|
185
|
-
} else {
|
|
186
|
-
host.default_input_device()
|
|
187
|
-
.ok_or_else(|| "No default input device".to_string())?
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
let device_name_str = device.name().unwrap_or_else(|_| "Unknown".to_string());
|
|
191
|
-
|
|
192
|
-
// Initialize Whisper
|
|
193
|
-
let ctx = WhisperContext::new_with_params(
|
|
194
|
-
model_path.to_str().unwrap(),
|
|
195
|
-
WhisperContextParameters::default(),
|
|
196
|
-
)
|
|
197
|
-
.map_err(|e| format!("Failed to load whisper model: {}", e))?;
|
|
198
|
-
|
|
199
|
-
// Get the device's default config and use its sample rate
|
|
200
|
-
let default_config = device.default_input_config()
|
|
201
|
-
.map_err(|e| format!("Failed to get default input config: {}", e))?;
|
|
202
|
-
|
|
203
|
-
let device_sample_rate = default_config.sample_rate().0;
|
|
204
|
-
let device_channels = default_config.channels();
|
|
205
|
-
|
|
206
|
-
emit(&OutputMessage::Ready {
|
|
207
|
-
device: device_name_str.clone(),
|
|
208
|
-
model: model_path.file_stem()
|
|
209
|
-
.and_then(|s| s.to_str())
|
|
210
|
-
.unwrap_or("unknown")
|
|
211
|
-
.to_string(),
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Use device's native sample rate and resample to 16kHz for Whisper
|
|
215
|
-
let config = cpal::StreamConfig {
|
|
216
|
-
channels: device_channels,
|
|
217
|
-
sample_rate: cpal::SampleRate(device_sample_rate),
|
|
218
|
-
buffer_size: cpal::BufferSize::Default,
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
let resample_ratio = SAMPLE_RATE as f32 / device_sample_rate as f32;
|
|
222
|
-
|
|
223
|
-
// Shared state
|
|
224
|
-
let running = Arc::new(AtomicBool::new(true));
|
|
225
|
-
let running_clone = running.clone();
|
|
226
|
-
|
|
227
|
-
// Ring buffer for audio samples (5 seconds of audio at 16kHz)
|
|
228
|
-
let ring_buffer = HeapRb::<f32>::new(SAMPLE_RATE as usize * 5);
|
|
229
|
-
let (mut producer, mut consumer) = ring_buffer.split();
|
|
230
|
-
|
|
231
|
-
// Buffer for level calculation (100ms chunks)
|
|
232
|
-
let level_buffer = Arc::new(Mutex::new(Vec::with_capacity(SAMPLE_RATE as usize / 10)));
|
|
233
|
-
let level_buffer_clone = level_buffer.clone();
|
|
234
|
-
|
|
235
|
-
// Resampling state - accumulate fractional samples
|
|
236
|
-
let resample_accum = Arc::new(Mutex::new(0.0f32));
|
|
237
|
-
let resample_accum_clone = resample_accum.clone();
|
|
238
|
-
|
|
239
|
-
// Handle Ctrl+C
|
|
240
|
-
ctrlc::set_handler(move || {
|
|
241
|
-
running_clone.store(false, Ordering::SeqCst);
|
|
242
|
-
})
|
|
243
|
-
.map_err(|e| format!("Failed to set signal handler: {}", e))?;
|
|
244
|
-
|
|
245
|
-
// Start audio capture
|
|
246
|
-
let stream = device
|
|
247
|
-
.build_input_stream(
|
|
248
|
-
&config,
|
|
249
|
-
move |data: &[f32], _: &cpal::InputCallbackInfo| {
|
|
250
|
-
// Convert to mono and resample to 16kHz
|
|
251
|
-
let num_channels = device_channels as usize;
|
|
252
|
-
|
|
253
|
-
// First convert to mono by averaging channels
|
|
254
|
-
let mono_samples: Vec<f32> = data
|
|
255
|
-
.chunks(num_channels)
|
|
256
|
-
.map(|frame| frame.iter().sum::<f32>() / num_channels as f32)
|
|
257
|
-
.collect();
|
|
258
|
-
|
|
259
|
-
// Simple linear resampling to 16kHz
|
|
260
|
-
// For better quality, could use a proper resampler crate
|
|
261
|
-
let mut accum = resample_accum_clone.lock().unwrap();
|
|
262
|
-
let mut resampled = Vec::new();
|
|
263
|
-
|
|
264
|
-
for &sample in &mono_samples {
|
|
265
|
-
*accum += resample_ratio;
|
|
266
|
-
while *accum >= 1.0 {
|
|
267
|
-
resampled.push(sample);
|
|
268
|
-
*accum -= 1.0;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Push resampled samples to ring buffer for transcription
|
|
273
|
-
for sample in &resampled {
|
|
274
|
-
let _ = producer.try_push(*sample);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Also collect for level metering (use original mono samples for responsiveness)
|
|
278
|
-
if let Ok(mut buf) = level_buffer_clone.lock() {
|
|
279
|
-
buf.extend_from_slice(&mono_samples);
|
|
280
|
-
}
|
|
281
|
-
},
|
|
282
|
-
|err| {
|
|
283
|
-
emit(&OutputMessage::Error {
|
|
284
|
-
message: format!("Audio stream error: {}", err),
|
|
285
|
-
});
|
|
286
|
-
},
|
|
287
|
-
None,
|
|
288
|
-
)
|
|
289
|
-
.map_err(|e| format!("Failed to build input stream: {}", e))?;
|
|
290
|
-
|
|
291
|
-
stream.play().map_err(|e| format!("Failed to start stream: {}", e))?;
|
|
292
|
-
|
|
293
|
-
// Main processing loop
|
|
294
|
-
let mut accumulated_audio: Vec<f32> = Vec::new();
|
|
295
|
-
let mut last_level_time = Instant::now();
|
|
296
|
-
let mut last_transcribe_time = Instant::now();
|
|
297
|
-
let level_interval = Duration::from_millis(50); // 20 Hz level updates
|
|
298
|
-
let transcribe_interval = Duration::from_millis(500); // Transcribe every 500ms
|
|
299
|
-
|
|
300
|
-
while running.load(Ordering::SeqCst) {
|
|
301
|
-
// Read stdin for commands (non-blocking would be better but this works)
|
|
302
|
-
// For now, just rely on Ctrl+C / signal handling
|
|
303
|
-
|
|
304
|
-
// Emit audio levels
|
|
305
|
-
if last_level_time.elapsed() >= level_interval {
|
|
306
|
-
if let Ok(mut buf) = level_buffer.lock() {
|
|
307
|
-
if !buf.is_empty() {
|
|
308
|
-
let (db, rms) = calculate_db(&buf);
|
|
309
|
-
emit(&OutputMessage::Level { db, rms });
|
|
310
|
-
buf.clear();
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
last_level_time = Instant::now();
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Collect audio for transcription
|
|
317
|
-
while let Some(sample) = consumer.try_pop() {
|
|
318
|
-
accumulated_audio.push(sample);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Transcribe periodically
|
|
322
|
-
if last_transcribe_time.elapsed() >= transcribe_interval && !accumulated_audio.is_empty() {
|
|
323
|
-
// Run whisper on accumulated audio
|
|
324
|
-
let mut params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 });
|
|
325
|
-
params.set_language(Some("en"));
|
|
326
|
-
params.set_print_special(false);
|
|
327
|
-
params.set_print_progress(false);
|
|
328
|
-
params.set_print_realtime(false);
|
|
329
|
-
params.set_print_timestamps(false);
|
|
330
|
-
params.set_single_segment(true);
|
|
331
|
-
params.set_no_context(true);
|
|
332
|
-
|
|
333
|
-
let mut state = ctx.create_state()
|
|
334
|
-
.map_err(|e| format!("Failed to create whisper state: {}", e))?;
|
|
335
|
-
|
|
336
|
-
if state.full(params, &accumulated_audio).is_ok() {
|
|
337
|
-
let num_segments = state.full_n_segments()
|
|
338
|
-
.unwrap_or(0);
|
|
339
|
-
|
|
340
|
-
let mut text = String::new();
|
|
341
|
-
for i in 0..num_segments {
|
|
342
|
-
if let Ok(segment) = state.full_get_segment_text(i) {
|
|
343
|
-
text.push_str(&segment);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
let text = text.trim().to_string();
|
|
348
|
-
if !text.is_empty() {
|
|
349
|
-
emit(&OutputMessage::Text {
|
|
350
|
-
content: text,
|
|
351
|
-
partial: true,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
last_transcribe_time = Instant::now();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
std::thread::sleep(Duration::from_millis(10));
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Final transcription
|
|
363
|
-
if !accumulated_audio.is_empty() {
|
|
364
|
-
let mut params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 });
|
|
365
|
-
params.set_language(Some("en"));
|
|
366
|
-
params.set_print_special(false);
|
|
367
|
-
params.set_print_progress(false);
|
|
368
|
-
params.set_print_realtime(false);
|
|
369
|
-
params.set_print_timestamps(false);
|
|
370
|
-
|
|
371
|
-
let mut state = ctx.create_state()
|
|
372
|
-
.map_err(|e| format!("Failed to create whisper state: {}", e))?;
|
|
373
|
-
|
|
374
|
-
if state.full(params, &accumulated_audio).is_ok() {
|
|
375
|
-
let num_segments = state.full_n_segments().unwrap_or(0);
|
|
376
|
-
|
|
377
|
-
let mut text = String::new();
|
|
378
|
-
for i in 0..num_segments {
|
|
379
|
-
if let Ok(segment) = state.full_get_segment_text(i) {
|
|
380
|
-
text.push_str(&segment);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
let text = text.trim().to_string();
|
|
385
|
-
emit(&OutputMessage::Text {
|
|
386
|
-
content: text,
|
|
387
|
-
partial: false,
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
Ok(())
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
fn main() {
|
|
396
|
-
let args = Args::parse();
|
|
397
|
-
|
|
398
|
-
if args.list_devices {
|
|
399
|
-
list_devices();
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if args.download_model {
|
|
404
|
-
match download_model(&args.model) {
|
|
405
|
-
Ok(path) => {
|
|
406
|
-
eprintln!("Model ready at {:?}", path);
|
|
407
|
-
}
|
|
408
|
-
Err(e) => {
|
|
409
|
-
emit(&OutputMessage::Error { message: e });
|
|
410
|
-
std::process::exit(1);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Ensure model is downloaded
|
|
417
|
-
let model_path = match download_model(&args.model) {
|
|
418
|
-
Ok(path) => path,
|
|
419
|
-
Err(e) => {
|
|
420
|
-
emit(&OutputMessage::Error { message: e });
|
|
421
|
-
std::process::exit(1);
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
// Run audio capture and transcription
|
|
426
|
-
if let Err(e) = run_audio_capture(args.device.as_deref(), model_path) {
|
|
427
|
-
emit(&OutputMessage::Error { message: e });
|
|
428
|
-
std::process::exit(1);
|
|
429
|
-
}
|
|
430
|
-
}
|