""" 左侧栏组件 包含用户登录和历史对话列表 """ import streamlit as st from datetime import datetime # 使用绝对导入 from frontend.state import AppState from frontend.api_client import api_client from frontend.config import config def render_sidebar(): """渲染左侧栏""" _render_user_section() st.divider() _render_history_section() def _render_user_section(): """渲染用户登录区域""" st.header("👤 用户") if not AppState.is_logged_in(): _render_login_form() else: _render_user_info() def _render_login_form(): """渲染登录表单""" username = st.text_input( "输入用户名(可选)", key="login_input", placeholder="留空使用默认用户", help="未登录将使用 default_user,可能导致对话污染" ) if st.button("✅ 进入", type="primary", use_container_width=True): AppState.login(username) _refresh_threads() st.rerun() st.info("💡 建议登录以隔离对话历史") def _render_user_info(): """渲染用户信息""" st.success(f"✅ 当前用户: `{AppState.get_user_id()}`") if st.button("🔄 切换用户", use_container_width=True): AppState.logout() st.rerun() def _render_history_section(): """渲染历史对话列表""" st.header("📚 对话历史") # 操作按钮 _render_history_actions() st.divider() # 历史列表 _render_thread_list() def _render_history_actions(): """渲染历史操作按钮""" if st.button("🔄 刷新列表", use_container_width=True): _refresh_threads() if st.button("➕ 新对话", type="primary", use_container_width=True): AppState.start_new_thread() st.rerun() def _render_thread_list(): """渲染线程列表""" threads = AppState.get_threads() if not threads: st.info("暂无对话历史") return for thread in threads: _render_thread_item(thread) def _render_thread_item(thread: dict): """ 渲染单个线程项 Args: thread: 线程信息字典 """ thread_id = thread["thread_id"] summary = thread.get("summary", "空对话") message_count = thread.get("message_count", 0) last_updated = thread.get("last_updated", "") # 格式化时间 time_str = _format_time(last_updated) # 判断是否为当前线程 is_current = thread_id == AppState.get_current_thread_id() button_type = "primary" if is_current else "secondary" # 截断摘要 summary_display = summary[:config.summary_max_length] if len(summary) > config.summary_max_length: summary_display += "..." # 渲染按钮 if st.button( f"💬 {summary_display}\n\n🕐 {time_str} | {message_count}条", key=f"thread_{thread_id}", use_container_width=True, type=button_type ): _load_thread(thread_id) def _format_time(time_str: str) -> str: """ 格式化时间字符串 Args: time_str: ISO 格式时间字符串 Returns: 格式化后的时间字符串 """ if not time_str: return "未知" try: dt = datetime.fromisoformat(time_str.replace("Z", "+00:00")) return dt.strftime("%m-%d %H:%M") except Exception: return time_str[:10] def _refresh_threads(): """刷新历史线程列表""" threads = api_client.get_user_threads(AppState.get_user_id()) AppState.set_threads(threads) def _load_thread(thread_id: str): """ 加载指定线程的消息历史 Args: thread_id: 线程 ID """ messages = api_client.get_thread_messages(thread_id, AppState.get_user_id()) if messages: AppState.set_current_thread_id(thread_id) AppState.clear_messages() for msg in messages: AppState.add_message(msg["role"], msg["content"]) st.rerun() else: st.error("加载对话失败")