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: 2 additions & 2 deletions .github/actions/setup-nix/action.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: setup nix
description: install nix and configure cache
description: install Determinate Nix and enable FlakeHub Cache
runs:
using: composite
steps:
- uses: DeterminateSystems/determinate-nix-action@4eea0b33e3d1f02ecfe37cf16e7204c424009606 # v3.21.0
- uses: DeterminateSystems/magic-nix-cache-action@565684385bcd71bad329742eefe8d12f2e765b39 # v13
- uses: DeterminateSystems/flakehub-cache-action@c01e819d047464c3edf6ba778f075952af5a3aa7 # v3.21.0
18 changes: 18 additions & 0 deletions .github/actions/setup-rust-cache/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: setup rust cache
description: cache cargo registry and target/ between workflow runs
inputs:
devshell:
description: flake devShell attribute (e.g. ci or default)
required: false
default: ci
runs:
using: composite
steps:
- uses: Swatinem/rust-cache@23869a5bd66c73db3c0ac40331f3206eb23791dc # v2.9.1
with:
# Toolchain comes from Nix, not ~/.cargo/bin.
cache-bin: "false"
# Keep workspace crate artifacts for clippy/test across runs.
cache-workspace-crates: "true"
cache-on-failure: "true"
cmd-format: nix develop .#${{ inputs.devshell }} --quiet -c {0}
1 change: 1 addition & 0 deletions .github/workflows/nix.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ concurrency:
permissions:
contents: read
actions: write
id-token: write
jobs:
check:
runs-on: ubuntu-slim
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/rust-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ concurrency:
permissions:
contents: read
actions: write
id-token: write
jobs:
test:
# WispAudioKit uses APIs that only exist on macOS 26
Expand All @@ -31,6 +32,7 @@ jobs:
xcrun swift --version
xcrun --find metal
- uses: ./.github/actions/setup-nix
- uses: ./.github/actions/setup-rust-cache
# Builds the WispAudioKit Swift static library through build.rs and
# exercises the FFI round-trip via the smoke test in wisp-audiokit.
- run: nix develop --quiet --command cargo test -p wisp-audiokit
- run: nix develop .#ci --quiet --command cargo test -p wisp-audiokit
8 changes: 6 additions & 2 deletions .github/workflows/rust.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ concurrency:
permissions:
contents: read
actions: write
id-token: write
jobs:
check:
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/setup-nix
- uses: ./.github/actions/setup-rust-cache
- run: nix develop .#ci --quiet --command cargo fmt --all -- --check
- run: nix develop .#ci --quiet --command cargo clippy --workspace --all-targets -- -D warnings
- run: nix develop .#ci --quiet --command cargo test --workspace --all-targets
# wisp-desktop is macOS-only (GPUI + WispAudioKit); Linux lacks the X11
# libs GPUI links against. macOS coverage lives in rust-macos.yaml.
- run: nix develop .#ci --quiet --command cargo clippy --workspace --exclude wisp-desktop --all-targets -- -D warnings
- run: nix develop .#ci --quiet --command cargo test --workspace --exclude wisp-desktop --all-targets
1 change: 1 addition & 0 deletions .github/workflows/swift.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ concurrency:
permissions:
contents: read
actions: write
id-token: write
jobs:
check:
runs-on: ubuntu-slim
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions apps/wisp-desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ wisp-core = { path = "../../crates/wisp-core" }
wisp-storage = { path = "../../crates/wisp-storage" }
wisp-audiokit = { path = "../../crates/wisp-audiokit" }

# GPU-accelerated UI framework by the Zed team. V1 is macOS-only so the
# default features (x11/wayland/windows-manifest) are not strictly needed,
# but keeping them on means the workspace also builds on Linux CI where
# the platform-specific code paths are dead-stripped.
# GPU-accelerated UI framework by the Zed team. macOS-only in practice;
# Ubuntu CI excludes this crate (see .github/workflows/rust.yaml).
gpui = { workspace = true }
14 changes: 8 additions & 6 deletions apps/wisp-desktop/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//! - When the next segment for that source arrives, the previous one is
//! marked `final` (the speech engine has locked it in).

