2025-03-17 19:58:00 +08:00
|
|
|
|
import traceback
|
2025-03-25 05:40:07 +08:00
|
|
|
|
import uuid
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-12-28 22:55:42 +08:00
|
|
|
|
from fastapi import APIRouter, Body, Depends, HTTPException, Query, UploadFile, File
|
2025-03-25 05:40:07 +08:00
|
|
|
|
from fastapi.responses import StreamingResponse
|
2025-05-15 22:34:32 +08:00
|
|
|
|
from pydantic import BaseModel
|
2025-12-04 10:11:28 +08:00
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
2025-03-25 05:40:07 +08:00
|
|
|
|
|
2026-01-22 00:28:43 +08:00
|
|
|
|
from src.storage.postgres.models_business import User
|
2025-09-01 22:37:03 +08:00
|
|
|
|
from server.routers.auth_router import get_admin_user
|
|
|
|
|
|
from server.utils.auth_middleware import get_db, get_required_user
|
2025-09-18 20:32:33 +08:00
|
|
|
|
from src import config as conf
|
2025-03-25 05:40:07 +08:00
|
|
|
|
from src.agents import agent_manager
|
2025-03-16 01:11:13 +08:00
|
|
|
|
from src.models import select_model
|
2026-01-22 00:28:43 +08:00
|
|
|
|
from src.services.chat_stream_service import get_agent_state_view, stream_agent_chat, stream_agent_resume
|
|
|
|
|
|
from src.services.conversation_service import (
|
|
|
|
|
|
create_thread_view,
|
|
|
|
|
|
delete_thread_attachment_view,
|
|
|
|
|
|
delete_thread_view,
|
|
|
|
|
|
list_thread_attachments_view,
|
|
|
|
|
|
list_threads_view,
|
|
|
|
|
|
update_thread_view,
|
|
|
|
|
|
upload_thread_attachment_view,
|
2025-11-08 10:51:30 +08:00
|
|
|
|
)
|
2026-01-22 00:28:43 +08:00
|
|
|
|
from src.services.feedback_service import get_message_feedback_view, submit_message_feedback_view
|
|
|
|
|
|
from src.services.history_query_service import get_agent_history_view
|
2026-01-22 05:57:13 +08:00
|
|
|
|
from src.repositories.agent_config_repository import AgentConfigRepository
|
2025-02-27 19:35:25 +08:00
|
|
|
|
from src.utils.logging_config import logger
|
2025-11-12 14:04:34 +08:00
|
|
|
|
from src.utils.image_processor import process_uploaded_image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 图片上传响应模型
|
|
|
|
|
|
class ImageUploadResponse(BaseModel):
|
|
|
|
|
|
success: bool
|
|
|
|
|
|
image_content: str | None = None
|
|
|
|
|
|
thumbnail_content: str | None = None
|
|
|
|
|
|
width: int | None = None
|
|
|
|
|
|
height: int | None = None
|
|
|
|
|
|
format: str | None = None
|
|
|
|
|
|
mime_type: str | None = None
|
|
|
|
|
|
size_bytes: int | None = None
|
|
|
|
|
|
error: str | None = None
|
|
|
|
|
|
|
2024-10-02 20:11:28 +08:00
|
|
|
|
|
2026-01-22 05:57:13 +08:00
|
|
|
|
class AgentConfigCreate(BaseModel):
|
|
|
|
|
|
name: str
|
|
|
|
|
|
description: str | None = None
|
|
|
|
|
|
icon: str | None = None
|
|
|
|
|
|
pics: list[str] | None = None
|
|
|
|
|
|
examples: list[str] | None = None
|
|
|
|
|
|
config_json: dict | None = None
|
|
|
|
|
|
set_default: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AgentConfigUpdate(BaseModel):
|
|
|
|
|
|
name: str | None = None
|
|
|
|
|
|
description: str | None = None
|
|
|
|
|
|
icon: str | None = None
|
|
|
|
|
|
pics: list[str] | None = None
|
|
|
|
|
|
examples: list[str] | None = None
|
|
|
|
|
|
config_json: dict | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
|
chat = APIRouter(prefix="/chat", tags=["chat"])
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# > === 智能体管理分组 ===
|
|
|
|
|
|
# =============================================================================
|
2025-03-06 23:55:20 +08:00
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-05-02 23:56:59 +08:00
|
|
|
|
@chat.get("/default_agent")
|
|
|
|
|
|
async def get_default_agent(current_user: User = Depends(get_required_user)):
|
|
|
|
|
|
"""获取默认智能体ID(需要登录)"""
|
|
|
|
|
|
try:
|
2025-09-18 20:32:33 +08:00
|
|
|
|
default_agent_id = conf.default_agent_id
|
2025-05-02 23:56:59 +08:00
|
|
|
|
# 如果没有设置默认智能体,尝试获取第一个可用的智能体
|
|
|
|
|
|
if not default_agent_id:
|
2025-05-20 22:21:51 +08:00
|
|
|
|
agents = await agent_manager.get_agents_info()
|
2025-05-02 23:56:59 +08:00
|
|
|
|
if agents:
|
2025-07-22 17:30:00 +08:00
|
|
|
|
default_agent_id = agents[0].get("id", "")
|
2025-05-02 23:56:59 +08:00
|
|
|
|
|
|
|
|
|
|
return {"default_agent_id": default_agent_id}
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"获取默认智能体出错: {e}")
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"获取默认智能体出错: {str(e)}")
|
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-05-02 23:56:59 +08:00
|
|
|
|
@chat.post("/set_default_agent")
|
2025-09-01 22:37:03 +08:00
|
|
|
|
async def set_default_agent(request_data: dict = Body(...), current_user=Depends(get_admin_user)):
|
2025-05-02 23:56:59 +08:00
|
|
|
|
"""设置默认智能体ID (仅管理员)"""
|
|
|
|
|
|
try:
|
2025-07-24 00:45:47 +08:00
|
|
|
|
agent_id = request_data.get("agent_id")
|
|
|
|
|
|
if not agent_id:
|
|
|
|
|
|
raise HTTPException(status_code=422, detail="缺少必需的 agent_id 字段")
|
|
|
|
|
|
|
2025-05-02 23:56:59 +08:00
|
|
|
|
# 验证智能体是否存在
|
2025-05-20 22:21:51 +08:00
|
|
|
|
agents = await agent_manager.get_agents_info()
|
2025-07-22 17:30:00 +08:00
|
|
|
|
agent_ids = [agent.get("id", "") for agent in agents]
|
2025-05-02 23:56:59 +08:00
|
|
|
|
|
|
|
|
|
|
if agent_id not in agent_ids:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# 设置默认智能体ID
|
2025-09-18 20:32:33 +08:00
|
|
|
|
conf.default_agent_id = agent_id
|
2025-05-02 23:56:59 +08:00
|
|
|
|
# 保存配置
|
2025-09-18 20:32:33 +08:00
|
|
|
|
conf.save()
|
2025-05-02 23:56:59 +08:00
|
|
|
|
|
|
|
|
|
|
return {"success": True, "default_agent_id": agent_id}
|
|
|
|
|
|
except HTTPException as he:
|
|
|
|
|
|
raise he
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"设置默认智能体出错: {e}")
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"设置默认智能体出错: {str(e)}")
|
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2024-10-02 20:11:28 +08:00
|
|
|
|
@chat.post("/call")
|
2025-05-02 23:56:59 +08:00
|
|
|
|
async def call(query: str = Body(...), meta: dict = Body(None), current_user: User = Depends(get_required_user)):
|
|
|
|
|
|
"""调用模型进行简单问答(需要登录)"""
|
2025-04-10 11:41:01 +08:00
|
|
|
|
meta = meta or {}
|
2025-11-05 10:15:17 +08:00
|
|
|
|
|
|
|
|
|
|
# 确保 request_id 存在
|
|
|
|
|
|
if "request_id" not in meta or not meta.get("request_id"):
|
|
|
|
|
|
meta["request_id"] = str(uuid.uuid4())
|
|
|
|
|
|
|
2025-10-14 02:27:15 +08:00
|
|
|
|
model = select_model(
|
|
|
|
|
|
model_provider=meta.get("model_provider"),
|
|
|
|
|
|
model_name=meta.get("model_name"),
|
|
|
|
|
|
model_spec=meta.get("model_spec") or meta.get("model"),
|
|
|
|
|
|
)
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2026-01-28 11:30:55 +08:00
|
|
|
|
response = await model.call(query)
|
2024-10-02 20:11:28 +08:00
|
|
|
|
logger.debug({"query": query, "response": response.content})
|
|
|
|
|
|
|
2025-11-05 10:15:17 +08:00
|
|
|
|
return {"response": response.content, "request_id": meta["request_id"]}
|
2024-10-14 22:10:56 +08:00
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-03-25 05:40:07 +08:00
|
|
|
|
@chat.get("/agent")
|
2025-05-02 23:56:59 +08:00
|
|
|
|
async def get_agent(current_user: User = Depends(get_required_user)):
|
2025-11-17 20:18:46 +08:00
|
|
|
|
"""获取所有可用智能体的基本信息(需要登录)"""
|
2025-11-07 12:28:58 +08:00
|
|
|
|
agents_info = await agent_manager.get_agents_info()
|
|
|
|
|
|
|
2025-11-17 20:18:46 +08:00
|
|
|
|
# Return agents with basic information (without configurable_items for performance)
|
2025-11-07 12:28:58 +08:00
|
|
|
|
agents = [
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": agent_info["id"],
|
|
|
|
|
|
"name": agent_info.get("name", "Unknown"),
|
|
|
|
|
|
"description": agent_info.get("description", ""),
|
|
|
|
|
|
"examples": agent_info.get("examples", []),
|
2025-11-08 10:51:30 +08:00
|
|
|
|
"has_checkpointer": agent_info.get("has_checkpointer", False),
|
2025-11-12 11:00:39 +08:00
|
|
|
|
"capabilities": agent_info.get("capabilities", []), # 智能体能力列表
|
2025-11-07 12:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
for agent_info in agents_info
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
return {"agents": agents}
|
2025-03-25 05:40:07 +08:00
|
|
|
|
|
2025-10-24 00:11:52 +08:00
|
|
|
|
|
2025-11-17 20:18:46 +08:00
|
|
|
|
@chat.get("/agent/{agent_id}")
|
|
|
|
|
|
async def get_single_agent(agent_id: str, current_user: User = Depends(get_required_user)):
|
|
|
|
|
|
"""获取指定智能体的完整信息(包含配置选项)(需要登录)"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 检查智能体是否存在
|
|
|
|
|
|
if not (agent := agent_manager.get_agent(agent_id)):
|
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# 获取智能体的完整信息(包含 configurable_items)
|
|
|
|
|
|
agent_info = await agent.get_info()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": agent_info["id"],
|
|
|
|
|
|
"name": agent_info.get("name", "Unknown"),
|
|
|
|
|
|
"description": agent_info.get("description", ""),
|
|
|
|
|
|
"examples": agent_info.get("examples", []),
|
|
|
|
|
|
"configurable_items": agent_info.get("configurable_items", []),
|
|
|
|
|
|
"has_checkpointer": agent_info.get("has_checkpointer", False),
|
|
|
|
|
|
"capabilities": agent_info.get("capabilities", []),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
except HTTPException:
|
|
|
|
|
|
raise
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"获取智能体 {agent_id} 信息出错: {e}")
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"获取智能体信息出错: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-22 05:57:13 +08:00
|
|
|
|
@chat.get("/agent/{agent_id}/configs")
|
|
|
|
|
|
async def list_agent_configs(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
):
|
|
|
|
|
|
if not current_user.department_id:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
|
|
|
|
|
|
|
|
|
|
|
if not agent_manager.get_agent(agent_id):
|
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
|
|
|
|
|
|
|
|
|
|
|
repo = AgentConfigRepository(db)
|
|
|
|
|
|
items = await repo.list_by_department_agent(department_id=current_user.department_id, agent_id=agent_id)
|
|
|
|
|
|
if not items:
|
|
|
|
|
|
await repo.get_or_create_default(
|
|
|
|
|
|
department_id=current_user.department_id,
|
|
|
|
|
|
agent_id=agent_id,
|
|
|
|
|
|
created_by=str(current_user.id),
|
|
|
|
|
|
)
|
|
|
|
|
|
items = await repo.list_by_department_agent(department_id=current_user.department_id, agent_id=agent_id)
|
|
|
|
|
|
|
|
|
|
|
|
configs = [
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": item.id,
|
|
|
|
|
|
"name": item.name,
|
|
|
|
|
|
"description": item.description,
|
|
|
|
|
|
"icon": item.icon,
|
|
|
|
|
|
"pics": item.pics or [],
|
|
|
|
|
|
"examples": item.examples or [],
|
|
|
|
|
|
"is_default": bool(item.is_default),
|
|
|
|
|
|
}
|
|
|
|
|
|
for item in items
|
|
|
|
|
|
]
|
|
|
|
|
|
return {"configs": configs}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.get("/agent/{agent_id}/configs/{config_id}")
|
|
|
|
|
|
async def get_agent_config_profile(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
config_id: int,
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
):
|
|
|
|
|
|
if not current_user.department_id:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
|
|
|
|
|
|
|
|
|
|
|
if not agent_manager.get_agent(agent_id):
|
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
|
|
|
|
|
|
|
|
|
|
|
repo = AgentConfigRepository(db)
|
|
|
|
|
|
item = await repo.get_by_id(config_id)
|
|
|
|
|
|
if not item or item.agent_id != agent_id or item.department_id != current_user.department_id:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="配置不存在")
|
|
|
|
|
|
|
|
|
|
|
|
return {"config": item.to_dict()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.post("/agent/{agent_id}/configs")
|
|
|
|
|
|
async def create_agent_config_profile(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
payload: AgentConfigCreate,
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
):
|
|
|
|
|
|
if not current_user.department_id:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
|
|
|
|
|
|
|
|
|
|
|
if not agent_manager.get_agent(agent_id):
|
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
|
|
|
|
|
|
|
|
|
|
|
repo = AgentConfigRepository(db)
|
|
|
|
|
|
item = await repo.create(
|
|
|
|
|
|
department_id=current_user.department_id,
|
|
|
|
|
|
agent_id=agent_id,
|
|
|
|
|
|
name=payload.name,
|
|
|
|
|
|
description=payload.description,
|
|
|
|
|
|
icon=payload.icon,
|
|
|
|
|
|
pics=payload.pics,
|
|
|
|
|
|
examples=payload.examples,
|
|
|
|
|
|
config_json=payload.config_json,
|
|
|
|
|
|
is_default=payload.set_default,
|
|
|
|
|
|
created_by=str(current_user.id),
|
|
|
|
|
|
)
|
|
|
|
|
|
if payload.set_default:
|
|
|
|
|
|
item = await repo.set_default(config=item, updated_by=str(current_user.id))
|
|
|
|
|
|
|
|
|
|
|
|
return {"config": item.to_dict()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.put("/agent/{agent_id}/configs/{config_id}")
|
|
|
|
|
|
async def update_agent_config_profile(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
config_id: int,
|
|
|
|
|
|
payload: AgentConfigUpdate,
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
):
|
|
|
|
|
|
if not current_user.department_id:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
|
|
|
|
|
|
|
|
|
|
|
if not agent_manager.get_agent(agent_id):
|
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
|
|
|
|
|
|
|
|
|
|
|
repo = AgentConfigRepository(db)
|
|
|
|
|
|
item = await repo.get_by_id(config_id)
|
|
|
|
|
|
if not item or item.agent_id != agent_id or item.department_id != current_user.department_id:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="配置不存在")
|
|
|
|
|
|
|
|
|
|
|
|
updated = await repo.update(
|
|
|
|
|
|
item,
|
|
|
|
|
|
name=payload.name,
|
|
|
|
|
|
description=payload.description,
|
|
|
|
|
|
icon=payload.icon,
|
|
|
|
|
|
pics=payload.pics,
|
|
|
|
|
|
examples=payload.examples,
|
|
|
|
|
|
config_json=payload.config_json,
|
|
|
|
|
|
updated_by=str(current_user.id),
|
|
|
|
|
|
)
|
|
|
|
|
|
return {"config": updated.to_dict()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.post("/agent/{agent_id}/configs/{config_id}/set_default")
|
|
|
|
|
|
async def set_agent_config_default(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
config_id: int,
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
):
|
|
|
|
|
|
if not current_user.department_id:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
|
|
|
|
|
|
|
|
|
|
|
if not agent_manager.get_agent(agent_id):
|
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
|
|
|
|
|
|
|
|
|
|
|
repo = AgentConfigRepository(db)
|
|
|
|
|
|
item = await repo.get_by_id(config_id)
|
|
|
|
|
|
if not item or item.agent_id != agent_id or item.department_id != current_user.department_id:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="配置不存在")
|
|
|
|
|
|
|
|
|
|
|
|
updated = await repo.set_default(config=item, updated_by=str(current_user.id))
|
|
|
|
|
|
return {"config": updated.to_dict()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.delete("/agent/{agent_id}/configs/{config_id}")
|
|
|
|
|
|
async def delete_agent_config_profile(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
config_id: int,
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
):
|
|
|
|
|
|
if not current_user.department_id:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
|
|
|
|
|
|
|
|
|
|
|
if not agent_manager.get_agent(agent_id):
|
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
|
|
|
|
|
|
|
|
|
|
|
repo = AgentConfigRepository(db)
|
|
|
|
|
|
item = await repo.get_by_id(config_id)
|
|
|
|
|
|
if not item or item.agent_id != agent_id or item.department_id != current_user.department_id:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="配置不存在")
|
|
|
|
|
|
|
|
|
|
|
|
await repo.delete(config=item, updated_by=str(current_user.id))
|
|
|
|
|
|
return {"success": True}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-07-22 17:30:00 +08:00
|
|
|
|
@chat.post("/agent/{agent_id}")
|
2025-09-01 22:37:03 +08:00
|
|
|
|
async def chat_agent(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
query: str = Body(...),
|
|
|
|
|
|
config: dict = Body({}),
|
|
|
|
|
|
meta: dict = Body({}),
|
2025-11-12 14:04:34 +08:00
|
|
|
|
image_content: str | None = Body(None),
|
2025-09-01 22:37:03 +08:00
|
|
|
|
current_user: User = Depends(get_required_user),
|
2025-12-04 10:11:28 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2025-09-01 22:37:03 +08:00
|
|
|
|
):
|
2025-05-02 23:56:59 +08:00
|
|
|
|
"""使用特定智能体进行对话(需要登录)"""
|
2025-08-31 00:34:26 +08:00
|
|
|
|
logger.info(f"agent_id: {agent_id}, query: {query}, config: {config}, meta: {meta}")
|
2025-11-12 14:04:34 +08:00
|
|
|
|
logger.info(f"image_content present: {image_content is not None}")
|
|
|
|
|
|
if image_content:
|
|
|
|
|
|
logger.info(f"image_content length: {len(image_content)}")
|
|
|
|
|
|
logger.info(f"image_content preview: {image_content[:50]}...")
|
2025-08-31 00:34:26 +08:00
|
|
|
|
|
2025-11-05 10:15:17 +08:00
|
|
|
|
# 确保 request_id 存在
|
|
|
|
|
|
if "request_id" not in meta or not meta.get("request_id"):
|
|
|
|
|
|
meta["request_id"] = str(uuid.uuid4())
|
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
meta.update(
|
|
|
|
|
|
{
|
|
|
|
|
|
"query": query,
|
|
|
|
|
|
"agent_id": agent_id,
|
|
|
|
|
|
"server_model_name": config.get("model", agent_id),
|
|
|
|
|
|
"thread_id": config.get("thread_id"),
|
|
|
|
|
|
"user_id": current_user.id,
|
2025-11-12 14:04:34 +08:00
|
|
|
|
"has_image": bool(image_content),
|
2025-09-01 22:37:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
)
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return StreamingResponse(
|
|
|
|
|
|
stream_agent_chat(
|
|
|
|
|
|
agent_id=agent_id,
|
|
|
|
|
|
query=query,
|
|
|
|
|
|
config=config,
|
|
|
|
|
|
meta=meta,
|
|
|
|
|
|
image_content=image_content,
|
|
|
|
|
|
current_user=current_user,
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
),
|
|
|
|
|
|
media_type="application/json",
|
|
|
|
|
|
)
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-04-01 00:39:54 +08:00
|
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# > === 模型管理分组 ===
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-04-01 00:39:54 +08:00
|
|
|
|
@chat.get("/models")
|
2025-05-02 23:56:59 +08:00
|
|
|
|
async def get_chat_models(model_provider: str, current_user: User = Depends(get_admin_user)):
|
|
|
|
|
|
"""获取指定模型提供商的模型列表(需要登录)"""
|
2025-04-01 00:39:54 +08:00
|
|
|
|
model = select_model(model_provider=model_provider)
|
2026-01-28 11:30:55 +08:00
|
|
|
|
models = await model.get_models()
|
|
|
|
|
|
return {"models": models}
|
2025-04-05 17:27:52 +08:00
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-04-06 20:33:15 +08:00
|
|
|
|
@chat.post("/models/update")
|
2025-09-01 22:37:03 +08:00
|
|
|
|
async def update_chat_models(model_provider: str, model_names: list[str], current_user=Depends(get_admin_user)):
|
2025-05-02 23:56:59 +08:00
|
|
|
|
"""更新指定模型提供商的模型列表 (仅管理员)"""
|
2025-10-22 11:51:32 +08:00
|
|
|
|
conf.model_names[model_provider].models = model_names
|
|
|
|
|
|
conf._save_models_to_file(model_provider)
|
|
|
|
|
|
return {"models": conf.model_names[model_provider].models}
|
2025-04-06 20:33:15 +08:00
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-11-01 21:34:16 +08:00
|
|
|
|
@chat.post("/agent/{agent_id}/resume")
|
|
|
|
|
|
async def resume_agent_chat(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
thread_id: str = Body(...),
|
|
|
|
|
|
approved: bool = Body(...),
|
2026-01-22 05:57:13 +08:00
|
|
|
|
config: dict = Body({}),
|
2025-11-01 21:34:16 +08:00
|
|
|
|
current_user: User = Depends(get_required_user),
|
2025-12-04 10:11:28 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2025-11-01 21:34:16 +08:00
|
|
|
|
):
|
|
|
|
|
|
"""恢复被人工审批中断的对话(需要登录)"""
|
|
|
|
|
|
logger.info(f"Resuming agent_id: {agent_id}, thread_id: {thread_id}, approved: {approved}")
|
|
|
|
|
|
|
|
|
|
|
|
meta = {
|
|
|
|
|
|
"agent_id": agent_id,
|
|
|
|
|
|
"thread_id": thread_id,
|
|
|
|
|
|
"user_id": current_user.id,
|
|
|
|
|
|
"approved": approved,
|
|
|
|
|
|
}
|
|
|
|
|
|
if "request_id" not in meta or not meta.get("request_id"):
|
|
|
|
|
|
meta["request_id"] = str(uuid.uuid4())
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return StreamingResponse(
|
|
|
|
|
|
stream_agent_resume(
|
|
|
|
|
|
agent_id=agent_id,
|
|
|
|
|
|
thread_id=thread_id,
|
|
|
|
|
|
approved=approved,
|
|
|
|
|
|
meta=meta,
|
2026-01-22 05:57:13 +08:00
|
|
|
|
config=config,
|
2026-01-22 00:28:43 +08:00
|
|
|
|
current_user=current_user,
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
),
|
|
|
|
|
|
media_type="application/json",
|
|
|
|
|
|
)
|
2025-11-01 21:34:16 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-07-22 17:30:00 +08:00
|
|
|
|
@chat.post("/agent/{agent_id}/config")
|
2025-12-28 22:55:42 +08:00
|
|
|
|
async def save_agent_config(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
config: dict = Body(...),
|
2025-12-30 19:19:49 +08:00
|
|
|
|
reload_graph: bool = Query(True),
|
2025-12-28 22:55:42 +08:00
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
):
|
2025-08-31 10:27:58 +08:00
|
|
|
|
"""保存智能体配置到YAML文件(需要登录)"""
|
2025-05-13 19:49:54 +08:00
|
|
|
|
try:
|
|
|
|
|
|
# 获取Agent实例和配置类
|
2025-08-31 00:34:26 +08:00
|
|
|
|
if not (agent := agent_manager.get_agent(agent_id)):
|
2025-07-22 17:30:00 +08:00
|
|
|
|
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
2025-05-13 19:49:54 +08:00
|
|
|
|
|
2026-01-20 02:14:51 +08:00
|
|
|
|
# === 校验知识库权限 ===
|
|
|
|
|
|
from src import knowledge_base
|
|
|
|
|
|
|
|
|
|
|
|
if "knowledges" in config and config["knowledges"]:
|
2026-01-22 02:16:08 +08:00
|
|
|
|
# 获取用户有权访问的知识库名称
|
2026-01-20 02:14:51 +08:00
|
|
|
|
try:
|
|
|
|
|
|
user_info = {"role": current_user.role, "department_id": current_user.department_id}
|
2026-01-21 15:15:52 +08:00
|
|
|
|
accessible_databases = await knowledge_base.get_databases_by_user(user_info)
|
2026-01-22 02:16:08 +08:00
|
|
|
|
accessible_kb_names = {
|
|
|
|
|
|
db.get("name") for db in accessible_databases.get("databases", []) if db.get("name")
|
2026-01-21 15:15:52 +08:00
|
|
|
|
}
|
2026-01-20 02:14:51 +08:00
|
|
|
|
except Exception as db_error:
|
|
|
|
|
|
logger.warning(f"获取知识库列表失败: {db_error}")
|
|
|
|
|
|
# 如果获取失败,superadmin 可以访问所有,非 superadmin 无法访问任何
|
|
|
|
|
|
if current_user.role != "superadmin":
|
|
|
|
|
|
raise HTTPException(status_code=500, detail="无法获取知识库列表")
|
2026-01-22 02:16:08 +08:00
|
|
|
|
# 回退:获取所有数据库名称
|
2026-01-21 15:15:52 +08:00
|
|
|
|
from src.repositories.knowledge_base_repository import KnowledgeBaseRepository
|
|
|
|
|
|
|
|
|
|
|
|
kb_repo = KnowledgeBaseRepository()
|
|
|
|
|
|
rows = await kb_repo.get_all()
|
2026-01-22 02:16:08 +08:00
|
|
|
|
accessible_kb_names = {row.name for row in rows if row.name}
|
2026-01-20 02:14:51 +08:00
|
|
|
|
|
|
|
|
|
|
# 检查配置中的知识库是否都可用
|
2026-01-22 02:16:08 +08:00
|
|
|
|
invalid_kbs = [kb for kb in config["knowledges"] if kb not in accessible_kb_names]
|
2026-01-20 02:14:51 +08:00
|
|
|
|
if invalid_kbs:
|
2026-01-21 15:15:52 +08:00
|
|
|
|
raise HTTPException(status_code=403, detail=f"无权访问以下知识库: {', '.join(invalid_kbs)}")
|
2026-01-20 02:14:51 +08:00
|
|
|
|
# === 校验结束 ===
|
|
|
|
|
|
|
2025-05-13 19:49:54 +08:00
|
|
|
|
# 使用配置类的save_to_file方法保存配置
|
2025-08-31 00:34:26 +08:00
|
|
|
|
result = agent.context_schema.save_to_file(config, agent.module_name)
|
2025-05-13 19:49:54 +08:00
|
|
|
|
|
|
|
|
|
|
if result:
|
2025-12-28 22:55:42 +08:00
|
|
|
|
if reload_graph:
|
|
|
|
|
|
agent_manager.get_agent(agent_id, reload_graph=True)
|
2025-07-22 17:30:00 +08:00
|
|
|
|
return {"success": True, "message": f"智能体 {agent.name} 配置已保存"}
|
2025-05-13 19:49:54 +08:00
|
|
|
|
else:
|
2025-05-24 11:29:45 +08:00
|
|
|
|
raise HTTPException(status_code=500, detail="保存智能体配置失败")
|
2025-05-13 19:49:54 +08:00
|
|
|
|
|
2026-01-20 02:14:51 +08:00
|
|
|
|
except HTTPException:
|
|
|
|
|
|
raise
|
2025-05-13 19:49:54 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"保存智能体配置出错: {e}, {traceback.format_exc()}")
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"保存智能体配置出错: {str(e)}")
|
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-07-22 17:30:00 +08:00
|
|
|
|
@chat.get("/agent/{agent_id}/history")
|
2025-10-04 22:21:30 +08:00
|
|
|
|
async def get_agent_history(
|
2025-12-04 10:11:28 +08:00
|
|
|
|
agent_id: str, thread_id: str, current_user: User = Depends(get_required_user), db: AsyncSession = Depends(get_db)
|
2025-10-04 22:21:30 +08:00
|
|
|
|
):
|
2025-12-24 11:41:03 +08:00
|
|
|
|
"""获取智能体历史消息(需要登录)- 包含用户反馈状态"""
|
2025-05-15 22:34:32 +08:00
|
|
|
|
try:
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return await get_agent_history_view(
|
|
|
|
|
|
agent_id=agent_id,
|
|
|
|
|
|
thread_id=thread_id,
|
|
|
|
|
|
current_user_id=str(current_user.id),
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
)
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"获取智能体历史消息出错: {e}, {traceback.format_exc()}")
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"获取智能体历史消息出错: {str(e)}")
|
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-11-17 12:01:59 +08:00
|
|
|
|
@chat.get("/agent/{agent_id}/state")
|
|
|
|
|
|
async def get_agent_state(
|
|
|
|
|
|
agent_id: str,
|
|
|
|
|
|
thread_id: str,
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
2025-12-04 10:11:28 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2025-11-17 12:01:59 +08:00
|
|
|
|
):
|
2026-01-16 14:06:42 +08:00
|
|
|
|
"""获取智能体当前状态(需要登录)"""
|
2025-11-17 12:01:59 +08:00
|
|
|
|
try:
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return await get_agent_state_view(
|
|
|
|
|
|
agent_id=agent_id,
|
|
|
|
|
|
thread_id=thread_id,
|
|
|
|
|
|
current_user_id=str(current_user.id),
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
)
|
2025-11-17 12:01:59 +08:00
|
|
|
|
except HTTPException:
|
|
|
|
|
|
raise
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"获取AgentState出错: {e}, {traceback.format_exc()}")
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"获取AgentState出错: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-07-22 17:30:00 +08:00
|
|
|
|
@chat.get("/agent/{agent_id}/config")
|
2025-09-01 22:37:03 +08:00
|
|
|
|
async def get_agent_config(agent_id: str, current_user: User = Depends(get_required_user)):
|
2025-05-15 23:45:14 +08:00
|
|
|
|
"""从YAML文件加载智能体配置(需要登录)"""
|
2025-05-13 19:49:54 +08:00
|
|
|
|
try:
|
|
|
|
|
|
# 检查智能体是否存在
|
2025-07-22 17:30:00 +08:00
|
|
|
|
if not (agent := agent_manager.get_agent(agent_id)):
|
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
2025-05-13 19:49:54 +08:00
|
|
|
|
|
2025-08-31 00:34:26 +08:00
|
|
|
|
config = await agent.get_config()
|
|
|
|
|
|
logger.debug(f"config: {config}, ContextClass: {agent.context_schema=}")
|
2025-05-13 19:49:54 +08:00
|
|
|
|
return {"success": True, "config": config}
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"加载智能体配置出错: {e}, {traceback.format_exc()}")
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"加载智能体配置出错: {str(e)}")
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-05-15 22:34:32 +08:00
|
|
|
|
# ==================== 线程管理 API ====================
|
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-05-15 22:34:32 +08:00
|
|
|
|
class ThreadCreate(BaseModel):
|
2025-05-24 11:29:45 +08:00
|
|
|
|
title: str | None = None
|
2025-05-15 22:34:32 +08:00
|
|
|
|
agent_id: str
|
2025-05-24 11:29:45 +08:00
|
|
|
|
metadata: dict | None = None
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ThreadResponse(BaseModel):
|
|
|
|
|
|
id: str
|
|
|
|
|
|
user_id: str
|
|
|
|
|
|
agent_id: str
|
2025-05-24 11:29:45 +08:00
|
|
|
|
title: str | None = None
|
2025-10-04 22:59:25 +08:00
|
|
|
|
created_at: str
|
|
|
|
|
|
updated_at: str
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-11-08 10:51:30 +08:00
|
|
|
|
class AttachmentResponse(BaseModel):
|
|
|
|
|
|
file_id: str
|
|
|
|
|
|
file_name: str
|
|
|
|
|
|
file_type: str | None = None
|
|
|
|
|
|
file_size: int
|
|
|
|
|
|
status: str
|
|
|
|
|
|
uploaded_at: str
|
|
|
|
|
|
truncated: bool | None = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AttachmentLimits(BaseModel):
|
|
|
|
|
|
allowed_extensions: list[str]
|
|
|
|
|
|
max_size_bytes: int
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AttachmentListResponse(BaseModel):
|
|
|
|
|
|
attachments: list[AttachmentResponse]
|
|
|
|
|
|
limits: AttachmentLimits
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# > === 会话管理分组 ===
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-05-15 22:34:32 +08:00
|
|
|
|
@chat.post("/thread", response_model=ThreadResponse)
|
|
|
|
|
|
async def create_thread(
|
2025-12-04 10:11:28 +08:00
|
|
|
|
thread: ThreadCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_required_user)
|
2025-05-15 22:34:32 +08:00
|
|
|
|
):
|
2025-10-04 22:21:30 +08:00
|
|
|
|
"""创建新对话线程 (使用新存储系统)"""
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return await create_thread_view(
|
2025-05-15 22:34:32 +08:00
|
|
|
|
agent_id=thread.agent_id,
|
2026-01-22 00:28:43 +08:00
|
|
|
|
title=thread.title,
|
2025-10-04 22:21:30 +08:00
|
|
|
|
metadata=thread.metadata,
|
2026-01-22 00:28:43 +08:00
|
|
|
|
db=db,
|
|
|
|
|
|
current_user_id=str(current_user.id),
|
2025-05-15 22:34:32 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-05-24 11:29:45 +08:00
|
|
|
|
@chat.get("/threads", response_model=list[ThreadResponse])
|
2025-12-04 10:12:01 +08:00
|
|
|
|
async def list_threads(
|
|
|
|
|
|
agent_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_required_user)
|
|
|
|
|
|
):
|
2025-10-04 22:21:30 +08:00
|
|
|
|
"""获取用户的所有对话线程 (使用新存储系统)"""
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return await list_threads_view(agent_id=agent_id, db=db, current_user_id=str(current_user.id))
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.delete("/thread/{thread_id}")
|
2025-12-04 10:12:01 +08:00
|
|
|
|
async def delete_thread(
|
|
|
|
|
|
thread_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_required_user)
|
|
|
|
|
|
):
|
2025-10-04 22:21:30 +08:00
|
|
|
|
"""删除对话线程 (使用新存储系统)"""
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return await delete_thread_view(thread_id=thread_id, db=db, current_user_id=str(current_user.id))
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ThreadUpdate(BaseModel):
|
2025-05-24 11:29:45 +08:00
|
|
|
|
title: str | None = None
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.put("/thread/{thread_id}", response_model=ThreadResponse)
|
|
|
|
|
|
async def update_thread(
|
|
|
|
|
|
thread_id: str,
|
|
|
|
|
|
thread_update: ThreadUpdate,
|
2025-12-04 10:11:28 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2025-09-01 22:37:03 +08:00
|
|
|
|
current_user: User = Depends(get_required_user),
|
2025-05-15 22:34:32 +08:00
|
|
|
|
):
|
2025-10-04 22:21:30 +08:00
|
|
|
|
"""更新对话线程信息 (使用新存储系统)"""
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return await update_thread_view(
|
2025-10-04 22:21:30 +08:00
|
|
|
|
thread_id=thread_id,
|
|
|
|
|
|
title=thread_update.title,
|
2026-01-22 00:28:43 +08:00
|
|
|
|
db=db,
|
|
|
|
|
|
current_user_id=str(current_user.id),
|
2025-10-04 22:21:30 +08:00
|
|
|
|
)
|
2025-05-15 22:34:32 +08:00
|
|
|
|
|
2025-10-05 14:50:34 +08:00
|
|
|
|
|
2025-11-08 10:51:30 +08:00
|
|
|
|
@chat.post("/thread/{thread_id}/attachments", response_model=AttachmentResponse)
|
|
|
|
|
|
async def upload_thread_attachment(
|
|
|
|
|
|
thread_id: str,
|
|
|
|
|
|
file: UploadFile = File(...),
|
2025-12-04 10:11:28 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2025-11-08 10:51:30 +08:00
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""上传并解析附件为 Markdown,附加到指定对话线程。"""
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return await upload_thread_attachment_view(
|
|
|
|
|
|
thread_id=thread_id,
|
|
|
|
|
|
file=file,
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
current_user_id=str(current_user.id),
|
|
|
|
|
|
)
|
2025-11-08 10:51:30 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.get("/thread/{thread_id}/attachments", response_model=AttachmentListResponse)
|
|
|
|
|
|
async def list_thread_attachments(
|
|
|
|
|
|
thread_id: str,
|
2025-12-04 10:11:28 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2025-11-08 10:51:30 +08:00
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""列出当前对话线程的所有附件元信息。"""
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return await list_thread_attachments_view(
|
|
|
|
|
|
thread_id=thread_id,
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
current_user_id=str(current_user.id),
|
|
|
|
|
|
)
|
2025-11-08 10:51:30 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.delete("/thread/{thread_id}/attachments/{file_id}")
|
|
|
|
|
|
async def delete_thread_attachment(
|
|
|
|
|
|
thread_id: str,
|
|
|
|
|
|
file_id: str,
|
2025-12-04 10:11:28 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2025-11-08 10:51:30 +08:00
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""移除指定附件。"""
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return await delete_thread_attachment_view(
|
|
|
|
|
|
thread_id=thread_id,
|
|
|
|
|
|
file_id=file_id,
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
current_user_id=str(current_user.id),
|
|
|
|
|
|
)
|
2025-11-08 10:51:30 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-10-05 14:50:34 +08:00
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# > === 消息反馈分组 ===
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MessageFeedbackRequest(BaseModel):
|
|
|
|
|
|
rating: str # 'like' or 'dislike'
|
|
|
|
|
|
reason: str | None = None # Optional reason for dislike
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MessageFeedbackResponse(BaseModel):
|
|
|
|
|
|
id: int
|
|
|
|
|
|
message_id: int
|
|
|
|
|
|
rating: str
|
|
|
|
|
|
reason: str | None
|
|
|
|
|
|
created_at: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.post("/message/{message_id}/feedback", response_model=MessageFeedbackResponse)
|
|
|
|
|
|
async def submit_message_feedback(
|
|
|
|
|
|
message_id: int,
|
|
|
|
|
|
feedback_data: MessageFeedbackRequest,
|
2025-12-04 10:11:28 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2025-10-05 14:50:34 +08:00
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
):
|
2026-01-16 14:06:42 +08:00
|
|
|
|
"""提交消息反馈(需要登录)"""
|
2026-01-22 00:28:43 +08:00
|
|
|
|
result = await submit_message_feedback_view(
|
|
|
|
|
|
message_id=message_id,
|
|
|
|
|
|
rating=feedback_data.rating,
|
|
|
|
|
|
reason=feedback_data.reason,
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
current_user_id=str(current_user.id),
|
|
|
|
|
|
)
|
|
|
|
|
|
return MessageFeedbackResponse(**result)
|
2025-10-05 14:50:34 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.get("/message/{message_id}/feedback")
|
|
|
|
|
|
async def get_message_feedback(
|
|
|
|
|
|
message_id: int,
|
2025-12-04 10:11:28 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2025-10-05 14:50:34 +08:00
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
):
|
2026-01-16 14:06:42 +08:00
|
|
|
|
"""获取指定消息的用户反馈(需要登录)"""
|
2026-01-22 00:28:43 +08:00
|
|
|
|
return await get_message_feedback_view(
|
|
|
|
|
|
message_id=message_id,
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
current_user_id=str(current_user.id),
|
|
|
|
|
|
)
|
2025-11-12 14:04:34 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
# > === 多模态图片支持分组 ===
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@chat.post("/image/upload", response_model=ImageUploadResponse)
|
|
|
|
|
|
async def upload_image(file: UploadFile = File(...), current_user: User = Depends(get_required_user)):
|
|
|
|
|
|
"""
|
|
|
|
|
|
上传并处理图片,返回base64编码的图片数据
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 验证文件类型
|
|
|
|
|
|
if not file.content_type or not file.content_type.startswith("image/"):
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="只支持图片文件上传")
|
|
|
|
|
|
|
|
|
|
|
|
# 读取文件内容
|
|
|
|
|
|
image_data = await file.read()
|
|
|
|
|
|
|
|
|
|
|
|
# 检查文件大小(10MB限制,超过后会压缩到5MB)
|
|
|
|
|
|
if len(image_data) > 10 * 1024 * 1024:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="图片文件过大,请上传小于10MB的图片")
|
|
|
|
|
|
|
|
|
|
|
|
# 处理图片
|
|
|
|
|
|
result = process_uploaded_image(image_data, file.filename)
|
|
|
|
|
|
|
|
|
|
|
|
if not result["success"]:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail=f"图片处理失败: {result['error']}")
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"用户 {current_user.id} 成功上传图片: {file.filename}, "
|
|
|
|
|
|
f"尺寸: {result['width']}x{result['height']}, "
|
|
|
|
|
|
f"格式: {result['format']}, "
|
|
|
|
|
|
f"大小: {result['size_bytes']} bytes"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return ImageUploadResponse(**result)
|
|
|
|
|
|
|
|
|
|
|
|
except HTTPException:
|
|
|
|
|
|
raise
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"图片上传处理失败: {str(e)}, {traceback.format_exc()}")
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"图片处理失败: {str(e)}")
|