Implements ApiClient class and manager class.

This commit is contained in:
Jonas Luz Jr. 2025-11-22 19:50:01 -03:00
parent b6e5930ead
commit 6739596fb0
4 changed files with 223 additions and 112 deletions

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Linq;
using System.Text; using System.Text;
using UnityEngine; using UnityEngine;
@ -9,61 +8,23 @@ using UnityEngine.Networking;
namespace PPGIA.X540.Project3.API namespace PPGIA.X540.Project3.API
{ {
public class ApiClient : MonoBehaviour internal class ApiClient
{ {
#region -- Inspector Fields ------------------------------------------- internal static byte[] EncodePayload(object payload)
[Header("API URL Settings")] {
[SerializeField] var json = JsonUtility.ToJson(payload);
private string _apiBaseUrl = "https://api.example.com"; return Encoding.UTF8.GetBytes(json);
}
[SerializeField] static IEnumerator WaitForTimeout(
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(
UnityWebRequestAsyncOperation operation, UnityWebRequestAsyncOperation operation,
float timeoutInSeconds,
Action callbackIfTimeout = null) Action callbackIfTimeout = null)
{ {
float startTime = Time.realtimeSinceStartup; float startTime = Time.realtimeSinceStartup;
while (!operation.isDone) while (!operation.isDone)
{ {
if (Time.realtimeSinceStartup - startTime > _timeoutInSeconds) if (Time.realtimeSinceStartup - startTime > timeoutInSeconds)
{ {
callbackIfTimeout?.Invoke(); callbackIfTimeout?.Invoke();
yield break; yield break;
@ -72,87 +33,99 @@ namespace PPGIA.X540.Project3.API
} }
} }
IEnumerator CallEndpointWithGetCoroutine( static IEnumerator CallEndpointCoroutine(string url,
string url, Action<UnityWebRequest> callback) string method,
object payload,
float timeoutInSeconds,
Action<UnityWebRequest> callbackOnSuccess)
{ {
using (var request = UnityWebRequest.Get(url)) using (var request = new UnityWebRequest(url, method))
{ {
var op = request.SendWebRequest(); if (method == "POST" || method == "PUT")
yield return WaitForTimeout(op, () =>
{ {
Debug.LogError("Request timed out."); request.SetRequestHeader("Content-Type", "application/json");
});
callback?.Invoke(request);
}
} }
IEnumerator CallEndpointWithPostCoroutine( if (payload != null)
string url, object payload, Action<UnityWebRequest> callback)
{
using (var request = new UnityWebRequest(url, "POST"))
{ {
byte[] bodyRaw = EncodePayload(payload); byte[] bodyRaw = EncodePayload(payload);
Debug.Log($"Payload size: {bodyRaw.Length} bytes - {payload}");
request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.uploadHandler = new UploadHandlerRaw(bodyRaw);
}
request.downloadHandler = new DownloadHandlerBuffer(); 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(); var op = request.SendWebRequest();
yield return WaitForTimeout(op, () => yield return WaitForTimeout(op, timeoutInSeconds, () =>
{ {
Debug.LogError("Request timed out."); Debug.LogError("Request timed out.");
}); });
callback?.Invoke(request);
}
}
#endregion -- Helper Methods ------------------------------------------
#region -- API Calls --------------------------------------------------
[ContextMenu("Test API Availability")]
public void TestApiAvailability()
{
var url = EndpointUrl("");
Debug.Log($"Testing API availability at: {url}");
StartCoroutine(CallEndpointWithGetCoroutine(url, (request) =>
{
if (request.result == UnityWebRequest.Result.Success) if (request.result == UnityWebRequest.Result.Success)
{ {
var body = request.downloadHandler?.text ?? string.Empty; callbackOnSuccess?.Invoke(request);
Debug.Log($"API call returned: {body}");
} }
else else
{
Debug.LogError(
$"API availability check failed: {request.error} (HTTP {request.responseCode})");
}
}));
}
[ContextMenu("Initiate Session")]
public string InitiateSession()
{
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 body = request.downloadHandler?.text ?? string.Empty;
var session = JsonUtility.FromJson<Session>(body); var errorTrace = @$"API call failed: {request.error} (HTTP {request.responseCode})
_session = session; Request Method: {method}
Request URL: {url}
Request Payload: {JsonUtility.ToJson(payload)}
Response Body: {body}";
Debug.LogError(errorTrace);
}
} }
else
{
Debug.LogError(
$"Session init failed: {request.error} (HTTP {request.responseCode})");
} }
}));
return _session.SessionId; internal static IEnumerator CallEndpointWithGetCoroutine(
string url, float timeoutInSeconds,
Action<UnityWebRequest> callbackOnSuccess)
{
yield return CallEndpointCoroutine(
url, "GET", null, timeoutInSeconds, callbackOnSuccess);
} }
#endregion -- API Calls ------------------------------------------------
internal static IEnumerator CallEndpointWithPostCoroutine(
string url, float timeoutInSeconds, object payload,
Action<UnityWebRequest> callbackOnSuccess)
{
yield return CallEndpointCoroutine(
url, "POST", payload, timeoutInSeconds, callbackOnSuccess);
}
internal static IEnumerator CallEndpointWithPutCoroutine(
string url, float timeoutInSeconds, object payload,
Action<UnityWebRequest> callbackOnSuccess)
{
yield return CallEndpointCoroutine(
url, "PUT", payload, timeoutInSeconds, callbackOnSuccess);
}
internal static IEnumerator CallEndpointWithDeleteCoroutine(
string url, float timeoutInSeconds,
Action<UnityWebRequest> callbackOnSuccess)
{
yield return CallEndpointCoroutine(
url, "DELETE", null, timeoutInSeconds, callbackOnSuccess);
}
}
internal enum Environment
{
Development,
Production
}
[Serializable]
internal struct ChatServicePayload
{
public string message;
} }
} }

View File

@ -1,2 +1,2 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 6df1f7169d6eca04abd7db0f712639ff guid: 1a14c6225c9606e4baff928b379f19fb

View File

@ -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<Session>(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 ------------------------------------------------
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6df1f7169d6eca04abd7db0f712639ff