Skip to content

Commit

Permalink
refactor: Model boundaries
Browse files Browse the repository at this point in the history
  • Loading branch information
Dustin Blackman committed Dec 20, 2023
1 parent 9b5de54 commit b489508
Show file tree
Hide file tree
Showing 14 changed files with 89 additions and 51 deletions.
9 changes: 7 additions & 2 deletions src/application/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::config::Config;
use crate::config::ConfigKey;
use crate::domain::models::Action;
use crate::domain::models::Author;
use crate::domain::models::BackendName;
use crate::domain::models::BackendPrompt;
use crate::domain::models::EditorName;
use crate::domain::models::Event;
Expand All @@ -34,6 +35,7 @@ use crate::domain::services::events::EventsService;
use crate::domain::services::AppState;
use crate::domain::services::AppStateProps;
use crate::domain::services::Bubble;
use crate::infrastructure::backends::BackendManager;
use crate::infrastructure::editors::EditorManager;

/// Verifies that the current window size is large enough to handle the bare
Expand Down Expand Up @@ -273,9 +275,12 @@ pub async fn start(
session_id = Some(Config::get(ConfigKey::SessionID));
}

let backend =
BackendManager::get(BackendName::parse(Config::get(ConfigKey::Backend)).unwrap())?;
let editor = EditorManager::get(EditorName::parse(Config::get(ConfigKey::Editor)).unwrap())?;
let app_state_pros = AppStateProps {
backend_name: Config::get(ConfigKey::Backend),
editor_name: editor_name.clone(),
backend,
editor,
model_name: Config::get(ConfigKey::Model),
theme_name: Config::get(ConfigKey::Theme),
theme_file: Config::get(ConfigKey::ThemeFile),
Expand Down
5 changes: 5 additions & 0 deletions src/domain/models/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ pub struct BackendResponse {

#[async_trait]
pub trait Backend {
/// Returns the name of the backend
fn name(&self) -> BackendName;

/// Used at startup to verify all configurations are available to work with
/// the backend.
async fn health_check(&self) -> Result<()>;
Expand All @@ -91,3 +94,5 @@ pub trait Backend {
tx: &'a mpsc::UnboundedSender<Event>,
) -> Result<()>;
}

pub type BackendBox = Box<dyn Backend + Send + Sync>;
5 changes: 5 additions & 0 deletions src/domain/models/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ File: {file_path}

#[async_trait]
pub trait Editor {
/// Returns the name of the editor.
fn name(&self) -> EditorName;

/// Used at startup to verify all configurations are available to work with
/// the editor.
async fn health_check(&self) -> Result<()>;
Expand All @@ -98,3 +101,5 @@ pub trait Editor {
accept_type: AcceptType,
) -> Result<()>;
}

pub type EditorBox = Box<dyn Editor + Send + Sync>;
21 changes: 9 additions & 12 deletions src/domain/services/actions.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::sync::Arc;

use anyhow::Result;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
Expand All @@ -8,15 +10,13 @@ use crate::config::ConfigKey;
use crate::domain::models::AcceptType;
use crate::domain::models::Action;
use crate::domain::models::Author;
use crate::domain::models::BackendName;
use crate::domain::models::BackendBox;
use crate::domain::models::EditorContext;
use crate::domain::models::EditorName;
use crate::domain::models::Event;
use crate::domain::models::Message;
use crate::domain::models::MessageType;
use crate::domain::models::SlashCommand;
use crate::infrastructure::backends::BackendBox;
use crate::infrastructure::backends::BackendManager;
use crate::infrastructure::editors::EditorManager;

pub fn help_text() -> String {
Expand Down Expand Up @@ -221,11 +221,11 @@ pub struct ActionsService {}

impl ActionsService {
pub async fn start(
backend: BackendBox,
tx: mpsc::UnboundedSender<Event>,
rx: &mut mpsc::UnboundedReceiver<Action>,
) -> Result<()> {
let backend =
BackendManager::get(BackendName::parse(Config::get(ConfigKey::Backend)).unwrap())?;
let backend_arc = Arc::new(backend);

// Lazy default.
let mut worker: JoinHandle<Result<()>> = tokio::spawn(async {
Expand All @@ -252,11 +252,11 @@ impl ActionsService {
Action::BackendRequest(prompt) => {
if let Some(command) = SlashCommand::parse(&prompt.text) {
if command.is_model_list() {
model_list(&backend, &tx).await?;
model_list(&backend_arc, &tx).await?;
continue;
}
if command.is_model_set() {
model_set(&backend, &tx, &prompt.text).await?;
model_set(&backend_arc, &tx, &prompt.text).await?;
continue;
}
if command.is_help() {
Expand All @@ -265,12 +265,9 @@ impl ActionsService {
}
}

let backend_worker = backend_arc.clone();
worker = tokio::spawn(async move {
let res = BackendManager::get(
BackendName::parse(Config::get(ConfigKey::Backend)).unwrap(),
)?
.get_completion(prompt, &worker_tx)
.await;
let res = backend_worker.get_completion(prompt, &worker_tx).await;

if let Err(err) = res {
worker_error(err, &worker_tx)?;
Expand Down
38 changes: 12 additions & 26 deletions src/domain/services/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,21 @@ use super::Themes;
use crate::domain::models::AcceptType;
use crate::domain::models::Action;
use crate::domain::models::Author;
use crate::domain::models::BackendName;
use crate::domain::models::BackendBox;
use crate::domain::models::BackendResponse;
use crate::domain::models::EditorBox;
use crate::domain::models::EditorContext;
use crate::domain::models::EditorName;
use crate::domain::models::Message;
use crate::domain::models::MessageType;
use crate::domain::models::SlashCommand;
use crate::infrastructure::backends::BackendManager;
use crate::infrastructure::editors::EditorManager;

#[cfg(test)]
#[path = "app_state_test.rs"]
mod tests;

pub struct AppStateProps {
pub backend_name: String,
pub editor_name: EditorName,
pub backend: BackendBox,
pub editor: EditorBox,
pub model_name: String,
pub theme_name: String,
pub theme_file: String,
Expand Down Expand Up @@ -58,7 +56,6 @@ impl<'a> AppState<'a> {
}

async fn init(props: AppStateProps) -> Result<AppState<'a>> {
let backend_name = &props.backend_name;
let model_name = &props.model_name;
let theme = Themes::get(&props.theme_name, &props.theme_file)?;

Expand All @@ -76,8 +73,8 @@ impl<'a> AppState<'a> {
waiting_for_backend: false,
};

let backend = BackendManager::get(BackendName::parse(backend_name.to_string()).unwrap())?;
if let Err(err) = backend.health_check().await {
let backend_name = props.backend.name();
if let Err(err) = props.backend.health_check().await {
app_state
.messages
.push(Message::new_with_type(
Expand All @@ -86,7 +83,7 @@ impl<'a> AppState<'a> {
&format!("Hey, it looks like backend {backend_name} isn't running, I can't connect to it. You should double check that before we start talking, otherwise I may crash.\n\nError: {err}"),
));
} else {
let models = backend.list_models().await?;
let models = props.backend.list_models().await?;
if !models.contains(&model_name.to_string()) {
app_state
.messages
Expand All @@ -99,11 +96,7 @@ impl<'a> AppState<'a> {
}

// Fallback to the default intro message when there's no editor context.
if app_state
.add_editor_context(props.editor_name)
.await
.is_err()
{
if app_state.add_editor_context(props.editor).await.is_err() {
app_state.messages.push(Message::new(
Author::Model,
"Hey there! What can I do for you?",
Expand Down Expand Up @@ -136,22 +129,15 @@ impl<'a> AppState<'a> {
.codeblocks
.replace_from_messages(&app_state.messages);

if let Ok(editor) = EditorManager::get(props.editor_name) {
if editor.health_check().await.is_ok() {
app_state.editor_context = editor.get_context().await?;
}
if props.editor.health_check().await.is_ok() {
app_state.editor_context = props.editor.get_context().await?;
}

return Ok(app_state);
}

async fn add_editor_context(&mut self, editor_name: EditorName) -> Result<()> {
let editor_res = EditorManager::get(editor_name.clone());
if editor_res.is_err() {
return Err(anyhow!("Failed to load editor from manager"));
}

let editor = editor_res.unwrap();
async fn add_editor_context(&mut self, editor: EditorBox) -> Result<()> {
let editor_name = editor.name();
if let Err(err) = editor.health_check().await {
self
.messages
Expand Down
18 changes: 14 additions & 4 deletions src/domain/services/app_state_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use super::AppState;
use crate::domain::models::AcceptType;
use crate::domain::models::Action;
use crate::domain::models::Author;
use crate::domain::models::BackendName;
use crate::domain::models::BackendResponse;
use crate::domain::models::EditorName;
use crate::domain::models::Message;
Expand All @@ -18,6 +19,8 @@ use crate::domain::services::CodeBlocks;
use crate::domain::services::Scroll;
use crate::domain::services::Sessions;
use crate::domain::services::Themes;
use crate::infrastructure::backends::BackendManager;
use crate::infrastructure::editors::EditorManager;

impl Default for AppState<'static> {
fn default() -> AppState<'static> {
Expand Down Expand Up @@ -247,13 +250,17 @@ mod handle_backend_response {
}

mod init {

use super::*;

#[tokio::test]
async fn it_inits_and_reloads_from_session() -> Result<()> {
let backend = BackendManager::get(BackendName::Ollama)?;
let editor = EditorManager::get(EditorName::None)?;

let app_state = AppState::new(AppStateProps {
backend_name: "ollama".to_string(),
editor_name: EditorName::Clipboard,
backend,
editor,
model_name: "codellama:latest".to_string(),
theme_name: "base16-onedark".to_string(),
theme_file: "".to_string(),
Expand All @@ -263,9 +270,12 @@ mod init {
app_state.save_session().await?;

let session_id = app_state.session_id;
let backend = BackendManager::get(BackendName::Ollama)?;
let editor = EditorManager::get(EditorName::None)?;

AppState::new(AppStateProps {
backend_name: "ollama".to_string(),
editor_name: EditorName::Clipboard,
backend,
editor,
model_name: "codellama:latest".to_string(),
theme_name: "base16-onedark".to_string(),
theme_file: "".to_string(),
Expand Down
4 changes: 1 addition & 3 deletions src/infrastructure/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ pub mod openai;
use anyhow::bail;
use anyhow::Result;

use crate::domain::models::Backend;
use crate::domain::models::BackendBox;
use crate::domain::models::BackendName;

pub type BackendBox = Box<dyn Backend + Send + Sync>;

pub struct BackendManager {}

impl BackendManager {
Expand Down
5 changes: 5 additions & 0 deletions src/infrastructure/backends/ollama.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::config::Config;
use crate::config::ConfigKey;
use crate::domain::models::Author;
use crate::domain::models::Backend;
use crate::domain::models::BackendName;
use crate::domain::models::BackendPrompt;
use crate::domain::models::BackendResponse;
use crate::domain::models::Event;
Expand Down Expand Up @@ -67,6 +68,10 @@ impl Default for Ollama {

#[async_trait]
impl Backend for Ollama {
fn name(&self) -> BackendName {
return BackendName::Ollama;
}

#[allow(clippy::implicit_return)]
async fn health_check(&self) -> Result<()> {
let res = reqwest::Client::new()
Expand Down
5 changes: 5 additions & 0 deletions src/infrastructure/backends/openai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::config::Config;
use crate::config::ConfigKey;
use crate::domain::models::Author;
use crate::domain::models::Backend;
use crate::domain::models::BackendName;
use crate::domain::models::BackendPrompt;
use crate::domain::models::BackendResponse;
use crate::domain::models::Event;
Expand Down Expand Up @@ -83,6 +84,10 @@ impl Default for OpenAI {

#[async_trait]
impl Backend for OpenAI {
fn name(&self) -> BackendName {
return BackendName::OpenAI;
}

#[allow(clippy::implicit_return)]
async fn health_check(&self) -> Result<()> {
if self.url.is_empty() {
Expand Down
5 changes: 5 additions & 0 deletions src/infrastructure/editors/clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ use async_trait::async_trait;
use crate::domain::models::AcceptType;
use crate::domain::models::Editor;
use crate::domain::models::EditorContext;
use crate::domain::models::EditorName;
use crate::domain::services::clipboard::ClipboardService;

#[derive(Default)]
pub struct Clipboard {}

#[async_trait]
impl Editor for Clipboard {
fn name(&self) -> EditorName {
return EditorName::Clipboard;
}

#[allow(clippy::implicit_return)]
async fn health_check(&self) -> Result<()> {
if let Err(err) = ClipboardService::healthcheck() {
Expand Down
4 changes: 1 addition & 3 deletions src/infrastructure/editors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ pub mod noop;
use anyhow::bail;
use anyhow::Result;

use crate::domain::models::Editor;
use crate::domain::models::EditorBox;
use crate::domain::models::EditorName;

pub type EditorBox = Box<dyn Editor + Send + Sync>;

pub struct EditorManager {}

impl EditorManager {
Expand Down
5 changes: 5 additions & 0 deletions src/infrastructure/editors/neovim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use tokio::process::Command;
use crate::domain::models::AcceptType;
use crate::domain::models::Editor;
use crate::domain::models::EditorContext;
use crate::domain::models::EditorName;

fn base64_to_string<'de, D: Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> {
let val = match serde::de::Deserialize::deserialize(deserializer)? {
Expand Down Expand Up @@ -88,6 +89,10 @@ pub struct Neovim {}

#[async_trait]
impl Editor for Neovim {
fn name(&self) -> EditorName {
return EditorName::Neovim;
}

#[allow(clippy::implicit_return)]
async fn health_check(&self) -> Result<()> {
if env::var("NVIM").is_err() {
Expand Down
4 changes: 4 additions & 0 deletions src/infrastructure/editors/noop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ use async_trait::async_trait;
use crate::domain::models::AcceptType;
use crate::domain::models::Editor;
use crate::domain::models::EditorContext;
use crate::domain::models::EditorName;

#[derive(Default)]
pub struct NoopEditor {}

#[async_trait]
impl Editor for NoopEditor {
fn name(&self) -> EditorName {
return EditorName::None;
}
#[allow(clippy::implicit_return)]
async fn health_check(&self) -> Result<()> {
return Ok(());
Expand Down
Loading

0 comments on commit b489508

Please sign in to comment.