diff options
| author | YurenHao0426 <blackhao0426@gmail.com> | 2026-02-13 03:02:36 +0000 |
|---|---|---|
| committer | YurenHao0426 <blackhao0426@gmail.com> | 2026-02-13 03:02:36 +0000 |
| commit | 7d897ad9bb5ee46839ec91992cbbf4593168f119 (patch) | |
| tree | b4549f64176e93474b3b6c4b36294d30a46230b7 /backend/app/auth | |
| parent | 2f19d8cb84598e0822b525f5fb5c456c07448fb7 (diff) | |
Add Claude provider, OpenRouter fallback, and GFM markdown support
- Add Claude (Anthropic) as third LLM provider with streaming support
- Add OpenRouter as transparent fallback when official API keys are missing or fail
- Add remark-gfm to ReactMarkdown for table/strikethrough rendering
- Claude models: sonnet-4.5, opus-4, opus-4.5, opus-4.6
- Backend: new stream_claude(), stream_openrouter(), provider routing, API key CRUD
- Frontend: model selectors, API key inputs for Claude and OpenRouter
- Auto-migration for new DB columns (claude_api_key, openrouter_api_key)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'backend/app/auth')
| -rw-r--r-- | backend/app/auth/models.py | 12 | ||||
| -rw-r--r-- | backend/app/auth/routes.py | 14 |
2 files changed, 24 insertions, 2 deletions
diff --git a/backend/app/auth/models.py b/backend/app/auth/models.py index 8477ba2..14f5bb3 100644 --- a/backend/app/auth/models.py +++ b/backend/app/auth/models.py @@ -1,5 +1,5 @@ import os -from sqlalchemy import Column, Integer, String, DateTime, Text, create_engine +from sqlalchemy import Column, Integer, String, DateTime, Text, create_engine, text from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from datetime import datetime @@ -26,12 +26,22 @@ class User(Base): # API Keys (stored encrypted in production, plain for simplicity here) openai_api_key = Column(Text, nullable=True) gemini_api_key = Column(Text, nullable=True) + claude_api_key = Column(Text, nullable=True) + openrouter_api_key = Column(Text, nullable=True) def init_db(): """Initialize database tables""" os.makedirs(DATA_ROOT, exist_ok=True) Base.metadata.create_all(bind=engine) + # Migrate: add columns that may be missing in existing SQLite databases + with engine.connect() as conn: + for col in ("claude_api_key", "openrouter_api_key"): + try: + conn.execute(text(f"ALTER TABLE users ADD COLUMN {col} TEXT")) + conn.commit() + except Exception: + pass # Column already exists def get_db(): diff --git a/backend/app/auth/routes.py b/backend/app/auth/routes.py index 3c906b5..eaf897e 100644 --- a/backend/app/auth/routes.py +++ b/backend/app/auth/routes.py @@ -228,14 +228,20 @@ async def get_api_keys(current_user: User = Depends(get_current_user)): return { "openai_api_key": mask_key(current_user.openai_api_key), "gemini_api_key": mask_key(current_user.gemini_api_key), + "claude_api_key": mask_key(current_user.claude_api_key), + "openrouter_api_key": mask_key(current_user.openrouter_api_key), "has_openai_key": bool(current_user.openai_api_key), "has_gemini_key": bool(current_user.gemini_api_key), + "has_claude_key": bool(current_user.claude_api_key), + "has_openrouter_key": bool(current_user.openrouter_api_key), } class ApiKeysUpdate(BaseModel): openai_api_key: Optional[str] = None gemini_api_key: Optional[str] = None + claude_api_key: Optional[str] = None + openrouter_api_key: Optional[str] = None @router.post("/api-keys") @@ -253,7 +259,13 @@ async def update_api_keys( if keys.gemini_api_key is not None: current_user.gemini_api_key = keys.gemini_api_key if keys.gemini_api_key else None - + + if keys.claude_api_key is not None: + current_user.claude_api_key = keys.claude_api_key if keys.claude_api_key else None + + if keys.openrouter_api_key is not None: + current_user.openrouter_api_key = keys.openrouter_api_key if keys.openrouter_api_key else None + db.commit() return {"message": "API keys updated successfully"} |
