Some checks failed
BotServer CI / build (push) Failing after 1m34s
Split 20+ files over 1000 lines into focused subdirectories for better maintainability and code organization. All changes maintain backward compatibility through re-export wrappers. Major splits: - attendance/llm_assist.rs (2074→7 modules) - basic/keywords/face_api.rs → face_api/ (7 modules) - basic/keywords/file_operations.rs → file_ops/ (8 modules) - basic/keywords/hear_talk.rs → hearing/ (6 modules) - channels/wechat.rs → wechat/ (10 modules) - channels/youtube.rs → youtube/ (5 modules) - contacts/mod.rs → contacts_api/ (6 modules) - core/bootstrap/mod.rs → bootstrap/ (5 modules) - core/shared/admin.rs → admin_*.rs (5 modules) - designer/canvas.rs → canvas_api/ (6 modules) - designer/mod.rs → designer_api/ (6 modules) - docs/handlers.rs → handlers_api/ (11 modules) - drive/mod.rs → drive_handlers.rs, drive_types.rs - learn/mod.rs → types.rs - main.rs → main_module/ (7 modules) - meet/webinar.rs → webinar_api/ (8 modules) - paper/mod.rs → (10 modules) - security/auth.rs → auth_api/ (7 modules) - security/passkey.rs → (4 modules) - sources/mod.rs → sources_api/ (5 modules) - tasks/mod.rs → task_api/ (5 modules) Stats: 38,040 deletions, 1,315 additions across 318 files Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
293 lines
11 KiB
Rust
293 lines
11 KiB
Rust
use crate::core::config::ConfigManager;
|
|
#[cfg(feature = "nvidia")]
|
|
use crate::nvidia::get_system_metrics;
|
|
use crate::security::command_guard::SafeCommand;
|
|
use crate::core::shared::models::schema::bots::dsl::*;
|
|
use crate::core::shared::state::AppState;
|
|
use diesel::prelude::*;
|
|
use std::sync::Arc;
|
|
use sysinfo::System;
|
|
|
|
/// Cache for component status to avoid spawning pgrep processes too frequently
|
|
struct ComponentStatusCache {
|
|
statuses: Vec<(String, bool, String)>, // (name, is_running, port)
|
|
last_check: std::time::Instant,
|
|
}
|
|
|
|
impl ComponentStatusCache {
|
|
fn new() -> Self {
|
|
Self {
|
|
statuses: Vec::new(),
|
|
last_check: std::time::Instant::now() - std::time::Duration::from_secs(60),
|
|
}
|
|
}
|
|
|
|
fn needs_refresh(&self) -> bool {
|
|
// Only check component status every 10 seconds
|
|
self.last_check.elapsed() > std::time::Duration::from_secs(10)
|
|
}
|
|
|
|
fn refresh(&mut self) {
|
|
let components = vec![
|
|
("Tables", "postgres", "5432"),
|
|
("Cache", "valkey-server", "6379"),
|
|
("Drive", "minio", "9000"),
|
|
("LLM", "llama-server", "8081"),
|
|
];
|
|
|
|
self.statuses.clear();
|
|
for (comp_name, process, port) in components {
|
|
let is_running = Self::check_component_running(process);
|
|
self.statuses
|
|
.push((comp_name.to_string(), is_running, port.to_string()));
|
|
}
|
|
self.last_check = std::time::Instant::now();
|
|
}
|
|
|
|
fn check_component_running(process_name: &str) -> bool {
|
|
SafeCommand::new("pgrep")
|
|
.and_then(|c| c.arg("-f"))
|
|
.and_then(|c| c.arg(process_name))
|
|
.ok()
|
|
.and_then(|cmd| cmd.execute().ok())
|
|
.map(|output| !output.stdout.is_empty())
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
fn get_statuses(&self) -> &[(String, bool, String)] {
|
|
&self.statuses
|
|
}
|
|
}
|
|
|
|
/// Cache for bot list to avoid database queries too frequently
|
|
struct BotListCache {
|
|
bot_list: Vec<(String, uuid::Uuid)>,
|
|
last_check: std::time::Instant,
|
|
}
|
|
|
|
impl BotListCache {
|
|
fn new() -> Self {
|
|
Self {
|
|
bot_list: Vec::new(),
|
|
last_check: std::time::Instant::now() - std::time::Duration::from_secs(60),
|
|
}
|
|
}
|
|
|
|
fn needs_refresh(&self) -> bool {
|
|
// Only query database every 5 seconds
|
|
self.last_check.elapsed() > std::time::Duration::from_secs(5)
|
|
}
|
|
|
|
fn refresh(&mut self, app_state: &Arc<AppState>) {
|
|
if let Ok(mut conn) = app_state.conn.get() {
|
|
if let Ok(list) = bots
|
|
.filter(is_active.eq(true))
|
|
.select((name, id))
|
|
.load::<(String, uuid::Uuid)>(&mut *conn)
|
|
{
|
|
self.bot_list = list;
|
|
}
|
|
}
|
|
self.last_check = std::time::Instant::now();
|
|
}
|
|
|
|
fn get_bots(&self) -> &[(String, uuid::Uuid)] {
|
|
&self.bot_list
|
|
}
|
|
}
|
|
|
|
pub struct StatusPanel {
|
|
app_state: Arc<AppState>,
|
|
last_update: std::time::Instant,
|
|
last_system_refresh: std::time::Instant,
|
|
cached_content: String,
|
|
system: System,
|
|
component_cache: ComponentStatusCache,
|
|
bot_cache: BotListCache,
|
|
}
|
|
|
|
impl std::fmt::Debug for StatusPanel {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("StatusPanel")
|
|
.field("app_state", &"Arc<AppState>")
|
|
.field("last_update", &self.last_update)
|
|
.field("cached_content_len", &self.cached_content.len())
|
|
.field("system", &"System")
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl StatusPanel {
|
|
pub fn new(app_state: Arc<AppState>) -> Self {
|
|
// Only initialize with CPU and memory info, not all system info
|
|
let mut system = System::new();
|
|
system.refresh_cpu_all();
|
|
system.refresh_memory();
|
|
|
|
Self {
|
|
app_state,
|
|
last_update: std::time::Instant::now(),
|
|
last_system_refresh: std::time::Instant::now(),
|
|
cached_content: String::new(),
|
|
system,
|
|
component_cache: ComponentStatusCache::new(),
|
|
bot_cache: BotListCache::new(),
|
|
}
|
|
}
|
|
|
|
pub fn update(&mut self) -> Result<(), std::io::Error> {
|
|
// Only refresh system metrics every 2 seconds instead of every call
|
|
// This is the main CPU hog - refresh_all() is very expensive
|
|
if self.last_system_refresh.elapsed() > std::time::Duration::from_secs(2) {
|
|
// Only refresh CPU and memory, not ALL system info
|
|
self.system.refresh_cpu_all();
|
|
self.system.refresh_memory();
|
|
self.last_system_refresh = std::time::Instant::now();
|
|
}
|
|
|
|
// Refresh component status cache if needed (every 10 seconds)
|
|
if self.component_cache.needs_refresh() {
|
|
self.component_cache.refresh();
|
|
}
|
|
|
|
// Refresh bot list cache if needed (every 5 seconds)
|
|
if self.bot_cache.needs_refresh() {
|
|
self.bot_cache.refresh(&self.app_state);
|
|
}
|
|
|
|
self.cached_content = self.render(None);
|
|
self.last_update = std::time::Instant::now();
|
|
Ok(())
|
|
}
|
|
|
|
pub fn render(&mut self, selected_bot: Option<String>) -> String {
|
|
let mut lines = vec![
|
|
"╔═══════════════════════════════════════╗".to_string(),
|
|
"║ SYSTEM METRICS ║".to_string(),
|
|
"╚═══════════════════════════════════════╝".to_string(),
|
|
String::new(),
|
|
];
|
|
|
|
// Use cached CPU usage - don't refresh here
|
|
let cpu_usage = self.system.global_cpu_usage();
|
|
let cpu_bar = Self::create_progress_bar(cpu_usage, 20);
|
|
lines.push(format!(" CPU: {:5.1}% {}", cpu_usage, cpu_bar));
|
|
|
|
#[cfg(feature = "nvidia")]
|
|
{
|
|
let system_metrics = get_system_metrics().unwrap_or_default();
|
|
if let Some(gpu_usage) = system_metrics.gpu_usage {
|
|
let gpu_bar = Self::create_progress_bar(gpu_usage, 20);
|
|
lines.push(format!(" GPU: {:5.1}% {}", gpu_usage, gpu_bar));
|
|
} else {
|
|
lines.push(" GPU: Not available".to_string());
|
|
}
|
|
}
|
|
#[cfg(not(feature = "nvidia"))]
|
|
{
|
|
lines.push(" GPU: Feature not enabled".to_string());
|
|
}
|
|
|
|
let total_mem = self.system.total_memory() as f32 / 1024.0 / 1024.0 / 1024.0;
|
|
let used_mem = self.system.used_memory() as f32 / 1024.0 / 1024.0 / 1024.0;
|
|
let mem_percentage = if total_mem > 0.0 {
|
|
(used_mem / total_mem) * 100.0
|
|
} else {
|
|
0.0
|
|
};
|
|
let mem_bar = Self::create_progress_bar(mem_percentage, 20);
|
|
lines.push(format!(
|
|
" MEM: {:5.1}% {} ({:.1}/{:.1} GB)",
|
|
mem_percentage, mem_bar, used_mem, total_mem
|
|
));
|
|
|
|
lines.push("".to_string());
|
|
lines.push("╔═══════════════════════════════════════╗".to_string());
|
|
lines.push("║ COMPONENTS STATUS ║".to_string());
|
|
lines.push("╚═══════════════════════════════════════╝".to_string());
|
|
lines.push("".to_string());
|
|
|
|
// Use cached component statuses instead of spawning pgrep every time
|
|
for (comp_name, is_running, port) in self.component_cache.get_statuses() {
|
|
let status = if *is_running {
|
|
format!(" ONLINE [Port: {}]", port)
|
|
} else {
|
|
" OFFLINE".to_string()
|
|
};
|
|
lines.push(format!(" {:<10} {}", comp_name, status));
|
|
}
|
|
|
|
lines.push("".to_string());
|
|
lines.push("╔═══════════════════════════════════════╗".to_string());
|
|
lines.push("║ ACTIVE BOTS ║".to_string());
|
|
lines.push("╚═══════════════════════════════════════╝".to_string());
|
|
lines.push("".to_string());
|
|
|
|
// Use cached bot list instead of querying database every time
|
|
let bot_list = self.bot_cache.get_bots();
|
|
if bot_list.is_empty() {
|
|
lines.push(" No active bots".to_string());
|
|
} else {
|
|
for (bot_name, bot_id) in bot_list {
|
|
let marker = if let Some(ref selected) = selected_bot {
|
|
if selected == bot_name {
|
|
"►"
|
|
} else {
|
|
" "
|
|
}
|
|
} else {
|
|
" "
|
|
};
|
|
lines.push(format!(" {} {}", marker, bot_name));
|
|
|
|
if let Some(ref selected) = selected_bot {
|
|
if selected == bot_name {
|
|
lines.push("".to_string());
|
|
lines.push(" ┌─ Bot Configuration ─────────┐".to_string());
|
|
let config_manager = ConfigManager::new(self.app_state.conn.clone());
|
|
let llm_model = config_manager
|
|
.get_config(bot_id, "llm-model", None)
|
|
.unwrap_or_else(|_| "N/A".to_string());
|
|
lines.push(format!(" Model: {}", llm_model));
|
|
let ctx_size = config_manager
|
|
.get_config(bot_id, "llm-server-ctx-size", None)
|
|
.unwrap_or_else(|_| "N/A".to_string());
|
|
lines.push(format!(" Context: {}", ctx_size));
|
|
let temp = config_manager
|
|
.get_config(bot_id, "llm-temperature", None)
|
|
.unwrap_or_else(|_| "N/A".to_string());
|
|
lines.push(format!(" Temp: {}", temp));
|
|
lines.push(" └─────────────────────────────┘".to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
lines.push("".to_string());
|
|
lines.push("╔═══════════════════════════════════════╗".to_string());
|
|
lines.push("║ SESSIONS ║".to_string());
|
|
lines.push("╚═══════════════════════════════════════╝".to_string());
|
|
|
|
let session_count = self
|
|
.app_state
|
|
.response_channels
|
|
.try_lock()
|
|
.map(|channels| channels.len())
|
|
.unwrap_or(0);
|
|
lines.push(format!(" Active Sessions: {}", session_count));
|
|
|
|
lines.join("\n")
|
|
}
|
|
|
|
fn create_progress_bar(percentage: f32, width: usize) -> String {
|
|
let filled = (percentage / 100.0 * width as f32).round() as usize;
|
|
let empty = width.saturating_sub(filled);
|
|
let filled_chars = "█".repeat(filled);
|
|
let empty_chars = "░".repeat(empty);
|
|
format!("[{}{}]", filled_chars, empty_chars)
|
|
}
|
|
|
|
pub fn check_component_running(process_name: &str) -> bool {
|
|
ComponentStatusCache::check_component_running(process_name)
|
|
}
|
|
}
|