-
Notifications
You must be signed in to change notification settings - Fork 68
Cli list models #691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
agolokoz
wants to merge
15
commits into
main
Choose a base branch
from
cli-list-models
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Cli list models #691
Changes from 10 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
2e515a6
Add saving last used model
agolokoz 7b0eb70
Show models registry if model is not set or were removed
agolokoz 4f0fd57
Merge branch 'main' into cli-improvements
agolokoz 4d5567a
Merge branch 'main' into cli-save-last-model
agolokoz 83d0137
Fix checking if model exists
agolokoz d1a095f
Move CliApplication to separate file
agolokoz 587da09
Add list-models
agolokoz 5ca377c
Add list-checkpoints
agolokoz 2a2f210
Add models resolving
agolokoz d48395b
Merge branch 'main' into cli-list-models
agolokoz 8486f4b
Move app id to static var
agolokoz 795a169
Handle known shorthands when no checkpoint fits
agolokoz 9a8ce0a
Refactoring
agolokoz a975df7
Merge branch 'main' into cli-list-models
agolokoz 0899b1b
Merge branch 'main' into cli-list-models
agolokoz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| use std::io::IsTerminal; | ||
|
|
||
| use iocraft::prelude::*; | ||
| use uzu::{ | ||
| engine::{Engine, EngineConfig, EngineError}, | ||
| settings::SettingsError, | ||
| }; | ||
|
|
||
| use crate::interactive::{ | ||
| components::{AppSettings, Application, Preferences, Theme}, | ||
| model::resolve_model_id, | ||
| }; | ||
|
|
||
| #[derive(Debug, Clone, PartialEq, thiserror::Error)] | ||
| #[non_exhaustive] | ||
| pub enum CliError { | ||
| #[error(transparent)] | ||
| Engine(#[from] EngineError), | ||
| #[error(transparent)] | ||
| Settigs(#[from] SettingsError), | ||
| #[error("Rendering error: {message}")] | ||
| RenderingError { | ||
| message: String, | ||
| }, | ||
| } | ||
|
|
||
| #[derive(Clone)] | ||
| pub struct CliApplication { | ||
| engine: Engine, | ||
| } | ||
|
|
||
| impl CliApplication { | ||
| pub async fn create(config: EngineConfig) -> Result<Self, CliError> { | ||
| let engine = Engine::new(config).await?; | ||
| Ok(Self::new(engine)) | ||
| } | ||
|
|
||
| pub fn new(engine: Engine) -> Self { | ||
| Self { | ||
| engine, | ||
| } | ||
| } | ||
|
|
||
| pub async fn run_with_model( | ||
| &self, | ||
| model: Option<String>, | ||
| ) -> Result<(), CliError> { | ||
| if !std::io::stdout().is_terminal() { | ||
| return Err(CliError::RenderingError { | ||
| message: "stdout is not a terminal".to_string(), | ||
| }); | ||
| } | ||
|
|
||
| let settings = self.engine.settings().await.ok(); | ||
| let theme = match &settings { | ||
| Some(settings) => Theme::load(settings)?.unwrap_or_default(), | ||
| None => Theme::default(), | ||
| }; | ||
| let preferences = match &settings { | ||
| Some(settings) => Preferences::load(settings)?, | ||
| None => Preferences::default(), | ||
| }; | ||
| let app_settings = match &settings { | ||
| Some(settings) => AppSettings::load(settings)?, | ||
| None => AppSettings::default(), | ||
| }; | ||
|
|
||
| let requested_model = model.or_else(|| app_settings.selected_model_id.clone()); | ||
| let selected_model = match requested_model { | ||
| Some(model) => resolve_model_id(&self.engine, model).await?, | ||
| None => None, | ||
| }; | ||
|
|
||
| element! { | ||
| Application( | ||
| engine: Some(self.engine.clone()), | ||
| settings: settings, | ||
| theme: Some(theme), | ||
| preferences: Some(preferences), | ||
| app_settings: Some(app_settings), | ||
| model: selected_model, | ||
| ) | ||
| } | ||
| .render_loop() | ||
| .await | ||
| .map_err(|error| CliError::RenderingError { | ||
| message: error.to_string(), | ||
| })?; | ||
|
|
||
| Ok(()) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| use std::collections::HashSet; | ||
|
|
||
| use shoji::types::model::Model; | ||
|
|
||
| pub struct ModelCheckpoint { | ||
| pub id: String, | ||
| pub name: String, | ||
| } | ||
|
|
||
| pub fn get_checkpoints( | ||
| models: &[Model], | ||
| model_id: &str, | ||
| ) -> Vec<ModelCheckpoint> { | ||
| models | ||
| .iter() | ||
| .filter(|model| { | ||
| let Some(family) = &model.family else { | ||
| return false; | ||
| }; | ||
| let Some(properties) = &model.properties else { | ||
| return false; | ||
| }; | ||
|
|
||
| let family_id = family.identifier.rsplit(':').next().unwrap_or(&family.identifier); | ||
| format!("{family_id}:{}", properties.identifier) == model_id | ||
| }) | ||
| .map(|model| ModelCheckpoint { | ||
| id: model.identifier.clone(), | ||
| name: model.name(), | ||
| }) | ||
| .collect() | ||
| } | ||
|
|
||
| pub struct ModelFamily { | ||
| pub id: String, | ||
| pub name: String, | ||
| } | ||
|
|
||
| pub fn get_families(models: &[Model]) -> Vec<ModelFamily> { | ||
| let mut families = Vec::<ModelFamily>::new(); | ||
| let mut ids_set = HashSet::<String>::new(); | ||
|
|
||
| for model in models.iter() { | ||
| if let Some(ref family) = model.family { | ||
| if let Some(ref properties) = model.properties { | ||
| let model_id = format!("{}:{}", family.identifier.split(":").last().unwrap(), properties.identifier); | ||
| if !ids_set.contains(&model_id) { | ||
| ids_set.insert(model_id.clone()); | ||
| families.push(ModelFamily { | ||
| id: model_id, | ||
| name: format!("{} {}", family.metadata.name, properties.metadata.name), | ||
| }) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| families | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,103 +1,76 @@ | ||
| use comfy_table::{ | ||
| ContentArrangement, Table, | ||
| modifiers::{UTF8_ROUND_CORNERS, UTF8_SOLID_INNER_BORDERS}, | ||
| presets::UTF8_FULL, | ||
| }; | ||
| use uzu::engine::{Engine, EngineConfig}; | ||
|
|
||
| use crate::interactive::{ | ||
| app::CliApplication, | ||
| list::{get_checkpoints, get_families}, | ||
| }; | ||
|
|
||
| mod app; | ||
| mod components; | ||
| mod flows; | ||
| mod helpers; | ||
| mod list; | ||
| mod model; | ||
| mod sessions; | ||
|
|
||
| use std::io::IsTerminal; | ||
|
|
||
| use components::{Application, Preferences, Theme}; | ||
| use iocraft::prelude::*; | ||
| use uzu::{ | ||
| engine::{Engine, EngineConfig, EngineError}, | ||
| settings::SettingsError, | ||
| }; | ||
|
|
||
| use crate::interactive::components::AppSettings; | ||
|
|
||
| #[derive(Debug, Clone, PartialEq, thiserror::Error)] | ||
| #[non_exhaustive] | ||
| pub enum CliError { | ||
| #[error(transparent)] | ||
| Engine(#[from] EngineError), | ||
| #[error(transparent)] | ||
| Settigs(#[from] SettingsError), | ||
| #[error("Rendering error: {message}")] | ||
| RenderingError { | ||
| message: String, | ||
| }, | ||
| } | ||
|
|
||
| #[derive(Clone)] | ||
| pub struct CliApplication { | ||
| engine: Engine, | ||
| pub async fn run_interactive(model: Option<String>) -> anyhow::Result<()> { | ||
| let engine_config = EngineConfig::default().with_application_identifier("com.trymirai.cli".to_string()); | ||
| let application = CliApplication::create(engine_config).await?; | ||
| application.run_with_model(model).await?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| impl CliApplication { | ||
| pub fn new(engine: Engine) -> Self { | ||
| Self { | ||
| engine, | ||
| } | ||
| pub async fn run_list_models() -> anyhow::Result<()> { | ||
| let engine_config = EngineConfig::default().with_application_identifier("com.trymirai.cli".to_string()); | ||
| let engine = Engine::new(engine_config).await?; | ||
| let models = engine.models().await?; | ||
| if models.is_empty() { | ||
| return Err(anyhow::anyhow!("No models to run")); | ||
| } | ||
|
|
||
| pub async fn run_with_model( | ||
| &self, | ||
| model: Option<String>, | ||
| ) -> Result<(), CliError> { | ||
| if !std::io::stdout().is_terminal() { | ||
| return Err(CliError::RenderingError { | ||
| message: "stdout is not a terminal".to_string(), | ||
| }); | ||
| } | ||
| let families = get_families(&models); | ||
| let mut table = Table::new(); | ||
| table | ||
| .load_preset(UTF8_FULL) | ||
| .apply_modifier(UTF8_ROUND_CORNERS) | ||
| .apply_modifier(UTF8_SOLID_INNER_BORDERS) | ||
| .set_content_arrangement(ContentArrangement::Dynamic) | ||
| .set_header(vec!["Name", "ID"]); | ||
|
|
||
| let settings = self.engine.settings().await.ok(); | ||
| let theme = match &settings { | ||
| Some(settings) => Theme::load(settings)?.unwrap_or_default(), | ||
| None => Theme::default(), | ||
| }; | ||
| let preferences = match &settings { | ||
| Some(settings) => Preferences::load(settings)?, | ||
| None => Preferences::default(), | ||
| }; | ||
| let app_settings = match &settings { | ||
| Some(settings) => AppSettings::load(settings)?, | ||
| None => AppSettings::default(), | ||
| }; | ||
|
|
||
| let mut selected_model = model; | ||
| if selected_model.is_none() { | ||
| selected_model = app_settings.selected_model_id.clone(); | ||
| } | ||
| for family in &families { | ||
| table.add_row(vec![&family.name, &family.id]); | ||
| } | ||
| println!("{table}"); | ||
|
|
||
| element! { | ||
| Application( | ||
| engine: Some(self.engine.clone()), | ||
| settings: settings, | ||
| theme: Some(theme), | ||
| preferences: Some(preferences), | ||
| app_settings: Some(app_settings), | ||
| model: selected_model, | ||
| ) | ||
| } | ||
| .render_loop() | ||
| .await | ||
| .map_err(|error| CliError::RenderingError { | ||
| message: error.to_string(), | ||
| })?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| Ok(()) | ||
| pub async fn run_list_checkpoints(model_id: String) -> anyhow::Result<()> { | ||
| let engine_config = EngineConfig::default().with_application_identifier("com.trymirai.cli".to_string()); | ||
| let engine = Engine::new(engine_config).await?; | ||
| let models = engine.models().await?; | ||
| let checkpoints = get_checkpoints(&models, &model_id); | ||
| if checkpoints.is_empty() { | ||
| return Err(anyhow::anyhow!("No checkpoints found for model: {model_id}")); | ||
| } | ||
| } | ||
|
|
||
| impl CliApplication { | ||
| pub async fn create(config: EngineConfig) -> Result<Self, CliError> { | ||
| let engine = Engine::new(config).await?; | ||
| Ok(Self::new(engine)) | ||
| let mut table = Table::new(); | ||
| table | ||
| .load_preset(UTF8_FULL) | ||
| .apply_modifier(UTF8_ROUND_CORNERS) | ||
| .apply_modifier(UTF8_SOLID_INNER_BORDERS) | ||
| .set_content_arrangement(ContentArrangement::Dynamic) | ||
| .set_header(vec!["Name", "ID"]); | ||
|
|
||
| for checkpoint in &checkpoints { | ||
| table.add_row(vec![&checkpoint.name, &checkpoint.id]); | ||
| } | ||
| } | ||
| println!("{table}"); | ||
|
|
||
| pub async fn run_interactive(model: Option<String>) -> anyhow::Result<()> { | ||
| let engine_config = EngineConfig::default().with_application_identifier("com.trymirai.cli".to_string()); | ||
| let application = CliApplication::create(engine_config).await?; | ||
| application.run_with_model(model).await?; | ||
| Ok(()) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.