From b6e5930ead8b9810676b9e405a0cf0e4ad9d06a5 Mon Sep 17 00:00:00 2001 From: "Jonas Luz Jr." Date: Sat, 22 Nov 2025 11:19:11 -0300 Subject: [PATCH] Implements API touching and session initiation calls. --- Assets/Scenes/SampleScene.unity | 96 ++++++++++- Assets/_Client/Scripts/ApiClient.cs | 158 +++++++++++++++++++ Assets/_Client/Scripts/ApiClient.cs.meta | 2 + Assets/_Client/Scripts/AudioCapture.cs | 57 +++---- Assets/_Client/Scripts/Types.meta | 8 + Assets/_Client/Scripts/Types/Session.cs | 21 +++ Assets/_Client/Scripts/Types/Session.cs.meta | 2 + 7 files changed, 314 insertions(+), 30 deletions(-) create mode 100644 Assets/_Client/Scripts/ApiClient.cs create mode 100644 Assets/_Client/Scripts/ApiClient.cs.meta create mode 100644 Assets/_Client/Scripts/Types.meta create mode 100644 Assets/_Client/Scripts/Types/Session.cs create mode 100644 Assets/_Client/Scripts/Types/Session.cs.meta diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 91fd828..b7d015b 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -150,6 +150,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Assembly-CSharp::PPGIA.X540.Project3.AudioCapture _sampleRateInHz: 16000 + _playingBack: 0 --- !u!4 &248765894 Transform: m_ObjectHideFlags: 0 @@ -158,12 +159,12 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 248765892} serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + 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: 0} + m_Father: {fileID: 1044611067} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!82 &248765895 AudioSource: @@ -575,6 +576,95 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1044611066 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1044611067} + 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 &1044611067 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1044611066} + 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: 1912693322} + - {fileID: 248765894} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1912693321 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1912693322} + - component: {fileID: 1912693323} + 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 &1912693322 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1912693321} + 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: 1044611067} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1912693323 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1912693321} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6df1f7169d6eca04abd7db0f712639ff, type: 3} + m_Name: + m_EditorClassIdentifier: PpgiaX540P3::PPGIA.X540.Project3.ApiClient + _apiBaseUrl: https://gyzypaneqa.execute-api.us-east-1.amazonaws.com/api/ + _sessionInitEndpoint: /session/init + _sessionCloseEndpoint: /session/close + _llmAgentEndpoint: /agent/ask + _ttsEndpoint: /tts/synthesize + _sttEndpoint: /stt/upload + _clientId: unity-client + _timeoutInSeconds: 10 + _query: + _session: + session_id: ddcc99fb-0474-4968-9b0c-ae13c56dfba6 + created_at: 1763819290 --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 @@ -582,4 +672,4 @@ SceneRoots: - {fileID: 330585546} - {fileID: 410087041} - {fileID: 832575519} - - {fileID: 248765894} + - {fileID: 1044611067} diff --git a/Assets/_Client/Scripts/ApiClient.cs b/Assets/_Client/Scripts/ApiClient.cs new file mode 100644 index 0000000..0988a08 --- /dev/null +++ b/Assets/_Client/Scripts/ApiClient.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections; +using System.Linq; +using System.Text; + +using UnityEngine; +using UnityEngine.Networking; + + +namespace PPGIA.X540.Project3.API +{ + public class ApiClient : MonoBehaviour + { + #region -- Inspector Fields ------------------------------------------- + [Header("API URL Settings")] + [SerializeField] + private string _apiBaseUrl = "https://api.example.com"; + + [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( + UnityWebRequestAsyncOperation operation, + Action callbackIfTimeout = null) + { + float startTime = Time.realtimeSinceStartup; + while (!operation.isDone) + { + if (Time.realtimeSinceStartup - startTime > _timeoutInSeconds) + { + callbackIfTimeout?.Invoke(); + yield break; + } + yield return null; + } + } + + IEnumerator CallEndpointWithGetCoroutine( + string url, Action callback) + { + using (var request = UnityWebRequest.Get(url)) + { + var op = request.SendWebRequest(); + yield return WaitForTimeout(op, () => + { + Debug.LogError("Request timed out."); + }); + + callback?.Invoke(request); + } + } + + 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"); + + var op = request.SendWebRequest(); + yield return WaitForTimeout(op, () => + { + 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) + { + 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})"); + } + })); + } + + [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 session = JsonUtility.FromJson(body); + _session = session; + } + else + { + Debug.LogError( + $"Session init failed: {request.error} (HTTP {request.responseCode})"); + } + })); + + return _session.SessionId; + } + #endregion -- API Calls ------------------------------------------------ + } +} \ No newline at end of file diff --git a/Assets/_Client/Scripts/ApiClient.cs.meta b/Assets/_Client/Scripts/ApiClient.cs.meta new file mode 100644 index 0000000..c98d09d --- /dev/null +++ b/Assets/_Client/Scripts/ApiClient.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6df1f7169d6eca04abd7db0f712639ff \ No newline at end of file diff --git a/Assets/_Client/Scripts/AudioCapture.cs b/Assets/_Client/Scripts/AudioCapture.cs index 40b65f2..3a99d5a 100644 --- a/Assets/_Client/Scripts/AudioCapture.cs +++ b/Assets/_Client/Scripts/AudioCapture.cs @@ -6,51 +6,54 @@ namespace PPGIA.X540.Project3 [RequireComponent(typeof(AudioSource))] public class AudioCapture : MonoBehaviour { - public enum SampleRate { + public enum SampleRate + { Hz16000 = 16000, - Hz44100 = 44100, - Hz48000 = 48000, - Hz96000 = 96000 + Hz44100 = 44100, + Hz48000 = 48000, + Hz96000 = 96000 } - + [Header("Audio Capture Settings")] [SerializeField] private SampleRate _sampleRateInHz = SampleRate.Hz44100; + [SerializeField] + private bool _playingBack = false; + private AudioSource _audioSource; private AudioClip _audioClip; - void Start() + void Awake() { _audioSource = GetComponent(); + } + + void OnDestroy() + { + Stop(); + } + + [ContextMenu("Record Audio")] + public void Record() + { _audioClip = Microphone.Start(null, true, 1, (int)_sampleRateInHz); _audioSource.clip = _audioClip; _audioSource.loop = true; // Wait until the microphone starts recording - while (!(Microphone.GetPosition(null) > 0)) { } + while (!(Microphone.GetPosition(null) > 0)) { } - _audioSource.Play(); + if (_playingBack) + { + _audioSource.Play(); + } } - // void Update() - // { - // var samples = new float[SAMPLE_RATE]; - // _audioClip.GetData(samples, 0); - - // for (int i = 0; i < samples.Length; i++) - // { - // samples[i] *= _gain; - // samples[i] = Mathf.Clamp(samples[i], -1.0f, 1.0f); - // } - - // var processedClip = AudioClip.Create( - // "ProcessedClip", samples.Length, _audioClip.channels, - // SAMPLE_RATE, false); - // processedClip.SetData(samples, 0); - - // _audioSource.clip = processedClip; - // if (!_audioSource.isPlaying) _audioSource.Play(); - // } + [ContextMenu("Stop Audio")] + public void Stop() + { + Microphone.End(null); + } } } diff --git a/Assets/_Client/Scripts/Types.meta b/Assets/_Client/Scripts/Types.meta new file mode 100644 index 0000000..b850240 --- /dev/null +++ b/Assets/_Client/Scripts/Types.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a9e9a88d8a4978c4ba0e6bf106276b7a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Client/Scripts/Types/Session.cs b/Assets/_Client/Scripts/Types/Session.cs new file mode 100644 index 0000000..ec35a43 --- /dev/null +++ b/Assets/_Client/Scripts/Types/Session.cs @@ -0,0 +1,21 @@ +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 diff --git a/Assets/_Client/Scripts/Types/Session.cs.meta b/Assets/_Client/Scripts/Types/Session.cs.meta new file mode 100644 index 0000000..e2442db --- /dev/null +++ b/Assets/_Client/Scripts/Types/Session.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 905309ff23223794faea2150bde7a080 \ No newline at end of file