# 注册界面 + 设置界面 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 实现用户注册界面和设置界面,支持多用户和用户级 LLM 配置 **Architecture:** - 后端:扩展 User 模型,新建 settings router/service,前端认证依赖现有 auth 机制 - 前端:LoginView 添加注册 Tab,新建 SettingsView 页面,复用现有 sci-fi 风格 - 数据:User 表增加 JSON 字段存储 llm_config 和 scheduler_config **Tech Stack:** FastAPI + SQLAlchemy + Vue 3 + axios + Pinia --- ## 文件总览 ``` backend/ app/ models/ user.py # 修改:添加 llm_config, scheduler_config 字段 schemas/ settings.py # 新建:Settings Pydantic schemas routers/ settings.py # 新建:settings API router services/ settings_service.py # 新建:设置逻辑服务 frontend/ src/ api/ settings.ts # 新建:settings API 客户端 views/ LoginView.vue # 修改:添加注册 Tab SettingsView.vue # 新建:设置页面 router/ index.ts # 修改:添加 /settings 路由 components/ SidebarNav.vue # 修改:添加设置菜单 ``` --- ## Task 1: 后端 - User 模型扩展 **Files:** - Modify: `backend/app/models/user.py` - [ ] **Step 1: 添加 JSON 字段到 User 模型** 读取现有 User 模型,添加 llm_config 和 scheduler_config 字段: ```python # 在 User 模型类中添加 from sqlalchemy import JSON llm_config = Column(JSON, nullable=True) # 用户 LLM 配置 scheduler_config = Column(JSON, nullable=True) # 定时任务配置 ``` - [ ] **Step 2: 设置默认值** 确保新用户创建时有默认配置(在 User 模型或 service 层处理) - [ ] **Step 3: 提交** ```bash git add backend/app/models/user.py git commit -m "feat(settings): add llm_config and scheduler_config fields to User model" ``` --- ## Task 2: 后端 - Settings Schema 定义 **Files:** - Create: `backend/app/schemas/settings.py` - [ ] **Step 1: 创建 settings schemas** ```python from pydantic import BaseModel, Field from typing import Optional # LLM Provider 类型 LLMProviderType = Literal["openai", "claude", "ollama", "deepseek", "custom"] LLMType = Literal["chat", "vlm", "embedding", "rerank"] # 单个模型配置 class LLMModelConfig(BaseModel): provider: LLMProviderType = "openai" model: str = "" base_url: str = "" api_key: str = "" # LLM 配置输入 class LLMConfigIn(BaseModel): chat: Optional[LLMModelConfig] = None vlm: Optional[LLMModelConfig] = None embedding: Optional[LLMModelConfig] = None rerank: Optional[LLMModelConfig] = None # 定时任务配置 class SchedulerConfigIn(BaseModel): daily_plan_time: Optional[str] = "08:00" forum_scan_interval_minutes: Optional[int] = 30 todo_ai_generate_time: Optional[str] = "08:00" enabled: Optional[bool] = True # 用户资料更新 class ProfileUpdateIn(BaseModel): full_name: Optional[str] = Field(None, min_length=2, max_length=50) password: Optional[str] = Field(None, min_length=8) current_password: Optional[str] = None # 修改密码时需要验证 # 完整设置输出 class SettingsOut(BaseModel): profile: "UserOut" # 引用 auth.py 中的 UserOut llm_config: Optional[dict] = None scheduler_config: Optional[dict] = None model_config = {"from_attributes": True} # 测试 LLM 连接请求 class LLMTestIn(BaseModel): type: LLMType provider: LLMProviderType model: str base_url: str api_key: str ``` - [ ] **Step 2: 提交** ```bash git add backend/app/schemas/settings.py git commit -m "feat(settings): add Pydantic schemas for settings API" ``` --- ## Task 3: 后端 - Settings Service **Files:** - Create: `backend/app/services/settings_service.py` - [ ] **Step 1: 创建设置服务** 主要功能: 1. `get_user_settings(user_id)` - 获取用户完整设置 2. `update_user_profile(user_id, data)` - 更新用户资料 3. `update_llm_config(user_id, config)` - 更新 LLM 配置 4. `update_scheduler_config(user_id, config)` - 更新定时任务配置 5. `test_llm_connection(data)` - 测试 LLM 连接 ```python import logging from typing import Optional from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.models.user import User from app.services.auth_service import verify_password, get_password_hash from app.services.llm_service import get_llm from langchain_core.messages import HumanMessage, SystemMessage logger = logging.getLogger(__name__) async def get_user_settings(user_id: str, db: AsyncSession) -> dict: """获取用户完整设置""" result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user: return None return { "profile": user, "llm_config": user.llm_config or {}, "scheduler_config": user.scheduler_config or {} } async def update_user_profile( user_id: str, db: AsyncSession, full_name: Optional[str] = None, password: Optional[str] = None, current_password: Optional[str] = None ) -> User: """更新用户资料""" result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user: raise ValueError("用户不存在") if password: if not current_password or not verify_password(current_password, user.hashed_password): raise ValueError("当前密码错误") user.hashed_password = get_password_hash(password) if full_name: user.full_name = full_name await db.commit() await db.refresh(user) return user async def update_llm_config(user_id: str, config: dict, db: AsyncSession) -> dict: """更新 LLM 配置""" result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user: raise ValueError("用户不存在") current = user.llm_config or {} # 合并配置 for key, value in config.items(): if value is not None: current[key] = value user.llm_config = current await db.commit() return current async def update_scheduler_config(user_id: str, config: dict, db: AsyncSession) -> dict: """更新定时任务配置""" result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user: raise ValueError("用户不存在") current = user.scheduler_config or {} for key, value in config.items(): if value is not None: current[key] = value user.scheduler_config = current await db.commit() return current async def test_llm_connection( provider: str, model: str, base_url: str, api_key: str ) -> dict: """测试 LLM 连接""" try: # 根据不同 provider 创建临时 LLM 实例并测试 if provider == "openai": from langchain_openai import ChatOpenAI llm = ChatOpenAI( api_key=api_key, model=model, base_url=base_url or None, timeout=30 ) elif provider == "claude": from langchain_anthropic import ChatAnthropic llm = ChatAnthropic( api_key=api_key, model=model, timeout=30 ) elif provider == "ollama": from langchain_ollama import ChatOllama llm = ChatOllama( base_url=base_url or "http://localhost:11434", model=model, timeout=30 ) elif provider == "deepseek": from langchain_openai import ChatOpenAI llm = ChatOpenAI( api_key=api_key, model=model, base_url=base_url or "https://api.deepseek.com/v1", timeout=30 ) else: return {"success": False, "error": f"不支持的 provider: {provider}"} # 简单测试调用 response = await llm.ainvoke([HumanMessage(content="Hi")]) return {"success": True, "message": f"连接成功,模型响应: {response.content[:50]}..."} except Exception as e: return {"success": False, "error": str(e)} ``` - [ ] **Step 2: 提交** ```bash git add backend/app/services/settings_service.py git commit -m "feat(settings): add settings service with LLM config management" ``` --- ## Task 4: 后端 - Settings Router **Files:** - Create: `backend/app/routers/settings.py` - [ ] **Step 1: 创建 settings router** ```python from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.models.user import User from app.routers.auth import get_current_user from app.schemas.settings import ( SettingsOut, ProfileUpdateIn, LLMConfigIn, SchedulerConfigIn, LLMTestIn ) from app.services.settings_service import ( get_user_settings, update_user_profile, update_llm_config, update_scheduler_config, test_llm_connection ) router = APIRouter(prefix="/api/settings", tags=["设置"]) @router.get("", response_model=SettingsOut) async def get_settings( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): settings = await get_user_settings(current_user.id, db) if not settings: raise HTTPException(status_code=404, detail="用户不存在") return settings @router.put("/profile") async def update_profile( data: ProfileUpdateIn, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): try: user = await update_user_profile( current_user.id, db, full_name=data.full_name, password=data.password, current_password=data.current_password ) return user except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) @router.put("/llm") async def update_llm( data: LLMConfigIn, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): try: config = await update_llm_config(current_user.id, data.model_dump(exclude_none=True), db) return {"llm_config": config} except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) @router.post("/llm/test") async def test_llm( data: LLMTestIn, current_user: User = Depends(get_current_user), ): result = await test_llm_connection( provider=data.provider, model=data.model, base_url=data.base_url, api_key=data.api_key ) return result @router.put("/scheduler") async def update_scheduler( data: SchedulerConfigIn, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): try: config = await update_scheduler_config( current_user.id, data.model_dump(exclude_none=True), db ) return {"scheduler_config": config} except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) ``` - [ ] **Step 2: 注册 router 到 main.py 和 routers/__init__.py** 在 `backend/app/routers/__init__.py` 添加: ```python from app.routers.settings import router as settings_router ``` 在 `backend/app/main.py` 添加: ```python from app.routers.settings import router as settings_router # ... app.include_router(settings_router) ``` - [ ] **Step 3: 提交** ```bash git add backend/app/routers/settings.py backend/app/routers/__init__.py backend/app/main.py git commit -m "feat(settings): add settings router with profile, LLM and scheduler endpoints" ``` --- ## Task 5: 前端 - Settings API 客户端 **Files:** - Create: `frontend/src/api/settings.ts` - [ ] **Step 1: 创建 settings API 客户端** ```typescript import api from './index' export type LLMProvider = 'openai' | 'claude' | 'ollama' | 'deepseek' | 'custom' export type LLMType = 'chat' | 'vlm' | 'embedding' | 'rerank' export interface LLMModelConfig { provider: LLMProvider model: string base_url: string api_key: string } export interface LLMConfig { chat?: LLMModelConfig vlm?: LLMModelConfig embedding?: LLMModelConfig rerank?: LLMModelConfig } export interface SchedulerConfig { daily_plan_time?: string forum_scan_interval_minutes?: number todo_ai_generate_time?: string enabled?: boolean } export interface ProfileUpdate { full_name?: string password?: string current_password?: string } export interface SettingsResponse { profile: { id: string email: string full_name: string created_at: string } llm_config: LLMConfig scheduler_config: SchedulerConfig } export const settingsApi = { // 获取设置 get() { return api.get('/api/settings') }, // 更新资料 updateProfile(data: ProfileUpdate) { return api.put('/api/settings/profile', data) }, // 更新 LLM 配置 updateLLM(config: Partial) { return api.put('/api/settings/llm', config) }, // 测试 LLM 连接 testLLM(data: { type: LLMType } & LLMModelConfig) { return api.post('/api/settings/llm/test', data) }, // 更新定时任务配置 updateScheduler(config: Partial) { return api.put('/api/settings/scheduler', config) }, } ``` - [ ] **Step 2: 提交** ```bash git add frontend/src/api/settings.ts git commit -m "feat(settings): add settings API client" ``` --- ## Task 6: 前端 - LoginView 注册功能 **Files:** - Modify: `frontend/src/views/LoginView.vue` - [ ] **Step 1: 添加注册 Tab 和表单** 在 script setup 中添加: ```typescript const isLogin = ref(true) const registerEmail = ref('') const registerPassword = ref('') const registerConfirmPassword = ref('') const registerName = ref('') const isRegistering = ref(false) const registerError = ref('') // 密码强度计算 function getPasswordStrength(pwd: string): { level: 'weak' | 'medium' | 'strong', text: string } { if (pwd.length < 8) return { level: 'weak', text: '太短' } let score = 0 if (pwd.length >= 8) score++ if (pwd.length >= 12) score++ if (/[a-z]/.test(pwd) && /[A-Z]/.test(pwd)) score++ if (/\d/.test(pwd)) score++ if (/[^a-zA-Z0-9]/.test(pwd)) score++ if (score <= 2) return { level: 'weak', text: '弱' } if (score <= 3) return { level: 'medium', text: '中' } return { level: 'strong', text: '强' } } const passwordStrength = computed(() => getPasswordStrength(registerPassword.value)) async function handleRegister() { if (registerPassword.value !== registerConfirmPassword.value) { registerError.value = '两次密码输入不一致' return } if (registerPassword.value.length < 8) { registerError.value = '密码至少需要8个字符' return } try { registerError.value = '' isRegistering.value = true await authApi.register({ email: registerEmail.value, password: registerPassword.value, full_name: registerName.value }) // 注册成功后自动登录 await auth.login(registerEmail.value, registerPassword.value) router.push('/chat') } catch (e: unknown) { registerError.value = (e as { response?: { data?: { detail?: string } } })?.response?.data?.detail || '注册失败' } finally { isRegistering.value = false } } ``` 在 template 中添加注册表单(与登录表单并列,用 v-if 切换) - [ ] **Step 2: 提交** ```bash git add frontend/src/views/LoginView.vue git commit -m "feat(auth): add registration form to LoginView" ``` --- ## Task 7: 前端 - SettingsView 页面 **Files:** - Create: `frontend/src/views/SettingsView.vue` - [ ] **Step 1: 创建设置页面** 页面结构: ```vue ``` 样式部分复用 AgentView 的 sci-fi 风格,保持一致。 - [ ] **Step 2: 提交** ```bash git add frontend/src/views/SettingsView.vue git commit -m "feat(settings): add SettingsView page with profile, LLM and scheduler config" ``` --- ## Task 8: 前端 - 路由和侧边栏 **Files:** - Modify: `frontend/src/router/index.ts` - Modify: `frontend/src/components/SidebarNav.vue` - [ ] **Step 1: 添加 /settings 路由** 在 children 数组中添加: ```typescript { path: 'settings', name: 'settings', component: () => import('@/views/SettingsView.vue'), } ``` - [ ] **Step 2: 添加设置菜单项** 在 navItems 中添加: ```typescript { name: '设置', path: '/settings', icon: Settings }, ``` 导入 Settings 图标: ```typescript import { Settings } from 'lucide-vue-next' ``` - [ ] **Step 3: 提交** ```bash git add frontend/src/router/index.ts frontend/src/components/SidebarNav.vue git commit -m "feat(settings): add /settings route and sidebar menu" ``` --- ## Task 9: 数据库迁移 - [ ] **Step 1: 创建迁移 SQL** 由于使用 SQLAlchemy 的 `init_db()` 会在启动时自动创建表,但现有数据库不会自动添加新字段。需要: 1. 直接在数据库上执行 ALTER TABLE: ```sql ALTER TABLE users ADD COLUMN llm_config TEXT; ALTER TABLE users ADD COLUMN scheduler_config TEXT; ``` 2. 或通过 Python 脚本: ```python import asyncio from app.database import engine async def migrate(): async with engine.begin() as conn: await conn.execute(text('ALTER TABLE users ADD COLUMN llm_config TEXT')) await conn.execute(text('ALTER TABLE users ADD COLUMN scheduler_config TEXT')) print('Migration complete') asyncio.run(migrate()) ``` - [ ] **Step 2: 提交迁移脚本** ```bash git add docs/superpowers/plans/2026-03-20-settings-migration.md git commit -m "feat(settings): add database migration for user settings fields" ``` --- ## 验证清单 完成所有 Task 后,验证以下内容: 1. **注册功能** - 可以通过注册页面创建新账号 2. **登录功能** - 新老用户都可以正常登录 3. **设置页面** - 可以访问 /settings 页面 4. **资料修改** - 用户名、密码可以修改 5. **LLM 配置** - 四种模型配置可以保存 6. **LLM 测试** - 测试连接功能正常 7. **定时任务** - 时间间隔可以修改 8. **配置持久化** - 重新登录后配置保留 9. **UI 风格** - 设置页面风格与其他页面一致 --- ## 实现顺序建议 1. Task 1 → 2 → 3 → 4(后端核心) 2. Task 5(前端 API) 3. Task 6(LoginView 注册功能) 4. Task 7(SettingsView) 5. Task 8(路由和侧边栏) 6. Task 9(数据库迁移)