新加ai 对话页面
This commit is contained in:
@@ -11,7 +11,6 @@ class ChatRequest(BaseModel):
|
|||||||
prompt: str
|
prompt: str
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/")
|
@router.post("/")
|
||||||
def chat_api(data: ChatRequest, user=Depends(get_current_user)):
|
def chat_api(data: ChatRequest, user=Depends(get_current_user)):
|
||||||
# return {"msg": "pong"}
|
# return {"msg": "pong"}
|
||||||
|
|||||||
18
chat/crud/ai_api_key.py
Normal file
18
chat/crud/ai_api_key.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from fastapi import HTTPException
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from crud.base import CRUDBase
|
||||||
|
from models.ai import AIApiKey # SQLAlchemy模型
|
||||||
|
from schemas.ai_api_key import AIApiKeyCreate, AIApiKeyUpdate
|
||||||
|
|
||||||
|
# 继承通用CRUD基类,指定模型和Pydantic类型
|
||||||
|
class CRUDApiKey(CRUDBase[AIApiKey, AIApiKeyCreate, AIApiKeyUpdate]):
|
||||||
|
# 如有特殊逻辑,可重写父类方法(如创建时验证平台唯一性)
|
||||||
|
def create(self, db: Session, *, obj_in: AIApiKeyCreate):
|
||||||
|
# 示例:验证平台+名称唯一
|
||||||
|
if self.get_by(db, platform=obj_in.platform, name=obj_in.name):
|
||||||
|
raise HTTPException(status_code=400, detail="该平台下名称已存在")
|
||||||
|
return super().create(db, obj_in=obj_in)
|
||||||
|
|
||||||
|
# 创建CRUD实例
|
||||||
|
ai_api_key_crud = CRUDApiKey(AIApiKey)
|
||||||
90
chat/crud/base.py
Normal file
90
chat/crud/base.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
from typing import Generic, TypeVar, List, Optional, Dict, Any
|
||||||
|
from fastapi import HTTPException
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# 定义泛型变量(分别对应:SQLAlchemy模型、创建Pydantic模型、更新Pydantic模型)
|
||||||
|
ModelType = TypeVar("ModelType")
|
||||||
|
CreateSchemaType = TypeVar("CreateSchemaType")
|
||||||
|
UpdateSchemaType = TypeVar("UpdateSchemaType")
|
||||||
|
|
||||||
|
|
||||||
|
class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
||||||
|
def __init__(self, model: ModelType):
|
||||||
|
"""
|
||||||
|
初始化CRUD类,需要传入SQLAlchemy模型
|
||||||
|
:param model: SQLAlchemy模型类(如AIApiKey、AIModel等)
|
||||||
|
"""
|
||||||
|
self.model = model
|
||||||
|
|
||||||
|
# 创建
|
||||||
|
def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType:
|
||||||
|
"""创建一条记录"""
|
||||||
|
obj_in_data = obj_in.model_dump() # 解构Pydantic模型为字典
|
||||||
|
|
||||||
|
# 自动填充时间字段(如果模型有created_at/updated_at)
|
||||||
|
if hasattr(self.model, "created_at"):
|
||||||
|
obj_in_data["created_at"] = datetime.now()
|
||||||
|
if hasattr(self.model, "updated_at"):
|
||||||
|
obj_in_data["updated_at"] = datetime.now()
|
||||||
|
|
||||||
|
db_obj = self.model(**obj_in_data) # 实例化模型
|
||||||
|
db.add(db_obj)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_obj)
|
||||||
|
return db_obj
|
||||||
|
|
||||||
|
# 按ID查询
|
||||||
|
def get(self, db: Session, id: int) -> Optional[ModelType]:
|
||||||
|
"""按ID查询单条记录"""
|
||||||
|
return db.query(self.model).filter(self.model.id == id).first()
|
||||||
|
|
||||||
|
# 按条件查询单条记录
|
||||||
|
def get_by(self, db: Session, **kwargs) -> Optional[ModelType]:
|
||||||
|
"""按条件查询单条记录(如get_by(name="test"))"""
|
||||||
|
return db.query(self.model).filter_by(**kwargs).first()
|
||||||
|
|
||||||
|
# 分页查询所有
|
||||||
|
def get_multi(
|
||||||
|
self, db: Session, *, page: int = 0, limit: int = 100
|
||||||
|
) -> List[ModelType]:
|
||||||
|
"""分页查询多条记录"""
|
||||||
|
return db.query(self.model).offset(page).limit(limit).all()
|
||||||
|
|
||||||
|
# 更新
|
||||||
|
def update(
|
||||||
|
self,
|
||||||
|
db: Session,
|
||||||
|
*,
|
||||||
|
db_obj: ModelType,
|
||||||
|
obj_in: UpdateSchemaType | Dict[str, Any]
|
||||||
|
) -> ModelType:
|
||||||
|
"""更新记录(支持Pydantic模型或字典)"""
|
||||||
|
if isinstance(obj_in, dict):
|
||||||
|
update_data = obj_in
|
||||||
|
else:
|
||||||
|
update_data = obj_in.model_dump(exclude_unset=True) # 只更新提供的字段
|
||||||
|
|
||||||
|
# 遍历更新字段
|
||||||
|
for field in update_data:
|
||||||
|
if hasattr(db_obj, field):
|
||||||
|
setattr(db_obj, field, update_data[field])
|
||||||
|
|
||||||
|
# 自动更新updated_at(如果模型有该字段)
|
||||||
|
if hasattr(db_obj, "updated_at"):
|
||||||
|
db_obj.updated_at = datetime.now()
|
||||||
|
|
||||||
|
db.add(db_obj)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_obj)
|
||||||
|
return db_obj
|
||||||
|
|
||||||
|
# 删除
|
||||||
|
def remove(self, db: Session, *, id: int) -> ModelType:
|
||||||
|
"""删除记录"""
|
||||||
|
obj = db.query(self.model).get(id)
|
||||||
|
if not obj:
|
||||||
|
raise HTTPException(status_code=404, detail=f"{self.model.__name__}不存在")
|
||||||
|
db.delete(obj)
|
||||||
|
db.commit()
|
||||||
|
return obj
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from api.v1 import ai_chat
|
from api.v1 import ai_chat
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from routers.ai_api_key import router as ai_api_key_router
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ app.add_middleware(
|
|||||||
|
|
||||||
# 注册路由
|
# 注册路由
|
||||||
app.include_router(ai_chat.router, prefix="/chat/api/v1", tags=["chat"])
|
app.include_router(ai_chat.router, prefix="/chat/api/v1", tags=["chat"])
|
||||||
|
app.include_router(ai_api_key_router, tags=["chat"])
|
||||||
|
|
||||||
# 健康检查
|
# 健康检查
|
||||||
@app.get("/ping")
|
@app.get("/ping")
|
||||||
|
|||||||
243
chat/models/ai.py
Normal file
243
chat/models/ai.py
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
from sqlalchemy import (
|
||||||
|
Column, Integer, String, Text, DateTime, Boolean, Float, ForeignKey
|
||||||
|
)
|
||||||
|
from sqlalchemy.orm import relationship, declarative_base
|
||||||
|
from models.user import DjangoUser # 确保导入 DjangoUser
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
# 状态选择类(示例)
|
||||||
|
class CommonStatus:
|
||||||
|
DISABLED = 0
|
||||||
|
ENABLED = 1
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def choices():
|
||||||
|
return [(0, '禁用'), (1, '启用')]
|
||||||
|
|
||||||
|
|
||||||
|
# 平台选择类(示例)
|
||||||
|
class PlatformChoices:
|
||||||
|
OPENAI = 'openai'
|
||||||
|
ALIMNS = 'alimns'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def choices():
|
||||||
|
return [('openai', 'OpenAI'), ('alimns', '阿里云MNS')]
|
||||||
|
|
||||||
|
|
||||||
|
# 消息类型选择类(示例)
|
||||||
|
class MessageType:
|
||||||
|
TEXT = 'text'
|
||||||
|
IMAGE = 'image'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def choices():
|
||||||
|
return [('text', '文本'), ('image', '图片')]
|
||||||
|
|
||||||
|
|
||||||
|
# 基础模型类
|
||||||
|
class CoreModel(Base):
|
||||||
|
__abstract__ = True
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
create_time = Column(DateTime)
|
||||||
|
update_time = Column(DateTime)
|
||||||
|
is_deleted = Column(Boolean, default=False)
|
||||||
|
|
||||||
|
|
||||||
|
# AI API 密钥表
|
||||||
|
class AIApiKey(CoreModel):
|
||||||
|
__tablename__ = 'ai_api_key'
|
||||||
|
|
||||||
|
name = Column(String(255), nullable=False)
|
||||||
|
platform = Column(String(100), nullable=False)
|
||||||
|
api_key = Column(String(255), nullable=False)
|
||||||
|
url = Column(String(255), nullable=True)
|
||||||
|
status = Column(Integer, default=CommonStatus.DISABLED)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
# AI 模型表
|
||||||
|
class AIModel(CoreModel):
|
||||||
|
__tablename__ = 'ai_model'
|
||||||
|
|
||||||
|
name = Column(String(64), nullable=False)
|
||||||
|
sort = Column(Integer, default=0)
|
||||||
|
status = Column(Integer, default=CommonStatus.DISABLED)
|
||||||
|
key_id = Column(Integer, ForeignKey('ai_api_key.id'), nullable=False)
|
||||||
|
model_type = Column(String(32), nullable=True)
|
||||||
|
platform = Column(String(32), nullable=False)
|
||||||
|
model = Column(String(64), nullable=False)
|
||||||
|
temperature = Column(Float, nullable=True)
|
||||||
|
max_tokens = Column(Integer, nullable=True)
|
||||||
|
max_contexts = Column(Integer, nullable=True)
|
||||||
|
|
||||||
|
key = relationship('AIApiKey', backref='models')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
# AI 工具表
|
||||||
|
# class Tool(CoreModel):
|
||||||
|
# __tablename__ = 'ai_tool'
|
||||||
|
|
||||||
|
# name = Column(String(128), nullable=False)
|
||||||
|
# description = Column(String(256), nullable=True)
|
||||||
|
# status = Column(Integer, default=0)
|
||||||
|
|
||||||
|
# def __str__(self):
|
||||||
|
# return self.name
|
||||||
|
|
||||||
|
|
||||||
|
# AI 知识库表
|
||||||
|
# class Knowledge(CoreModel):
|
||||||
|
# __tablename__ = 'ai_knowledge'
|
||||||
|
|
||||||
|
# name = Column(String(255), nullable=False)
|
||||||
|
# description = Column(Text, nullable=True)
|
||||||
|
# embedding_model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False)
|
||||||
|
# embedding_model = Column(String(32), nullable=False)
|
||||||
|
# top_k = Column(Integer, default=0)
|
||||||
|
# similarity_threshold = Column(Float, nullable=False)
|
||||||
|
# status = Column(Integer, default=CommonStatus.DISABLED)
|
||||||
|
|
||||||
|
# embedding_model_rel = relationship('AIModel', backref='knowledges')
|
||||||
|
# documents = relationship('KnowledgeDocument', backref='knowledge', cascade='all, delete-orphan')
|
||||||
|
# segments = relationship('KnowledgeSegment', backref='knowledge', cascade='all, delete-orphan')
|
||||||
|
# roles = relationship('ChatRole', secondary='ai_chat_role_knowledge', backref='knowledges')
|
||||||
|
|
||||||
|
# def __str__(self):
|
||||||
|
# return self.name
|
||||||
|
|
||||||
|
|
||||||
|
# AI 知识库文档表
|
||||||
|
# class KnowledgeDocument(CoreModel):
|
||||||
|
# __tablename__ = 'ai_knowledge_document'
|
||||||
|
|
||||||
|
# knowledge_id = Column(Integer, ForeignKey('ai_knowledge.id'), nullable=False)
|
||||||
|
# name = Column(String(255), nullable=False)
|
||||||
|
# url = Column(String(1024), nullable=False)
|
||||||
|
# content = Column(Text, nullable=False)
|
||||||
|
# content_length = Column(Integer, nullable=False)
|
||||||
|
# tokens = Column(Integer, nullable=False)
|
||||||
|
# segment_max_tokens = Column(Integer, nullable=False)
|
||||||
|
# retrieval_count = Column(Integer, default=0)
|
||||||
|
# status = Column(Integer, default=CommonStatus.DISABLED)
|
||||||
|
|
||||||
|
# segments = relationship('KnowledgeSegment', backref='document', cascade='all, delete-orphan')
|
||||||
|
|
||||||
|
# def __str__(self):
|
||||||
|
# return self.name
|
||||||
|
|
||||||
|
|
||||||
|
# AI 知识库分段表
|
||||||
|
# class KnowledgeSegment(CoreModel):
|
||||||
|
# __tablename__ = 'ai_knowledge_segment'
|
||||||
|
|
||||||
|
# knowledge_id = Column(Integer, ForeignKey('ai_knowledge.id'), nullable=False)
|
||||||
|
# document_id = Column(Integer, ForeignKey('ai_knowledge_document.id'), nullable=False)
|
||||||
|
# content = Column(Text, nullable=False)
|
||||||
|
# content_length = Column(Integer, nullable=False)
|
||||||
|
# tokens = Column(Integer, nullable=False)
|
||||||
|
# vector_id = Column(String(100), nullable=True)
|
||||||
|
# retrieval_count = Column(Integer, default=0)
|
||||||
|
# status = Column(Integer, default=CommonStatus.DISABLED)
|
||||||
|
|
||||||
|
# def __str__(self):
|
||||||
|
# return f"Segment {self.id}"
|
||||||
|
|
||||||
|
|
||||||
|
# AI 聊天角色表
|
||||||
|
# class ChatRole(CoreModel):
|
||||||
|
# __tablename__ = 'ai_chat_role'
|
||||||
|
|
||||||
|
# name = Column(String(128), nullable=False)
|
||||||
|
# avatar = Column(String(256), nullable=False)
|
||||||
|
# description = Column(String(256), nullable=True)
|
||||||
|
# status = Column(Integer, default=CommonStatus.DISABLED)
|
||||||
|
# sort = Column(Integer, default=0)
|
||||||
|
# public_status = Column(Boolean, default=False)
|
||||||
|
# category = Column(String(32), nullable=True)
|
||||||
|
# model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False)
|
||||||
|
# system_message = Column(String(1024), nullable=True)
|
||||||
|
# user_id = Column(
|
||||||
|
# Integer,
|
||||||
|
# ForeignKey('system_users.id'), # 假设DjangoUser表名是system_users
|
||||||
|
# nullable=True # 允许为空(如匿名角色)
|
||||||
|
# )
|
||||||
|
# user = relationship(DjangoUser, backref='chat_roles') # 正确:DjangoUser 已定义并导入
|
||||||
|
|
||||||
|
# model = relationship('AIModel', backref='chat_roles')
|
||||||
|
# tools = relationship('Tool', secondary='ai_chat_role_tool', backref='roles')
|
||||||
|
# # conversations = relationship('ChatConversation', backref='role', cascade='all, delete-orphan')
|
||||||
|
# # messages = relationship('ChatMessage', backref='role', cascade='all, delete-orphan')
|
||||||
|
|
||||||
|
# def __str__(self):
|
||||||
|
# return self.name
|
||||||
|
|
||||||
|
|
||||||
|
# AI 聊天对话表
|
||||||
|
# class ChatConversation(CoreModel):
|
||||||
|
# __tablename__ = 'ai_chat_conversation'
|
||||||
|
|
||||||
|
# title = Column(String(256), nullable=False)
|
||||||
|
# pinned = Column(Boolean, default=False)
|
||||||
|
# pinned_time = Column(DateTime, nullable=True)
|
||||||
|
# # user_id = Column(Integer, ForeignKey('system_users.id'), nullable=True)
|
||||||
|
# role_id = Column(Integer, ForeignKey('ai_chat_role.id'), nullable=True)
|
||||||
|
# model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False)
|
||||||
|
# model = Column(String(32), nullable=False)
|
||||||
|
# system_message = Column(String(1024), nullable=True)
|
||||||
|
# temperature = Column(Float, nullable=False)
|
||||||
|
# max_tokens = Column(Integer, nullable=False)
|
||||||
|
# max_contexts = Column(Integer, nullable=False)
|
||||||
|
# # user = relationship(DjangoUser, backref='conversations') # 正确:DjangoUser 已定义并导入
|
||||||
|
|
||||||
|
# model_rel = relationship('AIModel', backref='conversations')
|
||||||
|
# messages = relationship('ChatMessage', backref='conversation', cascade='all, delete-orphan')
|
||||||
|
|
||||||
|
# def __str__(self):
|
||||||
|
# return self.title
|
||||||
|
|
||||||
|
|
||||||
|
# AI 聊天消息表
|
||||||
|
# class ChatMessage(CoreModel):
|
||||||
|
# __tablename__ = 'ai_chat_message'
|
||||||
|
|
||||||
|
# conversation_id = Column(Integer, nullable=False)
|
||||||
|
# # user_id = Column(Integer, ForeignKey('system_users.id'), nullable=True)
|
||||||
|
# role_id = Column(Integer, ForeignKey('ai_chat_role.id'), nullable=True)
|
||||||
|
# model = Column(String(32), nullable=False)
|
||||||
|
# model_id = Column(Integer, ForeignKey('ai_model.id'), nullable=False)
|
||||||
|
# type = Column(String(16), nullable=False)
|
||||||
|
# reply_id = Column(Integer, nullable=True)
|
||||||
|
# content = Column(String(2048), nullable=False)
|
||||||
|
# use_context = Column(Boolean, default=False)
|
||||||
|
# segment_ids = Column(String(2048), nullable=True)
|
||||||
|
|
||||||
|
# # user = relationship(DjangoUser, backref='messages') # 正确:DjangoUser 已定义并导入
|
||||||
|
# model_rel = relationship('AIModel', backref='messages')
|
||||||
|
|
||||||
|
# def __str__(self):
|
||||||
|
# return self.content[:30]
|
||||||
|
|
||||||
|
|
||||||
|
# # 聊天角色与知识库的关联表
|
||||||
|
# class ChatRoleKnowledge(Base):
|
||||||
|
# __tablename__ = 'ai_chat_role_knowledge'
|
||||||
|
|
||||||
|
# chat_role_id = Column(Integer, ForeignKey('ai_chat_role.id'), primary_key=True)
|
||||||
|
# knowledge_id = Column(Integer, ForeignKey('ai_knowledge.id'), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
# # 聊天角色与工具的关联表
|
||||||
|
# class ChatRoleTool(Base):
|
||||||
|
# __tablename__ = 'ai_chat_role_tool'
|
||||||
|
|
||||||
|
# chat_role_id = Column(Integer, ForeignKey('ai_chat_role.id'), primary_key=True)
|
||||||
|
# tool_id = Column(Integer, ForeignKey('ai_tool.id'), primary_key=True)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from sqlalchemy import Column, Integer, String, DateTime
|
from sqlalchemy import Column, Integer, String, DateTime, Boolean
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
@@ -9,8 +9,15 @@ class AuthToken(Base):
|
|||||||
user_id = Column(Integer, nullable=False)
|
user_id = Column(Integer, nullable=False)
|
||||||
created = Column(DateTime)
|
created = Column(DateTime)
|
||||||
|
|
||||||
|
|
||||||
class DjangoUser(Base):
|
class DjangoUser(Base):
|
||||||
__tablename__ = 'system_users'
|
__tablename__ = 'system_users'
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
username = Column(String(150), nullable=False)
|
username = Column(String(150), nullable=False)
|
||||||
email = Column(String(254))
|
email = Column(String(254))
|
||||||
|
password = Column(String(128))
|
||||||
|
is_active = Column(Boolean, default=True)
|
||||||
|
is_staff = Column(Boolean, default=False)
|
||||||
|
is_superuser = Column(Boolean, default=False)
|
||||||
|
last_login = Column(DateTime)
|
||||||
|
date_joined = Column(DateTime)
|
||||||
13
chat/routers/ai_api_key.py
Normal file
13
chat/routers/ai_api_key.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from schemas.ai_api_key import AIApiKeyCreate, AIApiKeyUpdate, AIApiKeyRead
|
||||||
|
from crud.ai_api_key import ai_api_key_crud
|
||||||
|
from routers.base import GenericRouter
|
||||||
|
|
||||||
|
# 继承通用路由基类,传入参数即可生成所有CRUD接口
|
||||||
|
router = GenericRouter(
|
||||||
|
crud=ai_api_key_crud,
|
||||||
|
create_schema=AIApiKeyCreate,
|
||||||
|
update_schema=AIApiKeyUpdate,
|
||||||
|
read_schema=AIApiKeyRead,
|
||||||
|
prefix="/chat/api/ai-api-keys",
|
||||||
|
tags=["AI API密钥"]
|
||||||
|
)
|
||||||
101
chat/routers/base.py
Normal file
101
chat/routers/base.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import Generic, TypeVar, List
|
||||||
|
|
||||||
|
from db.session import get_db
|
||||||
|
from schemas.base import ReadSchemaType # 通用的响应模型基类
|
||||||
|
from crud.base import CRUDBase
|
||||||
|
|
||||||
|
# 泛型变量(对应:CRUD类、创建模型、更新模型、响应模型)
|
||||||
|
CRUDType = TypeVar("CRUDType")
|
||||||
|
CreateSchemaType = TypeVar("CreateSchemaType")
|
||||||
|
UpdateSchemaType = TypeVar("UpdateSchemaType")
|
||||||
|
ReadSchemaType = TypeVar("ReadSchemaType")
|
||||||
|
|
||||||
|
|
||||||
|
class GenericRouter(
|
||||||
|
APIRouter,
|
||||||
|
Generic[CRUDType, CreateSchemaType, UpdateSchemaType, ReadSchemaType]
|
||||||
|
):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
crud: CRUDType,
|
||||||
|
create_schema: CreateSchemaType,
|
||||||
|
update_schema: UpdateSchemaType,
|
||||||
|
read_schema: ReadSchemaType,
|
||||||
|
prefix: str,
|
||||||
|
tags: List[str],
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化通用路由
|
||||||
|
:param crud: CRUD实例(如CRUDApiKey)
|
||||||
|
:param create_schema: 创建Pydantic模型
|
||||||
|
:param update_schema: 更新Pydantic模型
|
||||||
|
:param read_schema: 响应Pydantic模型
|
||||||
|
:param prefix: 路由前缀(如"/api/ai-api-keys")
|
||||||
|
:param tags: 文档标签
|
||||||
|
"""
|
||||||
|
super().__init__(prefix=prefix, tags=tags,** kwargs)
|
||||||
|
self.crud = crud
|
||||||
|
self.create_schema = create_schema
|
||||||
|
self.update_schema = update_schema
|
||||||
|
self.read_schema = read_schema
|
||||||
|
|
||||||
|
# 注册通用路由
|
||||||
|
self.add_api_route(
|
||||||
|
"/",
|
||||||
|
self.create,
|
||||||
|
methods=["POST"],
|
||||||
|
response_model=read_schema,
|
||||||
|
status_code=201
|
||||||
|
)
|
||||||
|
self.add_api_route(
|
||||||
|
"/",
|
||||||
|
self.get_multi,
|
||||||
|
methods=["GET"],
|
||||||
|
response_model=List[read_schema]
|
||||||
|
)
|
||||||
|
self.add_api_route(
|
||||||
|
"/{id}/",
|
||||||
|
self.get,
|
||||||
|
methods=["GET"],
|
||||||
|
response_model=read_schema
|
||||||
|
)
|
||||||
|
self.add_api_route(
|
||||||
|
"/{id}/",
|
||||||
|
self.update,
|
||||||
|
methods=["PUT"],
|
||||||
|
response_model=read_schema
|
||||||
|
)
|
||||||
|
self.add_api_route(
|
||||||
|
"/{id}/",
|
||||||
|
self.remove,
|
||||||
|
methods=["DELETE"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建
|
||||||
|
def create(self, obj_in: CreateSchemaType, db: Session = Depends(get_db)):
|
||||||
|
return self.crud.create(db=db, obj_in=obj_in)
|
||||||
|
|
||||||
|
# 按ID查询
|
||||||
|
def get(self, id: int, db: Session = Depends(get_db)):
|
||||||
|
obj = self.crud.get(db=db, id=id)
|
||||||
|
if not obj:
|
||||||
|
raise HTTPException(status_code=404, detail=f"记录不存在")
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# 分页查询
|
||||||
|
def get_multi(self, page: int = 0, limit: int = 10, db: Session = Depends(get_db)):
|
||||||
|
return self.crud.get_multi(db=db, page=page, limit=limit)
|
||||||
|
|
||||||
|
# 更新
|
||||||
|
def update(self, id: int, obj_in: UpdateSchemaType, db: Session = Depends(get_db)):
|
||||||
|
obj = self.crud.get(db=db, id=id)
|
||||||
|
if not obj:
|
||||||
|
raise HTTPException(status_code=404, detail=f"记录不存在")
|
||||||
|
return self.crud.update(db=db, db_obj=obj, obj_in=obj_in)
|
||||||
|
|
||||||
|
# 删除
|
||||||
|
def remove(self, id: int, db: Session = Depends(get_db)):
|
||||||
|
return self.crud.remove(db=db, id=id)
|
||||||
33
chat/schemas/ai_api_key.py
Normal file
33
chat/schemas/ai_api_key.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# 基础模型(共享字段)
|
||||||
|
class AIApiKeyBase(BaseModel):
|
||||||
|
name: str = Field(..., max_length=255, description="密钥名称")
|
||||||
|
platform: str = Field(..., max_length=100, description="平台(如openai)")
|
||||||
|
api_key: str = Field(..., max_length=255, description="API密钥")
|
||||||
|
url: Optional[str] = Field(None, max_length=255, description="自定义API地址")
|
||||||
|
status: int = Field(0, description="状态(0=禁用,1=启用)")
|
||||||
|
|
||||||
|
# 创建请求模型(无需ID和时间字段)
|
||||||
|
class AIApiKeyCreate(AIApiKeyBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 更新请求模型(所有字段可选)
|
||||||
|
class AIApiKeyUpdate(BaseModel):
|
||||||
|
name: Optional[str] = Field(None, max_length=255)
|
||||||
|
platform: Optional[str] = Field(None, max_length=100)
|
||||||
|
api_key: Optional[str] = Field(None, max_length=255)
|
||||||
|
url: Optional[str] = Field(None, max_length=255)
|
||||||
|
status: Optional[int] = None
|
||||||
|
|
||||||
|
# 响应模型(包含数据库自动生成的字段)
|
||||||
|
class AIApiKeyRead(AIApiKeyBase):
|
||||||
|
id: int
|
||||||
|
created_at: Optional[datetime]
|
||||||
|
updated_at: Optional[datetime]
|
||||||
|
|
||||||
|
# 支持ORM模型直接转换为响应
|
||||||
|
class Config:
|
||||||
|
from_attributes = True # Pydantic v2用from_attributes,v1用orm_mode
|
||||||
19
chat/schemas/base.py
Normal file
19
chat/schemas/base.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class ReadSchemaType(BaseModel):
|
||||||
|
"""
|
||||||
|
所有响应模型的基类,包含公共字段和ORM转换配置
|
||||||
|
"""
|
||||||
|
id: int
|
||||||
|
created_at: Optional[datetime] = None # 数据创建时间(可选,部分模型可能没有)
|
||||||
|
updated_at: Optional[datetime] = None # 数据更新时间(可选)
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
"""
|
||||||
|
配置Pydantic模型如何处理ORM对象:
|
||||||
|
- from_attributes=True:支持直接从SQLAlchemy ORM模型转换(Pydantic v2)
|
||||||
|
- 若使用Pydantic v1,需替换为 orm_mode=True
|
||||||
|
"""
|
||||||
|
from_attributes = True
|
||||||
@@ -15,5 +15,9 @@
|
|||||||
"knowledge": {
|
"knowledge": {
|
||||||
"title": "KNOWLEDGE Management",
|
"title": "KNOWLEDGE Management",
|
||||||
"name": "KNOWLEDGE Management"
|
"name": "KNOWLEDGE Management"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"title": "AI CHAT",
|
||||||
|
"name": "AI CHAT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,5 +15,9 @@
|
|||||||
"knowledge": {
|
"knowledge": {
|
||||||
"title": "知识库管理",
|
"title": "知识库管理",
|
||||||
"name": "知识库管理"
|
"name": "知识库管理"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"title": "AI对话",
|
||||||
|
"name": "AI对话"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
web/apps/web-antd/src/views/ai/chat/index.vue
Normal file
11
web/apps/web-antd/src/views/ai/chat/index.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>dsads</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="css">
|
||||||
|
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user