From 689cf94d4be01abbcfa831e82b8195d7e6e0a0bd Mon Sep 17 00:00:00 2001 From: Yuuuu <1569752928@qq.com> Date: Fri, 20 Feb 2026 22:48:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=89=8D=E7=AB=AF=E6=B7=BB=E5=8A=A0=20?= =?UTF-8?q?LLM=20=E9=85=8D=E7=BD=AE=E8=AE=BE=E7=BD=AE=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: - 前端设置页面可切换 LLM 提供商 - 支持 DeepSeek, OpenAI, Qwen, Ollama - API 密钥配置界面 - 配置保存到 .env 文件 后端 API: - GET /ai/config - 获取当前配置 - POST /ai/config - 更新配置 使用方式: 1. 打开前端 "⚙️ 设置" 页面 2. 选择 LLM 提供商 3. 输入 API 密钥(云端)或设置地址(本地) 4. 保存配置并重启后端 Co-Authored-By: Claude Opus 4.6 --- backend/app/main.py | 79 ++++++++ frontend/src/App.vue | 5 +- frontend/src/components/LLMSettings.vue | 252 ++++++++++++++++++++++++ 3 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/LLMSettings.vue diff --git a/backend/app/main.py b/backend/app/main.py index 4adfeef..d624002 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -958,3 +958,82 @@ async def stop_ai_service(): "status": "not_running", "message": "Ollama was not running" } + + +# LLM 配置管理 +class LLMConfig(BaseModel): + provider: str = "deepseek" # ollama, deepseek, openai, qwen + api_key: Optional[str] = None + deepseek_api_key: Optional[str] = None + openai_api_key: Optional[str] = None + qwen_api_key: Optional[str] = None + + +@app.get("/ai/config") +async def get_ai_config(): + """ + 获取当前 LLM 配置 + """ + from dotenv import load_dotenv + import os + + # 重新加载环境变量 + env_path = Path(__file__).parent.parent / ".env" + load_dotenv(env_path) + + provider = os.getenv("LLM_PROVIDER", "ollama") + + return { + "provider": provider, + "ollama_host": os.getenv("OLLAMA_HOST", "http://127.0.0.1:11434"), + "has_deepseek_key": bool(os.getenv("DEEPSEEK_API_KEY")), + "has_openai_key": bool(os.getenv("OPENAI_API_KEY")), + "has_qwen_key": bool(os.getenv("QWEN_API_KEY")), + } + + +@app.post("/ai/config") +async def update_ai_config(config: LLMConfig): + """ + 更新 LLM 配置(需要重启后端生效) + """ + from dotenv import load_dotenv + import os + + env_path = Path(__file__).parent.parent / ".env" + + # 读取现有 .env 文件 + env_vars = {} + if env_path.exists(): + load_dotenv(env_path) + # 保留现有配置 + for key, value in os.environ.items(): + if key.startswith(("DEEPSEEK_", "OPENAI_", "QWEN_", "OLLAMA_", "LLM_")): + env_vars[key] = value + + # 更新配置 + env_vars["LLM_PROVIDER"] = config.provider + + # 保存 API 密钥 + if config.deepseek_api_key: + env_vars["DEEPSEEK_API_KEY"] = config.deepseek_api_key + if config.openai_api_key: + env_vars["OPENAI_API_KEY"] = config.openai_api_key + if config.qwen_api_key: + env_vars["QWEN_API_KEY"] = config.qwen_api_key + + # 写入 .env 文件 + with open(env_path, "w") as f: + for key, value in env_vars.items(): + # 只保存 LLM 相关的环境变量 + if key.startswith(("DEEPSEEK_", "OPENAI_", "QWEN_", "OLLAMA_", "LLM_")): + f.write(f"{key}={value}\n") + + print(f"[AI] LLM 配置已更新: provider={config.provider}") + + return { + "status": "success", + "message": "配置已保存,请重启后端服务生效", + "provider": config.provider, + "requires_restart": True + } diff --git a/frontend/src/App.vue b/frontend/src/App.vue index b514833..2e87a33 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -24,6 +24,7 @@ import { darkTheme } from 'naive-ui' import SignalDashboard from './components/SignalDashboard.vue' import ExperimentBoard from './components/ExperimentBoard.vue' import ContextManager from './components/ContextManager.vue' +import LLMSettings from './components/LLMSettings.vue' const theme = ref(darkTheme) const activeKey = ref('signals') @@ -31,7 +32,8 @@ const activeKey = ref('signals') const menuOptions: MenuOption[] = [ { label: '信号总线', key: 'signals' }, { label: '实验看板', key: 'experiments' }, - { label: '认知核心', key: 'context' } + { label: '认知核心', key: 'context' }, + { label: '⚙️ 设置', key: 'settings' } ] const currentComponent = computed(() => { @@ -39,6 +41,7 @@ const currentComponent = computed(() => { case 'signals': return SignalDashboard case 'experiments': return ExperimentBoard case 'context': return ContextManager + case 'settings': return LLMSettings default: return SignalDashboard } }) diff --git a/frontend/src/components/LLMSettings.vue b/frontend/src/components/LLMSettings.vue new file mode 100644 index 0000000..01b956b --- /dev/null +++ b/frontend/src/components/LLMSettings.vue @@ -0,0 +1,252 @@ + + +