use std::collections::VecDeque;
use std::time::Instant;

use wisp_audiokit::{Event, SessionResult, SourceLabel};
Expand Down Expand Up @@ -36,10 +37,11 @@ pub struct Segment {
pub is_final: bool,
}

#[derive(Debug)]
pub struct AppModel {
pub state: SessionState,
pub segments: Vec<Segment>,
pub recent_log: Vec<String>,
pub recent_log: VecDeque<String>,
pub last_error: Option<String>,
}

Expand All @@ -48,7 +50,7 @@ impl AppModel {
Self {
state: SessionState::Idle,
segments: Vec::new(),
recent_log: Vec::new(),
recent_log: VecDeque::new(),
last_error: None,
}
}
Expand Down Expand Up @@ -85,9 +87,9 @@ impl AppModel {
match event {
Event::Result(result) => self.upsert_segment(result),
Event::Log(line) => {
self.recent_log.push(line);
if self.recent_log.len() > 200 {
self.recent_log.drain(0..self.recent_log.len() - 200);
self.recent_log.push_back(line);
while self.recent_log.len() > 200 {
self.recent_log.pop_front();
}
},
}
Expand Down Expand Up @@ -327,6 +329,6 @@ mod tests {
m.ingest(Event::Log(format!("line {i}")));
}
assert!(m.recent_log.len() <= 200);
assert!(m.recent_log.last().unwrap().contains("299"));
assert!(m.recent_log.back().unwrap().contains("299"));
}
}
10 changes: 3 additions & 7 deletions apps/wisp-desktop/src/session_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::sync::mpsc::{Receiver, Sender, TryRecvError, channel};
use std::thread::JoinHandle;
use std::time::Duration;

use wisp_audiokit::{Event, Session, SessionError};
use wisp_audiokit::{Event, Session};

