imetting/backend/app/api/endpoints/hot_words.py

288 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

from fastapi import APIRouter, Depends, HTTPException
from app.core.database import get_db_connection
from app.core.auth import get_current_admin_user
from app.core.response import create_api_response
from app.services.system_config_service import SystemConfigService
from pydantic import BaseModel
from typing import Optional, List
import dashscope
from dashscope.audio.asr import VocabularyService
from datetime import datetime
from http import HTTPStatus
router = APIRouter()
def _resolve_dashscope_api_key() -> str:
audio_config = SystemConfigService.get_active_audio_model_config("asr") or {}
api_key = str(audio_config.get("api_key") or "").strip()
if not api_key:
raise HTTPException(status_code=500, detail="未在启用的 ASR 模型配置中设置 DashScope API Key")
return api_key
# ── Request Models ──────────────────────────────────────────
class CreateGroupRequest(BaseModel):
name: str
description: Optional[str] = None
status: int = 1
class UpdateGroupRequest(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
status: Optional[int] = None
class CreateItemRequest(BaseModel):
text: str
weight: int = 4
lang: str = "zh"
status: int = 1
class UpdateItemRequest(BaseModel):
text: Optional[str] = None
weight: Optional[int] = None
lang: Optional[str] = None
status: Optional[int] = None
# ── Hot-Word Group CRUD ─────────────────────────────────────
@router.get("/admin/hot-word-groups", response_model=dict)
async def list_groups(current_user: dict = Depends(get_current_admin_user)):
"""列表(含每组热词数量统计)"""
try:
with get_db_connection() as conn:
cursor = conn.cursor(dictionary=True)
cursor.execute("""
SELECT g.*,
COUNT(i.id) AS item_count,
SUM(CASE WHEN i.status = 1 THEN 1 ELSE 0 END) AS enabled_item_count
FROM hot_word_group g
LEFT JOIN hot_word_item i ON i.group_id = g.id
GROUP BY g.id
ORDER BY g.update_time DESC
""")
groups = cursor.fetchall()
cursor.close()
return create_api_response(code="200", message="获取成功", data=groups)
except Exception as e:
return create_api_response(code="500", message=f"获取失败: {str(e)}")
@router.post("/admin/hot-word-groups", response_model=dict)
async def create_group(request: CreateGroupRequest, current_user: dict = Depends(get_current_admin_user)):
try:
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO hot_word_group (name, description, status) VALUES (%s, %s, %s)",
(request.name, request.description, request.status),
)
new_id = cursor.lastrowid
conn.commit()
cursor.close()
return create_api_response(code="200", message="创建成功", data={"id": new_id})
except Exception as e:
return create_api_response(code="500", message=f"创建失败: {str(e)}")
@router.put("/admin/hot-word-groups/{id}", response_model=dict)
async def update_group(id: int, request: UpdateGroupRequest, current_user: dict = Depends(get_current_admin_user)):
try:
with get_db_connection() as conn:
cursor = conn.cursor()
fields, params = [], []
if request.name is not None:
fields.append("name = %s"); params.append(request.name)
if request.description is not None:
fields.append("description = %s"); params.append(request.description)
if request.status is not None:
fields.append("status = %s"); params.append(request.status)
if not fields:
return create_api_response(code="400", message="无更新内容")
params.append(id)
cursor.execute(f"UPDATE hot_word_group SET {', '.join(fields)} WHERE id = %s", tuple(params))
conn.commit()
cursor.close()
return create_api_response(code="200", message="更新成功")
except Exception as e:
return create_api_response(code="500", message=f"更新失败: {str(e)}")
@router.delete("/admin/hot-word-groups/{id}", response_model=dict)
async def delete_group(id: int, current_user: dict = Depends(get_current_admin_user)):
"""删除组级联删除条目同时清除引用该词表ID的音频模型配置"""
try:
with get_db_connection() as conn:
cursor = conn.cursor(dictionary=True)
cursor.execute("SELECT vocabulary_id FROM hot_word_group WHERE id = %s", (id,))
group_row = cursor.fetchone()
vocabulary_id = group_row.get("vocabulary_id") if group_row else None
if vocabulary_id:
cursor.execute(
"""
UPDATE audio_model_config
SET extra_config = JSON_REMOVE(COALESCE(extra_config, JSON_OBJECT()), '$.vocabulary_id')
WHERE JSON_UNQUOTE(JSON_EXTRACT(extra_config, '$.vocabulary_id')) = %s
""",
(vocabulary_id,),
)
cursor.execute("DELETE FROM hot_word_item WHERE group_id = %s", (id,))
cursor.execute("DELETE FROM hot_word_group WHERE id = %s", (id,))
conn.commit()
cursor.close()
return create_api_response(code="200", message="删除成功")
except Exception as e:
return create_api_response(code="500", message=f"删除失败: {str(e)}")
@router.post("/admin/hot-word-groups/{id}/sync", response_model=dict)
async def sync_group(id: int, current_user: dict = Depends(get_current_admin_user)):
"""同步指定组到阿里云 DashScope"""
try:
dashscope.api_key = _resolve_dashscope_api_key()
with get_db_connection() as conn:
cursor = conn.cursor(dictionary=True)
# 获取组信息
cursor.execute("SELECT * FROM hot_word_group WHERE id = %s", (id,))
group = cursor.fetchone()
if not group:
return create_api_response(code="404", message="热词组不存在")
# 获取该组下启用的热词
cursor.execute(
"SELECT text, weight, lang FROM hot_word_item WHERE group_id = %s AND status = 1",
(id,),
)
items = cursor.fetchall()
if not items:
return create_api_response(code="400", message="该组没有启用的热词可同步")
vocabulary_list = [{"text": it["text"], "weight": it["weight"], "lang": it["lang"]} for it in items]
# ASR 模型名(同步时需要)
asr_model_name = SystemConfigService.get_config_attribute('audio_model', 'model', 'paraformer-v2')
existing_vocab_id = group.get("vocabulary_id")
service = VocabularyService()
vocab_id = existing_vocab_id
try:
if existing_vocab_id:
try:
service.update_vocabulary(
vocabulary_id=existing_vocab_id,
vocabulary=vocabulary_list,
)
except Exception:
existing_vocab_id = None # 更新失败,重建
if not existing_vocab_id:
vocab_id = service.create_vocabulary(
prefix="imeeting",
target_model=asr_model_name,
vocabulary=vocabulary_list,
)
except Exception as api_error:
return create_api_response(code="500", message=f"同步到阿里云失败: {str(api_error)}")
# 回写 vocabulary_id 到热词组
cursor.execute(
"UPDATE hot_word_group SET vocabulary_id = %s, last_sync_time = NOW() WHERE id = %s",
(vocab_id, id),
)
conn.commit()
cursor.close()
return create_api_response(
code="200",
message="同步成功",
data={"vocabulary_id": vocab_id, "synced_count": len(vocabulary_list)},
)
except Exception as e:
return create_api_response(code="500", message=f"同步异常: {str(e)}")
# ── Hot-Word Item CRUD ──────────────────────────────────────
@router.get("/admin/hot-word-groups/{group_id}/items", response_model=dict)
async def list_items(group_id: int, current_user: dict = Depends(get_current_admin_user)):
try:
with get_db_connection() as conn:
cursor = conn.cursor(dictionary=True)
cursor.execute(
"SELECT * FROM hot_word_item WHERE group_id = %s ORDER BY update_time DESC",
(group_id,),
)
items = cursor.fetchall()
cursor.close()
return create_api_response(code="200", message="获取成功", data=items)
except Exception as e:
return create_api_response(code="500", message=f"获取失败: {str(e)}")
@router.post("/admin/hot-word-groups/{group_id}/items", response_model=dict)
async def create_item(group_id: int, request: CreateItemRequest, current_user: dict = Depends(get_current_admin_user)):
try:
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO hot_word_item (group_id, text, weight, lang, status) VALUES (%s, %s, %s, %s, %s)",
(group_id, request.text, request.weight, request.lang, request.status),
)
new_id = cursor.lastrowid
conn.commit()
cursor.close()
return create_api_response(code="200", message="创建成功", data={"id": new_id})
except Exception as e:
if "Duplicate entry" in str(e):
return create_api_response(code="400", message="该组内已存在相同热词")
return create_api_response(code="500", message=f"创建失败: {str(e)}")
@router.put("/admin/hot-word-items/{id}", response_model=dict)
async def update_item(id: int, request: UpdateItemRequest, current_user: dict = Depends(get_current_admin_user)):
try:
with get_db_connection() as conn:
cursor = conn.cursor()
fields, params = [], []
if request.text is not None:
fields.append("text = %s"); params.append(request.text)
if request.weight is not None:
fields.append("weight = %s"); params.append(request.weight)
if request.lang is not None:
fields.append("lang = %s"); params.append(request.lang)
if request.status is not None:
fields.append("status = %s"); params.append(request.status)
if not fields:
return create_api_response(code="400", message="无更新内容")
params.append(id)
cursor.execute(f"UPDATE hot_word_item SET {', '.join(fields)} WHERE id = %s", tuple(params))
conn.commit()
cursor.close()
return create_api_response(code="200", message="更新成功")
except Exception as e:
if "Duplicate entry" in str(e):
return create_api_response(code="400", message="该组内已存在相同热词")
return create_api_response(code="500", message=f"更新失败: {str(e)}")
@router.delete("/admin/hot-word-items/{id}", response_model=dict)
async def delete_item(id: int, current_user: dict = Depends(get_current_admin_user)):
try:
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM hot_word_item WHERE id = %s", (id,))
conn.commit()
cursor.close()
return create_api_response(code="200", message="删除成功")
except Exception as e:
return create_api_response(code="500", message=f"删除失败: {str(e)}")