Improves /chat/ endpoint client.

This commit is contained in:
Jonas Luz Jr. 2025-11-23 13:46:51 -03:00
parent b3679cdffe
commit ebcb98d8d3
10 changed files with 465 additions and 69 deletions

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a9e9a88d8a4978c4ba0e6bf106276b7a guid: b8301e63683d16d41abf9fbbf3b74507
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f21e446ea6b394b43b44c4d1e396ff48
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 004ea84725223334994cf72d2115c180
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2a324b79bfeaa9842b22d76378b74b5d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -117,51 +117,36 @@ Response Body: {body}";
url, "DELETE", null, timeoutInSeconds, callbackOnSuccess); url, "DELETE", null, timeoutInSeconds, callbackOnSuccess);
} }
internal static IEnumerator ReadAudioResponseCoroutine( internal static IEnumerator DownloadAudioCoroutine(
UnityWebRequest request, string url,
float timeoutInSeconds,
Action<AudioClip> callbackOnSuccess) Action<AudioClip> callbackOnSuccess)
{ {
byte[] audioBytes = request.downloadHandler.data; using (UnityWebRequest mmRequest =
if (audioBytes == null || audioBytes.Length == 0) UnityWebRequestMultimedia.GetAudioClip(url, AudioType.OGGVORBIS))
{ {
Debug.LogError("No audio data received.");
yield break;
}
// Save temporarily to file for loading as AudioClip var op = mmRequest.SendWebRequest();
string tempPath = Path.Combine(Application.persistentDataPath, "tts_temp.ogg"); yield return WaitForTimeout(op, timeoutInSeconds, () =>
File.WriteAllBytes(tempPath, audioBytes);
using (var file = UnityWebRequestMultimedia.GetAudioClip(
"file://" + tempPath, AudioType.OGGVORBIS))
{
yield return file.SendWebRequest();
if (file.result == UnityWebRequest.Result.Success)
{ {
AudioClip clip = DownloadHandlerAudioClip.GetContent(file); Debug.LogError("Request timed out.");
callbackOnSuccess?.Invoke(clip); });
}
else
{
Debug.LogError($"Error loading AudioClip: {file.error}");
}
}
// Remove temporary file if (mmRequest.result != UnityWebRequest.Result.Success)
File.Delete(tempPath); {
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;
}
} }

View File

@ -90,6 +90,8 @@ namespace PPGIA.X540.Project3.API
[ContextMenu("Initiate Session")] [ContextMenu("Initiate Session")]
public void InitiateSession() public void InitiateSession()
{ {
StopAllCoroutines();
var url = EndpointUrl(_sessionInitEndpoint, _clientId); var url = EndpointUrl(_sessionInitEndpoint, _clientId);
StartCoroutine(ApiClient.CallEndpointWithPostCoroutine( StartCoroutine(ApiClient.CallEndpointWithPostCoroutine(
@ -110,6 +112,8 @@ namespace PPGIA.X540.Project3.API
return; return;
} }
StopAllCoroutines();
var url = EndpointUrl(_sessionCloseEndpoint, _session.SessionId); var url = EndpointUrl(_sessionCloseEndpoint, _session.SessionId);
StartCoroutine(ApiClient.CallEndpointWithDeleteCoroutine( StartCoroutine(ApiClient.CallEndpointWithDeleteCoroutine(
@ -130,6 +134,8 @@ namespace PPGIA.X540.Project3.API
return; return;
} }
StopAllCoroutines();
// Build the endpoint URL and payload // Build the endpoint URL and payload
var url = EndpointUrl(_chatEndpoint, _session.SessionId); var url = EndpointUrl(_chatEndpoint, _session.SessionId);
var payload = new ChatServicePayload { message = _query }; var payload = new ChatServicePayload { message = _query };
@ -138,8 +144,18 @@ namespace PPGIA.X540.Project3.API
StartCoroutine(ApiClient.CallEndpointWithPostCoroutine( StartCoroutine(ApiClient.CallEndpointWithPostCoroutine(
url, _timeoutInSeconds, payload, (request) => url, _timeoutInSeconds, payload, (request) =>
{ {
StartCoroutine(ApiClient.ReadAudioResponseCoroutine( var body = request.downloadHandler?.text ?? string.Empty;
request, (audioClip) => var response = ApiModel.FromJson<ChatServiceResponse>(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) if (audioClip == null)
{ {

View File

@ -0,0 +1,56 @@
using System;
using UnityEngine;
namespace PPGIA.X540.Project3.API
{
[Serializable]
public class ApiModel
{
public static T FromJson<T>(string json) =>
JsonUtility.FromJson<T>(json);
public static string ToJson<T>(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
}
}

View File

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