Skip to content

Commit

Permalink
wip - updating profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
aprxi committed Aug 7, 2024
1 parent 08c8b57 commit a5ba7ad
Show file tree
Hide file tree
Showing 14 changed files with 313 additions and 210 deletions.
1 change: 1 addition & 0 deletions lumni/src/apps/builtin/llm/prompt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub mod src {
mod app;
mod chat;
mod cli;
mod defaults;
mod error;
mod handler;
Expand Down
181 changes: 8 additions & 173 deletions lumni/src/apps/builtin/llm/prompt/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::io::{self, Write};
use std::sync::Arc;

use clap::{Arg, ArgAction, ArgMatches, Command};
use clap::{ArgMatches, Command};
use crossterm::cursor::Show;
use crossterm::event::{poll, DisableMouseCapture, EnableMouseCapture};
use crossterm::execute;
Expand All @@ -14,187 +14,21 @@ use lumni::api::error::ApplicationError;
use lumni::api::spec::ApplicationSpec;
use ratatui::backend::CrosstermBackend;
use ratatui::Terminal;
use serde_json::Value as JsonValue;
use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
use tokio::signal;
use tokio::sync::Mutex;
use tokio::time::{timeout, Duration};

use super::chat::db::{ConversationDatabase, UserProfileDbHandler};
use super::chat::db::{ConversationDatabase, ModelServerName};
use super::chat::{
prompt_app, App, AssistantManager, ChatEvent, NewConversation,
PromptInstruction, ThreadedChatSession,
};
use super::server::{ModelServer, ModelServerName, ServerTrait};
pub use crate::external as lumni;

fn parse_cli_arguments(spec: ApplicationSpec) -> Command {
let name = Box::leak(spec.name().into_boxed_str()) as &'static str;
let version = Box::leak(spec.version().into_boxed_str()) as &'static str;

Command::new(name)
.version(version)
.about("CLI for prompt interaction")
.arg_required_else_help(false)
.subcommand(
Command::new("db")
.about("Query the conversation database")
.arg(
Arg::new("list")
.long("list")
.short('l')
.help("List recent conversations")
.num_args(0..=1)
.value_name("LIMIT"),
)
.arg(
Arg::new("id")
.long("id")
.short('i')
.help("Fetch a specific conversation by ID")
.num_args(1),
),
)
.subcommand(
Command::new("profile")
.about("Manage user profiles")
.arg(
Arg::new("name")
.help("Name of the profile")
.required(true)
.index(1),
)
.arg(
Arg::new("set")
.long("set")
.short('s')
.help("Set profile values")
.num_args(2)
.value_names(["KEY", "VALUE"])
.action(ArgAction::Append),
)
.arg(
Arg::new("get")
.long("get")
.short('g')
.help("Get a specific profile value")
.num_args(1)
.value_name("KEY"),
)
.arg(
Arg::new("show")
.long("show")
.help("Show all profile values")
.num_args(0),
)
.arg(
Arg::new("delete")
.long("delete")
.short('d')
.help("Delete the profile")
.num_args(0),
)
.arg(
Arg::new("default")
.long("default")
.help("Set as the default profile")
.num_args(0),
),
)
.arg(
Arg::new("profile")
.long("profile")
.short('p')
.help("Use a specific profile")
.global(true),
)
.arg(
Arg::new("system")
.long("system")
.short('s')
.help("System prompt"),
)
.arg(
Arg::new("assistant")
.long("assistant")
.short('a')
.help("Specify an assistant to use"),
)
.arg(
Arg::new("server")
.long("server")
.short('S')
.help("Server to use for processing the request"),
)
.arg(Arg::new("options").long("options").short('o').help(
"Comma-separated list of model options e.g., \
temperature=1,max_tokens=100",
))
}

async fn handle_db_subcommand(
db_matches: &ArgMatches,
db_conn: &Arc<ConversationDatabase>,
) -> Result<(), ApplicationError> {
if db_matches.contains_id("list") {
let limit = match db_matches.get_one::<String>("list") {
Some(value) => value.parse().unwrap_or(20),
None => 20,
};
db_conn.print_conversation_list(limit).await
} else if let Some(id_value) = db_matches.get_one::<String>("id") {
db_conn.print_conversation_by_id(id_value).await
} else {
db_conn.print_last_conversation().await
}
}

async fn handle_profile_subcommand(
profile_matches: &ArgMatches,
db_handler: &mut UserProfileDbHandler,
) -> Result<(), ApplicationError> {
let profile_name = profile_matches.get_one::<String>("name").unwrap();

if profile_matches.contains_id("set") {
let mut settings = JsonValue::default();
let values: Vec<&str> = profile_matches
.get_many::<String>("set")
.unwrap()
.map(AsRef::as_ref)
.collect();
for chunk in values.chunks(2) {
if let [key, value] = chunk {
settings[key.to_string()] =
JsonValue::String(value.to_string());
}
}
db_handler
.set_profile_settings(&profile_name.to_string(), &settings)
.await?;
println!("Profile '{}' updated.", profile_name);
} else if let Some(key) = profile_matches.get_one::<String>("get") {
let settings = db_handler.get_profile_settings(profile_name).await?;
if let Some(value) = settings.get(key) {
println!("{}: {}", key, value);
} else {
println!("Key '{}' not found in profile '{}'", key, profile_name);
}
} else if profile_matches.contains_id("show") {
let settings = db_handler.get_profile_settings(profile_name).await?;
println!("Profile '{}' settings:", profile_name);
for (key, value) in settings.as_object().unwrap() {
println!(" {}: {}", key, value);
}
} else if profile_matches.contains_id("delete") {
db_handler.delete_profile(profile_name).await?;
println!("Profile '{}' deleted.", profile_name);
} else if profile_matches.contains_id("default") {
db_handler.set_default_profile(profile_name).await?;
println!("Profile '{}' set as default.", profile_name);
}

Ok(())
}
use super::cli::{
handle_db_subcommand, handle_profile_subcommand, parse_cli_arguments,
};
use super::server::{ModelServer, ServerTrait};
use crate::external as lumni;

async fn create_prompt_instruction(
matches: Option<&ArgMatches>,
Expand Down Expand Up @@ -358,6 +192,7 @@ pub async fn run_cli(
}
}
}

