restructure

This commit is contained in:
tom.hempel
2025-09-30 18:03:19 +02:00
parent 69b0c79692
commit 78e5dcd53e
4821 changed files with 762 additions and 417 deletions

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 91fa1040db584e95b2fd142549dbd268
timeCreated: 1722957380

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d295dcb12d9e445dbf269ab6e3e299b0
timeCreated: 1722960791

View File

@ -0,0 +1,103 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup.AccountsSection
{
public class APIKeyReferralWindow : EditorWindow
{
private static APIKeyReferralWindow _window;
private void OnEnable()
{
CreateAPIKeyReferralWindow();
}
public static void ShowWindow()
{
_window = GetWindow<APIKeyReferralWindow>();
_window.titleContent = new GUIContent("Step 2: Referral Source");
_window.minSize = new Vector2(300, 200);
_window.Show();
}
private void CreateAPIKeyReferralWindow()
{
VisualElement root = rootVisualElement;
root.Clear();
Label attributionSourceLabel = new("[Step 2/2] Where did you discover Convai?")
{
style =
{
fontSize = 14,
unityFontStyleAndWeight = FontStyle.Bold
}
};
List<string> attributionSourceOptions = new()
{
"Search Engine (Google, Bing, etc.)",
"Youtube",
"Social Media (Facebook, Instagram, TikTok, etc.)",
"Friend Referral",
"Unity Asset Store",
"Others"
};
TextField otherOptionTextField = new();
ToolbarMenu toolbarMenu = new() { text = "Click here to select option..." };
foreach (string choice in attributionSourceOptions)
toolbarMenu.menu.AppendAction(choice,
action =>
{
_ = choice;
toolbarMenu.text = choice;
});
toolbarMenu.style.paddingBottom = 10;
toolbarMenu.style.paddingLeft = 30;
toolbarMenu.style.paddingRight = 30;
toolbarMenu.style.paddingTop = 10;
Button continueButton = new(ClickEvent)
{
text = "Continue",
style =
{
fontSize = 16,
unityFontStyleAndWeight = FontStyle.Bold,
alignSelf = Align.Center,
paddingBottom = 5,
paddingLeft = 30,
paddingRight = 30,
paddingTop = 5
}
};
root.Add(new Label("\n"));
root.Add(attributionSourceLabel);
root.Add(new Label("\n"));
root.Add(toolbarMenu);
root.Add(new Label("\nIf selected Others above, please specify from where: "));
root.Add(otherOptionTextField);
root.Add(new Label("\n"));
root.Add(continueButton);
root.style.marginBottom = 20;
root.style.marginLeft = 20;
root.style.marginRight = 20;
root.style.marginTop = 20;
return;
async void ClickEvent()
{
await APIKeySetupLogic.ContinueEvent(toolbarMenu.text, otherOptionTextField.text, _window);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9fc713f380f7468884e4a104009e7458
timeCreated: 1722956898

View File

@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Convai.Scripts.Runtime.LoggerSystem;
using Newtonsoft.Json;
using UnityEditor;
using UnityEditor.VSAttribution;
using UnityEngine;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup.AccountsSection
{
public abstract class APIKeySetupLogic
{
private const string API_KEY_ASSET_PATH = "Assets/Resources/ConvaiAPIKey.asset";
private const string API_URL = "https://api.convai.com/user/referral-source-status";
public static async Task<(bool isSuccessful, bool shouldShowPage2)> BeginButtonTask(string apiKey)
{
ConvaiAPIKeySetup aPIKeySetup = ScriptableObject.CreateInstance<ConvaiAPIKeySetup>();
aPIKeySetup.APIKey = apiKey;
if (string.IsNullOrEmpty(apiKey))
{
EditorUtility.DisplayDialog("Error", "Please enter a valid API Key.", "OK");
return (false, false);
}
string referralStatus = await CheckReferralStatus(API_URL, apiKey);
if (referralStatus == null)
{
EditorUtility.DisplayDialog("Error", "Something went wrong. Please check your API Key. Contact support@convai.com for more help.", "OK");
return (false, false);
}
CreateOrUpdateAPIKeyAsset(aPIKeySetup);
if (referralStatus.Trim().ToLower() != "undefined" && referralStatus.Trim().ToLower() != "") return (true, false);
EditorUtility.DisplayDialog("Success", "[Step 1/2] API Key loaded successfully!", "OK");
return (true, false);
}
private static async Task<string> CheckReferralStatus(string url, string apiKey)
{
WebRequest request = WebRequest.Create(url);
request.Method = "post";
request.ContentType = "application/json";
string bodyJsonString = "{}";
byte[] jsonBytes = Encoding.UTF8.GetBytes(bodyJsonString);
request.Headers.Add("CONVAI-API-KEY", apiKey);
await using (Stream requestStream = await request.GetRequestStreamAsync())
{
await requestStream.WriteAsync(jsonBytes, 0, jsonBytes.Length);
}
try
{
using HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
ReferralSourceStatus referralStatus = null;
await using (Stream streamResponse = response.GetResponseStream())
{
if (streamResponse != null)
{
using StreamReader reader = new(streamResponse);
string responseContent = await reader.ReadToEndAsync();
referralStatus = JsonConvert.DeserializeObject<ReferralSourceStatus>(responseContent);
}
}
return referralStatus?.ReferralSourceStatusProperty;
}
catch (WebException e)
{
ConvaiLogger.Error(e.Message + "\nPlease check if API Key is correct.", ConvaiLogger.LogCategory.GRPC);
return null;
}
catch (Exception e)
{
ConvaiLogger.Error(e.Message, ConvaiLogger.LogCategory.GRPC);
return null;
}
}
public static async Task ContinueEvent(string selectedOption, string otherOption, APIKeyReferralWindow window)
{
List<string> attributionSourceOptions = new()
{
"Search Engine (Google, Bing, etc.)",
"Youtube",
"Social Media (Facebook, Instagram, TikTok, etc.)",
"Friend Referral",
"Unity Asset Store",
"Others"
};
int currentChoiceIndex = attributionSourceOptions.IndexOf(selectedOption);
if (currentChoiceIndex < 0)
{
EditorUtility.DisplayDialog("Error", "Please select a valid referral source!", "OK");
return;
}
UpdateSource updateSource = new(attributionSourceOptions[currentChoiceIndex]);
if (attributionSourceOptions[currentChoiceIndex] == "Others")
updateSource.ReferralSource = otherOption;
ConvaiAPIKeySetup apiKeyObject = AssetDatabase.LoadAssetAtPath<ConvaiAPIKeySetup>(API_KEY_ASSET_PATH);
await SendReferralRequest("https://api.convai.com/user/update-source", JsonConvert.SerializeObject(updateSource), apiKeyObject.APIKey);
if (attributionSourceOptions[currentChoiceIndex] == "Unity Asset Store")
VSAttribution.SendAttributionEvent("Initial Setup", "Convai Technologies, Inc.", apiKeyObject.APIKey);
EditorUtility.DisplayDialog("Success", "Setup completed successfully!", "OK");
window.Close();
}
private static async Task SendReferralRequest(string url, string bodyJsonString, string apiKey)
{
WebRequest request = WebRequest.Create(url);
request.Method = "post";
request.ContentType = "application/json";
byte[] jsonBytes = Encoding.UTF8.GetBytes(bodyJsonString);
request.Headers.Add("CONVAI-API-KEY", apiKey);
await using (Stream requestStream = await request.GetRequestStreamAsync())
{
await requestStream.WriteAsync(jsonBytes, 0, jsonBytes.Length);
}
try
{
using HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
await using (Stream streamResponse = response.GetResponseStream())
{
if (streamResponse != null)
{
using StreamReader reader = new(streamResponse);
await reader.ReadToEndAsync();
}
}
if ((int)response.StatusCode == 200) ConvaiLogger.DebugLog("Referral sent successfully.", ConvaiLogger.LogCategory.GRPC);
}
catch (WebException e)
{
ConvaiLogger.Error(e.Message + "\nPlease check if API Key is correct.", ConvaiLogger.LogCategory.GRPC);
}
catch (Exception e)
{
ConvaiLogger.Exception(e, ConvaiLogger.LogCategory.GRPC);
}
}
private static void CreateOrUpdateAPIKeyAsset(ConvaiAPIKeySetup aPIKeySetup)
{
if (!File.Exists(API_KEY_ASSET_PATH))
{
if (!AssetDatabase.IsValidFolder("Assets/Resources"))
AssetDatabase.CreateFolder("Assets", "Resources");
AssetDatabase.CreateAsset(aPIKeySetup, API_KEY_ASSET_PATH);
}
else
{
AssetDatabase.DeleteAsset(API_KEY_ASSET_PATH);
AssetDatabase.CreateAsset(aPIKeySetup, API_KEY_ASSET_PATH);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
public static void LoadExistingApiKey(TextField apiKeyField, Button saveApiKeyButton)
{
ConvaiAPIKeySetup existingApiKey = AssetDatabase.LoadAssetAtPath<ConvaiAPIKeySetup>(API_KEY_ASSET_PATH);
if (existingApiKey != null && !string.IsNullOrEmpty(existingApiKey.APIKey))
{
apiKeyField.value = existingApiKey.APIKey;
saveApiKeyButton.text = "Update API Key";
ConvaiSDKSetupEditorWindow.IsApiKeySet = true;
}
}
#region Nested type: ReferralSourceStatus
public class ReferralSourceStatus
{
[JsonProperty("referral_source_status")]
public string ReferralSourceStatusProperty;
[JsonProperty("status")] public string Status;
}
#endregion
#region Nested type: UpdateSource
public class UpdateSource
{
[JsonProperty("referral_source")] public string ReferralSource;
public UpdateSource(string referralSource)
{
ReferralSource = referralSource;
}
}
#endregion
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7d11cc95c0c14a60b1bf1b440ae94766
timeCreated: 1722954432

View File

@ -0,0 +1,60 @@
using Convai.Scripts.Runtime.LoggerSystem;
using UnityEditor;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup.AccountsSection
{
public class APIKeySetupUI
{
private readonly TextField _apiKeyField;
private readonly Button _saveApiKeyButton;
private readonly Button _togglePasswordButton;
public APIKeySetupUI(VisualElement root)
{
_apiKeyField = root.Q<TextField>("api-key");
_togglePasswordButton = root.Q<Button>("toggle-password");
_saveApiKeyButton = root.Q<Button>("save-api-key");
if (_apiKeyField == null || _togglePasswordButton == null || _saveApiKeyButton == null)
{
ConvaiLogger.Error("One or more UI elements not found. Check your UI structure.", ConvaiLogger.LogCategory.UI);
return;
}
APIKeySetupLogic.LoadExistingApiKey(_apiKeyField, _saveApiKeyButton);
SetupEventHandlers();
}
private void SetupEventHandlers()
{
_togglePasswordButton.clicked += TogglePasswordVisibility;
_saveApiKeyButton.clicked += () => ClickEvent(_apiKeyField.value);
}
private void TogglePasswordVisibility()
{
_apiKeyField.isPasswordField = !_apiKeyField.isPasswordField;
_togglePasswordButton.text = _apiKeyField.isPasswordField ? "Show" : "Hide";
}
private async void ClickEvent(string apiKey)
{
(bool isSuccessful, bool shouldShowPage2) result = await APIKeySetupLogic.BeginButtonTask(apiKey);
if (result.isSuccessful)
{
if (result.shouldShowPage2)
APIKeyReferralWindow.ShowWindow();
else
EditorUtility.DisplayDialog("Success", "API Key loaded successfully!", "OK");
_apiKeyField.isReadOnly = false;
_saveApiKeyButton.text = "Update API Key";
ConvaiSDKSetupEditorWindow.IsApiKeySet = true;
}
AccountInformationUI.GetUserAPIUsageData(result.isSuccessful);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 93617c4578954fb5b11b00d6b666c67a
timeCreated: 1722956705

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1dcb3be6b2394712b3af09c8b688f3ab
timeCreated: 1722963570

View File

@ -0,0 +1,127 @@
using System;
using System.Globalization;
using Convai.Scripts.Runtime.LoggerSystem;
using Newtonsoft.Json.Linq;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup.AccountsSection {
public class AccountInformationUI {
private static Label _planNameLabel;
private static Label _expiryTsLabel;
private static ProgressBar _dailyUsageBar;
private static ProgressBar _monthlyUsageBar;
private static Label _dailyUsageLabel;
private static Label _monthlyUsageLabel;
public AccountInformationUI( VisualElement root ) {
_planNameLabel = root.Q<Label>( "plan-name" );
_expiryTsLabel = root.Q<Label>( "expiry-date" );
_dailyUsageBar = root.Q<ProgressBar>( "DailyUsageBar" );
_monthlyUsageBar = root.Q<ProgressBar>( "MonthlyUsageBar" );
_dailyUsageLabel = root.Q<Label>( "daily-usage-label" );
_monthlyUsageLabel = root.Q<Label>( "monthly-usage-label" );
SetupApiKeyField( root );
}
private static void SetupApiKeyField( VisualElement root ) {
TextField apiKeyField = root.Q<TextField>( "api-key" );
if ( apiKeyField != null && ConvaiAPIKeySetup.GetAPIKey( out string apiKey ) ) {
apiKeyField.value = apiKey;
apiKeyField.isReadOnly = false;
ConvaiSDKSetupEditorWindow.IsApiKeySet = true;
// apiKeyField.RegisterValueChangedCallback(evt =>
// {
// if (!string.IsNullOrEmpty(evt.newValue))
// {
// apiKeyField.value = string.Empty;
// }
// });
// _ = new UserAPIUsage();
GetUserAPIUsageData();
}
else
ConvaiSDKSetupEditorWindow.IsApiKeySet = false;
}
public static async void GetUserAPIUsageData( bool validApiKey = true ) {
if ( !validApiKey ) {
SetInvalidApiKeyUI();
return;
}
try {
string userAPIUsage = await UserAPIUsage.GetUserAPIUsage();
JObject jsonObject = JObject.Parse( userAPIUsage );
UsageData usageData = jsonObject["usage"]?.ToObject<UsageData>();
DetailedUsage detailedUsage = jsonObject["usage_v2"]?.ToObject<DetailedUsage>();
if ( usageData != null )
UpdateUIWithUsageData( usageData );
else {
ConvaiLogger.Warn( "Failed to parse usage data.", ConvaiLogger.LogCategory.GRPC );
SetInvalidApiKeyUI();
}
if ( detailedUsage != null ) {
Metric coreAPI = detailedUsage.Metrics.Find( x => x.Id == "core-api" );
ConvaiSDKSetupEditorWindow.IsCoreAPIAllowed = coreAPI != null;
}
}
catch ( Exception ex ) {
ConvaiLogger.Exception( $"Error fetching API usage data: {ex.Message}", ConvaiLogger.LogCategory.GRPC );
SetInvalidApiKeyUI();
}
}
private static void UpdateUIWithUsageData( UsageData usageData ) {
_planNameLabel.text = usageData.planName;
_expiryTsLabel.text = GetFormattedDate( usageData.expiryTs );
_dailyUsageBar.value = CalculateUsagePercentage( usageData.dailyUsage, usageData.dailyLimit );
_monthlyUsageBar.value = CalculateUsagePercentage( usageData.monthlyUsage, usageData.monthlyLimit );
_dailyUsageLabel.text = FormatUsageLabel( usageData.dailyUsage, usageData.dailyLimit );
_monthlyUsageLabel.text = FormatUsageLabel( usageData.monthlyUsage, usageData.monthlyLimit );
}
private static void SetInvalidApiKeyUI() {
_planNameLabel.text = "Invalid API Key";
_expiryTsLabel.text = "Invalid API Key";
_dailyUsageBar.value = 0;
_monthlyUsageBar.value = 0;
_dailyUsageLabel.text = "0/0 interactions";
_monthlyUsageLabel.text = "0/0 interactions";
}
private static float CalculateUsagePercentage( int usage, int limit ) {
return limit > 0 ? ( float )usage / limit * 100 : 0;
}
private static string FormatUsageLabel( int usage, int limit ) {
return $"{usage}/{limit} interactions";
}
private static string GetFormattedDate( string dateString ) {
if ( string.IsNullOrEmpty( dateString ) ) return dateString;
if ( DateTime.TryParseExact( dateString, "MM/dd/yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime date ) ) {
string daySuffix = GetDaySuffix( date.Day );
return date.ToString( $"MMMM dd'{daySuffix}' yyyy" );
}
ConvaiLogger.Warn( $"Failed to parse date: {dateString}", ConvaiLogger.LogCategory.GRPC );
return dateString;
}
private static string GetDaySuffix( int day ) {
return ( day % 10, day / 10 ) switch {
(1, 1) or (2, 1) or (3, 1) => "th",
(1, _) => "st",
(2, _) => "nd",
(3, _) => "rd",
_ => "th"
};
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f1f34d1d84db4af6b62212a1ae6876e9
timeCreated: 1722970221

View File

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using Convai.Scripts.Runtime.Attributes;
using Newtonsoft.Json;
using UnityEngine;
namespace Convai.Scripts.Editor.Setup.AccountsSection {
[Serializable]
public class UsageData {
[JsonProperty( "plan_name" )] [ReadOnly] [SerializeField]
public string planName;
[JsonProperty( "expiry_ts" )] [ReadOnly] [SerializeField]
public string expiryTs;
[JsonProperty( "daily_limit" )] [ReadOnly] [SerializeField]
public int dailyLimit;
[JsonProperty( "monthly_limit" )] [ReadOnly] [SerializeField]
public int monthlyLimit;
[JsonProperty( "extended_isAllowed" )] [ReadOnly] [SerializeField]
public bool extendedIsAllowed;
[JsonProperty( "daily_usage" )] [ReadOnly] [SerializeField]
public int dailyUsage;
[JsonProperty( "monthly_usage" )] [ReadOnly] [SerializeField]
public int monthlyUsage;
[JsonProperty( "extended_usage" )] [ReadOnly] [SerializeField]
public int extendedUsage;
}
[Serializable]
public class Metric {
[JsonProperty( "id" )] [ReadOnly] [SerializeField]
public string Id;
[JsonProperty( "name" )] [ReadOnly] [SerializeField]
public string Name;
[JsonProperty( "units" )] [ReadOnly] [SerializeField]
public string Units;
[JsonProperty( "usage_details" )] [ReadOnly] [SerializeField]
public List<UsageDetail> UsageDetails;
[JsonProperty( "is_extended" )] [ReadOnly] [SerializeField]
public bool? IsExtended;
}
[Serializable]
public class UsageDetail {
[JsonProperty( "label" )] [ReadOnly] [SerializeField]
public string Label;
[JsonProperty( "limit" )] [ReadOnly] [SerializeField]
public double Limit;
[JsonProperty( "usage" )] [ReadOnly] [SerializeField]
public double Usage;
}
[Serializable]
public class DetailedUsage {
[JsonProperty( "metrics" )] [ReadOnly] [SerializeField]
public List<Metric> Metrics;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6cd221b6dafe4c1794021b46231db3b6
timeCreated: 1722971255

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Convai.Scripts.Runtime.LoggerSystem;
using Newtonsoft.Json;
namespace Convai.Scripts.Editor.Setup.AccountsSection
{
public class UserAPIUsage
{
private const string BASE_URL = "https://api.convai.com/user/";
private static HttpClient _httpClient;
public static async Task<string> GetUserAPIUsage()
{
const string endpoint = "user-api-usage";
HttpContent content = CreateHttpContent(new Dictionary<string, object>());
string userAPIUsage = await SendPostRequestAsync(endpoint, content);
if (userAPIUsage == null)
{
ConvaiLogger.Warn("User API Usage is null", ConvaiLogger.LogCategory.UI);
// return a dummy json string to avoid null reference exception
return
"{\"usage\":{\"planName\":\"Invalid API Key\",\"expiryTs\":\"2022-12-31T23:59:59Z\",\"dailyLimit\":0,\"dailyUsage\":0,\"monthlyLimit\":0,\"monthlyUsage\":0}}";
}
return userAPIUsage;
}
private static HttpContent CreateHttpContent(Dictionary<string, object> data)
{
_httpClient = new HttpClient
{
// Set a default request timeout if needed
Timeout = TimeSpan.FromSeconds(30)
};
if (ConvaiAPIKeySetup.GetAPIKey(out string apiKey))
{
if (apiKey == null)
{
ConvaiLogger.Warn("API Key is null", ConvaiLogger.LogCategory.GRPC);
return null;
}
// Set default request headers here
_httpClient.DefaultRequestHeaders.Add("CONVAI-API-KEY", apiKey);
// Set default headers like Accept to expect a JSON response
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
//Dictionary where all values are not null
Dictionary<string, object> dataToSend =
data.Where(keyValuePair => keyValuePair.Value != null).ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value);
// Serialize the dictionary to JSON
string json = JsonConvert.SerializeObject(dataToSend);
// Convert JSON to HttpContent
return new StringContent(json, Encoding.UTF8, "application/json");
}
private static async Task<string> SendPostRequestAsync(string endpoint, HttpContent content)
{
if (content == null)
{
ConvaiLogger.Warn("Content is null", ConvaiLogger.LogCategory.UI);
return null;
}
try
{
HttpResponseMessage response = await _httpClient.PostAsync(BASE_URL + endpoint, content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException e)
{
ConvaiLogger.Warn($"Request to {endpoint} failed: {e.Message}", ConvaiLogger.LogCategory.UI);
return null;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 12a663852e0448c9a654350280989401
timeCreated: 1722963605

View File

@ -0,0 +1,172 @@
using Assets.Convai.Scripts.Editor.Setup.LongTermMemory;
using Convai.Scripts.Editor.CustomPackage;
using Convai.Scripts.Editor.Setup.AccountsSection;
using Convai.Scripts.Editor.Setup.Documentation;
using Convai.Scripts.Editor.Setup.LoggerSettings;
using Convai.Scripts.Editor.Setup.ServerAnimation.View;
using Convai.Scripts.Editor.Setup.Updates;
using Convai.Scripts.Runtime.LoggerSystem;
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup
{
public class ConvaiSDKSetupEditorWindow : EditorWindow
{
private const string STYLE_SHEET_PATH = "Assets/Convai/Art/UI/Editor/ConvaiSDKSetupWindow.uss";
private const string VISUAL_TREE_PATH = "Assets/Convai/Art/UI/Editor/ConvaiSDKSetupWindow.uxml";
private static VisualElement _contentContainer;
private static VisualElement _root;
private static readonly Dictionary<string, (VisualElement Section, Button Button)> Sections = new();
private static readonly string[] SectionNames =
{ "welcome", "account", "package-management", "logger-settings", "updates", "documentation", "contact-us", "ltm", "server-anim" };
private static readonly HashSet<string> ApiKeyDependentSections = new() { "logger-settings", "package-management", "ltm", "server-anim" };
private static bool _isApiKeySet;
public static bool IsCoreAPIAllowed = false;
public static bool IsApiKeySet
{
get => _isApiKeySet;
set
{
_isApiKeySet = value;
OnAPIKeySet?.Invoke();
}
}
private void CreateGUI()
{
InitializeUI();
SetupNavigationHandlers();
}
public static event Action OnAPIKeySet;
[MenuItem("Convai/Welcome", priority = 1)]
public static void OpenWindow()
{
OpenSection("welcome");
}
[MenuItem("Convai/API Key Setup", priority = 2)]
public static void OpenAPIKeySetup()
{
OpenSection("account");
}
[MenuItem("Convai/Logger Settings", priority = 4)]
public static void OpenLoggerSettings()
{
OpenSection("logger-settings");
}
[MenuItem("Convai/Custom Package Installer", priority = 5)]
public static void OpenCustomPackageInstaller()
{
OpenSection("package-management");
}
[MenuItem("Convai/Documentation", priority = 6)]
public static void OpenDocumentation()
{
OpenSection("documentation");
}
[MenuItem("Convai/Long Term Memory", priority = 7)]
public static void OpenLTM()
{
OpenSection("ltm");
}
[MenuItem("Convai/Server Animation", priority = 8)]
public static void OpenServerAnimation()
{
OpenSection("server-anim");
}
private static void OpenSection(string sectionName)
{
Rect rect = new(100, 100, 1200, 550);
ConvaiSDKSetupEditorWindow window = GetWindowWithRect<ConvaiSDKSetupEditorWindow>(rect, true, "Convai SDK Setup", true);
window.minSize = window.maxSize = rect.size;
window.Show();
ShowSection(sectionName);
}
private void InitializeUI()
{
_root = rootVisualElement;
VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(VISUAL_TREE_PATH);
_root.Add(visualTree.Instantiate());
StyleSheet styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(STYLE_SHEET_PATH);
_root.styleSheets.Add(styleSheet);
_contentContainer = _root.Q<VisualElement>("content-container");
InitializeSections();
_ = new APIKeySetupUI(_root);
_ = new AccountInformationUI(_root);
_ = new LoggerSettingsUI(_root);
_ = new DocumentationUI(_root);
_ = new UpdatesSectionUI(_root);
_ = new ConvaiCustomPackageInstaller(_root);
_ = new LongTermMemoryUI(_root);
_ = new ServerAnimationPageView(_root);
_root.Q<Button>("documentation-page").clicked += () => Application.OpenURL("https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin");
_root.Q<Button>("download-meta-quest-app").clicked += () => Application.OpenURL("https://www.meta.com/en-gb/experiences/convai-animation-capture/28320335120898955/");
}
private static void InitializeSections()
{
foreach (string section in SectionNames)
Sections[section] = (_contentContainer.Q(section), _root.Q<Button>($"{section}-btn"));
}
private static void SetupNavigationHandlers()
{
foreach (KeyValuePair<string, (VisualElement Section, Button Button)> section in Sections)
section.Value.Button.clicked += () => ShowSection(section.Key);
}
public static void ShowSection(string sectionName)
{
if (!IsApiKeySet && ApiKeyDependentSections.Contains(sectionName))
{
EditorUtility.DisplayDialog("API Key Required", "Please set up your API Key to access this section.", "OK");
return;
}
if (Sections.TryGetValue(sectionName, out (VisualElement Section, Button Button) sectionData))
{
foreach ((VisualElement Section, Button Button) section in Sections.Values)
section.Section.style.display = section.Section == sectionData.Section ? DisplayStyle.Flex : DisplayStyle.None;
UpdateNavigationState(sectionName);
}
else
ConvaiLogger.Warn($"Section '{sectionName}' not found.", ConvaiLogger.LogCategory.Character);
}
private static void UpdateNavigationState(string activeSectionName)
{
foreach (KeyValuePair<string, (VisualElement Section, Button Button)> section in Sections)
{
bool isActive = section.Key == activeSectionName;
section.Value.Button.EnableInClassList("sidebar-link--active", isActive);
section.Value.Button.EnableInClassList("sidebar-link", !isActive);
}
}
}
}

View File

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

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6b9e16bfe94040548214ec275f408efb
timeCreated: 1723460414

View File

@ -0,0 +1,67 @@
using System.Collections.Generic;
using Convai.Scripts.Runtime.LoggerSystem;
using UnityEngine;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup.Documentation
{
public class DocumentationUI
{
private readonly VisualElement _root;
public DocumentationUI(VisualElement root)
{
_root = root;
SetupLinkHandlers();
}
private void SetupLinkHandlers()
{
Dictionary<string, string> links = new()
{
{ "unity-plugin-setup", "https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/setting-up-unity-plugin" },
{ "quick-start-tutorial", "https://youtu.be/anb9ityi0MQ" },
{ "video-tutorials", "https://www.youtube.com/playlist?list=PLn_7tCx0ChipYHtbe8yzdV5kMbozN2EeB" },
{ "narrative-design", "https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/adding-narrative-design-to-your-character" },
{ "transcript-system", "https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/utilities/transcript-ui-system" },
{ "actions", "https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/adding-actions-to-your-character" },
{ "npc-interaction", "https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/adding-npc-to-npc-conversation" },
{ "facial-expressions", "https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/adding-lip-sync-to-your-character" },
{ "platform-specific-builds", "https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/building-for-supported-platforms" },
{ "ios-build", "https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/building-for-supported-platforms/building-for-ios-ipados" },
{
"macos-build",
"https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/building-for-supported-platforms/microphone-permission-issue-on-intel-macs-with-universal-builds"
},
{ "ar-vr-build", "https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/building-for-supported-platforms/convai-xr" },
{ "faq", "https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/troubleshooting-guide" },
{ "developer-forum", "https://forum.convai.com/" }
};
foreach (KeyValuePair<string, string> link in links) SetupLinkHandler(link.Key, link.Value);
}
private void SetupLinkHandler(string elementName, string url)
{
VisualElement element = _root.Q<VisualElement>(elementName);
switch (element)
{
case null:
ConvaiLogger.Warn($"Element '{elementName}' not found.", ConvaiLogger.LogCategory.UI);
return;
case Button button:
button.clicked += () => Application.OpenURL(url);
break;
default:
element.AddManipulator(new Clickable(() => Application.OpenURL(url)));
break;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: abea5fdbf367417d9a4d3cefdc402a51
timeCreated: 1723460429

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4c25aa8ea78d429ab1c7716dcb052391
timeCreated: 1723280701

View File

@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Convai.Scripts.Runtime.LoggerSystem;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup.LoggerSettings
{
public class LoggerSettingsLogic
{
private readonly Runtime.LoggerSystem.LoggerSettings _loggerSettings = Resources.Load<Runtime.LoggerSystem.LoggerSettings>("LoggerSettings");
private Dictionary<FieldInfo, Toggle> _selectAllMapping;
private Dictionary<FieldInfo, List<Toggle>> _toggleMapping;
public bool GetLogLevelEnabledStatus(FieldInfo fieldInfo, ConvaiLogger.LogLevel logLevel)
{
return ((ConvaiLogger.LogLevel)fieldInfo.GetValue(_loggerSettings) & logLevel) != 0;
}
public bool IsAllSelectedForCategory(FieldInfo fieldInfo)
{
ConvaiLogger.LogLevel logLevel = (ConvaiLogger.LogLevel)fieldInfo.GetValue(_loggerSettings);
return Enum.GetValues(typeof(ConvaiLogger.LogLevel))
.Cast<ConvaiLogger.LogLevel>()
.Where(enumType => enumType != ConvaiLogger.LogLevel.None)
.All(enumType => (logLevel & enumType) != 0);
}
public void AddToToggleDictionary(FieldInfo fieldInfo, Toggle toggle)
{
_toggleMapping ??= new Dictionary<FieldInfo, List<Toggle>>();
if (_toggleMapping.ContainsKey(fieldInfo))
_toggleMapping[fieldInfo].Add(toggle);
else
_toggleMapping.Add(fieldInfo, new List<Toggle> { toggle });
}
public void AddToSelectAllDictionary(FieldInfo fieldInfo, Toggle toggle)
{
_selectAllMapping ??= new Dictionary<FieldInfo, Toggle>();
_selectAllMapping[fieldInfo] = toggle;
}
public void OnToggleClicked(FieldInfo fieldInfo, ConvaiLogger.LogLevel logLevel, bool status)
{
UpdateEnumFlag(fieldInfo, logLevel, status);
_selectAllMapping[fieldInfo].SetValueWithoutNotify(IsAllSelectedForCategory(fieldInfo));
EditorUtility.SetDirty(_loggerSettings);
}
private void UpdateEnumFlag(FieldInfo fieldInfo, ConvaiLogger.LogLevel logLevel, bool status)
{
ConvaiLogger.LogLevel value = (ConvaiLogger.LogLevel)fieldInfo.GetValue(_loggerSettings);
switch (status)
{
case true:
value |= logLevel;
break;
case false:
value &= ~logLevel;
break;
}
fieldInfo.SetValue(_loggerSettings, value);
}
public void OnSelectAllClicked(FieldInfo fieldInfo, bool status)
{
ConvaiLogger.LogLevel value = status ? (ConvaiLogger.LogLevel)31 : 0;
fieldInfo.SetValue(_loggerSettings, value);
EditorUtility.SetDirty(_loggerSettings);
UpdateToggleValues(fieldInfo, status);
}
private void UpdateToggleValues(FieldInfo fieldInfo, bool status)
{
foreach (Toggle toggle in _toggleMapping[fieldInfo]) toggle.SetValueWithoutNotify(status);
}
public void ClearAllOnClicked()
{
foreach (FieldInfo fieldInfo in typeof(Runtime.LoggerSystem.LoggerSettings).GetFields())
{
foreach (ConvaiLogger.LogLevel enumType in Enum.GetValues(typeof(ConvaiLogger.LogLevel)).Cast<ConvaiLogger.LogLevel>()) UpdateEnumFlag(fieldInfo, enumType, false);
UpdateToggleValues(fieldInfo, false);
UpdateSelectAllValues(fieldInfo, false);
}
EditorUtility.SetDirty(_loggerSettings);
}
private void UpdateSelectAllValues(FieldInfo fieldInfo, bool status)
{
_selectAllMapping[fieldInfo].SetValueWithoutNotify(status);
}
public void SelectAllOnClicked()
{
foreach (FieldInfo fieldInfo in typeof(Runtime.LoggerSystem.LoggerSettings).GetFields())
{
foreach (ConvaiLogger.LogLevel enumType in Enum.GetValues(typeof(ConvaiLogger.LogLevel)).Cast<ConvaiLogger.LogLevel>()) UpdateEnumFlag(fieldInfo, enumType, true);
UpdateToggleValues(fieldInfo, true);
UpdateSelectAllValues(fieldInfo, true);
}
EditorUtility.SetDirty(_loggerSettings);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 89de643989fd4cfea9cfea6be828d4ae
timeCreated: 1723360157

View File

@ -0,0 +1,139 @@
using System;
using System.Reflection;
using Convai.Scripts.Runtime.LoggerSystem;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup.LoggerSettings
{
public class LoggerSettingsUI
{
private readonly LoggerSettingsLogic _loggerSettingsLogic;
public LoggerSettingsUI(VisualElement rootElement)
{
VisualElement contentContainer = rootElement.Q<VisualElement>("content-container");
if (contentContainer == null)
{
ConvaiLogger.Warn("Cannot find Content Container", ConvaiLogger.LogCategory.UI);
return;
}
VisualElement loggerTable = contentContainer.Q<VisualElement>("logger-table");
if (loggerTable == null)
{
ConvaiLogger.Warn("Cannot find loggerTable", ConvaiLogger.LogCategory.UI);
return;
}
_loggerSettingsLogic = new LoggerSettingsLogic();
CreateLoggerTable(contentContainer);
SetupButtons(contentContainer);
}
private void CreateLoggerTable(VisualElement contentContainer)
{
VisualElement loggerSettings = contentContainer.Q<VisualElement>("logger-settings");
VisualElement loggerTable = loggerSettings.Q<VisualElement>("logger-table");
CreateHeaders(loggerTable);
VisualElement lastRow = null;
foreach (FieldInfo fieldInfo in typeof(Runtime.LoggerSystem.LoggerSettings).GetFields())
{
VisualElement tableRow = new();
Label categoryName = new(fieldInfo.Name);
Toggle selectAll = CreateSelectAllForCategory(fieldInfo);
_loggerSettingsLogic.AddToSelectAllDictionary(fieldInfo, selectAll);
categoryName.AddToClassList("logger-table-element");
tableRow.Add(selectAll);
tableRow.Add(categoryName);
CreateSeverityTogglesForCategory(fieldInfo, tableRow);
tableRow.AddToClassList("logger-table-row");
loggerTable.Add(tableRow);
lastRow = tableRow;
}
lastRow?.AddToClassList("logger-table-row-last");
}
private static void CreateHeaders(VisualElement loggerTable)
{
VisualElement tableHeader = new();
VisualElement selectAll = new Label("Select All");
VisualElement category = new Label("Category");
selectAll.AddToClassList("logger-table-element");
category.AddToClassList("logger-table-element");
tableHeader.Add(selectAll);
tableHeader.Add(category);
foreach (ConvaiLogger.LogLevel logLevel in Enum.GetValues(typeof(ConvaiLogger.LogLevel)))
{
if (logLevel == ConvaiLogger.LogLevel.None) continue;
VisualElement label = new Label(logLevel.ToString());
label.AddToClassList("logger-table-element");
tableHeader.Add(label);
}
tableHeader.AddToClassList("logger-table-row");
tableHeader.AddToClassList("logger-table-row-first");
loggerTable.Add(tableHeader);
}
private Toggle CreateSelectAllForCategory(FieldInfo fieldInfo)
{
Toggle selectAll = new()
{
value = _loggerSettingsLogic.IsAllSelectedForCategory(fieldInfo)
};
selectAll.RegisterValueChangedCallback(evt => { _loggerSettingsLogic.OnSelectAllClicked(fieldInfo, evt.newValue); });
selectAll.AddToClassList("logger-table-element");
return selectAll;
}
private void CreateSeverityTogglesForCategory(FieldInfo fieldInfo, VisualElement severityContainer)
{
foreach (ConvaiLogger.LogLevel enumType in Enum.GetValues(typeof(ConvaiLogger.LogLevel)))
{
if (enumType == ConvaiLogger.LogLevel.None) continue;
Toggle toggle = new()
{
value = _loggerSettingsLogic.GetLogLevelEnabledStatus(fieldInfo, enumType)
};
void Callback(ChangeEvent<bool> evt)
{
_loggerSettingsLogic.OnToggleClicked(fieldInfo, enumType, evt.newValue);
}
toggle.UnregisterValueChangedCallback(Callback);
toggle.RegisterValueChangedCallback(Callback);
toggle.AddToClassList("logger-table-element");
severityContainer.Add(toggle);
_loggerSettingsLogic.AddToToggleDictionary(fieldInfo, toggle);
}
severityContainer.AddToClassList("severity-container");
}
private void SetupButtons(VisualElement content)
{
VisualElement loggerSettings = content.Q<VisualElement>("logger-settings");
if (loggerSettings == null)
{
ConvaiLogger.Warn("Cannot find logger-settings", ConvaiLogger.LogCategory.UI);
return;
}
Button selectAll = loggerSettings.Q<Button>("select-all");
if (selectAll != null)
{
selectAll.clicked -= _loggerSettingsLogic.SelectAllOnClicked;
selectAll.clicked += _loggerSettingsLogic.SelectAllOnClicked;
}
Button clearAll = loggerSettings.Q<Button>("clear-all");
if (clearAll != null)
{
clearAll.clicked -= _loggerSettingsLogic.ClearAllOnClicked;
clearAll.clicked += _loggerSettingsLogic.ClearAllOnClicked;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 061d492629d1a5b4f818e1b2d7fd5997
timeCreated: 1723197447

View File

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

View File

@ -0,0 +1,137 @@
using Assets.Convai.Scripts.Runtime.PlayerStats.API.Model;
using Convai.Scripts.Editor.Setup;
using Convai.Scripts.Runtime.PlayerStats.API;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace Assets.Convai.Scripts.Editor.Setup.LongTermMemory {
internal class LongTermMemoryUI {
private VisualElement _uiContainer;
private VisualElement _listContainer;
private VisualElement _disclaimerContainer;
private Button _refreshButton;
private Button _toggleDisclaimerButton;
private Label _noSpeakerId;
internal LongTermMemoryUI ( VisualElement root ) {
Initialize( root );
ToggleDisclaimerButton_Clicked(forceShow: true);
if(ConvaiSDKSetupEditorWindow.IsApiKeySet) RefreshSpeakerList();
ConvaiSDKSetupEditorWindow.OnAPIKeySet += RefreshSpeakerList;
_refreshButton.clicked += RefreshSpeakerList;
_toggleDisclaimerButton.clicked += () => { ToggleDisclaimerButton_Clicked( false ); };
}
private void ToggleDisclaimerButton_Clicked (bool forceShow) {
if ( forceShow ) {
ToggleVisibility( _disclaimerContainer, true );
_toggleDisclaimerButton.text = "Hide";
return;
}
ToggleVisibility( _disclaimerContainer, !_disclaimerContainer.visible );
_toggleDisclaimerButton.text = _disclaimerContainer.visible ? "Hide" : "Show";
}
~LongTermMemoryUI () {
ConvaiSDKSetupEditorWindow.OnAPIKeySet -= RefreshSpeakerList;
_refreshButton.clicked -= RefreshSpeakerList;
}
private void Initialize ( VisualElement root ) {
_uiContainer = root.Q<VisualElement>( "content-container" ).Q<VisualElement>( "ltm" );
Debug.Assert( _uiContainer != null, "UI Container cannot be found, something went wrong" );
_listContainer = _uiContainer.Q<VisualElement>( "container" );
Debug.Assert( _listContainer != null, "List Container cannot be found, something went wrong" );
_disclaimerContainer = _uiContainer.Q<VisualElement>( "disclaimer-content" );
Debug.Assert( _disclaimerContainer != null, "Disclaimer Container not found" );
_refreshButton = _uiContainer.Q<Button>( "refresh-btn" );
Debug.Assert( _refreshButton != null, "Cannot find Refresh Button" );
_toggleDisclaimerButton = _uiContainer.Q<Button>( "disclaimer-toggle-button" );
Debug.Assert( _toggleDisclaimerButton != null, "Toggle Disclaimer Button Not found" );
_noSpeakerId = _uiContainer.Q<Label>( "no-speaker-id-label" );
Debug.Assert( _noSpeakerId != null, "Cannot find No Speaker ID Label" );
}
public async void RefreshSpeakerList () {
if ( !ConvaiAPIKeySetup.GetAPIKey( out string apiKey ) )
return;
ToggleVisibility( _noSpeakerId, false );
List<SpeakerIDDetails> speakerIDs = await LongTermMemoryAPI.GetSpeakerIDList( apiKey );
RefreshSpeakerList( speakerIDs );
}
private void RefreshSpeakerList ( List<SpeakerIDDetails> speakerIDs ) {
_listContainer.Clear();
if(speakerIDs.Count == 0 ) {
ToggleVisibility( _noSpeakerId, true );
ToggleVisibility(_listContainer, false );
return;
}
else {
ToggleVisibility(_noSpeakerId, false);
ToggleVisibility(_listContainer, true);
}
foreach ( SpeakerIDDetails sid in speakerIDs ) {
VisualElement item = new VisualElement() {
name = "item",
};
item.AddToClassList( "ltm-item" );
item.Add( GetInformation( sid ) );
item.Add( GetButtonContainer( sid ) );
_listContainer.Add( item );
}
}
private static VisualElement GetInformation ( SpeakerIDDetails sid ) {
VisualElement visualElement = new VisualElement() {
name = "information",
};
visualElement.AddToClassList( "ltm-information" );
visualElement.Add( AddLabel( $"Name: {sid.Name}" ) );
visualElement.Add( AddLabel( $"Speaker ID: {sid.ID}" ) );
return visualElement;
}
private static Label AddLabel ( string value ) {
Label label = new Label( value );
label.AddToClassList( "ltm-label" );
return label;
}
private VisualElement GetButtonContainer ( SpeakerIDDetails sid ) {
VisualElement visualElement = new VisualElement() {
name = "button-container"
};
visualElement.AddToClassList( "ltm-button-container" );
Button button = new Button() {
name = "delete-btn",
text = "Delete"
};
button.AddToClassList( "button-small" );
button.AddToClassList( "ltm-button" );
button.clicked += () => { SpeakerID_Delete_Clicked( sid ); };
visualElement.Add( button );
return visualElement;
}
private async void SpeakerID_Delete_Clicked ( SpeakerIDDetails details ) {
if ( !ConvaiAPIKeySetup.GetAPIKey( out string apiKey ) )
return;
ToggleVisibility(_listContainer, false );
bool result = await LongTermMemoryAPI.DeleteSpeakerID( apiKey, details.ID );
ToggleVisibility( _listContainer, true );
_listContainer.Clear();
if ( !result )
return;
RefreshSpeakerList();
}
private static void ToggleVisibility ( VisualElement element, bool visible ) {
element.visible = visible;
element.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a3e21c62030a49848f0630c7404e2d7f
timeCreated: 1729858716

View File

@ -0,0 +1,72 @@
using Convai.Scripts.Editor.Setup.ServerAnimation.Model;
using Convai.Scripts.Editor.Setup.ServerAnimation.View;
using UnityEngine;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup.ServerAnimation.Controller {
internal class ServerAnimationItemController {
internal ServerAnimationItemController( ServerAnimationItemView view, ServerAnimationItemData itemData, ServerAnimationPageController controller ) {
Data = itemData;
View = view;
Controller = controller;
view.UpdateToggleState( Data.IsSelected, Data.ItemResponse.Status );
view.SetAnimationName( Data.ItemResponse.AnimationName );
Data.CanBeSelected = Data.IsSuccess;
view.Card.RegisterCallback<ClickEvent>( OnCardClicked );
UpdateThumbnail();
}
internal ServerAnimationItemData Data { get; }
private ServerAnimationItemView View { get; }
private ServerAnimationPageController Controller { get; }
private async void UpdateThumbnail() {
if( Controller.Data.Thumbnails.TryGetValue( Data.ItemResponse.AnimationID, out Texture2D cacheTexture2D ) ) {
View.Thumbnail.style.backgroundImage = new StyleBackground {
value = new Background {
texture = cacheTexture2D
}
};
return;
}
if ( string.IsNullOrEmpty( Data.ItemResponse.ThumbnailURL ) ) return;
Texture2D texture = await ServerAnimationAPI.GetThumbnail( Data.ItemResponse.ThumbnailURL );
View.Thumbnail.style.backgroundImage = new StyleBackground {
value = new Background {
texture = texture
}
};
Controller.Data.Thumbnails.Add( Data.ItemResponse.AnimationID, texture );
}
~ServerAnimationItemController() {
View.Card.UnregisterCallback<ClickEvent>( OnCardClicked );
}
private void OnCardClicked( ClickEvent evt ) {
if ( !Data.CanBeSelected ) return;
ToggleSelect();
}
internal void UpdateCanBeSelected( bool newValue ) {
Data.CanBeSelected = newValue;
}
internal void Reset() {
Data.CanBeSelected = Data.IsSuccess;
Data.IsSelected = false;
View.UpdateToggleState( Data.IsSelected, Data.ItemResponse.Status );
}
private void ToggleSelect() {
Data.IsSelected = !Data.IsSelected;
View.UpdateToggleState( Data.IsSelected, Data.ItemResponse.Status );
}
}
}

View File

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

View File

@ -0,0 +1,166 @@
using Convai.Scripts.Editor.Setup.ServerAnimation.Model;
using Convai.Scripts.Editor.Setup.ServerAnimation.View;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Convai.Scripts.Editor.Setup.ServerAnimation.Controller
{
internal class ServerAnimationPageController
{
private readonly ServerAnimationPageView _ui;
internal ServerAnimationPageController(ServerAnimationPageView view)
{
Data = new ServerAnimationPageData();
_ui = view;
if (ConvaiSDKSetupEditorWindow.IsApiKeySet)
{
InjectData();
}
else
{
ConvaiSDKSetupEditorWindow.OnAPIKeySet += () =>
{
if (ConvaiSDKSetupEditorWindow.IsApiKeySet)
{
InjectData();
}
};
}
_ui.RefreshBtn.clicked += RefreshBtnOnClicked;
_ui.ImportBtn.clicked += ImportBtnOnClicked;
_ui.NextPageBtn.clicked += NextPageBtnOnClicked;
_ui.PreviousPageBtn.clicked += PreviousPageBtnOnClicked;
}
internal ServerAnimationPageData Data { get; }
private List<ServerAnimationItemController> Items { get; set; } = new();
~ServerAnimationPageController()
{
_ui.RefreshBtn.clicked -= RefreshBtnOnClicked;
_ui.ImportBtn.clicked -= ImportBtnOnClicked;
}
private async void ImportBtnOnClicked()
{
List<ServerAnimationItemResponse> selectedAnimations = Items.FindAll(x => x.Data.IsSelected).Select(x => x.Data.ItemResponse).ToList();
if (selectedAnimations.Count == 0)
{
EditorUtility.DisplayDialog("Error", "No animations selected!", "OK");
return;
}
// Disable Refresh and Import buttons
_ui.RefreshBtn.SetEnabled(false);
_ui.ImportBtn.SetEnabled(false);
Items.ForEach(x => x.UpdateCanBeSelected(false));
try
{
Task importTask = ServerAnimationService.ImportAnimations(selectedAnimations);
// Show progress bar
float progress = 0f;
while (!importTask.IsCompleted)
{
EditorUtility.DisplayProgressBar("Importing Animations", $"Progress: {progress:P0}", progress);
await Task.Delay(100); // Update every 100ms
progress += 0.01f; // Increment progress (you may want to adjust this based on actual progress)
if (progress > 0.99f)
progress = Random.value; // Set it to random value
}
// Ensure task is completed and handle any exceptions
await importTask;
EditorUtility.DisplayProgressBar("Importing Animations", "Complete!", 1f);
await Task.Delay(500); // Show 100% for half a second
}
catch (Exception e)
{
EditorUtility.DisplayDialog("Import Error", $"An error occurred during import: {e.Message}", "OK");
}
finally
{
EditorUtility.ClearProgressBar();
// Re-enable Refresh and Import buttons
_ui.RefreshBtn.SetEnabled(true);
_ui.ImportBtn.SetEnabled(true);
Items.ForEach(x => x.Reset());
}
}
private void RefreshBtnOnClicked()
{
InjectData();
}
private async void InjectData()
{
_ui.PreviousPageBtn.SetEnabled(false);
_ui.NextPageBtn.SetEnabled(false);
_ui.RefreshBtn.SetEnabled(false);
_ui.ImportBtn.SetEnabled(false);
List<ServerAnimationItemData> list = new();
await foreach (ServerAnimationItemResponse serverAnimationItemResponse in Data.GetAnimationItems())
list.Add(new ServerAnimationItemData
{
ItemResponse = serverAnimationItemResponse
});
list = list.OrderBy(item =>
{
return item.ItemResponse.Status.ToLower() switch
{
"success" => 0,
"pending" or "processing" => 1,
"failed" => 2,
_ => 3
};
}).ToList();
Items = _ui.ShowAnimationList(list);
_ui.PreviousPageBtn.SetEnabled(Data.CurrentPage > 1);
_ui.NextPageBtn.SetEnabled(Data.CurrentPage < Data.TotalPages - 1);
_ui.RefreshBtn.SetEnabled(true);
_ui.ImportBtn.SetEnabled(true);
}
private void PreviousPageBtnOnClicked()
{
Data.CurrentPage--;
Data.CurrentPage = Math.Max(Data.CurrentPage, 1);
_ui.PreviousPageBtn.SetEnabled(false);
_ui.NextPageBtn.SetEnabled(false);
InjectData();
}
private void NextPageBtnOnClicked()
{
if (Data.CurrentPage < Data.TotalPages - 1)
{
Data.CurrentPage++;
}
else
{
Data.CurrentPage = 1;
}
_ui.PreviousPageBtn.SetEnabled(false);
_ui.NextPageBtn.SetEnabled(false);
InjectData();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cde2cc1e43134111918c555b5cb19ae6
timeCreated: 1729858864

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 972b84739ae24eee9b6c60299a9f554e
timeCreated: 1729858708

View File

@ -0,0 +1,13 @@
namespace Convai.Scripts.Editor.Setup.ServerAnimation.Model {
internal class ServerAnimationItemData {
public bool CanBeSelected;
public bool IsSelected;
public ServerAnimationItemResponse ItemResponse;
public bool IsPending => ItemResponse.Status == "pending";
public bool IsSuccess => ItemResponse.Status == "success";
public bool IsFailed => ItemResponse.Status == "failed";
}
}

View File

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

View File

@ -0,0 +1,23 @@
using System.Collections.Generic;
using UnityEngine;
namespace Convai.Scripts.Editor.Setup.ServerAnimation.Model {
internal class ServerAnimationPageData {
private ServerAnimationListResponse _animationListResponse;
public Dictionary<string, Texture2D> Thumbnails { get; } = new();
public int TotalPages => _animationListResponse.TotalPages;
public int CurrentPage { get; set; } = 1;
public async IAsyncEnumerable<ServerAnimationItemResponse> GetAnimationItems() {
if ( !ConvaiAPIKeySetup.GetAPIKey( out string apiKey ) ) yield break;
_animationListResponse = await ServerAnimationAPI.GetAnimationList( apiKey, CurrentPage );
if ( _animationListResponse == null || _animationListResponse.Animations == null ) yield break;
foreach ( ServerAnimationItemResponse serverAnimationItemResponse in _animationListResponse.Animations ) yield return serverAnimationItemResponse;
}
}
}

View File

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

View File

@ -0,0 +1,191 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
namespace Convai.Scripts.Editor.Setup.ServerAnimation
{
public static class ServerAnimationAPI
{
private const string BASE_URL = "https://api.convai.com/";
private const string GET_ANIMATION_LIST = "animations/list";
private const string GET_ANIMATION = "animations/get";
public static async Task<ServerAnimationListResponse> GetAnimationList(string apiKey, int page)
{
HttpClient client = CreateHttpClient(apiKey);
HttpContent content = CreateHttpContent(new Dictionary<string, object> {
{ "status", "success" },
{ "generate_signed_urls", true },
{ "page", page }
});
string response = await SendPostRequestAsync(GetEndPoint(GET_ANIMATION_LIST), client, content);
ServerAnimationListResponse serverAnimationListResponse = JsonConvert.DeserializeObject<ServerAnimationListResponse>(response);
return serverAnimationListResponse;
}
public static async Task<bool> DownloadAnimation(string animationID, string apiKey, string saveDirectory, string newFileName)
{
HttpClient client = CreateHttpClient(apiKey);
HttpContent content = CreateHttpContent(new Dictionary<string, object> {
{ "animation_id", animationID },
{ "generate_upload_video_urls", false }
});
string response = await SendPostRequestAsync(GetEndPoint(GET_ANIMATION), client, content);
dynamic animation = JsonConvert.DeserializeObject(response);
if (animation == null)
return false;
string gcpLink = animation.animation.fbx_gcp_file;
using HttpClient downloadClient = new();
try
{
byte[] fileBytes = await downloadClient.GetByteArrayAsync(gcpLink);
// Ensure the save directory exists
if (!Directory.Exists(saveDirectory))
Directory.CreateDirectory(saveDirectory);
// Construct the full file path with the new name and .fbx extension
string filePath = Path.Combine(saveDirectory, $"{newFileName}.fbx");
int counter = 1;
while (File.Exists(filePath))
{
filePath = Path.Combine(saveDirectory, $"{newFileName}_{counter}.fbx");
counter++;
}
// Write the file
await File.WriteAllBytesAsync(filePath, fileBytes);
string relativePath = filePath.Substring(Application.dataPath.Length + 1).Replace('\\', '/');
relativePath = "Assets/" + relativePath;
//AssetDatabase.ImportAsset(relativePath);
AssetDatabase.Refresh();
ModelImporter importer = AssetImporter.GetAtPath(relativePath) as ModelImporter;
if (importer != null)
{
importer.animationType = ModelImporterAnimationType.Human;
importer.importAnimatedCustomProperties = true;
importer.materialLocation = ModelImporterMaterialLocation.External;
importer.SaveAndReimport();
}
return true;
}
catch (Exception ex)
{
Debug.LogError($"Error downloading file: {ex.Message}");
return false;
}
}
public static async Task<Texture2D> GetThumbnail(string thumbnailURL)
{
using HttpClient client = new();
try
{
byte[] fileBytes = await client.GetByteArrayAsync(thumbnailURL);
Texture2D texture = new(256, 256);
texture.LoadImage(fileBytes);
return texture;
}
catch (Exception ex)
{
Debug.LogError($"Error downloading thumbnail: {ex.Message}");
return null;
}
}
private static string GetEndPoint(string endpoint)
{
return BASE_URL + endpoint;
}
private static HttpClient CreateHttpClient(string apiKey)
{
if (string.IsNullOrEmpty(apiKey))
return new HttpClient();
HttpClient httpClient = new()
{
Timeout = TimeSpan.FromSeconds(30),
DefaultRequestHeaders = {
Accept = {
new MediaTypeWithQualityHeaderValue( "application/json" )
}
}
};
httpClient.DefaultRequestHeaders.Add("CONVAI-API-KEY", apiKey);
httpClient.DefaultRequestHeaders.Add("Source", "convaiUI");
return httpClient;
}
private static HttpContent CreateHttpContent(Dictionary<string, object> dataToSend)
{
// Serialize the dictionary to JSON
string json = JsonConvert.SerializeObject(dataToSend);
// Convert JSON to HttpContent
return new StringContent(json, Encoding.UTF8, "application/json");
}
private static HttpContent CreateHttpContent(string json)
{
// Convert JSON to HttpContent
return new StringContent(json, Encoding.UTF8, "application/json");
}
private static async Task<string> SendPostRequestAsync(string endpoint, HttpClient httpClient, HttpContent content)
{
try
{
HttpResponseMessage response = await httpClient.PostAsync(endpoint, content);
response.EnsureSuccessStatusCode();
string responseContent = await response.Content.ReadAsStringAsync();
return responseContent;
}
catch (HttpRequestException e)
{
Debug.Log($"Request to {endpoint} failed: {e.Message}");
return null;
}
}
}
[Serializable]
public class ServerAnimationListResponse
{
[JsonProperty("animations")] public List<ServerAnimationItemResponse> Animations { get; private set; }
[JsonProperty("transaction_id")] public string TransactionID { get; private set; }
[JsonProperty("total_pages")] public int TotalPages { get; private set; }
[JsonProperty("page")] public int CurrentPage { get; private set; }
[JsonProperty("total")] public int TotalItems { get; private set; }
}
[Serializable]
public class ServerAnimationItemResponse
{
public ServerAnimationItemResponse(string animationID, string animationName, string status, string thumbnailURL)
{
AnimationID = animationID;
AnimationName = animationName;
Status = status;
ThumbnailURL = thumbnailURL;
}
[JsonProperty("animation_id")] public string AnimationID { get; private set; }
[JsonProperty("animation_name")] public string AnimationName { get; private set; }
[JsonProperty("status")] public string Status { get; private set; }
[JsonProperty("thumbnail_gcp_file")] public string ThumbnailURL { get; private set; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3163b8ff3c034b878e606e4924a02172
timeCreated: 1734419624

View File

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
namespace Convai.Scripts.Editor.Setup.ServerAnimation {
internal static class ServerAnimationService {
private const string DIRECTORY_SAVE_KEY = "CONVAI_SERVER_ANIMATION_SAVE_PATH";
public static async Task ImportAnimations( List<ServerAnimationItemResponse> animations ) {
if ( !ConvaiAPIKeySetup.GetAPIKey( out string apiKey ) ) return;
if ( animations.Count == 0 ) {
EditorUtility.DisplayDialog( "Import Animation Process", "Cannot start import process since no animations are selected", "Ok" );
return;
}
string savePath = UpdateAnimationSavePath();
if ( string.IsNullOrEmpty( savePath ) ) {
EditorUtility.DisplayDialog( "Failed", "Import Operation Cancelled", "Ok" );
return;
}
List<string> allAnimations = animations.Select( x => x.AnimationName ).ToList();
List<string> successfulImports = new();
foreach ( ServerAnimationItemResponse anim in animations ) {
bool result = await ServerAnimationAPI.DownloadAnimation( anim.AnimationID, apiKey, savePath, anim.AnimationName );
if ( result ) successfulImports.Add( anim.AnimationName );
}
LogResult( successfulImports, allAnimations );
AssetDatabase.Refresh();
}
private static void LogResult( List<string> successfulImports, List<string> animPaths ) {
string dialogMessage = $"Successfully Imported{Environment.NewLine}";
successfulImports.ForEach( x => dialogMessage += x + Environment.NewLine );
List<string> unSuccessFullImports = animPaths.Except( successfulImports ).ToList();
if ( unSuccessFullImports.Count > 0 ) {
dialogMessage += $"Could not import{Environment.NewLine}";
unSuccessFullImports.ForEach( x => dialogMessage += x + Environment.NewLine );
}
EditorUtility.DisplayDialog( "Import Animation Result", dialogMessage, "Ok" );
}
private static string UpdateAnimationSavePath() {
string selectedPath;
string currentPath = EditorPrefs.GetString( DIRECTORY_SAVE_KEY, Application.dataPath );
while ( true ) {
selectedPath = EditorUtility.OpenFolderPanel( "Select folder within project", currentPath, "" );
if ( string.IsNullOrEmpty( selectedPath ) ) {
selectedPath = string.Empty;
break;
}
if ( !IsSubfolder( selectedPath, Application.dataPath ) ) {
EditorUtility.DisplayDialog( "Invalid Folder Selected", "Please select a folder within the project", "Ok" );
continue;
}
EditorPrefs.SetString( DIRECTORY_SAVE_KEY, selectedPath );
break;
}
return selectedPath;
}
private static bool IsSubfolder( string pathA, string pathB ) {
// Get full paths to handle any relative path issues
string fullPathA = Path.GetFullPath( pathA );
string fullPathB = Path.GetFullPath( pathB );
// Create URI objects for the paths
Uri uriA = new(fullPathA);
Uri uriB = new(fullPathB);
// Check if pathA is under pathB
return uriB.IsBaseOf( uriA );
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,66 @@
using Convai.Scripts.Editor.Setup.ServerAnimation.Controller;
using Convai.Scripts.Editor.Setup.ServerAnimation.Model;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup.ServerAnimation.View {
internal class ServerAnimationItemView : VisualElement {
private readonly Color _animationProcessFailure = new(1f, 131f / 255f, 131f / 255f, 1f); //rgb(255,131,131)
private readonly Color _animationProcessPending = new(1f, 245f / 255f, 116f / 255f, 1f);
private readonly Color _animationProcessSuccess = new(0, 0, 0, 0.25f);
private readonly Color _selectedColor = new(11f / 255, 96f / 255, 73f / 255);
internal readonly ServerAnimationItemController Controller;
private Label _nameLabel;
internal ServerAnimationItemView( ServerAnimationItemData itemData, ServerAnimationPageController controller ) {
AddUI();
InitializeUI();
Controller = new ServerAnimationItemController( this, itemData, controller );
}
public VisualElement Card { get; private set; }
public VisualElement Thumbnail { get; private set; }
private void InitializeUI() {
_nameLabel = contentContainer.Q<Label>( "name" );
Card = contentContainer.Q<VisualElement>( "server-animation-card" );
Thumbnail = Card.Q<VisualElement>( "thumbnail" );
}
internal void SetAnimationName( string animationName ) {
_nameLabel.text = animationName;
}
internal void UpdateToggleState( bool isSelected, string status ) {
Card.style.borderTopColor = GetColor( isSelected, status );
Card.style.borderBottomColor = GetColor( isSelected, status );
Card.style.borderLeftColor = GetColor( isSelected, status );
Card.style.borderRightColor = GetColor( isSelected, status );
}
private Color GetColor( bool isSelected, string status ) {
if ( isSelected ) return _selectedColor;
return status switch {
"success" => _animationProcessSuccess,
"pending" => _animationProcessPending,
"processing" => _animationProcessPending,
"failed" => _animationProcessFailure,
_ => Color.white
};
}
private void AddUI() {
VisualTreeAsset treeAsset = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>( "Assets/Convai/Art/UI/Editor/server-animation-card.uxml" );
TemplateContainer child = treeAsset.CloneTree();
child.style.width = new Length( 100, LengthUnit.Percent );
child.style.height = new Length( 100, LengthUnit.Percent );
child.style.justifyContent = Justify.Center;
contentContainer.Add( child );
}
}
}

View File

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

View File

@ -0,0 +1,50 @@
using System.Collections.Generic;
using Convai.Scripts.Editor.Setup.ServerAnimation.Controller;
using Convai.Scripts.Editor.Setup.ServerAnimation.Model;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup.ServerAnimation.View {
internal class ServerAnimationPageView {
private ServerAnimationPageController _controller;
private VisualElement _listContainer;
private VisualElement _uiContainer;
internal Button ImportBtn;
internal Button NextPageBtn;
internal Button PreviousPageBtn;
internal Button RefreshBtn;
internal ServerAnimationPageView( VisualElement root ) {
Initialize( root );
_controller = new ServerAnimationPageController( this );
}
private void Initialize( VisualElement root ) {
_uiContainer = root.Q<VisualElement>( "content-container" ).Q<VisualElement>( "server-anim" );
_listContainer = _uiContainer.Q<ScrollView>( "container" ).Q<VisualElement>( "grid" );
RefreshBtn = _uiContainer.Q<Button>( "refresh-btn" );
ImportBtn = _uiContainer.Q<Button>( "import-btn" );
NextPageBtn = _uiContainer.Q<Button>( "next-page-btn" );
PreviousPageBtn = _uiContainer.Q<Button>( "previous-page-btn" );
}
internal List<ServerAnimationItemController> ShowAnimationList( List<ServerAnimationItemData> datas ) {
List<ServerAnimationItemController> cards = new();
_listContainer.Clear();
foreach ( ServerAnimationItemData data in datas ) {
ServerAnimationItemView animationItemView = new(data, _controller);
cards.Add( animationItemView.Controller );
_listContainer.Add( animationItemView );
}
_listContainer.MarkDirtyRepaint();
return cards;
}
}
}

View File

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

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5a8fc4b643dc44b69ca160965fdbf478
timeCreated: 1723460878

View File

@ -0,0 +1,49 @@
using Convai.Scripts.Runtime.LoggerSystem;
using UnityEngine;
using UnityEngine.UIElements;
namespace Convai.Scripts.Editor.Setup.Updates
{
public class UpdatesSectionUI
{
private readonly VisualElement _root;
public UpdatesSectionUI(VisualElement root)
{
_root = root;
SetupUpdateSectionHandlers();
SetInitialVersionNumber();
}
private void SetupUpdateSectionHandlers()
{
// Button checkUpdatesButton = _root.Q<Button>("check-updates");
// if (checkUpdatesButton != null)
// checkUpdatesButton.clicked += CheckForUpdates;
Button viewFullChangelogButton = _root.Q<Button>("view-full-changelog");
if (viewFullChangelogButton != null)
viewFullChangelogButton.clicked += () => Application.OpenURL("https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/changelogs");
}
private void SetInitialVersionNumber()
{
Label versionLabel = _root.Q<Label>("current-version");
if (versionLabel != null)
versionLabel.text = "v3.3.1";
}
private void CheckForUpdates()
{
ConvaiLogger.DebugLog("Checking for updates...", ConvaiLogger.LogCategory.UI);
UpdateStatusMessage("Checking for updates...");
}
private void UpdateStatusMessage(string message)
{
Label statusLabel = _root.Q<Label>("update-message");
if (statusLabel != null)
statusLabel.text = message;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6519e8d66ce94102bedf6862d8d18f5d
timeCreated: 1723460891