feat(models-dev): 高级设置新增 models.dev 在线同步 — 上下文/能力/单价即时生效 + 一键恢复内置;修复 public 快照构建漂移;确认适配 kimi-code 2.1.0 — bump to v0.7.21
Release / Version consistency (push) Canceled after 0s
Release / Build (macos-latest) (push) Canceled after 0s
Release / Build (ubuntu-latest) (push) Canceled after 0s
Release / Build (windows-latest) (push) Canceled after 0s
Release / Attach macOS install script (push) Canceled after 0s
Release / Version consistency (push) Canceled after 0s
Release / Build (macos-latest) (push) Canceled after 0s
Release / Build (ubuntu-latest) (push) Canceled after 0s
Release / Build (windows-latest) (push) Canceled after 0s
Release / Attach macOS install script (push) Canceled after 0s
This commit is contained in:
1 parent
26e783c2ba
commit
11286419b1
17 files changed
+282395
-267694
No files matched your search
+59
-44
@@ -6,7 +6,8 @@ use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::sync::{Arc, Mutex, OnceLock, RwLock};
|
||||
use std::time::SystemTime;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -534,50 +535,62 @@ struct ModelsDevCost {
|
||||
cache_write: Option<f64>,
|
||||
}
|
||||
|
||||
/// Compiled-in models.dev snapshot (`src/lib/models-dev.json`, generated by
|
||||
/// scripts/fetch-models-dev.mjs before every build via the `pretauri` hook).
|
||||
/// Keyed by "<provider>/<model>", lowercased.
|
||||
const MODELS_DEV_SNAPSHOT: &str = include_str!("../../src/lib/models-dev.json");
|
||||
|
||||
fn models_dev_cost_index() -> &'static HashMap<String, ModelsDevCost> {
|
||||
static INDEX: OnceLock<HashMap<String, ModelsDevCost>> = OnceLock::new();
|
||||
INDEX.get_or_init(|| {
|
||||
let mut map = HashMap::new();
|
||||
let Ok(v) = serde_json::from_str::<serde_json::Value>(MODELS_DEV_SNAPSHOT) else {
|
||||
return map;
|
||||
};
|
||||
let Some(obj) = v.as_object() else {
|
||||
return map;
|
||||
};
|
||||
for (key, entry) in obj {
|
||||
// Skip the "last_updated" metadata key and entries without cost.
|
||||
if !entry.is_object() || entry.get("cost").is_none() {
|
||||
continue;
|
||||
/// models.dev price index over the effective snapshot (runtime-synced copy
|
||||
/// when present, else the compiled-in `src/lib/models-dev.json` generated by
|
||||
/// scripts/fetch-models-dev.mjs). Keyed by "<provider>/<model>", lowercased.
|
||||
/// Cached and rebuilt only when the synced file's mtime changes, so an online
|
||||
/// sync (models_dev::sync_models_dev) refreshes prices without a restart.
|
||||
fn models_dev_cost_index() -> Arc<HashMap<String, ModelsDevCost>> {
|
||||
static CACHE: OnceLock<RwLock<Option<(Option<SystemTime>, Arc<HashMap<String, ModelsDevCost>>)>>>=
|
||||
OnceLock::new();
|
||||
let cache = CACHE.get_or_init(|| RwLock::new(None));
|
||||
let stamp = fs::metadata(crate::models_dev::synced_path())
|
||||
.and_then(|m| m.modified())
|
||||
.ok();
|
||||
{
|
||||
let cached = cache.read().expect("price index lock");
|
||||
if let Some((cached_stamp, idx)) = cached.as_ref() {
|
||||
if *cached_stamp == stamp {
|
||||
return idx.clone();
|
||||
}
|
||||
let Some(cost) = entry.get("cost").and_then(|c| c.as_object()) else {
|
||||
continue;
|
||||
};
|
||||
let num = |k: &str| cost.get(k).and_then(|x| x.as_f64());
|
||||
let (Some(input), Some(output)) = (num("input"), num("output")) else {
|
||||
continue;
|
||||
};
|
||||
map.insert(
|
||||
key.to_ascii_lowercase(),
|
||||
ModelsDevCost {
|
||||
input,
|
||||
output,
|
||||
cache_read: num("cache_read").unwrap_or(0.0),
|
||||
cache_write: num("cache_write"),
|
||||
},
|
||||
);
|
||||
}
|
||||
map
|
||||
})
|
||||
}
|
||||
let mut map = HashMap::new();
|
||||
let v = serde_json::from_str::<serde_json::Value>(&crate::models_dev::effective_snapshot());
|
||||
if let Ok(v) = v {
|
||||
if let Some(obj) = v.as_object() {
|
||||
for (key, entry) in obj {
|
||||
// Skip the "last_updated" metadata key and entries without cost.
|
||||
if !entry.is_object() || entry.get("cost").is_none() {
|
||||
continue;
|
||||
}
|
||||
let Some(cost) = entry.get("cost").and_then(|c| c.as_object()) else {
|
||||
continue;
|
||||
};
|
||||
let num = |k: &str| cost.get(k).and_then(|x| x.as_f64());
|
||||
let (Some(input), Some(output)) = (num("input"), num("output")) else {
|
||||
continue;
|
||||
};
|
||||
map.insert(
|
||||
key.to_ascii_lowercase(),
|
||||
ModelsDevCost {
|
||||
input,
|
||||
output,
|
||||
cache_read: num("cache_read").unwrap_or(0.0),
|
||||
cache_write: num("cache_write"),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
let idx = Arc::new(map);
|
||||
*cache.write().expect("price index lock") = Some((stamp, idx.clone()));
|
||||
idx
|
||||
}
|
||||
|
||||
/// Warm the compiled-in models.dev price index off the first dashboard open:
|
||||
/// parsing the ~1.3 MB snapshot costs tens of milliseconds, so build it on a
|
||||
/// background thread at startup instead of lazily on the first get_summary.
|
||||
/// Warm the models.dev price index off the first dashboard open: parsing the
|
||||
/// ~1.3 MB snapshot costs tens of milliseconds, so build it on a background
|
||||
/// thread at startup instead of lazily on the first get_summary.
|
||||
pub fn warm_price_index() {
|
||||
std::thread::spawn(|| {
|
||||
let _ = models_dev_cost_index();
|
||||
@@ -608,14 +621,15 @@ fn provider_rank(key: &str) -> usize {
|
||||
/// (deterministic tie-break). Returns None when nothing matches.
|
||||
fn models_dev_lookup(model_name: &str) -> Option<(String, ModelsDevCost)> {
|
||||
let lower = model_name.to_ascii_lowercase();
|
||||
if let Some(cost) = models_dev_cost_index().get(&lower) {
|
||||
let index = models_dev_cost_index();
|
||||
if let Some(cost) = index.get(&lower) {
|
||||
return Some((lower, *cost));
|
||||
}
|
||||
let bare = model_name.rsplit_once('/').map(|(_, b)| b).unwrap_or(model_name);
|
||||
let bare_l = bare.to_ascii_lowercase();
|
||||
// Match on the model part after the last '/', not the full key: aliases
|
||||
// strip the family prefix (record "k2.5" vs models.dev "kimi-k2.5").
|
||||
let mut matches: Vec<(&String, &ModelsDevCost)> = models_dev_cost_index()
|
||||
let mut matches: Vec<(&String, &ModelsDevCost)> = index
|
||||
.iter()
|
||||
.filter(|(key, _)| {
|
||||
key.rsplit_once('/')
|
||||
@@ -695,7 +709,8 @@ fn match_price(model_name: &str) -> (String, f64, f64, f64, bool) {
|
||||
fn priced_suffix_lookup(model_name: &str) -> Option<(String, ModelsDevCost)> {
|
||||
let bare = model_name.rsplit_once('/').map(|(_, b)| b).unwrap_or(model_name);
|
||||
let bare_l = bare.to_ascii_lowercase();
|
||||
let mut matches: Vec<(&String, &ModelsDevCost)> = models_dev_cost_index()
|
||||
let index = models_dev_cost_index();
|
||||
let mut matches: Vec<(&String, &ModelsDevCost)> = index
|
||||
.iter()
|
||||
.filter(|(key, cost)| {
|
||||
(cost.input > 0.0 || cost.output > 0.0)
|
||||
|
||||
Reference in new issue
Block a user