// TODO: Add support for --profile option in the prompt command
let prompt_instruction =
create_prompt_instruction(matches.as_ref(), &db_conn).await?;
Expand Down
2 changes: 1 addition & 1 deletion lumni/src/apps/builtin/llm/prompt/src/chat/db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ CREATE TABLE metadata (

CREATE TABLE user_profiles (
id INTEGER PRIMARY KEY,
name TEXT,
name TEXT UNIQUE NOT NULL,
options TEXT NOT NULL -- JSON string
);

Expand Down
68 changes: 46 additions & 22 deletions lumni/src/apps/builtin/llm/prompt/src/chat/db/user_profiles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,63 @@ impl UserProfileDbHandler {
self.profile_name = Some(profile_name);
}

pub async fn set_profile_settings(
pub async fn create_or_update(
&self,
profile_name: &str,
settings: &JsonValue,
new_settings: &JsonValue,
) -> Result<(), SqliteError> {
let mut db = self.db.lock().await;
db.process_queue_with_result(|tx| {
let json_string = serde_json::to_string(settings).map_err(|e| {
SqliteError::FromSqlConversionFailure(
0,
rusqlite::types::Type::Text,
Box::new(e),
// Check if the profile exists and get current settings
let current_settings: Option<String> = tx
.query_row(
"SELECT options FROM user_profiles WHERE name = ?",
params![profile_name],
|row| row.get(0),
)
})?;
.optional()?;

let merged_settings = if let Some(current_json) = current_settings {
// Parse current settings
let mut current: serde_json::Value =
serde_json::from_str(&current_json).map_err(|e| {
SqliteError::FromSqlConversionFailure(
0,
rusqlite::types::Type::Text,
Box::new(e),
)
})?;

// Merge new settings into current settings
if let Some(current_obj) = current.as_object_mut() {
if let Some(new_obj) = new_settings.as_object() {
for (key, value) in new_obj {
current_obj.insert(key.clone(), value.clone());
}
}
}
current
} else {
// If no current settings, use new settings as is
new_settings.clone()
};

let json_string =
serde_json::to_string(&merged_settings).map_err(|e| {
SqliteError::FromSqlConversionFailure(
0,
rusqlite::types::Type::Text,
Box::new(e),
)
})?;

// Insert or replace with merged settings
tx.execute(
"INSERT OR REPLACE INTO user_profiles (name, options) VALUES \
(?, ?)",
params![profile_name, json_string],
)?;

Ok(())
})
}
Expand Down Expand Up @@ -123,18 +161,4 @@ impl UserProfileDbHandler {
profiles.collect()
})
}

pub async fn update_profile_setting(
&self,
profile_name: &str,
key: &str,
value: &JsonValue,
) -> Result<(), SqliteError> {
let mut settings = self.get_profile_settings(profile_name).await?;
if let Some(obj) = settings.as_object_mut() {
obj.insert(key.to_string(), value.clone());
self.set_profile_settings(profile_name, &settings).await?;
}
Ok(())
}
}
2 changes: 1 addition & 1 deletion lumni/src/apps/builtin/llm/prompt/src/chat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub use session::{prompt_app, App, ChatEvent, ThreadedChatSession};

pub use super::defaults::*;
pub use super::error::{PromptError, PromptNotReadyReason};
pub use super::server::{CompletionResponse, ModelServer, ServerManager};
use super::server::{CompletionResponse, ModelServer, ServerManager};
use super::tui::{
draw_ui, AppUi, ColorScheme, ColorSchemeType, CommandLineAction,
ConversationEvent, KeyEventHandler, ModalWindowType, PromptAction,
Expand Down
51 changes: 51 additions & 0 deletions lumni/src/apps/builtin/llm/prompt/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
mod subcommands;

use clap::{Arg, Command};
use lumni::api::spec::ApplicationSpec;
use subcommands::db::create_db_subcommand;
pub use subcommands::db::handle_db_subcommand;
use subcommands::profile::create_profile_subcommand;
pub use subcommands::profile::handle_profile_subcommand;

use super::chat::db::{ConversationDatabase, UserProfileDbHandler};
use crate::external as lumni;

pub fn parse_cli_arguments(spec: ApplicationSpec) -> Command {
let name = Box::leak(spec.name().into_boxed_str()) as &'static str;
let version = Box::leak(spec.version().into_boxed_str()) as &'static str;
Command::new(name)
.version(version)
.about("CLI for prompt interaction")
.arg_required_else_help(false)
.subcommand(create_db_subcommand())
.subcommand(create_profile_subcommand())
.arg(
Arg::new("profile")
.long("profile")
.short('p')
.help("Use a specific profile")
.global(true),
)
.arg(
Arg::new("system")
.long("system")
.short('s')
.help("System prompt"),
)
.arg(
Arg::new("assistant")
.long("assistant")
.short('a')
.help("Specify an assistant to use"),
)
.arg(
Arg::new("server")
.long("server")
.short('S')
.help("Server to use for processing the request"),
)
.arg(Arg::new("options").long("options").short('o').help(
"Comma-separated list of model options e.g., \
temperature=1,max_tokens=100",
))
}
Loading

0 comments on commit a5ba7ad

Please sign in to comment.