休眠时段操作小程序加提示,博士角色语速调快一点,
刷自己卡没有留言情况下播放最后一条留言,增加角色切换提示音。
This commit is contained in:
BIN
talkingq-url/assets/audio/switch_role.mp3
Normal file
BIN
talkingq-url/assets/audio/switch_role.mp3
Normal file
Binary file not shown.
@@ -1,12 +1,12 @@
|
||||
from collections.abc import Awaitable, Callable
|
||||
import logging
|
||||
|
||||
from datetime import datetime, time, timedelta
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from sqlalchemy import text
|
||||
|
||||
from fastapi.responses import JSONResponse
|
||||
from database.connection import get_db_manager
|
||||
from banban.security import auth_error_response, decode_access_token
|
||||
|
||||
from banban.service.device import device_service
|
||||
|
||||
NO_AUTH_PATH_PREFIXES = (
|
||||
"/banban/auth/login",
|
||||
@@ -34,6 +34,28 @@ def _is_no_auth_path(path: str) -> bool:
|
||||
return True
|
||||
return False
|
||||
|
||||
def convert_to_time(value):
|
||||
"""将字符串或timedelta转换为time对象"""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, time):
|
||||
return value
|
||||
if isinstance(value, timedelta):
|
||||
# timedelta 可能表示时间间隔,例如 22:00:00 表示为 22小时
|
||||
total_seconds = value.total_seconds()
|
||||
hours = int(total_seconds // 3600)
|
||||
minutes = int((total_seconds % 3600) // 60)
|
||||
seconds = int(total_seconds % 60)
|
||||
return time(hours, minutes, seconds)
|
||||
if isinstance(value, str):
|
||||
# 支持 "HH:MM" 或 "HH:MM:SS"
|
||||
parts = value.split(':')
|
||||
if len(parts) == 2:
|
||||
return datetime.strptime(value, "%H:%M").time()
|
||||
elif len(parts) == 3:
|
||||
return datetime.strptime(value, "%H:%M:%S").time()
|
||||
raise ValueError(f"Unsupported type for time conversion: {type(value)}")
|
||||
|
||||
|
||||
def install_auth_middleware(app: FastAPI) -> None:
|
||||
@app.middleware("http")
|
||||
@@ -122,4 +144,53 @@ def install_auth_middleware(app: FastAPI) -> None:
|
||||
await session.close()
|
||||
|
||||
request.state.user_id = user_id
|
||||
async def is_device_sleep(device_id: str) -> bool:
|
||||
db_manager = await get_db_manager()
|
||||
session = await db_manager.get_session()
|
||||
try:
|
||||
row = await device_service.get_device_status(
|
||||
device_id=device_id,
|
||||
user_id=user_id,
|
||||
)
|
||||
sleep_start_str=row.get("disable_time_start")
|
||||
sleep_end_str=row.get("disable_time_end")
|
||||
print(sleep_start_str, sleep_end_str)
|
||||
now = datetime.now()
|
||||
current_time = now.time()
|
||||
print(current_time)
|
||||
start_time = convert_to_time(sleep_start_str)
|
||||
end_time = convert_to_time(sleep_end_str)
|
||||
print(start_time, end_time)
|
||||
# 如果任意一个转换后为 None,视为未配置睡眠时间
|
||||
if start_time is None or end_time is None:
|
||||
return True
|
||||
now = datetime.now()
|
||||
current_time = now.time()
|
||||
|
||||
if start_time <= end_time:
|
||||
return not (start_time <= current_time <= end_time)
|
||||
else:
|
||||
return not (current_time >= start_time or current_time <= end_time)
|
||||
|
||||
finally:
|
||||
await session.close()
|
||||
|
||||
path = request.url.path
|
||||
# 仅拦截以 /banban/devices/ 开头的路径
|
||||
if path.startswith("/banban/devices/") and ('volume' in path or 'location' in path or 'firmware/update' in path) :
|
||||
# 解析 device_id,假设路径格式为 /banban/devices/{device_id}/... 或 /banban/devices/{device_id}
|
||||
parts = path.split("/")
|
||||
# parts 示例: ['', 'banban', 'devices', 'device_id', 'action', ...]
|
||||
if len(parts) >= 4:
|
||||
device_id = parts[3] # 第四个片段即为 device_id
|
||||
if device_id: # 确保 device_id 非空
|
||||
if not await is_device_sleep(device_id):
|
||||
return JSONResponse(
|
||||
status_code=400, # 或 403 / 409
|
||||
content={"detail": f"设备 {device_id} 不在线或已配置睡眠时间,请稍后再试"}
|
||||
)
|
||||
else:
|
||||
# 路径格式不符合预期,可以放行或返回错误(根据业务决定)
|
||||
# 这里选择放行,让后续路由自行处理
|
||||
pass
|
||||
return await call_next(request)
|
||||
|
||||
@@ -10,7 +10,8 @@ from handlers.prompt_sound_handler import send_welcome_sound
|
||||
from services.conversation_history import DeviceConversationHistory, conversation_history_manager
|
||||
from services.device_config import DeviceConfig, device_config_manager
|
||||
from services.role_manager import role_manager
|
||||
|
||||
from handlers.mqtt_handler import TalkingQMQTTService
|
||||
from config import settings
|
||||
|
||||
router = APIRouter(prefix="/roles", tags=["banban-roles"])
|
||||
|
||||
@@ -121,8 +122,13 @@ async def update_device_role(
|
||||
role_key,
|
||||
)
|
||||
|
||||
if payload.play_welcome:
|
||||
await send_welcome_sound(device_id, role_key, language)
|
||||
# if payload.play_welcome:
|
||||
# await send_welcome_sound(device_id, role_key, language)
|
||||
service = await TalkingQMQTTService.get_instance()
|
||||
if service is None:
|
||||
raise HTTPException(status_code=503, detail="MQTT 服务未初始化")
|
||||
audio_url = f"http://{settings.server_host}:{settings.server_port}/assets/audio/switch_role.mp3"
|
||||
msg_id = await service.send_nfc_notice(device_id, audio_url)
|
||||
|
||||
return DeviceRoleResponse(
|
||||
device_id=device_id,
|
||||
|
||||
@@ -454,6 +454,7 @@ class TalkingQMQTTService:
|
||||
params = {f"url_{k}": audio_url for k, audio_url in enumerate(audio_urls, start=1)}
|
||||
payload = {"msg_id": "005", "type": 0, "params": params}
|
||||
await self._publish(topic, payload)
|
||||
await offline_audio_cache.add_audio_url(device_id, audio_urls[-1])
|
||||
return
|
||||
|
||||
payload = {
|
||||
|
||||
@@ -112,14 +112,16 @@ class MiniMaxTTS(TTS, BaseService):
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
speed = 1.0
|
||||
if self.selected_role and "boshi" in self.selected_role:
|
||||
speed = 2.0
|
||||
payload = {
|
||||
"model": self.model,
|
||||
"text": text,
|
||||
"stream": True,
|
||||
"voice_setting": {
|
||||
"voice_id": self.voice_id,
|
||||
"speed": 1.0,
|
||||
"speed": speed,
|
||||
"vol": 2.0,
|
||||
"pitch": 0
|
||||
},
|
||||
|
||||
@@ -18,8 +18,11 @@ class OfflineAudioCache:
|
||||
return list(self.offline_audio.get(device_id, []))
|
||||
|
||||
async def pop_audio_urls(self, device_id: str) -> List[str]:
|
||||
async with self.lock:
|
||||
return self.offline_audio.pop(device_id, [])
|
||||
urls = await self.get_audio_urls(device_id)
|
||||
if urls and len(urls) > 1:
|
||||
async with self.lock:
|
||||
return self.offline_audio.pop(device_id, [])
|
||||
return urls
|
||||
|
||||
async def clear_audio_urls(self, device_id: str):
|
||||
async with self.lock:
|
||||
|
||||
Reference in New Issue
Block a user