Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/desktop_ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ jobs:
--exclude tauri-plugin-webhook \
--exclude tauri-plugin-windows \
--exclude db3 \
--exclude db-core2
--exclude db-core2 \
--exclude activity-capture-macos \
--exclude tauri-plugin-activity-capture

ci:
if: always()
Expand Down
8 changes: 4 additions & 4 deletions apps/cli/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ impl AppContext {

fn analytics_client() -> hypr_analytics::AnalyticsClient {
let mut builder = hypr_analytics::AnalyticsClientBuilder::default();
if std::env::var_os("DO_NOT_TRACK").is_none() {
if let Some(key) = option_env!("POSTHOG_API_KEY") {
builder = builder.with_posthog(key);
}
if std::env::var_os("DO_NOT_TRACK").is_none()
&& let Some(key) = option_env!("POSTHOG_API_KEY")
{
builder = builder.with_posthog(key);
}
builder.build()
}
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use clap_verbosity_flag::{InfoLevel, Verbosity};
name = "char",
version,
propagate_version = true,
after_help = "Docs: https://char.com/docs/cli\nBugs: https://github.com/fastrepl/char/issues"
after_help = "Docs: https://cli.char.com\nDiscussions: https://github.com/fastrepl/char/discussions/4788\nBugs: https://github.com/fastrepl/char/issues"
)]
pub struct Cli {
#[command(subcommand)]
Expand Down
14 changes: 7 additions & 7 deletions apps/cli/src/commands/export/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ pub(super) fn build_segments(
})
.unwrap_or_else(|| format!("Channel {}", word.channel));

