From 6739596fb07462f04a966e0fef4c443c3b9d6e5c Mon Sep 17 00:00:00 2001 From: "Jonas Luz Jr." Date: Sat, 22 Nov 2025 19:50:01 -0300 Subject: [PATCH] Implements ApiClient class and manager class. --- Assets/_Client/Scripts/ApiClient.cs | 195 ++++++++---------- Assets/_Client/Scripts/ApiClient.cs.meta | 2 +- Assets/_Client/Scripts/ApiClientManager.cs | 136 ++++++++++++ .../_Client/Scripts/ApiClientManager.cs.meta | 2 + 4 files changed, 223 insertions(+), 112 deletions(-) create mode 100644 Assets/_Client/Scripts/ApiClientManager.cs create mode 100644 Assets/_Client/Scripts/ApiClientManager.cs.meta diff --git a/Assets/_Client/Scripts/ApiClient.cs b/Assets/_Client/Scripts/ApiClient.cs index 0988a08..3865fb1 100644 --- a/Assets/_Client/Scripts/ApiClient.cs +++ b/Assets/_Client/Scripts/ApiClient.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Collections; -using System.Linq; using System.Text; using UnityEngine; @@ -9,61 +8,23 @@ using UnityEngine.Networking; namespace PPGIA.X540.Project3.API { - public class ApiClient : MonoBehaviour + internal class ApiClient { - #region -- Inspector Fields ------------------------------------------- - [Header("API URL Settings")] - [SerializeField] - private string _apiBaseUrl = "https://api.example.com"; + internal static byte[] EncodePayload(object payload) + { + var json = JsonUtility.ToJson(payload); + return Encoding.UTF8.GetBytes(json); + } - [SerializeField] - private string _sessionInitEndpoint = "/session/init"; - - [SerializeField] - private string _sessionCloseEndpoint = "/session/close"; - - [SerializeField] - private string _llmAgentEndpoint = "/agent/ask"; - - [SerializeField] - private string _ttsEndpoint = "/tts/synthesize"; - - [SerializeField] - private string _sttEndpoint = "/stt/upload"; - - [Header("API Settings & Workload")] - [SerializeField] - private string _clientId = "unity-client"; - - [SerializeField] - private float _timeoutInSeconds = 10f; - - [SerializeField, Multiline, TextArea(3, 10)] - private string _query; - - [Header("API State Information")] - [SerializeField] - private Session _session; - #endregion ------------------------------------------------------------ - - #region -- Helper Methods --------------------------------------------- - // Helper Method to build endpoint URLs - string EndpointUrl(params string[] parts) => - _apiBaseUrl.TrimEnd('/') + '/' + - string.Join("/", parts.Select(p => p.Trim('/'))); - - // Helper Method to encode payloads as JSON byte arrays - byte[] EncodePayload(object payload) => - Encoding.UTF8.GetBytes(JsonUtility.ToJson(payload)); - - IEnumerator WaitForTimeout( + static IEnumerator WaitForTimeout( UnityWebRequestAsyncOperation operation, + float timeoutInSeconds, Action callbackIfTimeout = null) { float startTime = Time.realtimeSinceStartup; while (!operation.isDone) { - if (Time.realtimeSinceStartup - startTime > _timeoutInSeconds) + if (Time.realtimeSinceStartup - startTime > timeoutInSeconds) { callbackIfTimeout?.Invoke(); yield break; @@ -72,87 +33,99 @@ namespace PPGIA.X540.Project3.API } } - IEnumerator CallEndpointWithGetCoroutine( - string url, Action callback) + static IEnumerator CallEndpointCoroutine(string url, + string method, + object payload, + float timeoutInSeconds, + Action callbackOnSuccess) { - using (var request = UnityWebRequest.Get(url)) + using (var request = new UnityWebRequest(url, method)) { - var op = request.SendWebRequest(); - yield return WaitForTimeout(op, () => + if (method == "POST" || method == "PUT") { - Debug.LogError("Request timed out."); - }); + request.SetRequestHeader("Content-Type", "application/json"); + } - callback?.Invoke(request); - } - } + if (payload != null) + { + byte[] bodyRaw = EncodePayload(payload); + Debug.Log($"Payload size: {bodyRaw.Length} bytes - {payload}"); + request.uploadHandler = new UploadHandlerRaw(bodyRaw); + } - IEnumerator CallEndpointWithPostCoroutine( - string url, object payload, Action callback) - { - using (var request = new UnityWebRequest(url, "POST")) - { - byte[] bodyRaw = EncodePayload(payload); - request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); - request.SetRequestHeader("Content-Type", "application/json"); + + // Debug.Log($"Sending {method} request to {url}"); + // Debug.Log( + // payload != null ? + // $"Payload: {JsonUtility.ToJson(payload)}" : + // "No payload."); var op = request.SendWebRequest(); - yield return WaitForTimeout(op, () => + yield return WaitForTimeout(op, timeoutInSeconds, () => { Debug.LogError("Request timed out."); }); - callback?.Invoke(request); + if (request.result == UnityWebRequest.Result.Success) + { + callbackOnSuccess?.Invoke(request); + } + else + { + var body = request.downloadHandler?.text ?? string.Empty; + var errorTrace = @$"API call failed: {request.error} (HTTP {request.responseCode}) +Request Method: {method} +Request URL: {url} +Request Payload: {JsonUtility.ToJson(payload)} +Response Body: {body}"; + Debug.LogError(errorTrace); + } } } - #endregion -- Helper Methods ------------------------------------------ - #region -- API Calls -------------------------------------------------- - [ContextMenu("Test API Availability")] - public void TestApiAvailability() + internal static IEnumerator CallEndpointWithGetCoroutine( + string url, float timeoutInSeconds, + Action callbackOnSuccess) { - var url = EndpointUrl(""); - Debug.Log($"Testing API availability at: {url}"); - - StartCoroutine(CallEndpointWithGetCoroutine(url, (request) => - { - if (request.result == UnityWebRequest.Result.Success) - { - var body = request.downloadHandler?.text ?? string.Empty; - Debug.Log($"API call returned: {body}"); - } - else - { - Debug.LogError( - $"API availability check failed: {request.error} (HTTP {request.responseCode})"); - } - })); + yield return CallEndpointCoroutine( + url, "GET", null, timeoutInSeconds, callbackOnSuccess); } - [ContextMenu("Initiate Session")] - public string InitiateSession() + internal static IEnumerator CallEndpointWithPostCoroutine( + string url, float timeoutInSeconds, object payload, + Action callbackOnSuccess) { - var url = EndpointUrl(_sessionInitEndpoint, _clientId); - Debug.Log($"Initiating session at: {url}"); - - StartCoroutine(CallEndpointWithPostCoroutine(url, null, (request) => - { - if (request.result == UnityWebRequest.Result.Success) - { - var body = request.downloadHandler?.text ?? string.Empty; - var session = JsonUtility.FromJson(body); - _session = session; - } - else - { - Debug.LogError( - $"Session init failed: {request.error} (HTTP {request.responseCode})"); - } - })); - - return _session.SessionId; + yield return CallEndpointCoroutine( + url, "POST", payload, timeoutInSeconds, callbackOnSuccess); } - #endregion -- API Calls ------------------------------------------------ + + internal static IEnumerator CallEndpointWithPutCoroutine( + string url, float timeoutInSeconds, object payload, + Action callbackOnSuccess) + { + yield return CallEndpointCoroutine( + url, "PUT", payload, timeoutInSeconds, callbackOnSuccess); + } + + internal static IEnumerator CallEndpointWithDeleteCoroutine( + string url, float timeoutInSeconds, + Action callbackOnSuccess) + { + yield return CallEndpointCoroutine( + url, "DELETE", null, timeoutInSeconds, callbackOnSuccess); + } + } + + internal enum Environment + { + Development, + Production + } + + [Serializable] + internal struct ChatServicePayload + { + public string message; } } \ No newline at end of file diff --git a/Assets/_Client/Scripts/ApiClient.cs.meta b/Assets/_Client/Scripts/ApiClient.cs.meta index c98d09d..39c0640 100644 --- a/Assets/_Client/Scripts/ApiClient.cs.meta +++ b/Assets/_Client/Scripts/ApiClient.cs.meta @@ -1,2 +1,2 @@ fileFormatVersion: 2 -guid: 6df1f7169d6eca04abd7db0f712639ff \ No newline at end of file +guid: 1a14c6225c9606e4baff928b379f19fb \ No newline at end of file diff --git a/Assets/_Client/Scripts/ApiClientManager.cs b/Assets/_Client/Scripts/ApiClientManager.cs new file mode 100644 index 0000000..c089031 --- /dev/null +++ b/Assets/_Client/Scripts/ApiClientManager.cs @@ -0,0 +1,136 @@ +using System.Linq; + +using UnityEngine; + + +namespace PPGIA.X540.Project3.API +{ + public class ApiClientManager : MonoBehaviour + { + #region -- Inspector Fields ------------------------------------------- + [Header("API Base URL Settings")] + [SerializeField] + private string _apiBaseUrlDev = "http://127.0.0.1:8000"; + + [SerializeField] + private string _apiBaseUrlProd = "https://api.example.com"; + + [SerializeField] + private Environment _environment = Environment.Development; + + [Header("API Endpoints")] + [SerializeField] + private string _sessionInitEndpoint = "/session/init"; + + [SerializeField] + private string _sessionCloseEndpoint = "/session/close"; + + [SerializeField] + private string _chatEndpoint = "/chat/"; + + [SerializeField] + private string _llmAgentEndpoint = "/agent/ask"; + + [SerializeField] + private string _ttsEndpoint = "/tts/synthesize"; + + [SerializeField] + private string _sttEndpoint = "/stt/upload"; + + [Header("API Settings & Workload")] + [SerializeField] + private string _clientId = "unity-client"; + + [SerializeField] + private float _timeoutInSeconds = 10f; + + [SerializeField, Multiline, TextArea(3, 10)] + private string _query; + + [Header("API State Information")] + [SerializeField] + private Session _session; + #endregion ------------------------------------------------------------ + + #region -- Other Properties & Methods --------------------------------- + // Property to get the appropriate API base URL + private string ApiBaseUrl => + _environment == Environment.Development ? + _apiBaseUrlDev : _apiBaseUrlProd; + + // Helper Method to build endpoint URLs + string EndpointUrl(params string[] parts) => + ApiBaseUrl.TrimEnd('/') + '/' + + string.Join("/", parts.Select(p => p.Trim('/'))); + #endregion ------------------------------------------------------------ + + + #region -- API Calls -------------------------------------------------- + [ContextMenu("Test API Availability")] + public void TestApiAvailability() + { + var url = EndpointUrl(""); + + StartCoroutine(ApiClient.CallEndpointWithGetCoroutine( + url, _timeoutInSeconds, (request) => + { + var body = request.downloadHandler?.text ?? string.Empty; + Debug.Log($"API call returned: {body}"); + })); + } + + [ContextMenu("Initiate Session")] + public void InitiateSession() + { + var url = EndpointUrl(_sessionInitEndpoint, _clientId); + + StartCoroutine(ApiClient.CallEndpointWithPostCoroutine( + url, _timeoutInSeconds, null, (request) => + { + var body = request.downloadHandler?.text ?? string.Empty; + var session = JsonUtility.FromJson(body); + _session = session; + })); + } + + [ContextMenu("Close Session")] + public void CloseSession() + { + if (_session == null) + { + Debug.LogWarning("No active session to close."); + return; + } + + var url = EndpointUrl(_sessionCloseEndpoint, _session.SessionId); + + StartCoroutine(ApiClient.CallEndpointWithDeleteCoroutine( + url, _timeoutInSeconds, (request) => + { + Debug.Log("Session closed successfully."); + _session = null; + })); + } + + [ContextMenu("Send Chat Message")] + public void SendChatMessage() + { + if (_session == null) + { + Debug.LogWarning("No active session. Please initiate a session first."); + return; + } + + var url = EndpointUrl(_chatEndpoint, _session.SessionId); + var payload = new ChatServicePayload { message = _query }; + + StartCoroutine(ApiClient.CallEndpointWithPostCoroutine( + url, _timeoutInSeconds, payload, (request) => + { + var body = request.downloadHandler?.text ?? string.Empty; + Debug.Log($"Chat response: {body}"); + })); + } + #endregion -- API Calls ------------------------------------------------ + } +} diff --git a/Assets/_Client/Scripts/ApiClientManager.cs.meta b/Assets/_Client/Scripts/ApiClientManager.cs.meta new file mode 100644 index 0000000..c98d09d --- /dev/null +++ b/Assets/_Client/Scripts/ApiClientManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6df1f7169d6eca04abd7db0f712639ff \ No newline at end of file