initial upload

This commit is contained in:
tom.hempel
2025-09-30 17:58:33 +02:00
commit 69b0c79692
4818 changed files with 229318 additions and 0 deletions

View File

@ -0,0 +1,269 @@
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using ColorUtility = UnityEngine.ColorUtility;
namespace Convai.Scripts.Runtime.UI
{
/// <summary>
/// Manages the chat UI for displaying messages from characters and the player.
/// </summary>
public class ChatBoxUI : ChatUIBase
{
private const int MAX_MESSAGES = 25;
[SerializeField] private GameObject _playerMessageObject, _characterMessageObject;
private readonly List<Message> _messageList = new();
private GameObject _chatPanel;
private ScrollRect _chatScrollRect;
private Speaker _currentSpeaker;
private bool _isFirstMessage = true;
private bool _isNewMessage;
/// <summary>
/// Initializes the chat UI with the specified prefab.
/// </summary>
/// <param name="uiPrefab">The UI prefab to instantiate.</param>
public override void Initialize(GameObject uiPrefab)
{
UIInstance = Instantiate(uiPrefab);
_chatPanel = UIInstance.transform.GetChild(0).GetChild(0).GetChild(0).GetChild(0).gameObject;
_chatScrollRect = UIInstance.transform.GetChild(0).GetChild(0).GetComponent<ScrollRect>();
_isFirstMessage = true;
_currentSpeaker = Speaker.Player;
}
/// <summary>
/// Sends a message as a character.
/// </summary>
/// <param name="charName">The name of the character.</param>
/// <param name="text">The message text.</param>
/// <param name="characterTextColor">The color of the character's text.</param>
public override void SendCharacterText(string charName, string text, Color characterTextColor)
{
BroadcastCharacterDialogue(charName, text, characterTextColor);
}
/// <summary>
/// Sends a message as the player.
/// </summary>
/// <param name="playerName">The name of the player.</param>
/// <param name="text">The message text.</param>
/// <param name="playerTextColor">The color of the player's text.</param>
public override void SendPlayerText(string playerName, string text, Color playerTextColor)
{
BroadcastPlayerDialogue(playerName, text, playerTextColor);
}
/// <summary>
/// Clears all messages from the UI.
/// </summary>
public void ClearUI()
{
foreach (Message message in _messageList)
{
Destroy(message.SenderTextObject.gameObject);
Destroy(message.MessageTextObject.gameObject);
}
_messageList.Clear();
}
// Helper methods and private functions are below. These are not part of the public API
// and are used internally by the ChatBoxUI class to manage chat messages.
/// <summary>
/// Broadcasts a dialogue message from a character.
/// </summary>
/// <param name="characterName">Name of the character.</param>
/// <param name="text">Text of the dialogue message.</param>
/// <param name="characterTextColor">Color of the character's text.</param>
private void BroadcastCharacterDialogue(string characterName, string text, Color characterTextColor)
{
string trimmedText = text.Trim();
if (_currentSpeaker != Speaker.Character || _isFirstMessage)
{
_isFirstMessage = false;
_currentSpeaker = Speaker.Character;
HandleNewCharacterMessage(characterName, trimmedText, characterTextColor);
}
else
{
AppendToExistingMessage(trimmedText);
}
_currentSpeaker = Speaker.Character;
ScrollToBottom();
}
/// <summary>
/// Handles a new dialogue message from a character.
/// </summary>
/// <param name="characterName">Name of the character.</param>
/// <param name="text">Text of the dialogue message.</param>
/// <param name="characterTextColor">Color of the character's text.</param>
private void HandleNewCharacterMessage(string characterName, string text, Color characterTextColor)
{
if (_messageList.Count >= MAX_MESSAGES) DestroyOldestMessage();
CreateNewMessage(text, characterName, characterTextColor, false);
}
/// <summary>
/// Broadcasts a dialogue message from a player.
/// </summary>
/// <param name="playerName">Name of the player.</param>
/// <param name="text">Text of the dialogue message.</param>
/// <param name="playerTextColor">Color of the player's text.</param>
private void BroadcastPlayerDialogue(string playerName, string text, Color playerTextColor)
{
string trimmedText = text.Trim();
if (_currentSpeaker != Speaker.Player || !_messageList.Any())
{
_currentSpeaker = Speaker.Player;
HandleNewPlayerMessage(playerName, trimmedText, playerTextColor);
}
else
{
ReplaceExistingPlayerMessage(playerName, trimmedText, playerTextColor);
}
_currentSpeaker = Speaker.Player;
ScrollToBottom();
}
/// <summary>
/// Handles a new dialogue message from a player.
/// </summary>
/// <param name="playerName">Name of the player.</param>
/// <param name="text">Text of the dialogue message.</param>
/// <param name="playerTextColor">Color of the player's text.</param>
private void HandleNewPlayerMessage(string playerName, string text, Color playerTextColor)
{
if (_messageList.Count >= MAX_MESSAGES) DestroyOldestMessage();
CreateNewMessage(text, playerName, playerTextColor, true);
}
/// <summary>
/// Replaces an existing player message with a new one.
/// </summary>
/// <param name="playerName">Name of the player.</param>
/// <param name="text">New text of the dialogue message.</param>
/// <param name="playerTextColor">Color of the player's text.</param>
private void ReplaceExistingPlayerMessage(string playerName, string text, Color playerTextColor)
{
Message lastMessage = _messageList[^1];
lastMessage.MessageTextObject.text = text;
lastMessage.SenderTextObject.text = FormatSpeakerName(playerName, playerTextColor);
// RTL Update done due to text arriving after create message
// Once for every message
if (text != "" && _isNewMessage)
{
lastMessage.RTLUpdate();
_isNewMessage = false;
}
}
/// <summary>
/// Appends a text to the existing message.
/// </summary>
/// <param name="text">Text which needs to append to the existing message.</param>
private void AppendToExistingMessage(string text)
{
if (_messageList.Count > 0)
{
Message lastMessage = _messageList[^1];
lastMessage.MessageTextObject.text += " " + text;
if (text != "" && _isNewMessage)
{
lastMessage.RTLUpdate();
_isNewMessage = false;
}
}
}
/// <summary>
/// </summary>
private void ScrollToBottom()
{
LayoutRebuilder.ForceRebuildLayoutImmediate(_chatPanel.GetComponent<RectTransform>());
Canvas.ForceUpdateCanvases();
_chatScrollRect.verticalNormalizedPosition = 0f;
}
/// <summary>
/// Formats the speaker name
/// </summary>
/// <param name="speakerName">Name of the speaker.</param>
/// <param name="speakerColor">Color of the speaker's text.</param>
/// <returns>Formatted speaker's name with color tag.</returns>
private static string FormatSpeakerName(string speakerName, Color speakerColor)
{
string speakerColorHtml = ColorUtility.ToHtmlStringRGB(speakerColor);
return $"<color=#{speakerColorHtml}>{speakerName}</color>";
}
/// <summary>
/// Destroys the oldest message in the chat UI.
/// </summary>
private void DestroyOldestMessage()
{
Destroy(_messageList[0].SenderTextObject.gameObject);
Destroy(_messageList[0].MessageTextObject.gameObject);
_messageList.RemoveAt(0);
}
/// <summary>
/// Creates a new dialogue message.
/// </summary>
/// <param name="text">Text of the dialogue message.</param>
/// <param name="speakerName">Name of the speaker.</param>
/// <param name="speakerColor">Color of the speaker's text.</param>
/// <param name="isSpeakerPlayer"> Flag to check if the speaker is a player.</param>
private void CreateNewMessage(string text, string speakerName, Color speakerColor, bool isSpeakerPlayer)
{
_isNewMessage = true;
GameObject messageInstance = isSpeakerPlayer ? Instantiate(_playerMessageObject, _chatPanel.transform) : Instantiate(_characterMessageObject, _chatPanel.transform);
messageInstance.SetActive(true);
Transform container = messageInstance.transform.Find("Container");
Transform senderBox = container.Find("SenderBox");
Message newMessage = new()
{
SenderTextObject = senderBox.Find("Sender").GetComponent<TMP_Text>(),
MessageTextObject = container.Find("Message").GetComponent<TMP_Text>()
};
newMessage.SenderTextObject.text = FormatSpeakerName(speakerName, speakerColor);
newMessage.MessageTextObject.text = text;
_messageList.Add(newMessage);
}
/// <summary>
/// Enumeration of the possible speakers.
/// </summary>
private enum Speaker
{
Player,
Character
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f71cd28901fa48b285d005b201cff508
timeCreated: 1699396061

View File

@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Convai.Scripts.Runtime.Addons;
using Convai.Scripts.Runtime.Core;
using Convai.Scripts.Runtime.Extensions;
using Convai.Scripts.Runtime.LoggerSystem;
using UnityEngine;
using UnityEngine.UI;
namespace Convai.Scripts.Runtime.UI
{
/// <summary>
/// Base class for chat UI components, providing common functionality and abstract methods to be implemented by derived
/// classes.
/// </summary>
public abstract class ChatUIBase : MonoBehaviour, IChatUI
{
[SerializeField] protected GameObject recordingMarker;
private readonly List<Character> _characters = new();
private float _markerInitialAlpha;
private Image _recordingMarkerImage;
[NonSerialized] protected GameObject UIInstance;
/// <summary>
/// Initializes the recording marker and subscribes to the OnPlayerSpeakingChanged event.
/// </summary>
protected virtual void Start()
{
SetupMarkerImage();
SetRecordingMarkerActive(true);
ConvaiGRPCAPI.Instance.OnPlayerSpeakingChanged += OnPlayerSpeakingChanged;
}
/// <summary>
/// Initializes the UI with the provided prefab.
/// </summary>
/// <param name="uiPrefab">The UI prefab to instantiate.</param>
public abstract void Initialize(GameObject uiPrefab);
/// <summary>
/// Activates the UI instance if transcript UI status is active.
/// </summary>
public virtual void ActivateUI()
{
if (UISaveLoadSystem.Instance.TranscriptUIActiveStatus)
{
SetUIActive(true);
UIStatusChange?.Invoke(true);
}
}
/// <summary>
/// Deactivates the UI instance.
/// </summary>
public virtual void DeactivateUI()
{
SetUIActive(false);
UIStatusChange.Invoke(false);
}
/// <summary>
/// Sends character text to the UI.
/// </summary>
/// <param name="charName">The name of the character.</param>
/// <param name="text">The text to send.</param>
/// <param name="characterTextColor">The color of the character's text.</param>
public abstract void SendCharacterText(string charName, string text, Color characterTextColor);
/// <summary>
/// Sends player text to the UI.
/// </summary>
/// <param name="playerName">The name of the player.</param>
/// <param name="text">The text to send.</param>
/// <param name="playerTextColor">The color of the player's text.</param>
public abstract void SendPlayerText(string playerName, string text, Color playerTextColor);
/// <summary>
/// Retrieves the CanvasGroup component from the UI instance.
/// </summary>
/// <returns>The CanvasGroup component.</returns>
public CanvasGroup GetCanvasGroup()
{
return UIInstance.GetComponent<CanvasGroup>();
}
public static event Action<bool> UIStatusChange;
private void SetupMarkerImage()
{
if (recordingMarker == null) throw new NullReferenceException("Recording Marker Image cannot be null, please assign an image for it");
_recordingMarkerImage = recordingMarker.GetComponent<Image>();
if (_recordingMarkerImage == null) throw new NullReferenceException("Recording Marker does not have an Image Component attached, system cannot work without it");
_markerInitialAlpha = _recordingMarkerImage.color.a;
}
/// <summary>
/// Adds a character to the list of known characters if it does not already exist.
/// </summary>
/// <param name="character">The character to add.</param>
public void AddCharacter(Character character)
{
if (!HasCharacter(character.characterName))
_characters.Add(character);
}
/// <summary>
/// Checks if a character with the given name exists in the list of known characters.
/// </summary>
/// <param name="characterName">The name of the character to check.</param>
/// <returns>True if the character exists, false otherwise.</returns>
public bool HasCharacter(string characterName)
{
return _characters.Any(character => character.characterName == characterName);
}
/// <summary>
/// Handles the player speaking state change by updating the recording marker's visibility.
/// </summary>
/// <param name="isSpeaking">Whether the player is currently speaking.</param>
private void OnPlayerSpeakingChanged(bool isSpeaking)
{
if (_recordingMarkerImage != null)
_recordingMarkerImage = _recordingMarkerImage.WithColorValue(a: isSpeaking ? 1.0f : _markerInitialAlpha);
else
ConvaiLogger.Error("Image component not found on recording marker.", ConvaiLogger.LogCategory.Character);
}
/// <summary>
/// Sets the active state of the recording marker.
/// </summary>
/// <param name="active">The active state to set.</param>
private void SetRecordingMarkerActive(bool active)
{
if (recordingMarker != null)
recordingMarker.SetActive(active);
else
ConvaiLogger.Error("Recording marker GameObject is not assigned.", ConvaiLogger.LogCategory.Character);
}
/// <summary>
/// Sets the active state of the UI instance.
/// </summary>
/// <param name="active">The active state to set.</param>
private void SetUIActive(bool active)
{
if (UIInstance != null)
UIInstance.SetActive(active);
else
ConvaiLogger.Error("UI instance GameObject is not assigned.", ConvaiLogger.LogCategory.Character);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7cea0ba3421149a5ae768e07f2214a68
timeCreated: 1700807213

View File

@ -0,0 +1,313 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Convai.Scripts.Runtime.Addons;
using Convai.Scripts.Runtime.Attributes;
using Convai.Scripts.Runtime.Core;
using Convai.Scripts.Runtime.LoggerSystem;
using Convai.Scripts.Runtime.PlayerStats;
using UnityEngine;
namespace Convai.Scripts.Runtime.UI
{
[AddComponentMenu("Convai/Chat UI Handler")]
[DisallowMultipleComponent]
[DefaultExecutionOrder(-100)]
public class ConvaiChatUIHandler : MonoBehaviour
{
/// <summary>
/// Enum to represent different UI types.
/// </summary>
public enum UIType
{
ChatBox,
QuestionAnswer,
Subtitle
}
[Header("UI Prefabs")] [Tooltip("Prefab for the chat box UI.")]
public GameObject chatBoxPrefab;
[Tooltip("Prefab for the subtitle UI.")]
public GameObject subtitlePrefab;
[Tooltip("Prefab for the question-answer UI.")]
public GameObject questionAnswerPrefab;
[Header("Character List")] [Tooltip("List of characters.")]
public List<Character> characters = new();
[ColorUsage(true)] [Tooltip("Color of the player's text. Alpha value will be ignored.")]
public Color playerTextColor = Color.white;
private ConvaiPlayerDataSO _convaiPlayerData;
private IChatUI _currentUIImplementation;
private bool _hasPlayerData;
public static ConvaiChatUIHandler Instance { get; private set; }
public Dictionary<UIType, IChatUI> GetUIAppearances { get; } = new();
private void Awake()
{
if (Instance != null)
{
// Log a warning and destroy the duplicate instance
ConvaiLogger.DebugLog("<color=red> There's More Than One ConvaiChatUIHandler </color> " + transform + " - " + Instance, ConvaiLogger.LogCategory.UI);
Destroy(gameObject);
return;
}
// Set the singleton instance
Instance = this;
ValidateUIPrefabs();
InitializeUIStrategies();
_hasPlayerData = ConvaiPlayerDataSO.GetPlayerData(out _convaiPlayerData);
}
// Subscribe to events when this component is enabled.
private void OnEnable()
{
// Subscribe to the event when saved data is loaded.
UISaveLoadSystem.Instance.OnLoad += UISaveLoadSystem_OnLoad;
// Subscribe to the event when data is saved.
UISaveLoadSystem.Instance.OnSave += UISaveLoadSystem_OnSave;
}
// Unsubscribe from events when this component is disabled.
private void OnDisable()
{
// Unsubscribe from the event when saved data is loaded.
UISaveLoadSystem.Instance.OnLoad -= UISaveLoadSystem_OnLoad;
// Unsubscribe from the event when data is saved.
UISaveLoadSystem.Instance.OnSave -= UISaveLoadSystem_OnSave;
}
private void OnDestroy()
{
SaveUIType();
}
private void OnValidate()
{
UpdateCharacterList();
RemoveDuplicateCharacters();
}
/// <summary>
/// Updates the character list by synchronizing names between Convai Transcript UI Character list and NPC
/// characterName, removing null characters, and adding missing characters.
/// </summary>
public void UpdateCharacterList()
{
// Synchronize names between Convai Transcript UI Character list and NPC characterName
for (int i = 0; i < characters.Count; i++)
{
Character character = characters[i];
// If the character's game object is missing, remove it from the list
if (character.characterGameObject == null)
characters.Remove(character);
else
// Update the character's name using the game object's characterName
character.characterName = character.characterGameObject.characterName;
}
// Remove null characters
characters = characters.Where(c => c.characterGameObject != null).ToList();
// Add missing characters
foreach (ConvaiNPC convaiNpc in FindObjectsOfType<ConvaiNPC>())
{
if (characters.Any(c => c.characterGameObject == convaiNpc))
continue;
characters.Add(new Character
{
characterGameObject = convaiNpc,
characterName = convaiNpc.characterName
});
}
}
/// <summary>
/// Removes duplicate characters from the character list based on their GameObject.
/// </summary>
private void RemoveDuplicateCharacters()
{
characters = characters
.GroupBy(c => c.characterGameObject)
.Select(g => g.First())
.ToList();
}
/// <summary>
/// Event handler when saved data is loaded.
/// </summary>
private void UISaveLoadSystem_OnLoad()
{
_currentUIImplementation = GetChatUIByUIType(UISaveLoadSystem.Instance.UIType);
SetUIType(UISaveLoadSystem.Instance.UIType);
_currentUIImplementation.ActivateUI();
}
/// <summary>
/// Event handler when data is saved.
/// </summary>
private void UISaveLoadSystem_OnSave()
{
SaveUIType();
}
/// <summary>
/// Initializes the UI with the given prefab and UI type.
/// </summary>
private void InitializeUIStrategies()
{
InitializeUI(chatBoxPrefab, UIType.ChatBox);
InitializeUI(questionAnswerPrefab, UIType.QuestionAnswer);
InitializeUI(subtitlePrefab, UIType.Subtitle);
}
private void InitializeUI(GameObject uiPrefab, UIType uiType)
{
try
{
IChatUI uiComponent = uiPrefab.GetComponent<IChatUI>();
if (uiComponent == null)
{
ConvaiLogger.Warn($"The provided prefab for {uiType} does not have a component that implements IChatUI.", ConvaiLogger.LogCategory.UI);
return;
}
uiComponent.Initialize(uiPrefab);
GetUIAppearances[uiType] = uiComponent;
}
catch (Exception ex)
{
ConvaiLogger.Exception($"An error occurred while initializing the UI: {ex.Message}", ConvaiLogger.LogCategory.UI);
}
}
/// <summary>
/// Sends character text to the current UI.
/// </summary>
/// <param name="charName">The character's name.</param>
/// <param name="text">The text to send.</param>
public void SendCharacterText(string charName, string text)
{
Character character = characters.Find(c => c.characterName == charName);
if (character == null)
{
ConvaiLogger.Warn($"No character found named {charName}", ConvaiLogger.LogCategory.Character);
return;
}
if (!ConvaiNPCManager.Instance.CheckForNPCToNPCConversation(character.characterGameObject))
_currentUIImplementation?.SendCharacterText(charName, text, character.CharacterTextColor);
else
ConvaiLogger.DebugLog($"Character {charName} is in conversation with another NPC.", ConvaiLogger.LogCategory.Character);
}
/// <summary>
/// Sends player text to the current UI.
/// </summary>
/// <param name="text">The text to send.</param>
public void SendPlayerText(string text)
{
_currentUIImplementation?.SendPlayerText(_hasPlayerData ? _convaiPlayerData.PlayerName : _convaiPlayerData.DefaultPlayerName, text, playerTextColor);
}
/// <summary>
/// Validates that all UI prefabs are assigned.
/// </summary>
private void ValidateUIPrefabs()
{
try
{
if (chatBoxPrefab == null || subtitlePrefab == null || questionAnswerPrefab == null)
throw new InvalidOperationException("All UI prefabs must be assigned in the inspector.");
}
catch (InvalidOperationException ex)
{
ConvaiLogger.Exception($"An error occurred while validating UI prefabs: {ex.Message}", ConvaiLogger.LogCategory.UI);
}
}
/// <summary>
/// Sets the current UI type and fades between UIs.
/// </summary>
/// <param name="newUIType">The new UI type to set.</param>
public void SetUIType(UIType newUIType)
{
if (!GetUIAppearances.ContainsKey(newUIType))
{
ConvaiLogger.Warn($"The UI type {newUIType} is not available.", ConvaiLogger.LogCategory.UI);
return;
}
_currentUIImplementation = GetUIAppearances[newUIType];
}
private void SaveUIType()
{
foreach (KeyValuePair<UIType, IChatUI> strategy in GetUIAppearances.Where(strategy =>
strategy.Value == _currentUIImplementation))
{
UISaveLoadSystem.Instance.UIType = strategy.Key;
break;
}
}
public IChatUI GetChatUIByUIType(UIType uiType)
{
return GetUIAppearances[uiType];
}
/// <summary>
/// Gets the current UI implementation.
/// </summary>
/// <returns>The current IChatUI implementation.</returns>
public IChatUI GetCurrentUI()
{
return _currentUIImplementation;
}
public bool HasCharacter(string convaiNPCCharacterName)
{
return characters.Any(character => character.characterName == convaiNPCCharacterName);
}
public void AddCharacter(Character newCharacter)
{
characters.Add(newCharacter);
}
}
}
namespace Convai.Scripts.Runtime.Addons
{
[Serializable]
public class Character
{
[Header("Character settings")] [Tooltip("Convai NPC Game Object")]
public ConvaiNPC characterGameObject;
[ReadOnly] [Tooltip("Display name of the NPC")]
public string characterName = "Character";
[ColorUsage(true)] [Tooltip("Color of the NPC text. Alpha value will be ignored.")] [SerializeField]
private Color characterTextColor = Color.red;
public Color CharacterTextColor
{
get => characterTextColor;
set => characterTextColor = value;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 119d1116f25796d4da5239037da0977f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,116 @@
using System;
using Convai.Scripts.Runtime.Core;
using Convai.Scripts.Runtime.LoggerSystem;
using Service;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Convai.Scripts.Runtime.UI
{
public class ConvaiFeedbackHandler : MonoBehaviour
{
[SerializeField] private Button _thumbsUPButton;
[SerializeField] private Button _thumbsDownButton;
[SerializeField] private GameObject _thumbsUPFill;
[SerializeField] private GameObject _thumbsDownFill;
[SerializeField] private TextMeshProUGUI _feedbackText;
private string _feedbackTextString;
private string _interactionID;
/// <summary>
/// Called when the object becomes enabled and active.
/// </summary>
private void OnEnable()
{
ConvaiGRPCAPI.Instance.OnResultReceived += ConvaiGRPCAPI_OnResultReceived;
_thumbsUPButton.onClick.AddListener(() => OnFeedbackButtonClicked(_thumbsUPButton));
_thumbsDownButton.onClick.AddListener(() => OnFeedbackButtonClicked(_thumbsDownButton));
}
/// <summary>
/// Called when the object is disabled.
/// </summary>
private void OnDisable()
{
ConvaiGRPCAPI.Instance.OnResultReceived -= ConvaiGRPCAPI_OnResultReceived;
_thumbsUPButton.onClick.RemoveAllListeners();
_thumbsDownButton.onClick.RemoveAllListeners();
_thumbsUPFill.SetActive(false);
_thumbsDownFill.SetActive(false);
}
/// <summary>
/// Handles the event when a result is received from ConvaiGRPCAPI.
/// </summary>
/// <param name="result">The result received.</param>
private void ConvaiGRPCAPI_OnResultReceived(GetResponseResponse result)
{
// Check if InteractionId is not null or empty.
if (result.InteractionId.Length > 0)
{
_interactionID = result.InteractionId;
ConvaiGRPCAPI.Instance.OnResultReceived -= ConvaiGRPCAPI_OnResultReceived;
}
}
/// <summary>
/// Handles the event when the feedback button is clicked.
/// </summary>
private void OnFeedbackButtonClicked(Button button)
{
if (button == _thumbsUPButton)
SendFeedback(true);
else if (button == _thumbsDownButton) SendFeedback(false);
}
/// <summary>
/// Sends feedback to ConvaiGRPCAPI asynchronously.
/// </summary>
/// <param name="thumbsUP">Indicates whether the feedback is a thumbs up or thumbs down.</param>
private async void SendFeedback(bool thumbsUP)
{
if (string.IsNullOrEmpty(_interactionID))
{
ConvaiLogger.Error("InteractionId is null or empty", ConvaiLogger.LogCategory.Character);
return;
}
// Set the fill visuals for thumbs up and thumbs down buttons.
HandleThumbsFill(thumbsUP);
// Extract feedback text after the colon character.
string feedbackText = RemoveBeforeColon(_feedbackText.text);
// Send feedback to ConvaiGRPCAPI.
await ConvaiGRPCAPI.Instance.SendFeedback(thumbsUP, _interactionID, feedbackText);
}
/// <summary>
/// Removes the text before the colon character in the given string.
/// </summary>
/// <param name="text">The input text.</param>
/// <returns>The modified text after removing the portion before the colon.</returns>
private string RemoveBeforeColon(string text)
{
int colonIndex = text.IndexOf(':', StringComparison.Ordinal);
if (colonIndex != -1) return text.Substring(colonIndex + 2);
return text;
}
/// <summary>
/// Sets the fill state of the Thumbs Up and Thumbs Down buttons.
/// </summary>
/// <param name="thumbsUP">Indicates whether the feedback is a thumbs up or thumbs down.</param>
private void HandleThumbsFill(bool thumbsUP)
{
_thumbsUPFill.SetActive(thumbsUP);
_thumbsDownFill.SetActive(!thumbsUP);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8219b448c27c0849acf7d7425a79e54
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
using UnityEngine;
namespace Convai.Scripts.Runtime.UI
{
public interface IChatUI
{
void Initialize(GameObject uiPrefab);
void ActivateUI();
void DeactivateUI();
void SendCharacterText(string charName, string text, Color characterTextColor);
void SendPlayerText(string playerName, string text, Color playerTextColor);
CanvasGroup GetCanvasGroup();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3d6a823a222a48cc891ff35e847d3a87
timeCreated: 1699396034

View File

@ -0,0 +1,27 @@
using Convai.Scripts.Runtime.Utils;
using TMPro;
namespace Convai.Scripts.Runtime.UI
{
/// <summary>
/// Class to keep track of individual chat messages.
/// </summary>
public class Message
{
public TMP_Text SenderTextObject { get; set; }
public TMP_Text MessageTextObject { get; set; }
/// <summary>
/// Does an RTL check for the message and changes the order of sender-text in the UI if both are in an
/// RTL Language
/// </summary>
public void RTLUpdate()
{
// Enable the RTL on the Sender Component
if (ConvaiLanguageCheck.IsRTL(SenderTextObject.text)) MessageTextObject.isRightToLeftText = true;
// Enable the RTL on the Text Component
if (ConvaiLanguageCheck.IsRTL(MessageTextObject.text)) MessageTextObject.isRightToLeftText = true;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6e83a67a9db6402382ef267ca82285ae
timeCreated: 1718117860

View File

@ -0,0 +1,86 @@
using TMPro;
using UnityEngine;
namespace Convai.Scripts.Runtime.UI
{
/// <summary>
/// The QuestionAnswerUI class is responsible for managing the UI elements
/// that display questions and answers in a conversational interface.
/// </summary>
public class QuestionAnswerUI : ChatUIBase
{
private Message _answer;
private GameObject _feedbackButtons;
private Message _question;
/// <summary>
/// Initializes the UI with the provided prefab.
/// </summary>
/// <param name="uiPrefab">The UI prefab to instantiate.</param>
public override void Initialize(GameObject uiPrefab)
{
UIInstance = Instantiate(uiPrefab);
_question = new Message
{
SenderTextObject = UIInstance.transform.Find("Background").Find("Question").Find("Sender").GetComponent<TMP_Text>(),
MessageTextObject = UIInstance.transform.Find("Background").Find("Question").Find("Text").GetComponent<TMP_Text>()
};
_answer = new Message
{
SenderTextObject = UIInstance.transform.Find("Background").Find("Answer").Find("Sender").GetComponent<TMP_Text>(),
MessageTextObject = UIInstance.transform.Find("Background").Find("Answer").Find("AnswerText").Find("Text").GetComponent<TMP_Text>()
};
UIInstance.SetActive(false);
_feedbackButtons = _answer.MessageTextObject.transform.GetChild(0).gameObject;
}
/// <summary>
/// Sends the character's text to the UI, formatted with the character's color.
/// </summary>
/// <param name="charName">The name of the character speaking.</param>
/// <param name="text">The text spoken by the character.</param>
/// <param name="characterTextColor">The color associated with the character.</param>
public override void SendCharacterText(string charName, string text, Color characterTextColor)
{
if (string.IsNullOrEmpty(text)) return;
if (_answer != null)
{
_feedbackButtons.SetActive(false);
_answer.SenderTextObject.text = FormatSpeakerName(charName, characterTextColor);
_answer.MessageTextObject.text = text;
_answer.RTLUpdate();
_feedbackButtons.SetActive(true);
}
}
/// <summary>
/// Sends the player's text to the UI, formatted with the player's color.
/// </summary>
/// <param name="playerName">The name of the player speaking.</param>
/// <param name="text">The text spoken by the player.</param>
/// <param name="playerTextColor">The color associated with the player.</param>
public override void SendPlayerText(string playerName, string text, Color playerTextColor)
{
if (_question != null)
{
_question.SenderTextObject.text = FormatSpeakerName(playerName, playerTextColor);
_question.MessageTextObject.text = text;
_answer.RTLUpdate();
_feedbackButtons.SetActive(false);
}
}
/// <summary>
/// Formats the speaker's name with the color tag
/// </summary>
/// <param name="speakerName">The name of the speaker.</param>
/// <param name="speakerColor">The color associated with the speaker.</param>
/// <returns>Formatted speaker name.</returns>
private string FormatSpeakerName(string speakerName, Color speakerColor)
{
string colorHex = ColorUtility.ToHtmlStringRGBA(speakerColor);
return $"<color=#{colorHex}>{speakerName}</color>: ";
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 851f7ed955104489b2e32a7c6da4debc
timeCreated: 1699985777

View File

@ -0,0 +1,100 @@
using Convai.Scripts.Runtime.LoggerSystem;
using TMPro;
using UnityEngine;
namespace Convai.Scripts.Runtime.UI
{
/// <summary>
/// SubtitleChatUI is responsible for displaying subtitles on the screen.
/// It inherits from ChatUIBase and overrides methods to provide specific functionality for subtitle UI.
/// </summary>
public class SubtitleChatUI : ChatUIBase
{
private GameObject _feedbackButtons;
private Message _subtitle;
/// <summary>
/// Initializes the subtitle UI with the provided prefab.
/// </summary>
/// <param name="uiPrefab">The UI prefab to instantiate.</param>
public override void Initialize(GameObject uiPrefab)
{
// Instantiate the UI prefab and get the subtitle text component
UIInstance = Instantiate(uiPrefab);
_subtitle = new Message
{
SenderTextObject = UIInstance.transform.Find("Background").Find("ChatBox").Find("Subtitle").Find("Sender").GetComponent<TMP_Text>(),
MessageTextObject = UIInstance.transform.Find("Background").Find("ChatBox").Find("Subtitle").Find("Text").GetComponent<TMP_Text>()
};
// Start with the UI inactive
UIInstance.SetActive(false);
_feedbackButtons = _subtitle.MessageTextObject.transform.GetChild(0).gameObject;
}
/// <summary>
/// Sends the character's text to the subtitle UI.
/// </summary>
/// <param name="charName">The name of the character speaking.</param>
/// <param name="text">The text spoken by the character.</param>
/// <param name="characterTextColor">The color associated with the character.</param>
public override void SendCharacterText(string charName, string text, Color characterTextColor)
{
// Update the subtitle text with formatted character dialogue.
_feedbackButtons.SetActive(false);
UpdateSubtitleText(charName, text, characterTextColor);
_feedbackButtons.SetActive(true);
}
/// <summary>
/// Sends the player's text to the subtitle UI.
/// </summary>
/// <param name="playerName">The name of the player speaking.</param>
/// <param name="text">The text spoken by the player.</param>
/// <param name="playerTextColor">The color associated with the player.</param>
public override void SendPlayerText(string playerName, string text, Color playerTextColor)
{
// Update the subtitle text with formatted player dialogue.
UpdateSubtitleText(playerName, text, playerTextColor);
_feedbackButtons.SetActive(false);
}
/// <summary>
/// Updates the subtitle text with the provided speaker's name, text, and color.
/// </summary>
/// <param name="speakerName">The name of the speaker.</param>
/// <param name="text">The text spoken by the speaker.</param>
/// <param name="color">The color associated with the speaker.</param>
private void UpdateSubtitleText(string speakerName, string text, Color color)
{
if (string.IsNullOrEmpty(text)) return;
// Check if the subtitle text component is available before updating.
if (_subtitle != null)
{
_subtitle.SenderTextObject.text = FormatSpeakerName(speakerName, color);
_subtitle.MessageTextObject.text = text;
_subtitle.RTLUpdate();
}
else
{
ConvaiLogger.Warn("Subtitle text component is not available.", ConvaiLogger.LogCategory.UI);
}
}
/// <summary>
/// Formats the speaker's name with the color tag.
/// </summary>
/// <param name="speakerName">The name of the speaker.</param>
/// <param name="color">The color associated with the speaker.</param>
/// <returns>The formatted speaker name</returns>
private string FormatSpeakerName(string speakerName, Color color)
{
// Convert the color to a hex string for HTML color formatting.
string colorHex = ColorUtility.ToHtmlStringRGB(color);
// Return the formatted text with the speaker's name and color.
return $"<color=#{colorHex}>{speakerName}</color>: ";
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 57c8aa8be91242bc9cd11702857a3cde
timeCreated: 1699396102