Files
ailine/frontend/components/sidebar.py

177 lines
4.4 KiB
Python
Raw Normal View History

2026-04-16 03:21:38 +08:00
"""
左侧栏组件
包含用户登录和历史对话列表
"""
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():
"""渲染左侧栏"""
2026-04-17 01:26:05 +08:00
# 顶部放置新对话按钮,像 ChatGPT/DeepSeek 一样显眼
_render_history_actions()
2026-04-16 03:21:38 +08:00
st.divider()
2026-04-17 01:26:05 +08:00
# 历史列表
2026-04-16 03:21:38 +08:00
_render_history_section()
2026-04-17 01:26:05 +08:00
# 底部放用户部分
st.divider()
_render_user_section()
2026-04-16 03:21:38 +08:00
def _render_user_section():
"""渲染用户登录区域"""
2026-04-17 01:26:05 +08:00
# st.header("👤 用户") # 移除显眼的标题,改用更柔和的 caption
st.caption("👤 用户管理")
2026-04-16 03:21:38 +08:00
if not AppState.is_logged_in():
_render_login_form()
else:
_render_user_info()
def _render_login_form():
"""渲染登录表单"""
username = st.text_input(
2026-04-17 01:26:05 +08:00
"用户名",
2026-04-16 03:21:38 +08:00
key="login_input",
2026-04-17 01:26:05 +08:00
placeholder="输入用户名...",
help="未登录将使用 default_user可能导致对话污染",
label_visibility="collapsed"
2026-04-16 03:21:38 +08:00
)
2026-04-17 01:26:05 +08:00
if st.button("进入", type="secondary", use_container_width=True):
2026-04-16 03:21:38 +08:00
AppState.login(username)
_refresh_threads()
st.rerun()
2026-04-17 01:26:05 +08:00
# st.info("💡 建议登录以隔离对话历史") # 移除多余色块
2026-04-16 03:21:38 +08:00
def _render_user_info():
"""渲染用户信息"""
2026-04-17 01:26:05 +08:00
st.markdown(f"**当前用户**: `{AppState.get_user_id()}`")
2026-04-16 03:21:38 +08:00
2026-04-17 01:26:05 +08:00
if st.button("切换用户", type="secondary", use_container_width=True):
2026-04-16 03:21:38 +08:00
AppState.logout()
2026-04-17 01:26:05 +08:00
_refresh_threads()
2026-04-16 03:21:38 +08:00
st.rerun()
def _render_history_section():
"""渲染历史对话列表"""
2026-04-17 01:26:05 +08:00
col1, col2 = st.columns([3, 1])
with col1:
st.caption("📚 对话历史")
with col2:
if st.button("🔄", help="刷新列表", key="refresh_history_btn"):
_refresh_threads()
2026-04-16 03:21:38 +08:00
_render_thread_list()
def _render_history_actions():
"""渲染历史操作按钮"""
2026-04-17 01:26:05 +08:00
# 移除了 type="primary",让它变成普通的线框按钮,不再是大红块
if st.button(" 新对话", use_container_width=True):
2026-04-16 03:21:38 +08:00
AppState.start_new_thread()
st.rerun()
def _render_thread_list():
"""渲染线程列表"""
2026-04-17 01:26:05 +08:00
# 仅在初次加载时拉取,或由外部主动调用 _refresh_threads() 更新
if "threads_loaded" not in st.session_state:
_refresh_threads()
st.session_state.threads_loaded = True
2026-04-16 03:21:38 +08:00
threads = AppState.get_threads()
if not threads:
2026-04-17 01:26:05 +08:00
st.caption("暂无对话历史")
2026-04-16 03:21:38 +08:00
return
for thread in threads:
_render_thread_item(thread)
def _render_thread_item(thread: dict):
"""
渲染单个线程项
Args:
thread: 线程信息字典
"""
thread_id = thread["thread_id"]
2026-04-17 01:26:05 +08:00
summary = thread.get("summary", "新对话")
2026-04-16 03:21:38 +08:00
# 判断是否为当前线程
is_current = thread_id == AppState.get_current_thread_id()
2026-04-17 01:26:05 +08:00
# 根据是否当前线程改变按钮样式
btn_type = "primary" if is_current else "tertiary"
# 为了避免内容过长,截断摘要
display_text = summary[:15] + "..." if len(summary) > 15 else summary
2026-04-16 03:21:38 +08:00
if st.button(
2026-04-17 01:26:05 +08:00
display_text,
2026-04-16 03:21:38 +08:00
key=f"thread_{thread_id}",
2026-04-17 01:26:05 +08:00
help=f"完整摘要: {summary}",
2026-04-16 03:21:38 +08:00
use_container_width=True,
2026-04-17 01:26:05 +08:00
type=btn_type
2026-04-16 03:21:38 +08:00
):
_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("加载对话失败")