use dioxus::prelude::*; use crate::infrastructure::ollama::{get_ollama_status, OllamaStatus}; /// Right sidebar for the dashboard, showing Ollama 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 /// /// * `ollama_url` - Ollama instance 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( ollama_url: String, trending: Vec, recent_searches: Vec, on_topic_click: EventHandler, ) -> Element { // Fetch Ollama 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 = ollama_url.clone(); let status_resource = use_resource(move || { let u = url.clone(); async move { get_ollama_status(u).await.unwrap_or(OllamaStatus { online: false, models: Vec::new(), }) } }); let current_status: OllamaStatus = status_resource .read() .as_ref() .cloned() .unwrap_or(OllamaStatus { online: false, models: Vec::new(), }); rsx! { aside { class: "dashboard-sidebar", // -- Ollama Status Section -- div { class: "sidebar-section", h4 { class: "sidebar-section-title", "Ollama 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 { "Online" } else { "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", "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", "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}" } } } } } } } } }