feat: v0.5.0 - provider icon actions, duplicate & connectivity test, dashboard chart fixes

- ProviderList: icon buttons (edit/copy/test/delete) via lucide-react, speed-test badge before enable/switch button
- Duplicate provider: deep-copy provider + models as xxx-copy
- test_connectivity command (cc-switch semantics: GET base_url, any HTTP response = reachable), latency badge green/orange/red, auto-dismiss 6s
- Dashboard: daily bars anchored to panel bottom, Card flex height fix (heatmap stays content-height)
This commit is contained in:
KimiSwitch Dev committed 2026-07-28 21:04:47 +08:00
1 parent 6b172914be
commit b1d137f072
12 files changed
+232 -12

No files matched your search

+1 -1
View File
@@ -1958,7 +1958,7 @@ dependencies = [
[[package]]
name = "kimiswitch"
version = "0.4.0"
version = "0.5.0"
dependencies = [
"anyhow",
"chrono",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "kimiswitch"
version = "0.4.1"
version = "0.5.0"
description = "Kimi Switch - model config manager"
authors = ["you"]
edition = "2021"
+49
View File
@@ -211,6 +211,55 @@ pub async fn list_provider_models(provider: Provider) -> Result<Vec<DiscoveredMo
}
}
/// Test reachability of a provider's base URL (cc-switch semantics):
/// any HTTP response counts as reachable; only network-layer errors fail.
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ConnectivityResult {
pub ok: bool,
pub latency_ms: u64,
pub status_code: Option<u16>,
pub error: Option<String>,
}
#[tauri::command]
pub async fn test_connectivity(provider: Provider) -> Result<ConnectivityResult, String> {
let base = resolve_base_url(&provider);
if base.trim().is_empty() {
return Ok(ConnectivityResult {
ok: false,
latency_ms: 0,
status_code: None,
error: Some("no base URL configured".to_string()),
});
}
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(8))
.build()
.map_err(|e| format!("failed to build HTTP client: {e}"))?;
let start = std::time::Instant::now();
match client.get(base.as_str()).send().await {
Ok(resp) => Ok(ConnectivityResult {
ok: true,
latency_ms: start.elapsed().as_millis() as u64,
status_code: Some(resp.status().as_u16()),
error: None,
}),
Err(e) => Ok(ConnectivityResult {
ok: false,
latency_ms: start.elapsed().as_millis() as u64,
status_code: None,
error: Some(if e.is_connect() {
"connection refused / DNS failed".to_string()
} else if e.is_timeout() {
"request timed out".to_string()
} else {
e.to_string()
}),
}),
}
}
fn resolve_api_key(provider: &Provider) -> Option<String> {
if provider.managed {
return Some("managed".to_string());
+1
View File
@@ -99,6 +99,7 @@ pub fn run() {
commands::open_agent_config_dir,
commands::get_app_version,
commands::list_provider_models,
commands::test_connectivity,
commands::debug_log,
commands::get_app_setting,
commands::set_app_setting,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"productName": "Kimi Switch",
"version": "0.4.1",
"version": "0.5.0",
"identifier": "com.kimiswitch.app",
"build": {
"beforeDevCommand": "npm run dev",