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