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 @@ + + +