feat: store Pi Switch config in SQLite, activate on switch
This commit is contained in:
Generated
+95
@@ -8,6 +8,18 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -978,6 +990,18 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-streaming-iterator"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
@@ -1453,12 +1477,30 @@ version = "0.12.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashlink"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.14.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -1917,6 +1959,7 @@ dependencies = [
|
|||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"indexmap 2.14.0",
|
"indexmap 2.14.0",
|
||||||
"reqwest 0.12.28",
|
"reqwest 0.12.28",
|
||||||
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
@@ -1983,6 +2026,17 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libsqlite3-sys"
|
||||||
|
version = "0.30.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -2940,6 +2994,21 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusqlite"
|
||||||
|
version = "0.32.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"chrono",
|
||||||
|
"fallible-iterator",
|
||||||
|
"fallible-streaming-iterator",
|
||||||
|
"hashlink",
|
||||||
|
"libsqlite3-sys",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "2.1.3"
|
version = "2.1.3"
|
||||||
@@ -4332,6 +4401,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -5211,6 +5286,26 @@ dependencies = [
|
|||||||
"zvariant",
|
"zvariant",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.118",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ anyhow = "1.0"
|
|||||||
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
|
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
|
rusqlite = { version = "0.32", features = ["bundled", "chrono"] }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "kimiswitch_lib"
|
name = "kimiswitch_lib"
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use crate::models::{Agent, Config, DiscoveredModel, Provider, ProviderType};
|
use crate::db;
|
||||||
|
use crate::models::{Agent, Config, DiscoveredModel, Model, Provider, ProviderType};
|
||||||
use crate::pi_io;
|
use crate::pi_io;
|
||||||
|
use indexmap::IndexMap;
|
||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
|
|
||||||
fn fmt_anyhow(err: anyhow::Error) -> String {
|
fn fmt_anyhow(err: anyhow::Error) -> String {
|
||||||
@@ -16,6 +18,11 @@ pub fn debug_log(message: String) {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn load_agent_config_command(agent: Agent) -> Result<Config, String> {
|
pub fn load_agent_config_command(agent: Agent) -> Result<Config, String> {
|
||||||
|
// Load from Pi Switch's own SQLite database first.
|
||||||
|
match db::load_config(&agent) {
|
||||||
|
Ok(config) if !config.providers.is_empty() => Ok(config),
|
||||||
|
_ => {
|
||||||
|
// Fallback to the agent's native config on first use.
|
||||||
match agent {
|
match agent {
|
||||||
Agent::KimiCode => crate::kimi_code_io::load_kimi_code_config_as_config()
|
Agent::KimiCode => crate::kimi_code_io::load_kimi_code_config_as_config()
|
||||||
.map_err(fmt_anyhow),
|
.map_err(fmt_anyhow),
|
||||||
@@ -24,19 +31,62 @@ pub fn load_agent_config_command(agent: Agent) -> Result<Config, String> {
|
|||||||
Ok(pi_io::pi_file_to_config(&file))
|
Ok(pi_io::pi_file_to_config(&file))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn save_agent_config_command(agent: Agent, config: Config) -> Result<(), String> {
|
pub fn save_agent_config_command(agent: Agent, config: Config) -> Result<(), String> {
|
||||||
|
// Save the full Pi Switch configuration to local SQLite.
|
||||||
|
db::save_config(&agent, &config).map_err(fmt_anyhow)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn activate_agent_config_command(agent: Agent) -> Result<(), String> {
|
||||||
|
// Load the full config from SQLite and write only the active provider
|
||||||
|
// to the agent's native config file.
|
||||||
|
let config = db::load_config(&agent).map_err(fmt_anyhow)?;
|
||||||
|
let active_config = build_active_config(&config);
|
||||||
match agent {
|
match agent {
|
||||||
Agent::KimiCode => crate::kimi_code_io::save_config_as_kimi_code(&config).map_err(fmt_anyhow),
|
Agent::KimiCode => crate::kimi_code_io::save_config_as_kimi_code(&active_config)
|
||||||
|
.map_err(fmt_anyhow),
|
||||||
Agent::Pi => {
|
Agent::Pi => {
|
||||||
let file = pi_io::config_to_pi_file(&config);
|
let file = pi_io::config_to_pi_file(&active_config);
|
||||||
pi_io::save_pi_models(&file).map_err(fmt_anyhow)
|
pi_io::save_pi_models(&file).map_err(fmt_anyhow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_active_config(config: &Config) -> Config {
|
||||||
|
let is_kimi_native = |p: &Provider| p.managed;
|
||||||
|
|
||||||
|
let providers: IndexMap<String, Provider> = config
|
||||||
|
.providers
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, p)| is_kimi_native(p) || p.active)
|
||||||
|
.map(|(k, p)| (k.clone(), p.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let active_provider_names: std::collections::HashSet<&str> = providers
|
||||||
|
.values()
|
||||||
|
.map(|p| p.name.as_str())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let models: IndexMap<String, Model> = config
|
||||||
|
.models
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, m)| active_provider_names.contains(m.provider.as_str()))
|
||||||
|
.map(|(k, m)| (k.clone(), m.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Config {
|
||||||
|
default_model: config.default_model.clone(),
|
||||||
|
providers,
|
||||||
|
models,
|
||||||
|
raw_other: config.raw_other.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn open_agent_config_dir(app: tauri::AppHandle, agent: Agent) -> Result<(), String> {
|
pub fn open_agent_config_dir(app: tauri::AppHandle, agent: Agent) -> Result<(), String> {
|
||||||
let path = agent.config_dir();
|
let path = agent.config_dir();
|
||||||
|
|||||||
@@ -0,0 +1,266 @@
|
|||||||
|
//! Pi Switch local SQLite storage.
|
||||||
|
//!
|
||||||
|
//! This module stores the full Pi Switch configuration (all providers and models
|
||||||
|
//! for both Kimi Code and Pi agents) in a local SQLite database. It is separate
|
||||||
|
//! from the agent-specific config files that are written when the user activates
|
||||||
|
//! a provider.
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use rusqlite::{params, Connection};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::models::{Agent, Config, Model, Provider, ProviderType};
|
||||||
|
|
||||||
|
pub type DbResult<T> = anyhow::Result<T>;
|
||||||
|
|
||||||
|
pub fn pi_switch_data_dir() -> PathBuf {
|
||||||
|
dirs::home_dir()
|
||||||
|
.map(|h| h.join(".pi-switch"))
|
||||||
|
.expect("failed to resolve home directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn db_path() -> PathBuf {
|
||||||
|
pi_switch_data_dir().join("pi-switch.db")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_db() -> DbResult<Connection> {
|
||||||
|
let path = db_path();
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)
|
||||||
|
.with_context(|| format!("failed to create directory {}", parent.display()))?;
|
||||||
|
}
|
||||||
|
let conn = Connection::open(&path)
|
||||||
|
.with_context(|| format!("failed to open database {}", path.display()))?;
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"CREATE TABLE IF NOT EXISTS providers (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
agent TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
provider_type TEXT NOT NULL,
|
||||||
|
base_url TEXT,
|
||||||
|
api_key TEXT,
|
||||||
|
env TEXT,
|
||||||
|
note TEXT,
|
||||||
|
official_url TEXT,
|
||||||
|
managed INTEGER NOT NULL DEFAULT 0,
|
||||||
|
enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
|
active INTEGER NOT NULL DEFAULT 0,
|
||||||
|
raw_other TEXT,
|
||||||
|
UNIQUE(agent, name)
|
||||||
|
)",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"CREATE TABLE IF NOT EXISTS models (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
agent TEXT NOT NULL,
|
||||||
|
alias TEXT NOT NULL,
|
||||||
|
provider_name TEXT NOT NULL,
|
||||||
|
model TEXT NOT NULL,
|
||||||
|
max_context_size INTEGER NOT NULL,
|
||||||
|
display_name TEXT,
|
||||||
|
role TEXT,
|
||||||
|
supports_1m INTEGER NOT NULL DEFAULT 0,
|
||||||
|
capabilities TEXT,
|
||||||
|
raw_other TEXT,
|
||||||
|
UNIQUE(agent, alias)
|
||||||
|
)",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL
|
||||||
|
)",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_config(agent: &Agent) -> DbResult<Config> {
|
||||||
|
let mut conn = init_db()?;
|
||||||
|
let tx = conn.transaction()?;
|
||||||
|
|
||||||
|
let default_model = get_setting_tx(&tx, &default_model_key(agent))?;
|
||||||
|
|
||||||
|
let mut providers = IndexMap::new();
|
||||||
|
{
|
||||||
|
let mut stmt = tx.prepare(
|
||||||
|
"SELECT name, provider_type, base_url, api_key, env, note, official_url, managed, enabled, active, raw_other
|
||||||
|
FROM providers WHERE agent = ?1 ORDER BY id",
|
||||||
|
)?;
|
||||||
|
let provider_rows = stmt.query_map(params![agent.as_str()], |row| {
|
||||||
|
let provider_type: String = row.get(1)?;
|
||||||
|
let env_json: Option<String> = row.get(4)?;
|
||||||
|
let raw_json: Option<String> = row.get(10)?;
|
||||||
|
Ok(Provider {
|
||||||
|
name: row.get(0)?,
|
||||||
|
provider_type: provider_type_for_str(&provider_type),
|
||||||
|
base_url: row.get(2)?,
|
||||||
|
api_key: row.get(3)?,
|
||||||
|
env: env_json
|
||||||
|
.and_then(|s| serde_json::from_str(&s).ok())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
note: row.get(5)?,
|
||||||
|
official_url: row.get(6)?,
|
||||||
|
managed: row.get::<_, i32>(7)? != 0,
|
||||||
|
enabled: row.get::<_, i32>(8)? != 0,
|
||||||
|
active: row.get::<_, i32>(9)? != 0,
|
||||||
|
raw_other: raw_json
|
||||||
|
.and_then(|s| serde_json::from_str(&s).ok())
|
||||||
|
.unwrap_or(Value::Null),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for provider in provider_rows {
|
||||||
|
let p = provider?;
|
||||||
|
providers.insert(p.name.clone(), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut models = IndexMap::new();
|
||||||
|
{
|
||||||
|
let mut stmt = tx.prepare(
|
||||||
|
"SELECT alias, provider_name, model, max_context_size, display_name, role, supports_1m, capabilities, raw_other
|
||||||
|
FROM models WHERE agent = ?1 ORDER BY id",
|
||||||
|
)?;
|
||||||
|
let model_rows = stmt.query_map(params![agent.as_str()], |row| {
|
||||||
|
let caps_json: Option<String> = row.get(7)?;
|
||||||
|
let raw_json: Option<String> = row.get(8)?;
|
||||||
|
Ok(Model {
|
||||||
|
alias: row.get(0)?,
|
||||||
|
provider: row.get(1)?,
|
||||||
|
model: row.get(2)?,
|
||||||
|
max_context_size: row.get::<_, i64>(3)? as u64,
|
||||||
|
display_name: row.get(4)?,
|
||||||
|
role: row.get(5)?,
|
||||||
|
supports_1m: row.get::<_, i32>(6)? != 0,
|
||||||
|
capabilities: caps_json
|
||||||
|
.and_then(|s| serde_json::from_str(&s).ok())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
raw_other: raw_json
|
||||||
|
.and_then(|s| serde_json::from_str(&s).ok())
|
||||||
|
.unwrap_or(Value::Null),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for model in model_rows {
|
||||||
|
let m = model?;
|
||||||
|
models.insert(m.alias.clone(), m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit()?;
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
default_model,
|
||||||
|
providers,
|
||||||
|
models,
|
||||||
|
raw_other: Value::Null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_config(agent: &Agent, config: &Config) -> DbResult<()> {
|
||||||
|
let mut conn = init_db()?;
|
||||||
|
let tx = conn.transaction()?;
|
||||||
|
|
||||||
|
tx.execute("DELETE FROM providers WHERE agent = ?1", params![agent.as_str()])?;
|
||||||
|
tx.execute("DELETE FROM models WHERE agent = ?1", params![agent.as_str()])?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut insert_provider = tx.prepare(
|
||||||
|
"INSERT INTO providers
|
||||||
|
(agent, name, provider_type, base_url, api_key, env, note, official_url, managed, enabled, active, raw_other)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for provider in config.providers.values() {
|
||||||
|
insert_provider.execute(params![
|
||||||
|
agent.as_str(),
|
||||||
|
provider.name,
|
||||||
|
provider.provider_type.as_str(),
|
||||||
|
provider.base_url,
|
||||||
|
provider.api_key,
|
||||||
|
serde_json::to_string(&provider.env).ok(),
|
||||||
|
provider.note,
|
||||||
|
provider.official_url,
|
||||||
|
provider.managed as i32,
|
||||||
|
provider.enabled as i32,
|
||||||
|
provider.active as i32,
|
||||||
|
serde_json::to_string(&provider.raw_other).ok(),
|
||||||
|
])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut insert_model = tx.prepare(
|
||||||
|
"INSERT INTO models
|
||||||
|
(agent, alias, provider_name, model, max_context_size, display_name, role, supports_1m, capabilities, raw_other)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for model in config.models.values() {
|
||||||
|
insert_model.execute(params![
|
||||||
|
agent.as_str(),
|
||||||
|
model.alias,
|
||||||
|
model.provider,
|
||||||
|
model.model,
|
||||||
|
model.max_context_size as i64,
|
||||||
|
model.display_name,
|
||||||
|
model.role,
|
||||||
|
model.supports_1m as i32,
|
||||||
|
serde_json::to_string(&model.capabilities).ok(),
|
||||||
|
serde_json::to_string(&model.raw_other).ok(),
|
||||||
|
])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(default_model) = &config.default_model {
|
||||||
|
set_setting_tx(&tx, &default_model_key(agent), default_model)?;
|
||||||
|
} else {
|
||||||
|
tx.execute("DELETE FROM settings WHERE key = ?1", params![default_model_key(agent)])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_model_key(agent: &Agent) -> String {
|
||||||
|
format!("default_model:{}", agent.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_setting_tx(tx: &rusqlite::Transaction, key: &str) -> DbResult<Option<String>> {
|
||||||
|
let mut stmt = tx.prepare("SELECT value FROM settings WHERE key = ?1")?;
|
||||||
|
let mut rows = stmt.query(params![key])?;
|
||||||
|
if let Some(row) = rows.next()? {
|
||||||
|
Ok(Some(row.get(0)?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_setting_tx(tx: &rusqlite::Transaction, key: &str, value: &str) -> DbResult<()> {
|
||||||
|
tx.execute(
|
||||||
|
"INSERT INTO settings (key, value) VALUES (?1, ?2)
|
||||||
|
ON CONFLICT(key) DO UPDATE SET value = excluded.value",
|
||||||
|
params![key, value],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn provider_type_for_str(s: &str) -> ProviderType {
|
||||||
|
match s {
|
||||||
|
"anthropic" => ProviderType::Anthropic,
|
||||||
|
"openai_responses" => ProviderType::OpenaiResponses,
|
||||||
|
"google-genai" => ProviderType::GoogleGenai,
|
||||||
|
"vertexai" => ProviderType::Vertexai,
|
||||||
|
_ => ProviderType::Kimi,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod commands;
|
pub mod commands;
|
||||||
|
pub mod db;
|
||||||
pub mod kimi_code_io;
|
pub mod kimi_code_io;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod pi_io;
|
pub mod pi_io;
|
||||||
@@ -9,6 +10,7 @@ pub fn run() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
commands::load_agent_config_command,
|
commands::load_agent_config_command,
|
||||||
commands::save_agent_config_command,
|
commands::save_agent_config_command,
|
||||||
|
commands::activate_agent_config_command,
|
||||||
commands::open_agent_config_dir,
|
commands::open_agent_config_dir,
|
||||||
commands::get_app_version,
|
commands::get_app_version,
|
||||||
commands::list_provider_models,
|
commands::list_provider_models,
|
||||||
|
|||||||
+5
-1
@@ -191,7 +191,7 @@ export default function App() {
|
|||||||
updateConfig((cfg) => ({ ...cfg, default_model: alias }));
|
updateConfig((cfg) => ({ ...cfg, default_model: alias }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSwitchProvider = (name: string) => {
|
const handleSwitchProvider = async (name: string) => {
|
||||||
updateConfig((cfg) => {
|
updateConfig((cfg) => {
|
||||||
const target = cfg.providers[name];
|
const target = cfg.providers[name];
|
||||||
if (!target || target.managed) return cfg;
|
if (!target || target.managed) return cfg;
|
||||||
@@ -216,6 +216,10 @@ export default function App() {
|
|||||||
|
|
||||||
return { ...cfg, providers, default_model };
|
return { ...cfg, providers, default_model };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Persist the full config to Pi Switch's SQLite and activate the selected provider.
|
||||||
|
await save();
|
||||||
|
await invoke("activate_agent_config_command", { agent });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleApplyProviderJson = (provider: Provider, models: Model[]) => {
|
const handleApplyProviderJson = (provider: Provider, models: Model[]) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user