Spaces:
Running
Running
Commit
·
b740c8f
1
Parent(s):
801ae57
修復 app.py 縮排錯誤並更新 requirements.txt
Browse files- app.py +84 -76
- requirements.txt +1 -1
app.py
CHANGED
|
@@ -62,6 +62,8 @@ from services.voice_login import VoiceAuthService, VoiceLoginConfig
|
|
| 62 |
from services.welcome import compose_welcome
|
| 63 |
from core.pipeline import ChatPipeline, PipelineResult
|
| 64 |
from core.memory_system import memory_manager
|
|
|
|
|
|
|
| 65 |
|
| 66 |
|
| 67 |
# -----------------------------
|
|
@@ -457,6 +459,32 @@ class ConnectionManager:
|
|
| 457 |
manager = ConnectionManager()
|
| 458 |
|
| 459 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
# -----------------------------
|
| 461 |
# 語音綁定狀態管理器(關鍵字匹配,無 GPT)
|
| 462 |
# -----------------------------
|
|
@@ -1178,90 +1206,70 @@ async def websocket_endpoint_with_jwt(websocket: WebSocket, token: str = Query(N
|
|
| 1178 |
"timestamp": time.time()
|
| 1179 |
})
|
| 1180 |
|
| 1181 |
-
|
|
|
|
|
|
|
| 1182 |
# ===== 環境快照上報 =====
|
| 1183 |
-
|
| 1184 |
-
|
| 1185 |
-
|
| 1186 |
-
|
| 1187 |
-
|
| 1188 |
-
|
| 1189 |
-
|
| 1190 |
-
|
| 1191 |
-
|
| 1192 |
-
|
| 1193 |
-
|
| 1194 |
-
|
| 1195 |
-
|
| 1196 |
-
|
| 1197 |
-
|
| 1198 |
-
|
| 1199 |
-
|
| 1200 |
-
|
| 1201 |
-
if dist >= 100 or deg_diff >= 25:
|
| 1202 |
-
do_write_snapshot = True
|
| 1203 |
-
else:
|
| 1204 |
do_write_snapshot = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1205 |
|
| 1206 |
-
|
| 1207 |
-
|
| 1208 |
-
|
| 1209 |
-
|
| 1210 |
-
"lat": lat,
|
| 1211 |
-
"lon": lon,
|
| 1212 |
-
"accuracy_m": acc,
|
| 1213 |
-
"heading_deg": heading_deg,
|
| 1214 |
-
"heading_cardinal": heading_cardinal,
|
| 1215 |
-
"tz": tz,
|
| 1216 |
-
"locale": locale,
|
| 1217 |
-
"device": device,
|
| 1218 |
-
"geohash_7": geohash7,
|
| 1219 |
-
}
|
| 1220 |
-
|
| 1221 |
-
# 更新會話暫存
|
| 1222 |
-
manager.last_env[user_id] = env_payload
|
| 1223 |
-
info = manager.get_client_info(user_id) or {}
|
| 1224 |
-
info['env_context'] = env_payload
|
| 1225 |
-
manager.set_client_info(user_id, info)
|
| 1226 |
|
|
|
|
| 1227 |
try:
|
| 1228 |
-
|
|
|
|
|
|
|
| 1229 |
except Exception as e:
|
| 1230 |
-
logger.warning(f"
|
| 1231 |
-
|
| 1232 |
-
if do_write_snapshot:
|
| 1233 |
-
try:
|
| 1234 |
-
snap = env_payload.copy()
|
| 1235 |
-
snap['reason'] = 'threshold'
|
| 1236 |
-
await add_user_env_snapshot(user_id, snap)
|
| 1237 |
-
except Exception as e:
|
| 1238 |
-
logger.warning(f"寫入環境快照失敗: {e}")
|
| 1239 |
-
|
| 1240 |
-
await websocket.send_json({"type": "env_ack", "success": True, "geohash_7": geohash7, "heading": heading_cardinal})
|
| 1241 |
-
except Exception as e:
|
| 1242 |
-
logger.error(f"處理 env_snapshot 失敗: {e}")
|
| 1243 |
-
await websocket.send_json({"type": "env_ack", "success": False, "error": str(e)})
|
| 1244 |
-
# 保存 Agent 回應(已在 handle_message 中保存)
|
| 1245 |
-
|
| 1246 |
-
_async_lib.create_task(_process_voice_chat())
|
| 1247 |
-
else:
|
| 1248 |
-
await websocket.send_json({
|
| 1249 |
-
"type": "error",
|
| 1250 |
-
"message": "未找到音頻 session"
|
| 1251 |
-
})
|
| 1252 |
-
else:
|
| 1253 |
-
await websocket.send_json({
|
| 1254 |
-
"type": "error",
|
| 1255 |
-
"message": "語音服務未初始化"
|
| 1256 |
-
})
|
| 1257 |
|
|
|
|
| 1258 |
except Exception as e:
|
| 1259 |
-
logger.
|
| 1260 |
-
await websocket.send_json({
|
| 1261 |
-
"type": "error",
|
| 1262 |
-
"message": f"語音對話處理失敗: {str(e)}"
|
| 1263 |
-
})
|
| 1264 |
-
|
| 1265 |
else:
|
| 1266 |
await manager.send_message(f"未知的消息類型: {message_type}", user_id, "error")
|
| 1267 |
|
|
|
|
| 62 |
from services.welcome import compose_welcome
|
| 63 |
from core.pipeline import ChatPipeline, PipelineResult
|
| 64 |
from core.memory_system import memory_manager
|
| 65 |
+
# 環境 Context 寫入 API
|
| 66 |
+
from core.database import set_user_env_current, add_user_env_snapshot
|
| 67 |
|
| 68 |
|
| 69 |
# -----------------------------
|
|
|
|
| 459 |
manager = ConnectionManager()
|
| 460 |
|
| 461 |
|
| 462 |
+
# 地理工具函式(內部使用)
|
| 463 |
+
def _haversine_m(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
|
| 464 |
+
from math import radians, sin, cos, asin, sqrt
|
| 465 |
+
if None in (lat1, lon1, lat2, lon2):
|
| 466 |
+
return 0.0
|
| 467 |
+
R = 6371000.0
|
| 468 |
+
dlat = radians(lat2 - lat1)
|
| 469 |
+
dlon = radians(lon2 - lon1)
|
| 470 |
+
a = sin(dlat/2)**2 + cos(radians(lat1))*cos(radians(lat2))*sin(dlon/2)**2
|
| 471 |
+
c = 2 * asin(sqrt(a))
|
| 472 |
+
return R * c
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def _heading_to_cardinal(deg: float) -> str:
|
| 476 |
+
try:
|
| 477 |
+
val = float(deg)
|
| 478 |
+
except Exception:
|
| 479 |
+
return ""
|
| 480 |
+
dirs = [
|
| 481 |
+
"N","NNE","NE","ENE","E","ESE","SE","SSE",
|
| 482 |
+
"S","SSW","SW","WSW","W","WNW","NW","NNW"
|
| 483 |
+
]
|
| 484 |
+
ix = int((val % 360) / 22.5 + 0.5) % 16
|
| 485 |
+
return dirs[ix]
|
| 486 |
+
|
| 487 |
+
|
| 488 |
# -----------------------------
|
| 489 |
# 語音綁定狀態管理器(關鍵字匹配,無 GPT)
|
| 490 |
# -----------------------------
|
|
|
|
| 1206 |
"timestamp": time.time()
|
| 1207 |
})
|
| 1208 |
|
| 1209 |
+
finally:
|
| 1210 |
+
pass
|
| 1211 |
+
elif message_type == "env_snapshot":
|
| 1212 |
# ===== 環境快照上報 =====
|
| 1213 |
+
try:
|
| 1214 |
+
lat = float(message_data.get("lat")) if message_data.get("lat") is not None else None
|
| 1215 |
+
lon = float(message_data.get("lon")) if message_data.get("lon") is not None else None
|
| 1216 |
+
acc = message_data.get("accuracy_m")
|
| 1217 |
+
acc = float(acc) if acc is not None else None
|
| 1218 |
+
heading_deg = message_data.get("heading_deg")
|
| 1219 |
+
heading_deg = float(heading_deg) if heading_deg is not None else None
|
| 1220 |
+
tz = message_data.get("tz")
|
| 1221 |
+
locale = message_data.get("locale")
|
| 1222 |
+
device = message_data.get("device")
|
| 1223 |
+
|
| 1224 |
+
# 後端節流:距離<100m且方位差<25度則忽略
|
| 1225 |
+
do_write_snapshot = False
|
| 1226 |
+
last = manager.last_env.get(user_id)
|
| 1227 |
+
if last and lat is not None and lon is not None and last.get("lat") is not None:
|
| 1228 |
+
dist = _haversine_m(last.get("lat",0), last.get("lon",0), lat, lon)
|
| 1229 |
+
deg_diff = abs((heading_deg or 0) - (last.get("heading_deg") or 0))
|
| 1230 |
+
if dist >= 100 or deg_diff >= 25:
|
|
|
|
|
|
|
|
|
|
| 1231 |
do_write_snapshot = True
|
| 1232 |
+
else:
|
| 1233 |
+
do_write_snapshot = True
|
| 1234 |
+
|
| 1235 |
+
from geohash2 import encode as gh_encode
|
| 1236 |
+
geohash7 = gh_encode(lat, lon, precision=7) if (lat is not None and lon is not None) else None
|
| 1237 |
+
heading_cardinal = _heading_to_cardinal(heading_deg) if heading_deg is not None else None
|
| 1238 |
+
env_payload = {
|
| 1239 |
+
"lat": lat,
|
| 1240 |
+
"lon": lon,
|
| 1241 |
+
"accuracy_m": acc,
|
| 1242 |
+
"heading_deg": heading_deg,
|
| 1243 |
+
"heading_cardinal": heading_cardinal,
|
| 1244 |
+
"tz": tz,
|
| 1245 |
+
"locale": locale,
|
| 1246 |
+
"device": device,
|
| 1247 |
+
"geohash_7": geohash7,
|
| 1248 |
+
}
|
| 1249 |
+
|
| 1250 |
+
# 更新會話暫存
|
| 1251 |
+
manager.last_env[user_id] = env_payload
|
| 1252 |
+
info = manager.get_client_info(user_id) or {}
|
| 1253 |
+
info['env_context'] = env_payload
|
| 1254 |
+
manager.set_client_info(user_id, info)
|
| 1255 |
|
| 1256 |
+
try:
|
| 1257 |
+
await set_user_env_current(user_id, env_payload)
|
| 1258 |
+
except Exception as e:
|
| 1259 |
+
logger.warning(f"寫入環境現況失敗: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1260 |
|
| 1261 |
+
if do_write_snapshot:
|
| 1262 |
try:
|
| 1263 |
+
snap = env_payload.copy()
|
| 1264 |
+
snap['reason'] = 'threshold'
|
| 1265 |
+
await add_user_env_snapshot(user_id, snap)
|
| 1266 |
except Exception as e:
|
| 1267 |
+
logger.warning(f"寫入環境快照失敗: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1268 |
|
| 1269 |
+
await websocket.send_json({"type": "env_ack", "success": True, "geohash_7": geohash7, "heading": heading_cardinal})
|
| 1270 |
except Exception as e:
|
| 1271 |
+
logger.error(f"處理 env_snapshot 失敗: {e}")
|
| 1272 |
+
await websocket.send_json({"type": "env_ack", "success": False, "error": str(e)})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1273 |
else:
|
| 1274 |
await manager.send_message(f"未知的消息類型: {message_type}", user_id, "error")
|
| 1275 |
|
requirements.txt
CHANGED
|
@@ -24,7 +24,7 @@ matplotlib==3.7.1
|
|
| 24 |
jsonschema>=4.17.0
|
| 25 |
|
| 26 |
# Geospatial / directions
|
| 27 |
-
geohash2
|
| 28 |
|
| 29 |
# Machine Learning dependencies
|
| 30 |
numpy>=1.24.0,<2.0.0
|
|
|
|
| 24 |
jsonschema>=4.17.0
|
| 25 |
|
| 26 |
# Geospatial / directions
|
| 27 |
+
geohash2
|
| 28 |
|
| 29 |
# Machine Learning dependencies
|
| 30 |
numpy>=1.24.0,<2.0.0
|