Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/bin/vimit-gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn main() {
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{GuiAccount, endpoint_for_label, runtime_config};
use crate::config::{GuiAccount, runtime_config};
use crate::overlay::{
CreatureSound, CreatureState, OVERLAY_HISTORY_RETENTION, OverlayHistory,
build_overlay_state, creature_node_count, creature_node_count_for_skin,
Expand Down Expand Up @@ -152,15 +152,15 @@ mod tests {
#[test]
fn endpoint_label_maps_to_visible_api_url() {
assert_eq!(
endpoint_for_label("api", "https://custom.example"),
ng::dashboard_endpoint_for_label("api", "https://custom.example"),
ng::DEFAULT_API_BASE
);
assert_eq!(
endpoint_for_label("r-api", "https://custom.example"),
ng::dashboard_endpoint_for_label("r-api", "https://custom.example"),
ng::FALLBACK_API_BASE
);
assert_eq!(
endpoint_for_label("custom", "https://custom.example"),
ng::dashboard_endpoint_for_label("custom", "https://custom.example"),
"https://custom.example"
);
}
Expand Down
8 changes: 0 additions & 8 deletions src/bin/vimit-gui/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,3 @@ pub(crate) fn runtime_config(
auto_failover,
}
}

pub(crate) fn endpoint_for_label(label: &str, configured_api_base: &str) -> String {
match label {
"api" => ng::DEFAULT_API_BASE.to_string(),
"r-api" => ng::FALLBACK_API_BASE.to_string(),
_ => configured_api_base.to_string(),
}
}
96 changes: 34 additions & 62 deletions src/bin/vimit-gui/dashboard.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;

use slint::{ModelRc, VecModel, Weak};

use crate::AppWindow;
use crate::config::{GuiAccount, endpoint_for_label, gui_load_dotenv, runtime_config};
use crate::config::{GuiAccount, gui_load_dotenv, runtime_config};
use crate::ng;
use crate::overlay::{
CreatureState, OverlayHistory, OverlayState, build_overlay_state, creature_node_count_for_skin,
Expand Down Expand Up @@ -281,61 +282,36 @@ fn load_dashboard(
overlay_history: &Arc<Mutex<OverlayHistory>>,
auto_failover: bool,
) -> Result<GuiDashboardResult, String> {
let (payload, source, active_endpoint_label, abtop_bin, live_api_key_present) = if force_demo {
(
ng::demo_payload(),
"источник: встроенные демо-данные".to_string(),
"demo".to_string(),
String::new(),
false,
)
} else if let Some(path) = mock_path {
(
ng::load_mock(path)?,
format!("источник: mock {}", path),
"mock".to_string(),
String::new(),
false,
)
let dotenv = if force_demo || mock_path.is_some() {
HashMap::new()
} else {
let dotenv = gui_load_dotenv()?;
let config = runtime_config(&dotenv, account, auto_failover);
if config.api_key.is_empty() {
(
ng::demo_payload(),
"источник: демо; добавьте VIBEMODE_API_KEY в .env для live-лимитов".to_string(),
"demo".to_string(),
config.abtop_bin,
false,
)
} else {
let (value, label) = if config.auto_failover
&& config.api_base.trim_end_matches('/') == ng::DEFAULT_API_BASE
{
let mut router = router.lock().unwrap();
http.fetch_me_with_retry(&config.api_key, &mut router, &config.api_base)?
} else {
let mut router = ng::Router::new(
config.api_base.clone(),
ng::api_fallbacks_for(&config.api_base, config.auto_failover),
);
http.fetch_me_with_retry(&config.api_key, &mut router, &config.api_base)?
};
let endpoint = endpoint_for_label(&label, &config.api_base);
(
value,
format!("источник: live VibeMode /v1/me на {endpoint}"),
label,
config.abtop_bin,
true,
)
}
gui_load_dotenv()?
};
let config = runtime_config(&dotenv, account, auto_failover);
let request = ng::DashboardRefreshRequest {
force_demo,
mock_path,
warning_threshold: warning,
danger_threshold: danger,
missing_api_key_source: "источник: демо; добавьте VIBEMODE_API_KEY в .env для live-лимитов",
};
let shared =
if config.auto_failover && config.api_base.trim_end_matches('/') == ng::DEFAULT_API_BASE {
let mut shared_router = router.lock().unwrap();
ng::load_dashboard_refresh(request, &config, http, &mut shared_router)?
} else {
let mut local_router = ng::Router::new(
config.api_base.clone(),
ng::api_fallbacks_for(&config.api_base, config.auto_failover),
);
ng::load_dashboard_refresh(request, &config, http, &mut local_router)?
};

let windows = ng::summarize_me(&payload, warning, danger);
let status = ng::dashboard_status(&windows);
let abtop_bin = config.abtop_bin.clone();
let live_api_key_present = shared.live_api_key_present;
let mut dashboard = shared.dashboard;
let agent = read_agent_status_for_gui(&abtop_bin);
let overlay = build_overlay_state(&windows, &agent.token_rate, overlay_history);
let overlay = build_overlay_state(&dashboard.windows, &agent.token_rate, overlay_history);

let mut five_trend_data = Vec::new();
let mut day_trend_data = Vec::new();
Expand All @@ -344,7 +320,7 @@ fn load_dashboard(

if let Ok(Some(store)) = ng::cli::trends::TrendStore::open() {
if live_api_key_present {
let _ = store.save_snapshot(&windows, chrono::Utc::now());
let _ = store.save_snapshot(&dashboard.windows, chrono::Utc::now());
}
if let Ok(days) = store.query_trends(15) {
for day in &days {
Expand Down Expand Up @@ -382,16 +358,12 @@ fn load_dashboard(
];
}

dashboard.agent = agent.summary;
dashboard.token_rate = agent.token_rate;

Ok(GuiDashboardResult {
dashboard: ng::Dashboard {
source,
status,
agent: agent.summary,
token_rate: agent.token_rate,
windows,
daily: None,
},
active_endpoint_label,
dashboard,
active_endpoint_label: shared.active_endpoint_label,
five_trend_data,
day_trend_data,
week_trend_data,
Expand Down
156 changes: 156 additions & 0 deletions src/dashboard_refresh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use crate::{
DEFAULT_API_BASE, DEFAULT_DANGER_THRESHOLD, DEFAULT_WARNING_THRESHOLD, Dashboard, HttpClient,
Router, RuntimeConfig, demo_payload, load_mock, summarize_me,
};

#[derive(Debug, Clone)]
pub struct DashboardRefreshResult {
pub dashboard: Dashboard,
pub active_endpoint_label: String,
pub live_api_key_present: bool,
}

#[derive(Debug, Clone, Copy)]
pub struct DashboardRefreshRequest<'a> {
pub force_demo: bool,
pub mock_path: Option<&'a str>,
pub warning_threshold: f64,
pub danger_threshold: f64,
pub missing_api_key_source: &'a str,
}

impl<'a> Default for DashboardRefreshRequest<'a> {
fn default() -> Self {
Self {
force_demo: false,
mock_path: None,
warning_threshold: DEFAULT_WARNING_THRESHOLD,
danger_threshold: DEFAULT_DANGER_THRESHOLD,
missing_api_key_source: "источник: демо; добавьте VIBEMODE_API_KEY в .env для live-лимитов",
}
}
}

pub fn load_dashboard_refresh(
request: DashboardRefreshRequest<'_>,
config: &RuntimeConfig,
http: &HttpClient,
router: &mut Router,
) -> Result<DashboardRefreshResult, String> {
let (payload, source, active_endpoint_label, live_api_key_present) = if request.force_demo {
(
demo_payload(),
"источник: встроенные демо-данные".to_string(),
"demo".to_string(),
false,
)
} else if let Some(path) = request.mock_path {
(
load_mock(path)?,
format!("источник: mock {path}"),
"mock".to_string(),
false,
)
} else if config.api_key.is_empty() {
(
demo_payload(),
request.missing_api_key_source.to_string(),
"demo".to_string(),
false,
)
} else {
let (payload, label) =
http.fetch_me_with_retry(&config.api_key, router, &config.api_base)?;
let endpoint = dashboard_endpoint_for_label(&label, &config.api_base);
(
payload,
format!("источник: live VibeMode /v1/me на {endpoint}"),
label,
true,
)
};

let windows = summarize_me(
&payload,
request.warning_threshold,
request.danger_threshold,
);
let status = crate::dashboard_status(&windows);

Ok(DashboardRefreshResult {
dashboard: Dashboard {
source,
status,
agent: String::new(),
token_rate: String::new(),
windows,
daily: None,
},
active_endpoint_label,
live_api_key_present,
})
}

pub fn dashboard_endpoint_for_label(label: &str, configured_api_base: &str) -> String {
match label {
"api" => DEFAULT_API_BASE.to_string(),
"r-api" => crate::FALLBACK_API_BASE.to_string(),
_ => configured_api_base.to_string(),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn load_dashboard_refresh_falls_back_to_demo_without_api_key() {
let http = HttpClient::new(crate::USER_AGENT).unwrap();
let mut router = Router::new(
crate::DEFAULT_API_BASE.to_string(),
crate::api_fallbacks_for(crate::DEFAULT_API_BASE, true),
);
let config = RuntimeConfig {
api_base: crate::DEFAULT_API_BASE.to_string(),
api_key: String::new(),
abtop_bin: String::new(),
auto_failover: true,
};

let result = load_dashboard_refresh(
DashboardRefreshRequest {
missing_api_key_source:
"источник: демо; добавьте VIBEMODE_API_KEY в .env для live-лимитов",
..DashboardRefreshRequest::default()
},
&config,
&http,
&mut router,
)
.unwrap();

assert_eq!(result.active_endpoint_label, "demo");
assert!(!result.live_api_key_present);
assert_eq!(
result.dashboard.source,
"источник: демо; добавьте VIBEMODE_API_KEY в .env для live-лимитов"
);
assert!(!result.dashboard.windows.is_empty());
}

#[test]
fn dashboard_endpoint_for_label_maps_known_hosts() {
assert_eq!(
dashboard_endpoint_for_label("api", "https://custom.example"),
crate::DEFAULT_API_BASE
);
assert_eq!(
dashboard_endpoint_for_label("r-api", "https://custom.example"),
crate::FALLBACK_API_BASE
);
assert_eq!(
dashboard_endpoint_for_label("custom", "https://custom.example"),
"https://custom.example"
);
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pub mod android;
pub mod api;
pub mod cli;
pub mod dashboard_refresh;
pub mod parse;

#[cfg(all(target_os = "android", feature = "android-gui"))]
Expand All @@ -12,6 +13,7 @@ pub use android::{
android_runaway_alert_text,
};
pub use api::*;
pub use dashboard_refresh::*;
pub use parse::*;

use chrono::{SecondsFormat, Utc};
Expand Down
Loading