use dioxus::prelude::*; use crate::components::ChatBubble; use crate::models::{ChatMessage, ChatRole, ChatSession}; /// ChatGPT-style chat interface with session list and message area. /// /// Full-height layout: left panel shows session history, /// right panel shows messages and input bar. #[component] pub fn ChatPage() -> Element { let sessions = use_signal(mock_sessions); let mut active_session_id = use_signal(|| "session-1".to_string()); let mut input_text = use_signal(String::new); // Clone data out of signals before entering the rsx! block to avoid // holding a `Signal::read()` borrow across potential await points. let sessions_list = sessions.read().clone(); let current_id = active_session_id.read().clone(); let active_session = sessions_list.iter().find(|s| s.id == current_id).cloned(); rsx! { section { class: "chat-page", div { class: "chat-sidebar-panel", div { class: "chat-sidebar-header", h3 { "Conversations" } button { class: "btn-icon", "+" } } div { class: "chat-session-list", for session in &sessions_list { { let is_active = session.id == current_id; let class = if is_active { "chat-session-item chat-session-item--active" } else { "chat-session-item" }; let id = session.id.clone(); rsx! { button { class: "{class}", onclick: move |_| active_session_id.set(id.clone()), div { class: "chat-session-title", "{session.title}" } div { class: "chat-session-date", "{session.created_at}" } } } } } } } div { class: "chat-main-panel", if let Some(session) = &active_session { div { class: "chat-messages", for msg in &session.messages { ChatBubble { key: "{msg.id}", message: msg.clone() } } } } else { div { class: "chat-empty", p { "Select a conversation or start a new one." } } } div { class: "chat-input-bar", button { class: "btn-icon chat-attach-btn", "+" } input { class: "chat-input", r#type: "text", placeholder: "Type a message...", value: "{input_text}", oninput: move |evt: Event| { input_text.set(evt.value()); }, } button { class: "btn-primary chat-send-btn", "Send" } } } } } } /// Returns mock chat sessions with sample messages. fn mock_sessions() -> Vec { vec![ ChatSession { id: "session-1".into(), title: "RAG Pipeline Setup".into(), messages: vec![ ChatMessage { id: "msg-1".into(), role: ChatRole::User, content: "How do I set up a RAG pipeline with Ollama?".into(), attachments: vec![], timestamp: "10:30".into(), }, ChatMessage { id: "msg-2".into(), role: ChatRole::Assistant, content: "To set up a RAG pipeline with Ollama, you'll need to: \ 1) Install Ollama and pull your preferred model, \ 2) Set up a vector database (e.g. ChromaDB), \ 3) Create an embedding pipeline for your documents, \ 4) Wire the retrieval step into your prompt chain." .into(), attachments: vec![], timestamp: "10:31".into(), }, ], created_at: "2026-02-18".into(), }, ChatSession { id: "session-2".into(), title: "GDPR Compliance Check".into(), messages: vec![ ChatMessage { id: "msg-3".into(), role: ChatRole::User, content: "What data does CERTifAI store about users?".into(), attachments: vec![], timestamp: "09:15".into(), }, ChatMessage { id: "msg-4".into(), role: ChatRole::Assistant, content: "CERTifAI stores only the minimum data required: \ email address, session tokens, and usage metrics. \ All data stays on your infrastructure." .into(), attachments: vec![], timestamp: "09:16".into(), }, ], created_at: "2026-02-17".into(), }, ChatSession { id: "session-3".into(), title: "MCP Server Configuration".into(), messages: vec![ChatMessage { id: "msg-5".into(), role: ChatRole::User, content: "How do I add a new MCP server?".into(), attachments: vec![], timestamp: "14:00".into(), }], created_at: "2026-02-16".into(), }, ] }