if let Some(last) = segments.last_mut() {
if last.speaker == speaker {
last.text.push(' ');
last.text.push_str(&word.text);
last.end_ms = word.end_ms;
continue;
}
if let Some(last) = segments.last_mut()
&& last.speaker == speaker
{
last.text.push(' ');
last.text.push_str(&word.text);
last.end_ms = word.end_ms;
continue;
}

segments.push(Segment {
Expand Down
21 changes: 2 additions & 19 deletions apps/cli/src/commands/model/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ pub(crate) async fn collect_model_rows(
rows.sort_by(|a, b| {
status_rank(&a.status)
.cmp(&status_rank(&b.status))
.then_with(|| a.kind.cmp(&b.kind))
.then_with(|| a.name.cmp(&b.name))
});
rows
Expand Down Expand Up @@ -81,7 +80,7 @@ pub(super) async fn write_model_output(
.load_preset(UTF8_FULL_CONDENSED)
.set_content_arrangement(ContentArrangement::Dynamic);

table.set_header(vec!["Name", "Type", "Status", "Title", "Details", "Path"]);
table.set_header(vec!["Name", "Status", "Path"]);

for row in rows {
let path = match &home {
Expand All @@ -92,14 +91,7 @@ pub(super) async fn write_model_output(
.unwrap_or_else(|| row.install_path.clone()),
None => row.install_path.clone(),
};
table.add_row(vec![
row.name.clone(),
row.kind.clone(),
row.status.clone(),
row.display_name.clone(),
detail_text(row).to_string(),
path,
]);
table.add_row(vec![row.name.clone(), row.status.clone(), path]);
}

println!("{table}");
Expand All @@ -108,14 +100,6 @@ pub(super) async fn write_model_output(
Ok(())
}

fn detail_text(row: &ModelRow) -> &str {
if row.description.is_empty() {
"-"
} else {
row.description.as_str()
}
}

fn status_rank(status: &str) -> usize {
match status {
"downloaded" => 0,
Expand Down Expand Up @@ -154,7 +138,6 @@ mod tests {
rows.sort_by(|a, b| {
status_rank(&a.status)
.cmp(&status_rank(&b.status))
.then_with(|| a.kind.cmp(&b.kind))
.then_with(|| a.name.cmp(&b.name))
});

Expand Down
3 changes: 3 additions & 0 deletions apps/cli/src/commands/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ fn make_manager(
}

pub(crate) fn model_is_enabled(model: &LocalModel) -> bool {
if matches!(model, LocalModel::Am(_)) {
return false;
}
cfg!(all(
target_os = "macos",
any(target_arch = "arm", target_arch = "aarch64")
Expand Down
30 changes: 15 additions & 15 deletions apps/cli/src/commands/record/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,22 +131,22 @@ async fn run_with_audio<A: AudioProvider>(

let capture = runtime::capture(audio, args.audio, sample_rate, chunk_size, |progress| {
app.update(&progress);
if progress.emit_event {
if let Some(writer) = event_writer.as_mut() {
writer.emit(&RecordEvent::Progress {
elapsed_ms: progress.elapsed.as_millis() as u64,
audio_secs: progress.audio_secs,
sample_count: progress.sample_count,
level_left: progress.left_level,
level_right: progress.right_level,
})?;
}
if progress.emit_event
&& let Some(writer) = event_writer.as_mut()
{
writer.emit(&RecordEvent::Progress {
elapsed_ms: progress.elapsed.as_millis() as u64,
audio_secs: progress.audio_secs,
sample_count: progress.sample_count,
level_left: progress.left_level,
level_right: progress.right_level,
})?;
}
if progress.render_ui {
if let Some(view) = viewport.as_mut() {
view.poll_input();
view.draw(&app.lines());
}
if progress.render_ui
&& let Some(view) = viewport.as_mut()
{
view.poll_input();
view.draw(&app.lines());
}
Ok(())
})
Expand Down
87 changes: 27 additions & 60 deletions apps/cli/src/commands/transcribe/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
mod output;
mod response;

use std::io::BufWriter;
use std::path::{Path, PathBuf};
Expand All @@ -10,7 +9,7 @@ use serde::Serialize;
use tokio::sync::mpsc;

use hypr_listener2_core::{BatchErrorCode, BatchEvent};
use owhisper_interface::stream::StreamResponse;
use owhisper_interface::batch_stream::BatchStreamEvent;

use crate::OptTraceBuffer;
use crate::app::AppContext;
Expand Down Expand Up @@ -92,9 +91,9 @@ async fn start_batch(
stt: SttOverrides,
on_normalize_progress: Option<&mut dyn FnMut(f64)>,
) -> CliResult<BatchHandle> {
let resolved = resolve_config(None, stt).await?;
let (normalized_input_dir, normalized_input_path) =
normalize_input_file(input.path(), on_normalize_progress)?;
let resolved = resolve_config(None, stt).await?;
let params = build_batch_params(&resolved, &normalized_input_path, keywords)?;

let (batch_tx, batch_rx) = mpsc::unbounded_channel::<BatchEvent>();
Expand Down Expand Up @@ -149,13 +148,8 @@ fn build_batch_params(
))
}

fn extract_stream_transcript(response: &StreamResponse) -> Option<&str> {
match response {
StreamResponse::TranscriptResponse { channel, .. } => {
channel.alternatives.first().map(|a| a.transcript.as_str())
}
_ => None,
}
fn extract_stream_transcript(event: &BatchStreamEvent) -> Option<&str> {
event.text()
}

struct CollectedBatch {
Expand All @@ -168,36 +162,32 @@ fn finish_batch(
Result<hypr_listener2_core::BatchRunOutput, hypr_listener2_core::Error>,
tokio::task::JoinError,
>,
batch_response: Option<owhisper_interface::batch::Response>,
streamed_segments: Vec<StreamResponse>,
failure: Option<(BatchErrorCode, String)>,
started: std::time::Instant,
) -> CliResult<CollectedBatch> {
let result = task_result
.map_err(|e| CliError::operation_failed("batch transcription", e.to_string()))?;
if let Err(error) = result {
let output = if let Ok(output) = result {
output
} else {
let error = result.err().unwrap();
let message = if let Some((code, message)) = failure {
format!("{code:?}: {message}")
} else {
error.to_string()
};
return Err(CliError::operation_failed("batch transcription", message));
}

let response = batch_response
.or_else(|| response::batch_response_from_streams(streamed_segments))
.ok_or_else(|| {
CliError::operation_failed("batch transcription", "completed without a final response")
})?;
};

Ok(CollectedBatch {
response,
response: output.response,
elapsed: started.elapsed(),
})
}

// -- Entry point --

#[allow(clippy::unit_arg)]
pub async fn run(ctx: &AppContext, args: Args) -> CliResult<()> {
let format = args.format;
let output_path = args.output.clone();
Expand Down Expand Up @@ -246,43 +236,30 @@ async fn run_json(
}
};

let mut batch_response: Option<owhisper_interface::batch::Response> = None;
let mut streamed_segments: Vec<StreamResponse> = Vec::new();
let mut failure: Option<(BatchErrorCode, String)> = None;

while let Some(event) = handle.rx.recv().await {
match event {
BatchEvent::BatchStarted { .. } | BatchEvent::BatchCompleted { .. } => {}
BatchEvent::BatchResponseStreamed {
response: streamed,
percentage,
..
event: streamed, ..
} => {
let transcript = extract_stream_transcript(&streamed)
.unwrap_or("")
.to_string();
writer.emit(&TranscribeEvent::Progress {
percentage,
percentage: streamed.percentage(),
transcript,
})?;
streamed_segments.push(streamed);
}
BatchEvent::BatchResponse { response: next, .. } => {
batch_response = Some(next);
}
BatchEvent::BatchResponse { .. } => {}
BatchEvent::BatchFailed { code, error, .. } => {
failure = Some((code, error));
}
}
}

let result = match finish_batch(
handle.task.await,
batch_response,
streamed_segments,
failure,
handle.started,
) {
let result = match finish_batch(handle.task.await, failure, handle.started) {
Ok(r) => r,
Err(e) => {
let _ = writer.emit(&TranscribeEvent::Failed {
Expand Down Expand Up @@ -369,8 +346,6 @@ async fn run_pretty(
#[cfg(not(feature = "standalone"))]
let mut handle = start_batch(&args.input, args.keywords.clone(), stt, None).await?;

let mut batch_response: Option<owhisper_interface::batch::Response> = None;
let mut streamed_segments: Vec<StreamResponse> = Vec::new();
let mut failure: Option<(BatchErrorCode, String)> = None;

#[cfg(feature = "standalone")]
Expand Down Expand Up @@ -411,25 +386,23 @@ async fn run_pretty(
match event {
BatchEvent::BatchStarted { .. } | BatchEvent::BatchCompleted { .. } => {}
BatchEvent::BatchResponseStreamed {
response: streamed,
#[cfg(feature = "standalone")]
percentage,
..
event: streamed, ..
} => {
#[cfg(feature = "standalone")]
{
last_pct = percentage;
if let Some(t) = extract_stream_transcript(&streamed) {
if !t.is_empty() {
last_transcript = t.to_string();
}
last_pct = streamed.percentage();
if let Some(t) = extract_stream_transcript(&streamed)
&& !t.is_empty()
{
last_transcript = t.to_string();
}
}
streamed_segments.push(streamed);
}
BatchEvent::BatchResponse { response: next, .. } => {
batch_response = Some(next);
#[cfg(not(feature = "standalone"))]
{
let _ = streamed;
}
}
BatchEvent::BatchResponse { .. } => {}
BatchEvent::BatchFailed { code, error, .. } => {
failure = Some((code, error));
}
Expand All @@ -442,13 +415,7 @@ async fn run_pretty(
.map_err(|e| CliError::operation_failed("clear viewport", e.to_string()))?;
}

let result = finish_batch(
handle.task.await,
batch_response,
streamed_segments,
failure,
handle.started,
)?;
let result = finish_batch(handle.task.await, failure, handle.started)?;
let response = &result.response;

let pretty = output::format_pretty(response);
Expand Down
Loading
Loading