From 0ca0366f3af6b5334a72eb1fb184ac91427e4743 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Fri, 20 Feb 2026 19:43:35 +0100 Subject: [PATCH] feat(chat): integrate News namespace sessions from dashboard Dashboard article follow-up chats now persist to MongoDB as News namespace sessions, making them visible in the Chat page sidebar under "News Chats". On first follow-up message: creates a News session with the article title/URL, saves the system context message, then persists each user and assistant message. Subsequent messages in the same article reuse the existing session ID. Co-Authored-By: Claude Opus 4.6 --- src/infrastructure/database.rs | 5 +- src/pages/chat.rs | 9 +-- src/pages/dashboard.rs | 132 ++++++++++++++++++++++++--------- 3 files changed, 101 insertions(+), 45 deletions(-) diff --git a/src/infrastructure/database.rs b/src/infrastructure/database.rs index c6f0682..42eb1ba 100644 --- a/src/infrastructure/database.rs +++ b/src/infrastructure/database.rs @@ -62,10 +62,7 @@ impl Database { /// Raw BSON document collection for queries that need manual /// `_id` → `String` conversion (avoids `ObjectId` deserialization issues). - pub fn raw_collection( - &self, - name: &str, - ) -> Collection { + pub fn raw_collection(&self, name: &str) -> Collection { self.inner.collection(name) } } diff --git a/src/pages/chat.rs b/src/pages/chat.rs index d740178..e9259dd 100644 --- a/src/pages/chat.rs +++ b/src/pages/chat.rs @@ -187,13 +187,7 @@ pub fn ChatPage() -> Element { match chat_complete(sid.clone(), messages_json).await { Ok(response) => { // Save assistant message - match save_chat_message( - sid, - "assistant".to_string(), - response, - ) - .await - { + match save_chat_message(sid, "assistant".to_string(), response).await { Ok(msg) => { messages.write().push(msg); } @@ -258,4 +252,3 @@ pub fn ChatPage() -> Element { } } } - diff --git a/src/pages/dashboard.rs b/src/pages/dashboard.rs index b6520cb..2dc6d81 100644 --- a/src/pages/dashboard.rs +++ b/src/pages/dashboard.rs @@ -2,6 +2,7 @@ use dioxus::prelude::*; use dioxus_sdk::storage::use_persistent; use crate::components::{ArticleDetail, DashboardSidebar, NewsCardView, PageHeader}; +use crate::infrastructure::chat::{create_chat_session, save_chat_message}; use crate::infrastructure::llm::FollowUpMessage; use crate::models::NewsCard; @@ -50,6 +51,8 @@ pub fn DashboardPage() -> Element { let mut is_chatting = use_signal(|| false); // Stores the article text context for the chat system message let mut article_context = use_signal(String::new); + // MongoDB session ID for persisting News chat (created on first follow-up) + let mut news_session_id: Signal> = use_signal(|| None); // Recent search history, persisted in localStorage (capped at MAX_RECENT_SEARCHES) let mut recent_searches = @@ -310,6 +313,7 @@ pub fn DashboardPage() -> Element { summary.set(None); chat_messages.set(Vec::new()); article_context.set(String::new()); + news_session_id.set(None); let oll_url = ollama_url.read().clone(); @@ -358,6 +362,7 @@ pub fn DashboardPage() -> Element { selected_card.set(None); summary.set(None); chat_messages.set(Vec::new()); + news_session_id.set(None); }, summary: summary.read().clone(), is_summarizing: *is_summarizing.read(), @@ -367,52 +372,113 @@ pub fn DashboardPage() -> Element { let oll_url = ollama_url.read().clone(); let mdl = ollama_model.read().clone(); let ctx = article_context.read().clone(); + // Capture article info for News session creation + let card_title = selected_card + .read() + .as_ref() + .map(|c| c.title.clone()) + .unwrap_or_default(); + let card_url = selected_card + .read() + .as_ref() + .map(|c| c.url.clone()) + .unwrap_or_default(); - // Append user message to chat - chat_messages + // Append user message to local chat + chat_messages.write().push(FollowUpMessage { + role: "user".into(), + content: question.clone(), + }); - // Build full message history for Ollama - - .write() - .push(FollowUpMessage { - role: "user".into(), - content: question, - }); + // Build full message history for Ollama + let system_msg = format!( + "You are a helpful assistant. The user is reading \ + a news article. Use the following context to answer \ + their questions. Do NOT comment on the source, \ + dates, URLs, or formatting.\n\n{ctx}", + ); let msgs = { let history = chat_messages.read(); - let mut all = vec![ - FollowUpMessage { - role: "system".into(), - content: format!( - "You are a helpful assistant. The user is reading \ - a news article. Use the following context to answer \ - their questions. Do NOT comment on the source, \ - dates, URLs, or formatting.\n\n{ctx}", - ), - }, - ]; + let mut all = vec![FollowUpMessage { + role: "system".into(), + content: system_msg.clone(), + }]; all.extend(history.iter().cloned()); all }; + spawn(async move { is_chatting.set(true); - match crate::infrastructure::llm::chat_followup(msgs, oll_url, mdl).await { + + // Create News session on first follow-up message + let existing_sid = news_session_id.read().clone(); + let sid = if let Some(id) = existing_sid { + id + } else { + match create_chat_session( + card_title, + "News".to_string(), + "ollama".to_string(), + mdl.clone(), + card_url, + ) + .await + { + Ok(session) => { + let id = session.id.clone(); + news_session_id.set(Some(id.clone())); + // Persist system context as first message + let _ = save_chat_message( + id.clone(), + "system".to_string(), + system_msg, + ) + .await; + id + } + Err(e) => { + tracing::error!("Failed to create News session: {e}"); + String::new() + } + } + }; + + // Persist user message + if !sid.is_empty() { + let _ = save_chat_message( + sid.clone(), + "user".to_string(), + question, + ) + .await; + } + + match crate::infrastructure::llm::chat_followup( + msgs, oll_url, mdl, + ) + .await + { Ok(reply) => { - chat_messages - .write() - .push(FollowUpMessage { - role: "assistant".into(), - content: reply, - }); + // Persist assistant message + if !sid.is_empty() { + let _ = save_chat_message( + sid, + "assistant".to_string(), + reply.clone(), + ) + .await; + } + chat_messages.write().push(FollowUpMessage { + role: "assistant".into(), + content: reply, + }); } Err(e) => { tracing::error!("Chat failed: {e}"); - chat_messages - .write() - .push(FollowUpMessage { - role: "assistant".into(), - content: format!("Error: {e}"), - }); + chat_messages.write().push(FollowUpMessage { + role: "assistant".into(), + content: format!("Error: {e}"), + }); } } is_chatting.set(false);