287 lines
11 KiB
GDScript
287 lines
11 KiB
GDScript
extends Node
|
|
|
|
# ============================================================================
|
|
# CafeCompanionManager.gd - 咖啡店陪伴机器人网络管理器
|
|
# ============================================================================
|
|
# 负责咖啡店陪伴机器人服务点、付费聊天时长和 Agent
|
|
# 聊天代理的前端协议。客户端不保存模型 URL/token。
|
|
# ============================================================================
|
|
|
|
signal cafe_companion_products_ready(data: Dictionary)
|
|
signal cafe_companion_service_points_ready(data: Dictionary)
|
|
signal cafe_companion_chat_time_purchased(data: Dictionary)
|
|
signal cafe_companion_message_received(message: Dictionary)
|
|
signal cafe_companion_error_occurred(message: String)
|
|
signal cafe_companion_agent_registered(data: Dictionary)
|
|
signal cafe_companion_employment_resigned(data: Dictionary)
|
|
signal cafe_companion_models_ready(data: Dictionary)
|
|
|
|
const NetworkConfig = preload("res://_Core/utils/NetworkConfig.gd")
|
|
|
|
const REQUEST_TIMEOUT: float = 24.0
|
|
|
|
var _activeRequests: Array[HTTPRequest] = []
|
|
var _cachedProducts: Array = []
|
|
var _cachedServicePoints: Array = []
|
|
var _accountGeneration: int = -1
|
|
|
|
func _ready() -> void:
|
|
_accountGeneration = _current_account_generation()
|
|
var authManager := get_node_or_null("/root/AuthManager")
|
|
if authManager != null and authManager.has_signal("auth_state_changed"):
|
|
authManager.auth_state_changed.connect(_on_auth_state_changed)
|
|
|
|
func _exit_tree() -> void:
|
|
_cancel_active_requests()
|
|
|
|
func get_chat_time_products(forceRefresh: bool = false) -> void:
|
|
if not forceRefresh and not _cachedProducts.is_empty():
|
|
var cachedData := {"products": _cachedProducts}
|
|
cafe_companion_products_ready.emit(cachedData)
|
|
_emit_event(EventNames.CAFE_COMPANION_PRODUCTS_READY, cachedData)
|
|
return
|
|
if not _has_auth_token():
|
|
_emit_chat_error("请先登录后再查看咖啡店陪聊时长")
|
|
return
|
|
|
|
_request_json("/cafe-companion/chat-time-products", {}, _on_chat_time_products_response, HTTPClient.METHOD_GET)
|
|
|
|
func get_service_points(forceRefresh: bool = false) -> void:
|
|
if not forceRefresh and not _cachedServicePoints.is_empty():
|
|
var cachedData := {"service_points": _cachedServicePoints}
|
|
cafe_companion_service_points_ready.emit(cachedData)
|
|
_emit_event(EventNames.CAFE_COMPANION_SERVICE_POINTS_READY, cachedData)
|
|
return
|
|
if not _has_auth_token():
|
|
_emit_chat_error("请先登录后再查看咖啡店陪伴位")
|
|
return
|
|
|
|
_request_json("/cafe-companion/service-points", {}, _on_service_points_response, HTTPClient.METHOD_GET)
|
|
|
|
func purchase_chat_time(targetData: Dictionary, minutes: int) -> void:
|
|
if not _has_auth_token():
|
|
_emit_chat_error("请先登录后再购买咖啡店陪聊时长")
|
|
return
|
|
|
|
var payload := {
|
|
"service_point_id": str(targetData.get("service_point_id", "")),
|
|
"minutes": minutes,
|
|
}
|
|
var companionId := str(targetData.get("companion_id", "")).strip_edges()
|
|
if not companionId.is_empty():
|
|
payload["companion_id"] = companionId
|
|
|
|
_request_json("/cafe-companion/chat-time/purchase", payload, _on_purchase_chat_time_response)
|
|
|
|
func send_chat_message(sessionId: String, content: String) -> void:
|
|
var normalizedContent := content.strip_edges()
|
|
if sessionId.strip_edges().is_empty():
|
|
_emit_chat_error("咖啡店陪聊会话还没有准备好")
|
|
return
|
|
if normalizedContent.is_empty():
|
|
return
|
|
if not _has_auth_token():
|
|
_emit_chat_error("请先登录后再发送咖啡店陪聊消息")
|
|
return
|
|
|
|
_request_json("/cafe-companion/chat/messages", {
|
|
"session_id": sessionId,
|
|
"content": normalizedContent,
|
|
}, _on_send_chat_message_response)
|
|
|
|
func register_employment_agent(payload: Dictionary) -> void:
|
|
if not _has_auth_token():
|
|
_emit_chat_error("请先登录后再登记咖啡店陪伴机器人")
|
|
return
|
|
_request_json("/cafe-companion/employment/agents", payload, _on_register_employment_agent_response)
|
|
|
|
func resign_employment(servicePointId: String) -> void:
|
|
if not _has_auth_token():
|
|
_emit_chat_error("请先登录后再结束咖啡店雇佣")
|
|
return
|
|
_request_json("/cafe-companion/employment/resign", {
|
|
"service_point_id": servicePointId,
|
|
}, _on_resign_employment_response)
|
|
|
|
func get_employment_models(protocol: String, baseUrl: String, token: String) -> void:
|
|
if not _has_auth_token():
|
|
_emit_chat_error("请先登录后再获取咖啡店雇佣模型")
|
|
return
|
|
_request_json("/cafe-companion/employment/models", {
|
|
"protocol": protocol,
|
|
"base_url": baseUrl,
|
|
"token": token,
|
|
}, _on_employment_models_response)
|
|
|
|
func _request_json(endpoint: String, payload: Dictionary, callback: Callable, method: int = HTTPClient.METHOD_POST) -> void:
|
|
var request := HTTPRequest.new()
|
|
request.timeout = REQUEST_TIMEOUT
|
|
add_child(request)
|
|
_activeRequests.append(request)
|
|
var requestGeneration := _current_account_generation()
|
|
|
|
request.request_completed.connect(func(result: int, responseCode: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
|
_activeRequests.erase(request)
|
|
request.queue_free()
|
|
if requestGeneration != _current_account_generation():
|
|
return
|
|
_handle_json_response(result, responseCode, body, callback)
|
|
)
|
|
|
|
var headers := PackedStringArray(["Content-Type: application/json"])
|
|
var token := _get_auth_token()
|
|
if not token.is_empty():
|
|
headers.append("Authorization: Bearer %s" % token)
|
|
|
|
var body := "" if method == HTTPClient.METHOD_GET else JSON.stringify(payload)
|
|
var err := request.request("%s%s" % [NetworkConfig.get_api_base_url(), endpoint], headers, method, body)
|
|
if err != OK:
|
|
_activeRequests.erase(request)
|
|
request.queue_free()
|
|
if requestGeneration == _current_account_generation():
|
|
_emit_chat_error("咖啡店陪聊请求发送失败: %s" % error_string(err))
|
|
|
|
func _handle_json_response(result: int, responseCode: int, body: PackedByteArray, callback: Callable) -> void:
|
|
if result != HTTPRequest.RESULT_SUCCESS:
|
|
_emit_chat_error("咖啡店陪聊网络请求失败: %s" % _http_result_to_string(result))
|
|
return
|
|
|
|
var bodyText := body.get_string_from_utf8()
|
|
var json := JSON.new()
|
|
if json.parse(bodyText) != OK:
|
|
_emit_chat_error("咖啡店陪聊响应解析失败")
|
|
return
|
|
|
|
var payloadVariant: Variant = json.data
|
|
if not (payloadVariant is Dictionary):
|
|
_emit_chat_error("咖啡店陪聊响应格式错误")
|
|
return
|
|
|
|
var response: Dictionary = payloadVariant
|
|
var success := responseCode >= 200 and responseCode < 300 and bool(response.get("success", true))
|
|
if not success:
|
|
_emit_chat_error(str(response.get("message", "咖啡店陪聊请求失败")))
|
|
return
|
|
|
|
callback.call(response)
|
|
|
|
func _on_chat_time_products_response(response: Dictionary) -> void:
|
|
var data := _response_data(response)
|
|
var productsVariant: Variant = data.get("products", [])
|
|
_cachedProducts = productsVariant if productsVariant is Array else []
|
|
cafe_companion_products_ready.emit(data)
|
|
_emit_event(EventNames.CAFE_COMPANION_PRODUCTS_READY, data)
|
|
|
|
func _on_service_points_response(response: Dictionary) -> void:
|
|
var data := _response_data(response)
|
|
var servicePointsVariant: Variant = data.get("service_points", [])
|
|
_cachedServicePoints = servicePointsVariant if servicePointsVariant is Array else []
|
|
cafe_companion_service_points_ready.emit(data)
|
|
_emit_event(EventNames.CAFE_COMPANION_SERVICE_POINTS_READY, data)
|
|
|
|
func _on_purchase_chat_time_response(response: Dictionary) -> void:
|
|
var data := _response_data(response)
|
|
_apply_wallet_from_payload(data)
|
|
cafe_companion_chat_time_purchased.emit(data)
|
|
_emit_event(EventNames.CAFE_COMPANION_CHAT_TIME_PURCHASED, data)
|
|
|
|
func _on_send_chat_message_response(response: Dictionary) -> void:
|
|
var data := _response_data(response)
|
|
cafe_companion_message_received.emit(data)
|
|
_emit_event(EventNames.CAFE_COMPANION_MESSAGE_RECEIVED, data)
|
|
|
|
func _on_register_employment_agent_response(response: Dictionary) -> void:
|
|
var data := _response_data(response)
|
|
cafe_companion_agent_registered.emit(data)
|
|
_cachedServicePoints.clear()
|
|
_emit_event(EventNames.CAFE_COMPANION_AGENT_REGISTERED, data)
|
|
|
|
func _on_resign_employment_response(response: Dictionary) -> void:
|
|
var data := _response_data(response)
|
|
var penaltyVariant: Variant = data.get("penalty", {})
|
|
if penaltyVariant is Dictionary:
|
|
_apply_wallet_from_payload(penaltyVariant as Dictionary)
|
|
cafe_companion_employment_resigned.emit(data)
|
|
_cachedServicePoints.clear()
|
|
_emit_event(EventNames.CAFE_COMPANION_EMPLOYMENT_RESIGNED, data)
|
|
|
|
func _on_employment_models_response(response: Dictionary) -> void:
|
|
var data := _response_data(response)
|
|
cafe_companion_models_ready.emit(data)
|
|
_emit_event(EventNames.CAFE_COMPANION_MODELS_READY, data)
|
|
|
|
func _response_data(response: Dictionary) -> Dictionary:
|
|
var dataVariant: Variant = response.get("data", {})
|
|
if dataVariant is Dictionary:
|
|
return dataVariant
|
|
return {}
|
|
|
|
func _apply_wallet_from_payload(data: Dictionary) -> void:
|
|
if not data.has("balance"):
|
|
return
|
|
var playerStateManager := get_node_or_null("/root/PlayerStateManager")
|
|
if playerStateManager != null and playerStateManager.has_method("apply_wallet"):
|
|
playerStateManager.call("apply_wallet", {
|
|
"balance": int(data.get("balance", 0)),
|
|
"currency": str(data.get("currency", "whale_coin")),
|
|
})
|
|
|
|
func _emit_chat_error(message: String) -> void:
|
|
cafe_companion_error_occurred.emit(message)
|
|
_emit_event(EventNames.CAFE_COMPANION_ERROR_OCCURRED, {
|
|
"message": message
|
|
})
|
|
|
|
func _emit_event(eventName: String, data: Dictionary) -> void:
|
|
var eventSystem := get_node_or_null("/root/EventSystem")
|
|
if eventSystem != null:
|
|
eventSystem.call("emit_event", eventName, data)
|
|
|
|
func _has_auth_token() -> bool:
|
|
return not _get_auth_token().is_empty()
|
|
|
|
func _get_auth_token() -> String:
|
|
var authManager := get_node_or_null("/root/AuthManager")
|
|
if authManager == null or not authManager.has_method("get_access_token"):
|
|
return ""
|
|
return str(authManager.call("get_access_token")).strip_edges()
|
|
|
|
func _current_account_generation() -> int:
|
|
var authManager := get_node_or_null("/root/AuthManager")
|
|
if authManager == null or not authManager.has_method("get_account_generation"):
|
|
return -1
|
|
return int(authManager.call("get_account_generation"))
|
|
|
|
func _on_auth_state_changed(_isAuthenticated: bool, _user: Dictionary) -> void:
|
|
var currentGeneration := _current_account_generation()
|
|
if currentGeneration == _accountGeneration:
|
|
return
|
|
_accountGeneration = currentGeneration
|
|
_cancel_active_requests()
|
|
_cachedProducts.clear()
|
|
_cachedServicePoints.clear()
|
|
|
|
func _cancel_active_requests() -> void:
|
|
for request in _activeRequests.duplicate():
|
|
if is_instance_valid(request):
|
|
request.cancel_request()
|
|
request.queue_free()
|
|
_activeRequests.clear()
|
|
|
|
func _http_result_to_string(result: int) -> String:
|
|
match result:
|
|
HTTPRequest.RESULT_SUCCESS:
|
|
return "SUCCESS"
|
|
HTTPRequest.RESULT_TIMEOUT:
|
|
return "TIMEOUT"
|
|
HTTPRequest.RESULT_CANT_CONNECT:
|
|
return "CANT_CONNECT"
|
|
HTTPRequest.RESULT_CANT_RESOLVE:
|
|
return "CANT_RESOLVE"
|
|
HTTPRequest.RESULT_CONNECTION_ERROR:
|
|
return "CONNECTION_ERROR"
|
|
HTTPRequest.RESULT_TLS_HANDSHAKE_ERROR:
|
|
return "TLS_HANDSHAKE_ERROR"
|
|
_:
|
|
return "HTTP_RESULT_%d" % result
|