新增前后端AI聊天模块,包括流式对话、配置管理和UI组件优化。

This commit is contained in:
2026-03-19 01:35:13 +08:00
commit ba6ceb94c8
66 changed files with 8631 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
from datetime import datetime, timezone
import httpx
from fastapi import APIRouter, Depends
from sqlmodel import Session, select
from ..database import get_session
from ..models import AIConfig
from ..schemas import AIConfigSave, AIConfigRead
router = APIRouter(prefix="/api/ai", tags=["ai-config"])
def _mask_key(key: str) -> str:
if len(key) <= 8:
return "****"
return key[:4] + "****" + key[-4:]
def _get_or_create_config(session: Session) -> AIConfig:
config = session.exec(select(AIConfig)).first()
if not config:
config = AIConfig()
session.add(config)
session.commit()
session.refresh(config)
return config
def _config_to_read(config: AIConfig) -> AIConfigRead:
return AIConfigRead(
id=config.id,
provider=config.provider,
base_url=config.base_url,
api_key_masked=_mask_key(config.api_key) if config.api_key else "",
model=config.model,
updated_at=config.updated_at,
)
@router.get("/config", response_model=AIConfigRead)
def get_config(session: Session = Depends(get_session)):
config = _get_or_create_config(session)
return _config_to_read(config)
@router.put("/config", response_model=AIConfigRead)
def save_config(data: AIConfigSave, session: Session = Depends(get_session)):
config = _get_or_create_config(session)
config.provider = data.provider
config.base_url = data.base_url
config.api_key = data.api_key
config.model = data.model
config.updated_at = datetime.now(timezone.utc)
session.add(config)
session.commit()
session.refresh(config)
return _config_to_read(config)
@router.post("/config/test")
async def test_config(data: AIConfigSave):
"""测试AI连接是否有效"""
try:
async with httpx.AsyncClient(timeout=15) as client:
if data.provider == "anthropic":
resp = await client.post(
f"{data.base_url.rstrip('/')}/messages",
headers={
"x-api-key": data.api_key,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
json={
"model": data.model,
"max_tokens": 10,
"messages": [{"role": "user", "content": "Hi"}],
},
)
else:
resp = await client.post(
f"{data.base_url.rstrip('/')}/chat/completions",
headers={
"Authorization": f"Bearer {data.api_key}",
"Content-Type": "application/json",
},
json={
"model": data.model,
"max_tokens": 10,
"messages": [{"role": "user", "content": "Hi"}],
},
)
if resp.status_code < 400:
return {"ok": True, "message": "连接成功"}
return {"ok": False, "message": f"API返回错误: {resp.status_code}"}
except httpx.ConnectError:
return {"ok": False, "message": "无法连接到API地址"}
except Exception as e:
return {"ok": False, "message": str(e)}