" in raw_text:
+ think_match = re.search(r'(.*?)(|$)', raw_text, re.DOTALL)
+ if think_match:
+ display_thought = think_match.group(1).strip()
+ display_text = re.sub(r'.*?(|$)', '', raw_text, flags=re.DOTALL).strip()
+
+ if display_thought:
+ # 只有在最终结束时,才把它放进折叠面板
+ with thought_placeholder.container():
+ with st.expander("🤔 思考过程", expanded=False):
+ st.markdown(display_thought)
+ else:
+ thought_placeholder.empty()
+
+ # 移除光标
+ message_placeholder.markdown(display_text)
+
+ # 拼装包含思考过程的完整内容,以便后续在历史中正确渲染
+ final_content = display_text
+ if display_thought:
+ final_content = f"```thought\n{display_thought}\n```\n\n" + display_text
+
+ AppState.add_message("assistant", final_content)
tool_status_placeholder.empty()
+
+ # 消息发送完毕后,静默刷新历史记录列表
+ # (因为可能生成了新对话,或者旧对话摘要已更新)
+ from frontend.components.sidebar import _refresh_threads
+ _refresh_threads()
+
+ # 强制重绘页面,使侧边栏立即显示最新记录
+ st.rerun()
def _show_completion_stats(event: dict):
diff --git a/frontend/components/info_panel.py b/frontend/components/info_panel.py
index 7540cc4..0061551 100644
--- a/frontend/components/info_panel.py
+++ b/frontend/components/info_panel.py
@@ -10,50 +10,30 @@ from frontend.state import AppState
def render_info_panel():
- """渲染右侧信息面板"""
- st.header("📊 会话信息")
-
- # 当前线程信息
- _render_thread_info()
-
- st.divider()
+ """渲染右侧信息面板(现改为侧边栏底部)"""
+ st.caption("📊 会话信息")
# 消息统计
_render_message_stats()
- st.divider()
-
# 使用提示
_render_tips()
-def _render_thread_info():
- """渲染当前线程信息"""
- st.subheader("当前对话")
- thread_id = AppState.get_current_thread_id()
- st.code(thread_id[:8] + "...", language=None)
-
-
def _render_message_stats():
"""渲染消息统计"""
- st.subheader("消息统计")
-
stats = AppState.get_message_stats()
-
- col1, col2 = st.columns(2)
- with col1:
- st.metric("用户消息", stats["user"])
- with col2:
- st.metric("AI 回复", stats["assistant"])
+ st.markdown(f"共 {stats['user']} 问 / {stats['assistant']} 答", unsafe_allow_html=True)
def _render_tips():
"""渲染使用提示"""
- st.subheader("💡 使用提示")
- st.markdown("""
- - 左侧可切换历史对话
- - 点击"新对话"开始新话题
- - 登录后对话历史隔离
- - 支持流式实时响应
- - 模型可随时切换
- """)
+ with st.expander("💡 使用提示", expanded=False):
+ st.markdown("""
+
+ - 左侧可切换历史对话
+ - 点击"新对话"开始新话题
+ - 登录后对话历史隔离
+ - 模型可随时切换
+
+ """, unsafe_allow_html=True)
diff --git a/frontend/components/sidebar.py b/frontend/components/sidebar.py
index fb57c21..c23aa3e 100644
--- a/frontend/components/sidebar.py
+++ b/frontend/components/sidebar.py
@@ -14,14 +14,22 @@ from frontend.config import config
def render_sidebar():
"""渲染左侧栏"""
- _render_user_section()
+ # 顶部放置新对话按钮,像 ChatGPT/DeepSeek 一样显眼
+ _render_history_actions()
st.divider()
+
+ # 历史列表
_render_history_section()
+
+ # 底部放用户部分
+ st.divider()
+ _render_user_section()
def _render_user_section():
"""渲染用户登录区域"""
- st.header("👤 用户")
+ # st.header("👤 用户") # 移除显眼的标题,改用更柔和的 caption
+ st.caption("👤 用户管理")
if not AppState.is_logged_in():
_render_login_form()
@@ -32,58 +40,62 @@ def _render_user_section():
def _render_login_form():
"""渲染登录表单"""
username = st.text_input(
- "输入用户名(可选)",
+ "用户名",
key="login_input",
- placeholder="留空使用默认用户",
- help="未登录将使用 default_user,可能导致对话污染"
+ placeholder="输入用户名...",
+ help="未登录将使用 default_user,可能导致对话污染",
+ label_visibility="collapsed"
)
- if st.button("✅ 进入", type="primary", use_container_width=True):
+ if st.button("进入", type="secondary", use_container_width=True):
AppState.login(username)
_refresh_threads()
st.rerun()
- st.info("💡 建议登录以隔离对话历史")
+ # st.info("💡 建议登录以隔离对话历史") # 移除多余色块
def _render_user_info():
"""渲染用户信息"""
- st.success(f"✅ 当前用户: `{AppState.get_user_id()}`")
+ st.markdown(f"**当前用户**: `{AppState.get_user_id()}`")
- if st.button("🔄 切换用户", use_container_width=True):
+ if st.button("切换用户", type="secondary", use_container_width=True):
AppState.logout()
+ _refresh_threads()
st.rerun()
def _render_history_section():
"""渲染历史对话列表"""
- st.header("📚 对话历史")
+ col1, col2 = st.columns([3, 1])
+ with col1:
+ st.caption("📚 对话历史")
+ with col2:
+ if st.button("🔄", help="刷新列表", key="refresh_history_btn"):
+ _refresh_threads()
- # 操作按钮
- _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):
+ # 移除了 type="primary",让它变成普通的线框按钮,不再是大红块
+ if st.button("➕ 新对话", use_container_width=True):
AppState.start_new_thread()
st.rerun()
def _render_thread_list():
"""渲染线程列表"""
+ # 仅在初次加载时拉取,或由外部主动调用 _refresh_threads() 更新
+ if "threads_loaded" not in st.session_state:
+ _refresh_threads()
+ st.session_state.threads_loaded = True
+
threads = AppState.get_threads()
if not threads:
- st.info("暂无对话历史")
+ st.caption("暂无对话历史")
return
for thread in threads:
@@ -98,28 +110,23 @@ def _render_thread_item(thread: dict):
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)
+ summary = thread.get("summary", "新对话")
# 判断是否为当前线程
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 += "..."
+ # 根据是否当前线程改变按钮样式
+ btn_type = "primary" if is_current else "tertiary"
+
+ # 为了避免内容过长,截断摘要
+ display_text = summary[:15] + "..." if len(summary) > 15 else summary
- # 渲染按钮
if st.button(
- f"💬 {summary_display}\n\n🕐 {time_str} | {message_count}条",
+ display_text,
key=f"thread_{thread_id}",
+ help=f"完整摘要: {summary}",
use_container_width=True,
- type=button_type
+ type=btn_type
):
_load_thread(thread_id)
diff --git a/frontend/config.py b/frontend/config.py
index 1c7656f..b9b8c9d 100644
--- a/frontend/config.py
+++ b/frontend/config.py
@@ -24,7 +24,7 @@ class FrontendConfig:
layout: str = "wide"
# ==================== 模型配置 ====================
- default_model: str = "zhipu"
+ default_model: str = "local" # 更改为local作为默认模型
model_options: dict = None
# ==================== 用户配置 ====================
@@ -41,9 +41,9 @@ class FrontendConfig:
"""初始化后处理 - 设置默认值和加载环境变量"""
if self.model_options is None:
self.model_options = {
- "zhipu": "智谱 GLM-4.7-Flash(在线)",
- "deepseek": "DeepSeek V3.2(在线)",
- "local": "本地 llama.cpp(Gemma-4)"
+ "local": "本地 llama.cpp(Gemma-4)", # 本地模型作为第一个
+ "deepseek": "DeepSeek V3.2(在线)", # DeepSeek 作为中间
+ "zhipu": "智谱 GLM-4.7-Flash(在线)" # GLM-4.7 作为最后一个
}
# 从环境变量加载配置
@@ -53,9 +53,9 @@ class FrontendConfig:
"""从环境变量加载配置(优先级最高)"""
# API 地址(移除 /chat 后缀)
# 优先级:环境变量 API_URL > 默认值
- api_url = os.getenv("API_URL", "http://localhost:8083")
+ api_url = os.getenv("API_URL", "http://127.0.0.1:8083")
self.api_base = api_url.replace("/chat", "").rstrip("/")
# 全局配置实例(单例模式)
-config = FrontendConfig()
+config = FrontendConfig()
\ No newline at end of file
diff --git a/frontend/frontend.py b/frontend/frontend.py
deleted file mode 100644
index 8d70c6f..0000000
--- a/frontend/frontend.py
+++ /dev/null
@@ -1,409 +0,0 @@
-"""
-右侧栏组件:工具状态和统计信息
-"""
-import streamlit as st
-
-
-def render_info_panel():
- st.header("📊 会话信息")
-
- # 当前线程信息
- st.subheader("当前对话")
- st.code(st.session_state.current_thread_id[:8] + "...", language=None)
-
- st.divider()
-
- # 消息统计
- st.subheader("消息统计")
- user_msgs = len([m for m in st.session_state.messages if m["role"] == "user"])
- assistant_msgs = len([m for m in st.session_state.messages if m["role"] == "assistant"])
-
- st.metric("用户消息", user_msgs)
- st.metric("AI 回复", assistant_msgs)
-
- st.divider()
-
- # 使用提示
- st.subheader("💡 使用提示")
- st.markdown("""
- - 左侧可切换历史对话
- - 点击"新对话"开始新话题
- - 登录后对话历史隔离
- - 支持流式实时响应
- - 模型可随时切换
- """)
-"""
-中间栏组件:聊天区域
-"""
-import streamlit as st
-from ..config import config
-from ..api_client import stream_chat
-
-
-def render_chat_area():
- # 模型选择器
- col_model, col_empty = st.columns([2, 3])
- with col_model:
- selected_model_key = st.selectbox(
- "🧠 选择模型",
- options=list(config.model_options.keys()),
- format_func=lambda x: config.model_options[x],
- index=list(config.model_options.keys()).index(st.session_state.selected_model) if st.session_state.selected_model in config.model_options else 0
- )
- st.session_state.selected_model = selected_model_key
-
- st.divider()
-
- # 显示消息历史
- chat_container = st.container(height=500)
- with chat_container:
- for msg in st.session_state.messages:
- with st.chat_message(msg["role"]):
- st.markdown(msg["content"])
-
- # 输入框
- if prompt := st.chat_input("请输入您的问题...", key="chat_input"):
- # 显示用户消息
- with st.chat_message("user"):
- st.markdown(prompt)
- st.session_state.messages.append({"role": "user", "content": prompt})
-
- # 流式调用后端
- with st.chat_message("assistant"):
- message_placeholder = st.empty()
- tool_status_placeholder = st.empty()
- full_response = ""
-
- stream_gen = stream_chat(
- message=prompt,
- thread_id=st.session_state.current_thread_id,
- model=st.session_state.selected_model,
- user_id=st.session_state.user_id
- )
-
- if stream_gen:
- for data in stream_gen:
- if data["type"] == "token":
- full_response += data["content"]
- message_placeholder.markdown(full_response + "▌")
-
- elif data["type"] == "tool_start":
- tool_status_placeholder.info(f"🔧 调用工具: {data['tool']}...")
-
- elif data["type"] == "tool_end":
- tool_status_placeholder.success(f"✅ 工具 {data['tool']} 完成")
- tool_status_placeholder.empty()
-
- elif data["type"] == "done":
- # 最终响应
- token_usage = data.get("token_usage", {})
- elapsed = data.get("elapsed_time", 0)
- if token_usage:
- st.caption(f"📊 消耗 {token_usage.get('total_tokens', 0)} tokens | ⏱️ {elapsed:.2f}s")
-
- elif data["type"] == "error":
- st.error(f"❌ 错误: {data['message']}")
-
- # 显示完整响应
- message_placeholder.markdown(full_response)
- st.session_state.messages.append({"role": "assistant", "content": full_response})
- tool_status_placeholder.empty()
-"""
-左侧栏组件:用户登录 + 历史对话列表
-"""
-from datetime import datetime
-import streamlit as st
-from ..state import AppState
-from ..api_client import refresh_threads, load_thread_history
-
-
-def render_sidebar():
- st.header("👤 用户")
-
- # 用户登录区域
- if not st.session_state.logged_in:
- 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.session_state.user_id)
-
- st.info("💡 建议登录以隔离对话历史")
- else:
- st.success(f"✅ 当前用户: `{st.session_state.user_id}`")
-
- if st.button("🔄 切换用户", use_container_width=True):
- AppState.reset_login()
-
- st.divider()
-
- # 历史对话列表
- st.header("📚 对话历史")
-
- # 刷新按钮
- if st.button("🔄 刷新列表", use_container_width=True):
- refresh_threads(st.session_state.user_id)
-
- # 新对话按钮
- if st.button("➕ 新对话", type="primary", use_container_width=True):
- AppState.start_new_thread()
-
- st.divider()
-
- # 显示历史列表
- if st.session_state.threads:
- for thread in st.session_state.threads:
- thread_id = thread["thread_id"]
- summary = thread.get("summary", "空对话")
- message_count = thread.get("message_count", 0)
- last_updated = thread.get("last_updated", "")
-
- # 格式化时间
- if last_updated:
- try:
- dt = datetime.fromisoformat(last_updated.replace("Z", "+00:00"))
- time_str = dt.strftime("%m-%d %H:%M")
- except:
- time_str = last_updated[:10]
- else:
- time_str = "未知"
-
- # 按钮样式
- is_current = thread_id == st.session_state.current_thread_id
- button_type = "primary" if is_current else "secondary"
-
- if st.button(
- f"💬 {summary[:30]}{'...' if len(summary) > 30 else ''}\n\n🕐 {time_str} | {message_count}条",
- key=f"thread_{thread_id}",
- use_container_width=True,
- type=button_type
- ):
- load_thread_history(thread_id, st.session_state.user_id)
- else:
- st.info("暂无对话历史")
-# Components package
-"""
-后端 API 客户端封装
-"""
-import json
-import requests
-import streamlit as st
-from .config import config
-
-
-def refresh_threads(user_id: str):
- """刷新用户的历史对话列表"""
- try:
- resp = requests.get(
- f"{config.api_base}/threads",
- params={"user_id": user_id, "limit": 50},
- timeout=10
- )
- if resp.status_code == 200:
- st.session_state.threads = resp.json()["threads"]
- else:
- st.error(f"加载历史列表失败: HTTP {resp.status_code}")
- except Exception as e:
- st.error(f"加载历史列表失败: {e}")
-
-
-def load_thread_history(thread_id: str, user_id: str):
- """加载指定线程的完整消息历史"""
- try:
- resp = requests.get(
- f"{config.api_base}/thread/{thread_id}/messages",
- params={"user_id": user_id},
- timeout=10
- )
- if resp.status_code == 200:
- st.session_state.messages = resp.json()["messages"]
- st.session_state.current_thread_id = thread_id
- st.rerun()
- else:
- st.error(f"加载对话失败: HTTP {resp.status_code}")
- except Exception as e:
- st.error(f"加载对话失败: {e}")
-
-
-def stream_chat(message: str, thread_id: str, model: str, user_id: str):
- """流式调用后端聊天接口"""
- payload = {
- "message": message,
- "thread_id": thread_id,
- "model": model,
- "user_id": user_id
- }
-
- try:
- with requests.post(
- f"{config.api_base}/chat/stream",
- json=payload,
- stream=True,
- timeout=120
- ) as response:
- if response.status_code != 200:
- st.error(f"请求失败: HTTP {response.status_code}")
- return None
-
- full_response = ""
- for line in response.iter_lines():
- if line:
- line = line.decode('utf-8')
- if line.startswith("data: "):
- data_str = line[6:]
- if data_str == "[DONE]":
- break
- try:
- data = json.loads(data_str)
- yield data
- except json.JSONDecodeError:
- pass
- return full_response
-
- except Exception as e:
- st.error(f"请求失败: {e}")
- return None
-"""
-Session State 管理
-"""
-import uuid
-import streamlit as st
-
-
-class AppState:
- """管理 Streamlit Session State"""
-
- @staticmethod
- def init():
- """初始化必要的 session state 变量"""
- if "user_id" not in st.session_state:
- st.session_state.user_id = "default_user"
- if "logged_in" not in st.session_state:
- st.session_state.logged_in = False
- if "threads" not in st.session_state:
- st.session_state.threads = []
- if "current_thread_id" not in st.session_state:
- st.session_state.current_thread_id = str(uuid.uuid4())
- if "messages" not in st.session_state:
- st.session_state.messages = []
- if "selected_model" not in st.session_state:
- st.session_state.selected_model = "zhipu"
- if "loading_history" not in st.session_state:
- st.session_state.loading_history = False
-
- @staticmethod
- def reset_login():
- """重置登录状态"""
- st.session_state.logged_in = False
- st.session_state.user_id = "default_user"
- st.session_state.threads = []
- st.rerun()
-
- @staticmethod
- def login(username: str):
- """执行登录"""
- st.session_state.user_id = username.strip() if username.strip() else "default_user"
- st.session_state.logged_in = True
- st.rerun()
-
- @staticmethod
- def start_new_thread():
- """开始新对话"""
- st.session_state.current_thread_id = str(uuid.uuid4())
- st.session_state.messages = []
- st.rerun()
-"""
-应用配置
-"""
-import os
-from dataclasses import dataclass
-
-
-@dataclass
-class AppConfig:
- page_title: str = "AI 个人助手"
- page_icon: str = "🤖"
- layout: str = "wide"
- # 后端 API 地址配置
- # 优先级:环境变量 API_URL > Docker 内部服务名 > 本地开发地址
- api_base: str = os.getenv("API_URL", "http://localhost:8001").replace("/chat", "")
-
- model_options: dict = None
-
- def __post_init__(self):
- if self.model_options is None:
- self.model_options = {
- "zhipu": "智谱 GLM-4.7-Flash(在线)",
- "deepseek": "DeepSeek V3.2(在线)",
- "local": "本地 vLLM(Gemma-4)"
- }
-
-config = AppConfig()
-"""
-AI Agent 前端主入口
-采用模块化架构,仅负责组装各组件
-"""
-
-import sys
-import os
-
-# 添加项目根目录到 Python 路径,支持绝对导入
-sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-
-import streamlit as st
-
-# 使用绝对导入
-from frontend.config import config
-from frontend.state import AppState
-from frontend.components.sidebar import render_sidebar
-from frontend.components.chat_area import render_chat_area
-from frontend.components.info_panel import render_info_panel
-
-
-# =============================================================================
-# 页面配置
-# =============================================================================
-st.set_page_config(
- page_title=config.page_title,
- page_icon=config.page_icon,
- layout=config.layout
-)
-
-
-# =============================================================================
-# 初始化状态
-# =============================================================================
-AppState.init()
-
-
-# =============================================================================
-# 主界面
-# =============================================================================
-def main():
- """主界面渲染 - 三栏布局"""
- # 标题
- st.title("🤖 个人生活与数据分析助手")
-
- # 三栏布局:左侧栏(1) + 中间栏(3) + 右侧栏(1)
- col_sidebar, col_chat, col_info = st.columns([1, 3, 1])
-
- # 左侧栏:用户登录 + 历史对话
- with col_sidebar:
- render_sidebar()
-
- # 中间栏:模型选择 + 聊天区域 + 输入框
- with col_chat:
- render_chat_area()
-
- # 右侧栏:会话信息 + 统计 + 使用提示
- with col_info:
- render_info_panel()
-
-
-if __name__ == "__main__":
- main()
diff --git a/frontend/frontend_main.py b/frontend/frontend_main.py
new file mode 100644
index 0000000..1a38f89
--- /dev/null
+++ b/frontend/frontend_main.py
@@ -0,0 +1,125 @@
+"""
+AI Agent 前端主入口
+采用模块化架构,仅负责组装各组件
+"""
+
+import sys
+import os
+
+# 添加项目根目录到 Python 路径,支持绝对导入
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+import streamlit as st
+
+# 使用绝对导入
+from frontend.config import config
+from frontend.state import AppState
+from frontend.components.sidebar import render_sidebar
+from frontend.components.chat_area import render_chat_area
+from frontend.components.info_panel import render_info_panel
+
+
+# =============================================================================
+# 页面配置
+# =============================================================================
+st.set_page_config(
+ page_title=config.page_title,
+ page_icon=config.page_icon,
+ layout=config.layout
+)
+
+
+# =============================================================================
+# 初始化状态
+# =============================================================================
+AppState.init()
+
+def apply_custom_css():
+ """应用自定义CSS样式,实现极简风格"""
+ st.markdown("""
+
+ """, unsafe_allow_html=True)
+
+
+# =============================================================================
+# 主界面
+# =============================================================================
+def main():
+ """主界面渲染 - 极简宽屏布局"""
+ # 应用 CSS
+ apply_custom_css()
+
+ # 顶部标题(可选,也可以不放,让界面更像对话框)
+ st.markdown("个人助手
", unsafe_allow_html=True)
+
+ # 左侧边栏:合并用户登录、模型选择和历史对话
+ with st.sidebar:
+ render_sidebar()
+ # 将原本右侧的信息面板简化并移入侧边栏底部
+ st.divider()
+ render_info_panel()
+
+ # 中间主区域:全宽的聊天区域
+ render_chat_area()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/frontend/state.py b/frontend/state.py
index 5efa26f..e1d32bb 100644
--- a/frontend/state.py
+++ b/frontend/state.py
@@ -60,6 +60,8 @@ class AppState:
"""
st.session_state.user_id = username.strip() if username.strip() else config.default_user_id
st.session_state.logged_in = True
+ # 登录后必须开启一个干净的新对话
+ AppState.start_new_thread()
@staticmethod
def logout():
@@ -67,6 +69,8 @@ class AppState:
st.session_state.logged_in = False
st.session_state.user_id = config.default_user_id
st.session_state.threads = []
+ # 登出后必须开启一个干净的新对话
+ AppState.start_new_thread()
# ==================== 线程相关 ====================
diff --git a/scripts/start.sh b/scripts/start.sh
index d3387d2..6ee87f5 100755
--- a/scripts/start.sh
+++ b/scripts/start.sh
@@ -81,11 +81,11 @@ check_config() {
check_fail "ZHIPUAI_API_KEY 未配置或格式错误"
fi
- # 检查 VLLM_LOCAL_KEY
- if grep -q "^VLLM_LOCAL_KEY=" "$PROJECT_DIR/.env" 2>/dev/null; then
- check_pass "VLLM_LOCAL_KEY 已配置"
+ # 检查 LLAMACPP_API_KEY
+ if grep -q "^LLAMACPP_API_KEY=" "$PROJECT_DIR/.env" 2>/dev/null; then
+ check_pass "LLAMACPP_API_KEY 已配置"
else
- check_warn "VLLM_LOCAL_KEY 未配置(如不使用本地模型可忽略)"
+ check_warn "LLAMACPP_API_KEY 未配置(如不使用本地模型可忽略)"
fi
# 检查 DB_URI (远程服务器)
@@ -147,8 +147,7 @@ check_config() {
# 测试 PostgreSQL 连接
if command -v psql &> /dev/null; then
- # 注意:这里假设密码为 mysecretpassword,如果不同需调整或从 env 读取
- if PGPASSWORD=mysecretpassword psql -h 115.190.121.151 -U postgres -d langgraph_db -c "SELECT 1;" &> /dev/null; then
+ if PGPASSWORD=huang1998 psql -h 115.190.121.151 -U postgres -d langgraph_db -c "SELECT 1;" &> /dev/null; then
check_pass "PostgreSQL 远程连接正常 (115.190.121.151:5432)"
else
check_fail "PostgreSQL 远程连接失败"
@@ -306,11 +305,11 @@ start_frontend() {
set +a
export PYTHONPATH="$PROJECT_DIR"
- streamlit run frontend/frontend.py &
+ streamlit run frontend/frontend_main.py &
FRONTEND_PID=$!
echo -e "${GREEN}✓ 前端服务已启动 (PID: $FRONTEND_PID)${NC}"
echo -e "${GREEN}✓ 访问地址:${NC}"
- echo -e " 本地开发: http://localhost:8501"
+ echo -e " 本地开发: http://127.0.0.1:8501"
}
# =============================================================================
@@ -333,7 +332,7 @@ docker_up() {
echo -e "\n${GREEN}✓ Docker Compose 服务已启动${NC}"
echo -e "${BLUE}📊 查看服务状态:${NC} docker compose ps"
echo -e "${BLUE}📝 查看日志:${NC} docker compose logs -f"
- echo -e "${BLUE}🌐 访问应用:${NC} http://localhost:8501"
+ echo -e "${BLUE}🌐 访问应用:${NC} http://127.0.0.1:8501"
}
docker_down() {
@@ -395,6 +394,7 @@ case "${1:-help}" in
check_llamacpp || start_llamacpp
check_embedding || start_embedding
start_backend
+ sleep 3
start_frontend
echo -e "\n${GREEN}所有服务正在运行,按 Ctrl+C 停止 Python 服务${NC}"
echo -e "${YELLOW}注意:Docker 容器会在后台继续运行${NC}"