/// Commands the UI sends to the worker.
pub enum Command {
Expand Down Expand Up @@ -108,12 +108,12 @@ fn run_session(
let session = match Session::new(output_dir, locale) {
Ok(s) => s,
Err(e) => {
let _ = update_tx.send(Update::Error(format_err(&e)));
let _ = update_tx.send(Update::Error(e.to_string()));
return;
},
};
if let Err(e) = session.start() {
let _ = update_tx.send(Update::Error(format_err(&e)));
let _ = update_tx.send(Update::Error(e.to_string()));
return;
}
let _ = update_tx.send(Update::Started);
Expand Down Expand Up @@ -145,7 +145,3 @@ fn run_session(
}
let _ = update_tx.send(Update::Stopped);
}

fn format_err(e: &SessionError) -> String {
format!("{e}")
}
1 change: 1 addition & 0 deletions crates/wisp-audiokit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ unsafe_code = "allow"

[dependencies]
wisp-audiokit-sys = { path = "../wisp-audiokit-sys" }
wisp-core = { path = "../wisp-core" }
thiserror = { workspace = true }
81 changes: 71 additions & 10 deletions crates/wisp-audiokit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod imp {
use std::sync::mpsc;

use wisp_audiokit_sys as sys;
use wisp_core::SourceLabel;

/// `WispAudioKit` library version (e.g. `"0.1.0"`).
///
Expand All @@ -32,13 +33,6 @@ mod imp {

// ---- Types ---------------------------------------------------------

/// Which audio source produced a [`SessionResult`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SourceLabel {
Mic,
System,
}

/// One transcription update from a running [`Session`].
#[derive(Debug, Clone, PartialEq)]
pub struct SessionResult {
Expand Down Expand Up @@ -235,7 +229,8 @@ mod imp {
};
let label = match source {
sys::WISP_SOURCE_MIC => SourceLabel::Mic,
_ => SourceLabel::System,
sys::WISP_SOURCE_SYSTEM => SourceLabel::System,
_ => return,
};
let _ = ctx.sender.send(Event::Result(SessionResult {
source: label,
Expand Down Expand Up @@ -268,16 +263,82 @@ mod imp {

#[cfg(not(target_os = "macos"))]
mod imp {
use std::path::Path;

use wisp_core::SourceLabel;

/// `WispAudioKit` library version. Always empty on non-macOS targets.
#[must_use]
pub fn version() -> &'static str {
""
}

/// One transcription update from a running [`Session`].
#[derive(Debug, Clone, PartialEq)]
pub struct SessionResult {
pub source: SourceLabel,
pub segment_id: u64,
pub text: String,
pub start_seconds: f64,
pub end_seconds: f64,
}

/// Either a transcription result or a log line emitted by the session.
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
Result(SessionResult),
Log(String),
}

/// Errors surfaced by [`Session`] operations.
#[derive(Debug, thiserror::Error)]
pub enum SessionError {
#[error("WispAudioKit is only available on macOS")]
UnsupportedPlatform,
}

/// Result alias for session operations.
pub type Result<T> = std::result::Result<T, SessionError>;

/// Stub session — always returns [`SessionError::UnsupportedPlatform`].
pub struct Session;

impl Session {
/// # Errors
/// Always returns [`SessionError::UnsupportedPlatform`].
pub fn new(
_output_dir: impl AsRef<Path>,
_locale: &str,
) -> Result<Self> {
Err(SessionError::UnsupportedPlatform)
}

/// # Errors
/// Always returns [`SessionError::UnsupportedPlatform`].
pub fn start(&self) -> Result<()> {
Err(SessionError::UnsupportedPlatform)
}

/// No-op on non-macOS targets.
pub fn stop(&self) {}

/// Always returns `None`.
#[must_use]
pub fn try_recv(&self) -> Option<Event> {
None
}

/// Always returns `None`.
#[must_use]
pub fn recv(&self) -> Option<Event> {
None
}
}
}

pub use imp::version;
#[cfg(target_os = "macos")]
pub use imp::{Event, Session, SessionError, SessionResult, SourceLabel};
pub use imp::{Event, Session, SessionError, SessionResult};
pub use wisp_core::SourceLabel;

#[cfg(all(test, target_os = "macos"))]
mod tests {
Expand Down
16 changes: 14 additions & 2 deletions crates/wisp-core/src/ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
/// integer IDs (segment IDs, indices, etc.).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SessionId(pub i64);
pub struct SessionId(i64);

impl SessionId {
/// Returns the underlying rowid. Use when interacting with `SQLite` or
Expand All @@ -17,6 +17,12 @@ impl SessionId {
}
}

impl From<i64> for SessionId {
fn from(value: i64) -> Self {
Self(value)
}
}

impl std::fmt::Display for SessionId {
fn fmt(
&self,
Expand All @@ -29,7 +35,7 @@ impl std::fmt::Display for SessionId {
/// Identifier for one transcript segment.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SegmentId(pub i64);
pub struct SegmentId(i64);

impl SegmentId {
/// Returns the underlying rowid.
Expand All @@ -39,6 +45,12 @@ impl SegmentId {
}
}

impl From<i64> for SegmentId {
fn from(value: i64) -> Self {
Self(value)
}
}

impl std::fmt::Display for SegmentId {
fn fmt(
&self,
Expand Down
14 changes: 11 additions & 3 deletions crates/wisp-core/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,18 @@ impl SourceLabel {
/// Parse the stable string form. Returns `None` for unknown values.
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
s.parse().ok()
}
}

impl std::str::FromStr for SourceLabel {
type Err = ();

fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"mic" => Some(Self::Mic),
"system" => Some(Self::System),
_ => None,
"mic" => Ok(Self::Mic),
"system" => Ok(Self::System),
_ => Err(()),
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions crates/wisp-storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ pub struct Storage {
root: PathBuf,
}

impl std::fmt::Debug for Storage {
fn fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
f.debug_struct("Storage")
.field("root", &self.root)
.finish_non_exhaustive()
}
}

impl Storage {
/// Open (or create) the `SQLite` database at `<root>/sessions.db`.
///
Expand Down
Loading
Loading