mirror of
https://github.com/JefferyHcool/BiliNote.git
synced 2026-05-11 18:10:06 +08:00
后端: - /chat/index 改为 BackgroundTasks 异步执行,立即返回 - /chat/status 返回细粒度状态(idle/indexing/indexed/failed) - 内存追踪索引进度,避免重复触发 前端: - ChatPanel 每 2 秒轮询索引状态,索引完成后自动停止 - 索引中显示"正在索引笔记内容..."及首次下载模型提示 - 索引失败显示重试按钮 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
102 lines
3.1 KiB
Python
102 lines
3.1 KiB
Python
from fastapi import APIRouter, BackgroundTasks
|
||
from pydantic import BaseModel
|
||
|
||
from app.services.chat_service import chat as chat_service
|
||
from app.services.vector_store import VectorStoreManager
|
||
from app.utils.logger import get_logger
|
||
from app.utils.response import ResponseWrapper as R
|
||
|
||
logger = get_logger(__name__)
|
||
|
||
router = APIRouter()
|
||
|
||
# 索引状态追踪: task_id -> "indexing" | "indexed" | "failed"
|
||
_index_status: dict[str, str] = {}
|
||
|
||
|
||
class IndexRequest(BaseModel):
|
||
task_id: str
|
||
|
||
|
||
class ChatMessage(BaseModel):
|
||
role: str
|
||
content: str
|
||
|
||
|
||
class AskRequest(BaseModel):
|
||
task_id: str
|
||
question: str
|
||
history: list[ChatMessage] = []
|
||
provider_id: str
|
||
model_name: str
|
||
|
||
|
||
def _do_index(task_id: str):
|
||
"""后台执行索引任务。"""
|
||
try:
|
||
_index_status[task_id] = "indexing"
|
||
store = VectorStoreManager()
|
||
store.index_task(task_id)
|
||
_index_status[task_id] = "indexed"
|
||
logger.info(f"索引完成: {task_id}")
|
||
except Exception as e:
|
||
_index_status[task_id] = "failed"
|
||
logger.error(f"索引失败: {task_id}, {e}")
|
||
|
||
|
||
@router.post("/chat/index")
|
||
def index_task(data: IndexRequest, background_tasks: BackgroundTasks):
|
||
"""触发后台索引,立即返回。"""
|
||
if _index_status.get(data.task_id) == "indexing":
|
||
return R.success(msg="正在索引中")
|
||
|
||
# 如果已经索引过,直接返回
|
||
store = VectorStoreManager()
|
||
if store.is_indexed(data.task_id):
|
||
_index_status[data.task_id] = "indexed"
|
||
return R.success(msg="已完成索引")
|
||
|
||
_index_status[data.task_id] = "indexing"
|
||
background_tasks.add_task(_do_index, data.task_id)
|
||
return R.success(msg="开始索引")
|
||
|
||
|
||
@router.get("/chat/status")
|
||
def chat_status(task_id: str):
|
||
"""返回索引状态:idle / indexing / indexed / failed。"""
|
||
try:
|
||
# 优先检查内存状态
|
||
status = _index_status.get(task_id)
|
||
if status:
|
||
return R.success(data={"status": status, "indexed": status == "indexed"})
|
||
|
||
# 内存没有记录,检查持久化
|
||
store = VectorStoreManager()
|
||
indexed = store.is_indexed(task_id)
|
||
if indexed:
|
||
_index_status[task_id] = "indexed"
|
||
return R.success(data={"status": "indexed" if indexed else "idle", "indexed": indexed})
|
||
except Exception as e:
|
||
logger.error(f"查询索引状态失败: {e}")
|
||
return R.success(data={"status": "idle", "indexed": False})
|
||
|
||
|
||
@router.post("/chat/ask")
|
||
def ask_question(data: AskRequest):
|
||
"""基于笔记内容的 RAG 问答。"""
|
||
try:
|
||
history = [{"role": m.role, "content": m.content} for m in data.history]
|
||
result = chat_service(
|
||
task_id=data.task_id,
|
||
question=data.question,
|
||
history=history,
|
||
provider_id=data.provider_id,
|
||
model_name=data.model_name,
|
||
)
|
||
return R.success(data=result)
|
||
except ValueError as e:
|
||
return R.error(msg=str(e))
|
||
except Exception as e:
|
||
logger.error(f"Chat 问答失败: {e}", exc_info=True)
|
||
return R.error(msg=f"问答失败: {str(e)}")
|