use dioxus::prelude::*; use crate::i18n::{t, Locale}; use crate::infrastructure::litellm::{get_litellm_status, LitellmStatus}; /// Right sidebar for the dashboard, showing LiteLLM status, trending topics, /// and recent search history. /// /// Appears when no article card is selected. Disappears when the user opens /// the article detail split view. /// /// # Props /// /// * `litellm_url` - LiteLLM proxy URL for status polling /// * `trending` - Trending topic keywords extracted from recent news headlines /// * `recent_searches` - Recent search topics stored in localStorage /// * `on_topic_click` - Fires when a trending or recent topic is clicked #[component] pub fn DashboardSidebar( litellm_url: String, trending: Vec, recent_searches: Vec, on_topic_click: EventHandler, ) -> Element { let locale = use_context::>(); let l = *locale.read(); // Fetch LiteLLM status once on mount. // use_resource with no signal dependencies runs exactly once and // won't re-fire on parent re-renders (unlike use_effect). let url = litellm_url.clone(); let status_resource = use_resource(move || { let u = url.clone(); async move { get_litellm_status(u).await.unwrap_or(LitellmStatus { online: false, models: Vec::new(), }) } }); let current_status: LitellmStatus = status_resource .read() .as_ref() .cloned() .unwrap_or(LitellmStatus { online: false, models: Vec::new(), }); rsx! { aside { class: "dashboard-sidebar", // -- LiteLLM Status Section -- div { class: "sidebar-section", h4 { class: "sidebar-section-title", "{t(l, \"dashboard.litellm_status\")}" } div { class: "sidebar-status-row", span { class: if current_status.online { "sidebar-status-dot sidebar-status-dot--online" } else { "sidebar-status-dot sidebar-status-dot--offline" } } span { class: "sidebar-status-label", if current_status.online { "{t(l, \"common.online\")}" } else { "{t(l, \"common.offline\")}" } } } if !current_status.models.is_empty() { div { class: "sidebar-model-list", for model in current_status.models.iter() { span { class: "sidebar-model-tag", "{model}" } } } } } // -- Trending Topics Section -- if !trending.is_empty() { div { class: "sidebar-section", h4 { class: "sidebar-section-title", "{t(l, \"dashboard.trending\")}" } for topic in trending.iter() { { let t = topic.clone(); rsx! { button { class: "sidebar-topic-link", onclick: move |_| on_topic_click.call(t.clone()), "{topic}" } } } } } } // -- Recent Searches Section -- if !recent_searches.is_empty() { div { class: "sidebar-section", h4 { class: "sidebar-section-title", "{t(l, \"dashboard.recent_searches\")}" } for search in recent_searches.iter() { { let s = search.clone(); rsx! { button { class: "sidebar-topic-link", onclick: move |_| on_topic_click.call(s.clone()), "{search}" } } } } } } } } }