From ebcb98d8d36d419ab6dd23dab22d41438151525c Mon Sep 17 00:00:00 2001 From: "Jonas Luz Jr." Date: Sun, 23 Nov 2025 13:46:51 -0300 Subject: [PATCH] Improves /chat/ endpoint client. --- .../_Client/{Scripts/Types.meta => GUI.meta} | 2 +- Assets/_Client/Prefabs.meta | 8 + .../_Client/Prefabs/-- GAME MANAGER --.prefab | 337 ++++++++++++++++++ .../Prefabs/-- GAME MANAGER --.prefab.meta | 7 + Assets/_Client/Scenes.meta | 8 + Assets/_Client/Scripts/ApiClient.cs | 73 ++-- Assets/_Client/Scripts/ApiClientManager.cs | 22 +- Assets/_Client/Scripts/ApiModel.cs | 56 +++ .../Session.cs.meta => ApiModel.cs.meta} | 0 Assets/_Client/Scripts/Types/Session.cs | 21 -- 10 files changed, 465 insertions(+), 69 deletions(-) rename Assets/_Client/{Scripts/Types.meta => GUI.meta} (77%) create mode 100644 Assets/_Client/Prefabs.meta create mode 100644 Assets/_Client/Prefabs/-- GAME MANAGER --.prefab create mode 100644 Assets/_Client/Prefabs/-- GAME MANAGER --.prefab.meta create mode 100644 Assets/_Client/Scenes.meta create mode 100644 Assets/_Client/Scripts/ApiModel.cs rename Assets/_Client/Scripts/{Types/Session.cs.meta => ApiModel.cs.meta} (100%) delete mode 100644 Assets/_Client/Scripts/Types/Session.cs diff --git a/Assets/_Client/Scripts/Types.meta b/Assets/_Client/GUI.meta similarity index 77% rename from Assets/_Client/Scripts/Types.meta rename to Assets/_Client/GUI.meta index b850240..8abada2 100644 --- a/Assets/_Client/Scripts/Types.meta +++ b/Assets/_Client/GUI.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a9e9a88d8a4978c4ba0e6bf106276b7a +guid: b8301e63683d16d41abf9fbbf3b74507 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/_Client/Prefabs.meta b/Assets/_Client/Prefabs.meta new file mode 100644 index 0000000..5f24989 --- /dev/null +++ b/Assets/_Client/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f21e446ea6b394b43b44c4d1e396ff48 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Client/Prefabs/-- GAME MANAGER --.prefab b/Assets/_Client/Prefabs/-- GAME MANAGER --.prefab new file mode 100644 index 0000000..f6c5e37 --- /dev/null +++ b/Assets/_Client/Prefabs/-- GAME MANAGER --.prefab @@ -0,0 +1,337 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &3050228793350555746 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3228149243574310962} + m_Layer: 0 + m_Name: -- GAME MANAGER -- + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3228149243574310962 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3050228793350555746} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7625920793580372814} + - {fileID: 8663494998515940154} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &4601626619299515270 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7625920793580372814} + - component: {fileID: 1118623755729130604} + - component: {fileID: 4693018637014637836} + m_Layer: 0 + m_Name: -- API Manager -- + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7625920793580372814 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4601626619299515270} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3228149243574310962} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!82 &1118623755729130604 +AudioSource: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4601626619299515270} + m_Enabled: 1 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: 0} + m_audioClip: {fileID: 0} + m_Resource: {fileID: 0} + m_PlayOnAwake: 1 + m_Volume: 1 + m_Pitch: 1 + Loop: 0 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!114 &4693018637014637836 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4601626619299515270} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6df1f7169d6eca04abd7db0f712639ff, type: 3} + m_Name: + m_EditorClassIdentifier: PpgiaX540P3::PPGIA.X540.Project3.ApiClient + _apiBaseUrlDev: http://127.0.0.1:8000 + _apiBaseUrlProd: https://gyzypaneqa.execute-api.us-east-1.amazonaws.com/api/ + _environment: 0 + _sessionInitEndpoint: /session/init + _sessionCloseEndpoint: /session/close + _chatEndpoint: /chat/ + _llmAgentEndpoint: /agent/ask + _ttsEndpoint: /tts/synthesize + _sttEndpoint: /stt/upload + _clientId: unity-client + _timeoutInSeconds: 10 + _query: 'Onde o caso do ET de Varginha foi registrado? ' + _session: + session_id: 40abe282-9485-4bbd-aa3a-8d7209706626 + created_at: 1763850149 + _audioSource: {fileID: 1118623755729130604} +--- !u!1 &5836695571582163658 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8663494998515940154} + - component: {fileID: 1938294408165125026} + - component: {fileID: 796320736253214861} + m_Layer: 0 + m_Name: -- AudioManager -- + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8663494998515940154 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5836695571582163658} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3228149243574310962} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1938294408165125026 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5836695571582163658} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9523fc2f4430eb549ab3da789eaf70c1, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::PPGIA.X540.Project3.AudioCapture + _sampleRateInHz: 16000 + _playingBack: 0 +--- !u!82 &796320736253214861 +AudioSource: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5836695571582163658} + m_Enabled: 1 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: 4597973050289367049, guid: b4346293b16e5254aadce52e891ff5e9, type: 2} + m_audioClip: {fileID: 0} + m_Resource: {fileID: 0} + m_PlayOnAwake: 1 + m_Volume: 1 + m_Pitch: 1 + Loop: 0 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 diff --git a/Assets/_Client/Prefabs/-- GAME MANAGER --.prefab.meta b/Assets/_Client/Prefabs/-- GAME MANAGER --.prefab.meta new file mode 100644 index 0000000..04e011e --- /dev/null +++ b/Assets/_Client/Prefabs/-- GAME MANAGER --.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 004ea84725223334994cf72d2115c180 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Client/Scenes.meta b/Assets/_Client/Scenes.meta new file mode 100644 index 0000000..886e89a --- /dev/null +++ b/Assets/_Client/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2a324b79bfeaa9842b22d76378b74b5d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Client/Scripts/ApiClient.cs b/Assets/_Client/Scripts/ApiClient.cs index e303978..598a4bd 100644 --- a/Assets/_Client/Scripts/ApiClient.cs +++ b/Assets/_Client/Scripts/ApiClient.cs @@ -52,7 +52,7 @@ namespace PPGIA.X540.Project3.API byte[] bodyRaw = EncodePayload(payload); Debug.Log($"Payload size: {bodyRaw.Length} bytes - {payload}"); request.uploadHandler = new UploadHandlerRaw(bodyRaw); - } + } request.downloadHandler = new DownloadHandlerBuffer(); @@ -86,7 +86,7 @@ Response Body: {body}"; } internal static IEnumerator CallEndpointWithGetCoroutine( - string url, float timeoutInSeconds, + string url, float timeoutInSeconds, Action callbackOnSuccess) { yield return CallEndpointCoroutine( @@ -94,7 +94,7 @@ Response Body: {body}"; } internal static IEnumerator CallEndpointWithPostCoroutine( - string url, float timeoutInSeconds, object payload, + string url, float timeoutInSeconds, object payload, Action callbackOnSuccess) { yield return CallEndpointCoroutine( @@ -102,7 +102,7 @@ Response Body: {body}"; } internal static IEnumerator CallEndpointWithPutCoroutine( - string url, float timeoutInSeconds, object payload, + string url, float timeoutInSeconds, object payload, Action callbackOnSuccess) { yield return CallEndpointCoroutine( @@ -110,58 +110,43 @@ Response Body: {body}"; } internal static IEnumerator CallEndpointWithDeleteCoroutine( - string url, float timeoutInSeconds, + string url, float timeoutInSeconds, Action callbackOnSuccess) { yield return CallEndpointCoroutine( url, "DELETE", null, timeoutInSeconds, callbackOnSuccess); } - internal static IEnumerator ReadAudioResponseCoroutine( - UnityWebRequest request, + internal static IEnumerator DownloadAudioCoroutine( + string url, + float timeoutInSeconds, Action callbackOnSuccess) { - byte[] audioBytes = request.downloadHandler.data; - if (audioBytes == null || audioBytes.Length == 0) + using (UnityWebRequest mmRequest = + UnityWebRequestMultimedia.GetAudioClip(url, AudioType.OGGVORBIS)) { - Debug.LogError("No audio data received."); - yield break; - } - // Save temporarily to file for loading as AudioClip - string tempPath = Path.Combine(Application.persistentDataPath, "tts_temp.ogg"); - File.WriteAllBytes(tempPath, audioBytes); - - using (var file = UnityWebRequestMultimedia.GetAudioClip( - "file://" + tempPath, AudioType.OGGVORBIS)) - { - yield return file.SendWebRequest(); - - if (file.result == UnityWebRequest.Result.Success) + var op = mmRequest.SendWebRequest(); + yield return WaitForTimeout(op, timeoutInSeconds, () => { - AudioClip clip = DownloadHandlerAudioClip.GetContent(file); - callbackOnSuccess?.Invoke(clip); - } - else - { - Debug.LogError($"Error loading AudioClip: {file.error}"); - } - } + Debug.LogError("Request timed out."); + }); - // Remove temporary file - File.Delete(tempPath); + if (mmRequest.result != UnityWebRequest.Result.Success) + { + Debug.LogError($"Error loading audio: {mmRequest.error}"); + yield break; + } + + AudioClip clip = DownloadHandlerAudioClip.GetContent(mmRequest); + if (clip == null) + { + Debug.LogError("AudioClip is null after download."); + yield break; + } + + callbackOnSuccess?.Invoke(clip); + } } } - - internal enum Environment - { - Development, - Production - } - - [Serializable] - internal struct ChatServicePayload - { - public string message; - } } \ No newline at end of file diff --git a/Assets/_Client/Scripts/ApiClientManager.cs b/Assets/_Client/Scripts/ApiClientManager.cs index 5420ec4..7443721 100644 --- a/Assets/_Client/Scripts/ApiClientManager.cs +++ b/Assets/_Client/Scripts/ApiClientManager.cs @@ -90,6 +90,8 @@ namespace PPGIA.X540.Project3.API [ContextMenu("Initiate Session")] public void InitiateSession() { + StopAllCoroutines(); + var url = EndpointUrl(_sessionInitEndpoint, _clientId); StartCoroutine(ApiClient.CallEndpointWithPostCoroutine( @@ -103,13 +105,15 @@ namespace PPGIA.X540.Project3.API [ContextMenu("Close Session")] public void CloseSession() - { + { if (_session == null) { Debug.LogWarning("No active session to close."); return; } + StopAllCoroutines(); + var url = EndpointUrl(_sessionCloseEndpoint, _session.SessionId); StartCoroutine(ApiClient.CallEndpointWithDeleteCoroutine( @@ -130,6 +134,8 @@ namespace PPGIA.X540.Project3.API return; } + StopAllCoroutines(); + // Build the endpoint URL and payload var url = EndpointUrl(_chatEndpoint, _session.SessionId); var payload = new ChatServicePayload { message = _query }; @@ -138,8 +144,18 @@ namespace PPGIA.X540.Project3.API StartCoroutine(ApiClient.CallEndpointWithPostCoroutine( url, _timeoutInSeconds, payload, (request) => { - StartCoroutine(ApiClient.ReadAudioResponseCoroutine( - request, (audioClip) => + var body = request.downloadHandler?.text ?? string.Empty; + var response = ApiModel.FromJson(body); + var audioUrl = response?.AudioUrl; + if (string.IsNullOrEmpty(audioUrl)) + { + Debug.LogWarning("No audio URL in response."); + return; + } + + Debug.Log($"Downloading audio from: {audioUrl}"); + StartCoroutine(ApiClient.DownloadAudioCoroutine( + audioUrl, _timeoutInSeconds, (audioClip) => { if (audioClip == null) { diff --git a/Assets/_Client/Scripts/ApiModel.cs b/Assets/_Client/Scripts/ApiModel.cs new file mode 100644 index 0000000..5f4431b --- /dev/null +++ b/Assets/_Client/Scripts/ApiModel.cs @@ -0,0 +1,56 @@ +using System; + +using UnityEngine; + + +namespace PPGIA.X540.Project3.API +{ + [Serializable] + public class ApiModel + { + public static T FromJson(string json) => + JsonUtility.FromJson(json); + + public static string ToJson(T obj) => + JsonUtility.ToJson(obj); + } + + [Serializable] + public class Session: ApiModel + { + public string session_id; + public string created_at; + + // Optional convenience properties with C#-style names: + public string SessionId => session_id; + public DateTime CreatedAt => DateTime.Parse(created_at); + } + + [Serializable] + public class ChatServicePayload : ApiModel + { + public string message; + } + + public class ChatServiceResponse : ApiModel + { + public string session_id; + public string message; + public string audio_url; + public string audio_key; + public int expires_in; + + // Optional convenience properties with C#-style names: + public string SessionId => session_id; + public string Message => message; + public string AudioUrl => audio_url; + public string AudioKey => audio_key; + public int ExpiresIn => expires_in; + } + + internal enum Environment + { + Development, + Production + } +} \ No newline at end of file diff --git a/Assets/_Client/Scripts/Types/Session.cs.meta b/Assets/_Client/Scripts/ApiModel.cs.meta similarity index 100% rename from Assets/_Client/Scripts/Types/Session.cs.meta rename to Assets/_Client/Scripts/ApiModel.cs.meta diff --git a/Assets/_Client/Scripts/Types/Session.cs b/Assets/_Client/Scripts/Types/Session.cs deleted file mode 100644 index ec35a43..0000000 --- a/Assets/_Client/Scripts/Types/Session.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -using UnityEngine; - - -namespace PPGIA.X540.Project3.API -{ - [Serializable] - public class Session - { - public string session_id; - public string created_at; - - // Optional convenience properties with C#-style names: - public string SessionId => session_id; - public DateTime CreatedAt => DateTime.Parse(created_at); - - public static Session FromJson(string json) => - JsonUtility.FromJson(json); - } -} \ No newline at end of file