Skip to content

Commit

Permalink
unship(app): remove special command handling (#3)
Browse files Browse the repository at this point in the history
"Hey, robot", "clipboard mode", etc

Remove ability to directly convert transcription into command. Have come
to the general conclusion that the command capability of the system
should be much more flexible and not coupled to audio input.
  • Loading branch information
dev-msp committed Apr 15, 2024
1 parent 1762c60 commit 66815b0
Show file tree
Hide file tree
Showing 5 changed files with 11 additions and 234 deletions.
4 changes: 0 additions & 4 deletions src/app/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ impl Command {
_ => None,
}
}

pub fn as_json(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap()
}
}

pub struct CmdStream(Receiver<serde_json::Value>);
Expand Down
110 changes: 0 additions & 110 deletions src/app/input/iter.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/app/input/mod.rs

This file was deleted.

125 changes: 9 additions & 116 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
pub mod command;
mod input;
mod response;
mod state;

use anyhow::anyhow;
use cpal::Device;
use crossbeam::channel::unbounded;
use itertools::Itertools;
use regex::Regex;
use sttx::{IteratorExt, Timing};
use sttx::IteratorExt;

use crate::app::input::iter::alpha_only;
use crate::audio::input::{controlled_recording, Recording};
use crate::whisper::TranscriptionJob;
use crate::{socket::receive_instructions, sync, whisper, DaemonInit};

use self::command::{CmdStream, Command};
use self::input::iter;
use self::response::Response;
use self::state::{Chat, Mode};

pub struct Daemon {
config: DaemonInit,
Expand All @@ -43,101 +37,6 @@ impl Transcription {
}
}

impl TryFrom<&Timing> for Command {
type Error = ();

fn try_from(t: &Timing) -> Result<Self, Self::Error> {
let content = t.content().to_ascii_lowercase();
{
if let Some(cmd) = handle_reset(&content) {
return Ok(cmd);
};

if let Some(cmd) = handle_hey_robot(&content) {
return Ok(cmd);
}
}

let word_re = Regex::new(r"^(set|mode|to|chat|live|clipboard)$").unwrap();
let it = iter::Iter::from(content.to_string());
let mut words = it
.words()
.map(alpha_only)
.filter(|bos| word_re.is_match(bos));

let prefix = words.clone().map(|bos| bos.to_string()).take(3).join(" ");
log::trace!("Prefix: {}", prefix);

let mode = if prefix == "set mode to" {
words.nth(3)
} else {
let md = words.next();
let wd = words.next();
match (md, wd) {
(Some(word), Some(wd)) if wd.as_ref() == "mode" => Some(word),
_ => None,
}
};

let Some(mode) = mode else {
return Err(());
};

log::info!("Mode: {}", mode);

match mode.as_ref() {
"chat" => Ok(Command::Mode(Mode::Chat(Chat::StartNew(
"You are a terse assistant with minimal affect.".into(),
)))),
"live" => Ok(Command::Mode(Mode::LiveTyping)),
"clipboard" => Ok(Command::Mode(Mode::Clipboard {
use_clipboard: content.contains("use the clipboard"),
use_llm: false,
})),
_ => Err(()),
}
}
}

fn handle_reset(content: &str) -> Option<Command> {
let it = iter::Iter::from(content.to_string());
let (fst, snd) = it.words().tuple_windows().next()?;
match (fst.as_ref(), snd.as_ref()) {
("reset", "yourself") => Some(Command::Reset),
_ => None,
}
}

fn handle_hey_robot(content: &str) -> Option<Command> {
let it = iter::Iter::from(content.to_string());
let words = it.words().map(alpha_only);

log::trace!("Words: {:?}", it.words().collect::<Vec<_>>());
let (fst, snd, fol) = words.tuple_windows().next()?;
log::trace!("Fst: {:?}, Snd: {:?}", fst, snd);
match (fst.as_ref(), snd.as_ref()) {
("hey", "robot") => {
let use_clipboard = content.contains("use the clipboard");
// SAFETY: From the implementation of ByteOffsetString, the segment offset is known to
// be within the bounds of the content string.
let slice = &content[fol.segment_offset()..];
let content = if use_clipboard {
slice.replace("clipboard", "content provided")
} else {
slice.to_string()
};
Some(Command::Respond(Response::Transcription {
content: Some(content),
mode: Mode::Clipboard {
use_clipboard,
use_llm: true,
},
}))
}
_ => None,
}
}

impl Daemon {
pub fn new(config: DaemonInit, input_device: Option<Device>) -> Self {
Self {
Expand All @@ -159,7 +58,7 @@ impl Daemon {
let (to_whisper, from_recordings) = unbounded();
let (whisper_output, tx_worker) = whisper::transcription_worker(&model, from_recordings)?;

let ((rcmds, scmds), resps, listener) = receive_instructions(&self.config.socket_path)?;
let (rcmds, resps, listener) = receive_instructions(&self.config.socket_path)?;
let mut commands = CmdStream::new(rcmds);

#[allow(unused_assignments)]
Expand Down Expand Up @@ -227,19 +126,13 @@ impl Daemon {
log::info!("No transcription");
}

if let Some(cmd) = t.clone().and_then(|t| Command::try_from(&t).ok()) {
scmds.send(cmd.as_json())?;
log::info!("Sent command: {}", cmd.as_json().to_string());
resps.send(cmd.as_response().unwrap_or(Response::Ack).as_json())?;
} else {
resps.send(
Response::Transcription {
content: t.map(|t| t.content().to_string()),
mode: new_state.mode(),
}
.as_json(),
)?;
}
resps.send(
Response::Transcription {
content: t.map(|t| t.content().to_string()),
mode: new_state.mode(),
}
.as_json(),
)?;
}
Err(e) => {
log::error!("{e}");
Expand Down
5 changes: 2 additions & 3 deletions src/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,9 @@ fn handle_stream(
}

type Handle = std::thread::JoinHandle<Result<(), anyhow::Error>>;
type ChannelPair<T> = (Receiver<T>, Sender<T>);

// Triple of channel pair (commands), sender (responses), and handle for the socket thread
type InstructionHandle = (ChannelPair<Value>, Sender<Value>, Handle);
type InstructionHandle = (Receiver<Value>, Sender<Value>, Handle);

pub fn receive_instructions(socket_path: &str) -> Result<InstructionHandle, anyhow::Error> {
match std::fs::metadata(socket_path) {
Expand All @@ -210,7 +209,7 @@ pub fn receive_instructions(socket_path: &str) -> Result<InstructionHandle, anyh
let (rsend, rrecv) = unbounded();
let sock_path = socket_path.to_string();
Ok((
(crecv, csend.clone()),
crecv,
rsend,
std::thread::spawn(move || {
let listener = UnixListener::bind(sock_path).expect("Failed to bind to socket");
Expand Down

0 comments on commit 66815b0

Please sign in to comment.