use std::sync::Arc; use chrono::Utc; use reqwest::Client; use tokio::sync::{broadcast, RwLock}; use crate::models::{AgentEvent, TriageRecord}; #[derive(Debug, Clone)] pub struct AppSettings { pub vllm_url: String, pub vllm_api_key: Option, pub pubmed_email: Option, pub base_sepolia_rpc: String, pub private_key: Option, pub contract_address: Option, pub app_name: String, } #[derive(Debug, Clone)] pub struct AppState { pub settings: AppSettings, pub client: Client, pub history: Arc>>, pub agent_bus: broadcast::Sender, pub last_event: Arc>>, } impl AppState { pub fn from_env() -> anyhow::Result { let vllm_url = std::env::var("VLLM_URL") .unwrap_or_else(|_| "http://127.0.0.1:8000/v1/chat/completions".to_string()); let vllm_api_key = std::env::var("VLLM_API_KEY").ok(); let pubmed_email = std::env::var("PUBMED_EMAIL").ok(); let base_sepolia_rpc = std::env::var("BASE_SEPOLIA_RPC") .unwrap_or_else(|_| "https://sepolia.base.org".to_string()); let private_key = std::env::var("PRIVATE_KEY").ok(); let contract_address = std::env::var("AUDIT_CONTRACT_ADDRESS").ok(); let client = Client::builder() .timeout(std::time::Duration::from_secs(25)) .build()?; let (agent_bus, _) = broadcast::channel(128); Ok(Self { settings: AppSettings { vllm_url, vllm_api_key, pubmed_email, base_sepolia_rpc, private_key, contract_address, app_name: "RustVital-AMD".to_string(), }, client, history: Arc::new(RwLock::new(Vec::new())), agent_bus, last_event: Arc::new(RwLock::new(None)), }) } pub async fn push_record(&self, record: TriageRecord) { self.history.write().await.push(record); } pub async fn records(&self) -> Vec { self.history.read().await.clone() } pub async fn latest_record_id(&self) -> Option { self.history .read() .await .last() .map(|record| record.record_id.as_str().to_string()) } pub fn emit(&self, event: AgentEvent) { let _ = self.agent_bus.send(event.clone()); let last_event = self.last_event.clone(); tokio::spawn(async move { *last_event.write().await = Some(event); }); } pub fn now_rfc3339() -> String { Utc::now().to_rfc3339() } }