initial upload
This commit is contained in:
8
Assets/Convai/Scripts/Editor/Build.meta
Normal file
8
Assets/Convai/Scripts/Editor/Build.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75651c622be9a5e41909da6a6682e9f9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,23 @@
|
||||
using Convai.Scripts.Runtime.PlayerStats;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Convai.Scripts.Editor.Build
|
||||
{
|
||||
public class ConvaiPlayerDataSOChecker : IPreprocessBuildWithReport
|
||||
{
|
||||
#region IPreprocessBuildWithReport Members
|
||||
|
||||
public int callbackOrder { get; }
|
||||
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
if (Resources.Load<ConvaiPlayerDataSO>(nameof(ConvaiPlayerDataSO)) != null) return;
|
||||
ConvaiPlayerDataSO convaiPlayerDataSO = ScriptableObject.CreateInstance<ConvaiPlayerDataSO>();
|
||||
ConvaiPlayerDataSO.CreatePlayerDataSO(convaiPlayerDataSO);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fa01e9aef4cc664ea0e76e20a210eaa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Convai/Scripts/Editor/CustomPackage.meta
Normal file
8
Assets/Convai/Scripts/Editor/CustomPackage.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 597bd5d03de1f16428d8240a3cb00d23
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
156
Assets/Convai/Scripts/Editor/CustomPackage/CCToolsImporter.cs
Normal file
156
Assets/Convai/Scripts/Editor/CustomPackage/CCToolsImporter.cs
Normal file
@ -0,0 +1,156 @@
|
||||
#if !CC_TOOLS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.PackageManager;
|
||||
using UnityEditor.PackageManager.Requests;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace Convai.Scripts.Editor.CustomPackage
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public static class CCToolsImporter
|
||||
{
|
||||
private const string CC_TOOLS_SYMBOL = "CC_TOOLS";
|
||||
private const string URP_URL = "https://github.com/soupday/cc_unity_tools_URP.git";
|
||||
private const string HDRP_URL = "https://github.com/soupday/cc_unity_tools_HDRP.git";
|
||||
private const string BASE_URL = "https://github.com/soupday/cc_unity_tools_3D.git";
|
||||
private const int MAX_RETRY_ATTEMPTS = 5;
|
||||
private static int _retryCount = 0;
|
||||
|
||||
static CCToolsImporter()
|
||||
{
|
||||
EditorApplication.update += CheckAndInstallCCTools;
|
||||
}
|
||||
|
||||
private static void CheckAndInstallCCTools()
|
||||
{
|
||||
// Ensure we only attempt to check/install once
|
||||
EditorApplication.update -= CheckAndInstallCCTools;
|
||||
|
||||
// Check if the package is already installed
|
||||
var listRequest = Client.List(true);
|
||||
while (!listRequest.IsCompleted) { }
|
||||
|
||||
if (IsPackageAlreadyInstalled(listRequest))
|
||||
{
|
||||
Debug.Log("CC Tools package is already installed. Adding define symbol.");
|
||||
AddCCToolsDefineSymbol();
|
||||
return;
|
||||
}
|
||||
|
||||
// If not installed, proceed with installation
|
||||
TryInstallCCTools();
|
||||
}
|
||||
|
||||
private static bool IsPackageAlreadyInstalled(ListRequest listRequest)
|
||||
{
|
||||
return listRequest.Status == StatusCode.Success &&
|
||||
listRequest.Result.Any(package => package.packageId.Contains("cc_unity_tools"));
|
||||
}
|
||||
|
||||
private static void TryInstallCCTools()
|
||||
{
|
||||
// Determine appropriate package URL based on render pipeline
|
||||
string packageUrl = DeterminePackageUrl();
|
||||
|
||||
// Start package installation asynchronously
|
||||
AddRequest addRequest = Client.Add(packageUrl);
|
||||
|
||||
// Use a coroutine-friendly approach to handle installation
|
||||
EditorApplication.update += () => HandlePackageInstallation(addRequest);
|
||||
}
|
||||
|
||||
private static void HandlePackageInstallation(AddRequest addRequest)
|
||||
{
|
||||
// Check if the request is not completed
|
||||
if (!addRequest.IsCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the update method to prevent multiple executions
|
||||
EditorApplication.update -= () => HandlePackageInstallation(addRequest);
|
||||
|
||||
switch (addRequest.Status)
|
||||
{
|
||||
case StatusCode.Success:
|
||||
Debug.Log("CC Tools has been installed successfully.");
|
||||
AddCCToolsDefineSymbol();
|
||||
|
||||
if (DeterminePackageUrl() == URP_URL)
|
||||
{
|
||||
ConvaiCustomPackageInstaller convaiCustomPackageInstaller = new ConvaiCustomPackageInstaller();
|
||||
convaiCustomPackageInstaller.InstallConvaiURPConverter();
|
||||
}
|
||||
break;
|
||||
case StatusCode.Failure:
|
||||
HandleInstallationFailure(addRequest);
|
||||
break;
|
||||
case StatusCode.InProgress:
|
||||
Debug.Log("CC Tools installation is in progress.");
|
||||
break;
|
||||
default:
|
||||
Debug.LogWarning("Unexpected status during CC Tools installation.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleInstallationFailure(AddRequest addRequest)
|
||||
{
|
||||
Debug.LogError($"CC Tools installation failed: {addRequest.Error.message}");
|
||||
|
||||
// Implement a retry mechanism
|
||||
if (_retryCount < MAX_RETRY_ATTEMPTS)
|
||||
{
|
||||
_retryCount++;
|
||||
Debug.Log($"Retrying CC Tools installation. Attempt {_retryCount} of {MAX_RETRY_ATTEMPTS}");
|
||||
|
||||
// Wait a short time before retrying to reduce chances of conflict
|
||||
EditorApplication.delayCall += () => { TryInstallCCTools(); };
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Maximum retry attempts reached. CC Tools installation failed.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string DeterminePackageUrl()
|
||||
{
|
||||
if (GraphicsSettings.currentRenderPipeline == null)
|
||||
return BASE_URL;
|
||||
|
||||
string renderPipelineName = GraphicsSettings.currentRenderPipeline.GetType().Name;
|
||||
|
||||
return renderPipelineName == "UniversalRenderPipelineAsset"
|
||||
? URP_URL
|
||||
: HDRP_URL;
|
||||
}
|
||||
|
||||
private static void AddCCToolsDefineSymbol()
|
||||
{
|
||||
foreach (BuildTarget target in Enum.GetValues(typeof(BuildTarget)))
|
||||
{
|
||||
BuildTargetGroup group = BuildPipeline.GetBuildTargetGroup(target);
|
||||
|
||||
if (group == BuildTargetGroup.Unknown)
|
||||
continue;
|
||||
|
||||
NamedBuildTarget namedTarget = NamedBuildTarget.FromBuildTargetGroup(group);
|
||||
List<string> symbols = PlayerSettings.GetScriptingDefineSymbols(namedTarget)
|
||||
.Split(';')
|
||||
.Select(d => d.Trim())
|
||||
.ToList();
|
||||
|
||||
if (!symbols.Contains(CC_TOOLS_SYMBOL))
|
||||
symbols.Add(CC_TOOLS_SYMBOL);
|
||||
|
||||
PlayerSettings.SetScriptingDefineSymbols(namedTarget, string.Join(";", symbols.ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38ca92565cfb4bdba6cdb861acd779e4
|
||||
timeCreated: 1719503566
|
||||
@ -0,0 +1,554 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Convai.Scripts.Runtime.LoggerSystem;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.PackageManager;
|
||||
using UnityEditor.PackageManager.Requests;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
|
||||
|
||||
namespace Convai.Scripts.Editor.CustomPackage
|
||||
{
|
||||
public class ConvaiCustomPackageInstaller : IActiveBuildTargetChanged
|
||||
{
|
||||
#region Constants
|
||||
|
||||
// XR Package Paths
|
||||
private const string AR_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiARUpgrader.unitypackage";
|
||||
private const string VR_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiVRUpgrader.unitypackage";
|
||||
private const string MR_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiMRUpgrader.unitypackage";
|
||||
private const string XR_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiXR.unitypackage";
|
||||
|
||||
private const string XR_PREFAB_PATH = "Assets/Convai/ConvaiXR/Prefabs/Convai Essentials - XR.prefab";
|
||||
|
||||
// Other Package Paths
|
||||
private const string IOS_BUILD_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiiOSBuild.unitypackage";
|
||||
private const string URP_CONVERTER_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiURPConverter.unitypackage";
|
||||
private const string TMP_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiCustomTMP.unitypackage";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
private SetupTypes _currentSetup;
|
||||
private List<string> _setupSteps;
|
||||
private int _totalSetupSteps;
|
||||
private int _currentStep = 0;
|
||||
private string _currentStepDescription = "";
|
||||
private bool _assembliesLocked = false;
|
||||
|
||||
private BuildTarget _selectedARPlatform;
|
||||
private InstallationType _installationType;
|
||||
|
||||
public int callbackOrder { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enums
|
||||
|
||||
private enum SetupTypes
|
||||
{
|
||||
AR,
|
||||
VR,
|
||||
MR,
|
||||
iOS,
|
||||
NormalUnityPackage
|
||||
}
|
||||
|
||||
private enum InstallationType
|
||||
{
|
||||
Automatic,
|
||||
Manual
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public ConvaiCustomPackageInstaller()
|
||||
{
|
||||
}
|
||||
|
||||
public ConvaiCustomPackageInstaller(VisualElement root = null)
|
||||
{
|
||||
if (root != null)
|
||||
{
|
||||
InitializeUI(root);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI Initialization
|
||||
|
||||
private void InitializeUI(VisualElement root)
|
||||
{
|
||||
root.Q<Button>("install-ar-package").clicked += () => StartPackageInstall(SetupTypes.AR);
|
||||
root.Q<Button>("install-vr-package").clicked += () => StartPackageInstall(SetupTypes.VR);
|
||||
root.Q<Button>("install-mr-package").clicked += () => StartPackageInstall(SetupTypes.MR);
|
||||
root.Q<Button>("install-ios-build-package").clicked += () =>
|
||||
{
|
||||
InitializeSetupSteps(SetupTypes.iOS);
|
||||
InstallConvaiPackage(IOS_BUILD_PACKAGE_PATH);
|
||||
TryToDownloadiOSDLL();
|
||||
};
|
||||
root.Q<Button>("install-urp-converter").clicked += () =>
|
||||
{
|
||||
InitializeSetupSteps(SetupTypes.NormalUnityPackage);
|
||||
InstallConvaiPackage(URP_CONVERTER_PACKAGE_PATH);
|
||||
EditorUtility.ClearProgressBar();
|
||||
};
|
||||
root.Q<Button>("convai-custom-tmp-package").clicked += () =>
|
||||
{
|
||||
InitializeSetupSteps(SetupTypes.NormalUnityPackage);
|
||||
InstallConvaiPackage(TMP_PACKAGE_PATH);
|
||||
EditorUtility.ClearProgressBar();
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Build Target Change Handling
|
||||
|
||||
public void OnActiveBuildTargetChanged(BuildTarget previousTarget, BuildTarget newTarget)
|
||||
{
|
||||
if (newTarget == BuildTarget.iOS) TryToDownloadiOSDLL();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Package Installation
|
||||
|
||||
private async void StartPackageInstall(SetupTypes setupType)
|
||||
{
|
||||
_currentSetup = setupType;
|
||||
|
||||
InitializeSetupSteps(setupType);
|
||||
|
||||
if (setupType == SetupTypes.AR)
|
||||
{
|
||||
if (!await ConfirmARPlatform()) return;
|
||||
if (!await ConfirmAutomaticInstallation(SetupTypes.AR)) return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!await ConfirmInstallationType(setupType)) return;
|
||||
}
|
||||
|
||||
InitializeSetupSteps(setupType);
|
||||
_currentStep = 0;
|
||||
|
||||
ConvaiLogger.DebugLog($"Installation of {setupType} package has started... This process may take 3-5 minutes.", ConvaiLogger.LogCategory.Editor);
|
||||
|
||||
LockAssemblies();
|
||||
|
||||
try
|
||||
{
|
||||
await HandlePackageInstall();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConvaiLogger.Error($"An error occurred during package installation: {e.Message}", ConvaiLogger.LogCategory.Editor);
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnlockAssemblies();
|
||||
EditorUtility.ClearProgressBar();
|
||||
ConvaiLogger.DebugLog($"Convai {setupType} setup process completed.", ConvaiLogger.LogCategory.Editor);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeSetupSteps(SetupTypes setupType)
|
||||
{
|
||||
_setupSteps = new List<string>();
|
||||
if (setupType == SetupTypes.AR && _selectedARPlatform != EditorUserBuildSettings.activeBuildTarget)
|
||||
{
|
||||
_setupSteps.Add($"Change build platform to {_selectedARPlatform}");
|
||||
}
|
||||
else if ((setupType == SetupTypes.VR || setupType == SetupTypes.MR) &&
|
||||
_installationType == InstallationType.Automatic &&
|
||||
EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android)
|
||||
{
|
||||
_setupSteps.Add("Change build platform to Android");
|
||||
}
|
||||
|
||||
switch (setupType)
|
||||
{
|
||||
case SetupTypes.AR:
|
||||
if (_installationType == InstallationType.Automatic)
|
||||
{
|
||||
_setupSteps.AddRange(_selectedARPlatform == BuildTarget.Android
|
||||
? new[] { "Universal Render Pipeline (URP)", "ARCore", "Convai AR Package", "Convai URP Converter" }
|
||||
: new[] { "Universal Render Pipeline (URP)", "ARKit", "Convai iOS DLL", "Convai iOS Build Package", "Convai AR Package", "Convai URP Converter", });
|
||||
}
|
||||
else
|
||||
{
|
||||
_setupSteps.Add("Convai AR Package");
|
||||
}
|
||||
|
||||
break;
|
||||
case SetupTypes.VR:
|
||||
if (_installationType == InstallationType.Automatic)
|
||||
{
|
||||
_setupSteps.AddRange(new[] { "Universal Render Pipeline (URP)", "OpenXR", "XR Interaction Toolkit", "Convai VR Package", "Convai URP Converter" });
|
||||
}
|
||||
else
|
||||
{
|
||||
_setupSteps.Add("Convai VR Package");
|
||||
}
|
||||
|
||||
break;
|
||||
case SetupTypes.MR:
|
||||
if (_installationType == InstallationType.Automatic)
|
||||
{
|
||||
_setupSteps.AddRange(new[] { "Universal Render Pipeline (URP)", "XR Management", "Oculus XR Plugin", "Convai URP Converter", "Meta XR SDK All", "Convai MR Package" });
|
||||
}
|
||||
else
|
||||
{
|
||||
_setupSteps.Add("Convai MR Package");
|
||||
}
|
||||
|
||||
break;
|
||||
case SetupTypes.iOS:
|
||||
_setupSteps.AddRange(new[] { "Convai iOS Build Package", "Convai iOS DLL" });
|
||||
break;
|
||||
case SetupTypes.NormalUnityPackage:
|
||||
_setupSteps.Add("Convai Custom Package");
|
||||
break;
|
||||
}
|
||||
|
||||
_totalSetupSteps = _setupSteps.Count;
|
||||
}
|
||||
|
||||
private async Task HandlePackageInstall()
|
||||
{
|
||||
switch (_currentSetup)
|
||||
{
|
||||
case SetupTypes.AR:
|
||||
await HandleARPackageInstall();
|
||||
break;
|
||||
case SetupTypes.VR:
|
||||
await HandleVRPackageInstall();
|
||||
break;
|
||||
case SetupTypes.MR:
|
||||
await HandleMRPackageInstall();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleARPackageInstall()
|
||||
{
|
||||
ChangeBuildTarget(_selectedARPlatform);
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
await InitializeURPSetup();
|
||||
|
||||
if (_selectedARPlatform == BuildTarget.Android)
|
||||
{
|
||||
await InitializeARCoreSetup();
|
||||
}
|
||||
else if (_selectedARPlatform == BuildTarget.iOS)
|
||||
{
|
||||
await InitializeARKitSetup();
|
||||
TryToDownloadiOSDLL();
|
||||
InstallConvaiPackage(IOS_BUILD_PACKAGE_PATH);
|
||||
}
|
||||
|
||||
InstallConvaiPackage(AR_PACKAGE_PATH);
|
||||
InstallConvaiPackage(URP_CONVERTER_PACKAGE_PATH);
|
||||
}
|
||||
|
||||
private async Task HandleVRPackageInstall()
|
||||
{
|
||||
if (_installationType == InstallationType.Automatic)
|
||||
{
|
||||
ChangeBuildTarget(BuildTarget.Android);
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
await InitializeURPSetup();
|
||||
await InitializeOpenXRSetup();
|
||||
await InitializeXRInteractionToolkitSetup();
|
||||
InstallConvaiPackage(VR_PACKAGE_PATH);
|
||||
InstallConvaiPackage(URP_CONVERTER_PACKAGE_PATH);
|
||||
}
|
||||
else
|
||||
{
|
||||
InstallConvaiPackage(XR_PACKAGE_PATH);
|
||||
await TryToInstantiateXRPrefab();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleMRPackageInstall()
|
||||
{
|
||||
if (_installationType == InstallationType.Automatic)
|
||||
{
|
||||
ChangeBuildTarget(BuildTarget.Android);
|
||||
await InitializeURPSetup();
|
||||
await InitializeXRManagementSetup();
|
||||
await InitializeOculusXRSetup();
|
||||
InstallConvaiPackage(URP_CONVERTER_PACKAGE_PATH);
|
||||
await InitializeMetaXRSDKAllSetup();
|
||||
InstallConvaiPackage(MR_PACKAGE_PATH);
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
else
|
||||
{
|
||||
InstallConvaiPackage(XR_PACKAGE_PATH);
|
||||
await TryToInstantiateXRPrefab();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Package Initialization
|
||||
|
||||
private async Task InitializePackageSetup(string packageName, string stepDescription)
|
||||
{
|
||||
UpdateProgressBar(stepDescription);
|
||||
if (IsPackageInstalled(packageName))
|
||||
{
|
||||
IncrementProgress($"{packageName} is already installed. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
ConvaiLogger.DebugLog($"{packageName} Package Installation Request sent to Package Manager.", ConvaiLogger.LogCategory.Editor);
|
||||
AddRequest request = Client.Add(packageName);
|
||||
|
||||
while (!request.IsCompleted)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
if (request.Status == StatusCode.Success)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
IncrementProgress($"Successfully installed: {packageName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
ConvaiLogger.Error($"Failed to install {packageName}: {request.Error.message}", ConvaiLogger.LogCategory.Editor);
|
||||
}
|
||||
}
|
||||
|
||||
private Task InitializeURPSetup() => InitializePackageSetup("com.unity.render-pipelines.universal", "Installing Universal Render Pipeline (URP)");
|
||||
private Task InitializeARCoreSetup() => InitializePackageSetup("com.unity.xr.arcore@5.1.4", "Installing ARCore");
|
||||
private Task InitializeARKitSetup() => InitializePackageSetup("com.unity.xr.arkit@5.1.4", "Installing ARKit");
|
||||
private Task InitializeOpenXRSetup() => InitializePackageSetup("com.unity.xr.openxr@1.10.0", "Installing OpenXR");
|
||||
private Task InitializeOculusXRSetup() => InitializePackageSetup("com.unity.xr.oculus", "Installing Oculus XR Plugin");
|
||||
private Task InitializeMetaXRSDKAllSetup() => InitializePackageSetup("com.meta.xr.sdk.all", "Installing Meta XR SDK");
|
||||
private Task InitializeXRManagementSetup() => InitializePackageSetup("com.unity.xr.management", "Installing XR Management");
|
||||
private Task InitializeXRInteractionToolkitSetup() => InitializePackageSetup("com.unity.xr.interaction.toolkit@2.5.4", "Installing XR Interaction Toolkit");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Package Installation Methods
|
||||
|
||||
private void InstallConvaiPackage(string packagePath)
|
||||
{
|
||||
string packageName = Path.GetFileNameWithoutExtension(packagePath);
|
||||
UpdateProgressBar($"Installing {packageName}");
|
||||
|
||||
ConvaiLogger.DebugLog($"Importing: {packageName}", ConvaiLogger.LogCategory.Editor);
|
||||
|
||||
AssetDatabase.ImportPackage(packagePath, false);
|
||||
|
||||
IncrementProgress($"{packageName} Custom Unity Package Installation Completed.");
|
||||
}
|
||||
|
||||
private async Task TryToInstantiateXRPrefab()
|
||||
{
|
||||
if (_installationType != InstallationType.Manual || _currentSetup == SetupTypes.AR) return;
|
||||
|
||||
ConvaiLogger.DebugLog("Attempting to instantiate XR Prefab...", ConvaiLogger.LogCategory.Editor);
|
||||
|
||||
const int maxAttempts = 30;
|
||||
const int delayBetweenAttempts = 1000; // 1 second
|
||||
|
||||
for (int attempt = 0; attempt < maxAttempts; attempt++)
|
||||
{
|
||||
await DelayWithEditorAvailableCheck(delayBetweenAttempts);
|
||||
|
||||
if (TryInstantiatePrefab(out GameObject prefab))
|
||||
{
|
||||
ConvaiLogger.DebugLog($"Convai XR Prefab Instantiated on attempt {attempt + 1}.", ConvaiLogger.LogCategory.Editor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ConvaiLogger.Error($"Failed to load XR Prefab at path: {XR_PREFAB_PATH} after {maxAttempts} attempts.", ConvaiLogger.LogCategory.Editor);
|
||||
}
|
||||
|
||||
private bool TryInstantiatePrefab(out GameObject prefab)
|
||||
{
|
||||
AssetDatabase.Refresh();
|
||||
prefab = AssetDatabase.LoadAssetAtPath<GameObject>(XR_PREFAB_PATH);
|
||||
|
||||
if (prefab != null)
|
||||
{
|
||||
PrefabUtility.InstantiatePrefab(prefab);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void TryToDownloadiOSDLL() => iOSDLLDownloader.TryToDownload();
|
||||
|
||||
private async Task<bool> ConfirmARPlatform()
|
||||
{
|
||||
string[] options = { "Android", "iOS" };
|
||||
int choice = await DisplayDialogComplex("Select AR Platform", "Choose the target platform for AR development:", options);
|
||||
|
||||
if (choice == -1 || choice == 1) return false; // User cancelled
|
||||
|
||||
_selectedARPlatform = choice == 0 ? BuildTarget.Android : BuildTarget.iOS;
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> ConfirmInstallationType(SetupTypes setupType)
|
||||
{
|
||||
string[] options = { "Automatic", "Manual" };
|
||||
string message = $"Choose the installation type for {setupType}:\n\n" +
|
||||
"Automatic Installation: All necessary packages will be installed, and your project settings will be changed to prepare for building. This option is suitable for new projects. " +
|
||||
"Using it on existing projects may pose risks and could lead to errors in your current setup.\n\n" +
|
||||
"Manual Installation: Only the Convai XR package will be installed, and your project settings will remain unchanged. This option is suitable for those who already have an existing XR project.";
|
||||
|
||||
int choice = await DisplayDialogComplex($"Select {setupType} Installation Type", message, options);
|
||||
|
||||
if (choice == -1 || choice == 1) return false; // User cancelled
|
||||
|
||||
_installationType = choice == 0 ? InstallationType.Automatic : InstallationType.Manual;
|
||||
|
||||
if (_installationType == InstallationType.Automatic)
|
||||
{
|
||||
return await ConfirmAutomaticInstallation(setupType);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> ConfirmAutomaticInstallation(SetupTypes setupType)
|
||||
{
|
||||
InitializeSetupSteps(setupType);
|
||||
|
||||
string packageList = string.Join("\n", _setupSteps.Select(p => $"- {p}"));
|
||||
|
||||
string message = $"Automatic installation for {setupType} will change your project settings, import the following packages, and perform the following steps: \n\n" +
|
||||
$"{packageList}\n\n" +
|
||||
"* This process cannot be undone.\n\n" +
|
||||
"** Recommended for new projects.\n\n" +
|
||||
"Are you sure you want to proceed with the automatic installation?";
|
||||
|
||||
int choice = await DisplayDialogComplex("Confirm Automatic Installation", message, new[] { "Yes, proceed", "No, cancel" });
|
||||
|
||||
return choice == 0;
|
||||
}
|
||||
|
||||
private void ChangeBuildTarget(BuildTarget target)
|
||||
{
|
||||
UpdateProgressBar($"Changing Build Target to {target}");
|
||||
|
||||
if (EditorUserBuildSettings.activeBuildTarget != target)
|
||||
{
|
||||
ConvaiLogger.DebugLog($"Build Target Platform is being Changed to {target}...", ConvaiLogger.LogCategory.Editor);
|
||||
|
||||
BuildTargetGroup targetGroup = target == BuildTarget.Android ? BuildTargetGroup.Android : BuildTargetGroup.iOS;
|
||||
bool switched = EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroup, target);
|
||||
|
||||
if (switched)
|
||||
{
|
||||
IncrementProgress($"Build Target changed to {target}");
|
||||
}
|
||||
else
|
||||
{
|
||||
IncrementProgress($"Failed to Change Build Target to {target}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IncrementProgress($"Build Target is already set to {target}");
|
||||
}
|
||||
}
|
||||
|
||||
public void InstallConvaiURPConverter()
|
||||
{
|
||||
InitializeSetupSteps(SetupTypes.NormalUnityPackage);
|
||||
InstallConvaiPackage(URP_CONVERTER_PACKAGE_PATH);
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
private static bool IsPackageInstalled(string packageName) =>
|
||||
PackageInfo.GetAllRegisteredPackages().Any(packageInfo => packageInfo.name == packageName);
|
||||
|
||||
private void LockAssemblies()
|
||||
{
|
||||
if (!_assembliesLocked)
|
||||
{
|
||||
EditorApplication.LockReloadAssemblies();
|
||||
_assembliesLocked = true;
|
||||
ConvaiLogger.DebugLog("Assemblies locked for package installation.", ConvaiLogger.LogCategory.Editor);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnlockAssemblies()
|
||||
{
|
||||
if (_assembliesLocked)
|
||||
{
|
||||
EditorApplication.UnlockReloadAssemblies();
|
||||
_assembliesLocked = false;
|
||||
ConvaiLogger.DebugLog("Assemblies unlocked after package installation.", ConvaiLogger.LogCategory.Editor);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DelayWithEditorAvailableCheck(int delayBetweenAttempts)
|
||||
{
|
||||
await Task.Delay(delayBetweenAttempts);
|
||||
|
||||
if (EditorApplication.isCompiling || EditorApplication.isUpdating)
|
||||
{
|
||||
ConvaiLogger.DebugLog("Waiting for Unity to finish compiling or updating...", ConvaiLogger.LogCategory.Editor);
|
||||
await DelayWithEditorAvailableCheck(delayBetweenAttempts); // Recursive call to wait again
|
||||
}
|
||||
}
|
||||
|
||||
private Task<int> DisplayDialogComplex(string title, string message, string[] options)
|
||||
{
|
||||
return Task.FromResult(EditorUtility.DisplayDialogComplex(title, message, options[0], "Cancel", options[1]));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Progress Management
|
||||
|
||||
private void UpdateProgressBar(string stepDescription)
|
||||
{
|
||||
_currentStepDescription = stepDescription;
|
||||
float progress = (float)_currentStep / _totalSetupSteps;
|
||||
int progressPercentage = Mathf.RoundToInt(progress * 100);
|
||||
string title = $"Convai {_currentSetup} Setup Progress";
|
||||
string info = $"Step {_currentStep + 1} of {_totalSetupSteps}: {_currentStepDescription} ({progressPercentage}% Complete)";
|
||||
|
||||
EditorUtility.DisplayProgressBar(title, info, progress);
|
||||
}
|
||||
|
||||
private void IncrementProgress(string completedStepDescription)
|
||||
{
|
||||
_currentStep++;
|
||||
ConvaiLogger.DebugLog(completedStepDescription, ConvaiLogger.LogCategory.Editor);
|
||||
if (_currentStep < _totalSetupSteps)
|
||||
{
|
||||
UpdateProgressBar(_setupSteps[_currentStep]);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53fd043db40ab5b4798e83e7f3204952
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
172
Assets/Convai/Scripts/Editor/CustomPackage/IOSDLLDownloader.cs
Normal file
172
Assets/Convai/Scripts/Editor/CustomPackage/IOSDLLDownloader.cs
Normal file
@ -0,0 +1,172 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using Convai.Scripts.Runtime.LoggerSystem;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Convai.Scripts.Editor.CustomPackage
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor window for downloading and extracting iOS DLL from a specified URL.
|
||||
/// </summary>
|
||||
public class iOSDLLDownloader
|
||||
{
|
||||
private const string DOWNLOAD_ENDPOINT_URL = "https://api.convai.com/user/downloadAsset";
|
||||
private const string RELATIVE_PATH = "Convai/Plugins/gRPC/Grpc.Core/runtimes";
|
||||
private static string _targetDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to download the iOS DLL if it doesn't already exist.
|
||||
/// </summary>
|
||||
public static void TryToDownload()
|
||||
{
|
||||
if (CheckFileExistence()) return;
|
||||
ConvaiLogger.DebugLog("<color=lime>The iOS DLL download has started...</color>", ConvaiLogger.LogCategory.Editor);
|
||||
DownloadAndExtract(GetTargetDirectory());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine to download and extract the ZIP file from the specified URL.
|
||||
/// </summary>
|
||||
/// <param name="outputPath">Directory to extract the contents to.</param>
|
||||
/// <returns></returns>
|
||||
private static void DownloadAndExtract(string outputPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
string downloadURL = GetDownloadURL();
|
||||
|
||||
if (downloadURL == null)
|
||||
ConvaiLogger.Error("Failed to get download URL. Please check the API key and try again.", ConvaiLogger.LogCategory.Editor);
|
||||
|
||||
using UnityWebRequest webRequest = UnityWebRequest.Get(downloadURL);
|
||||
webRequest.SendWebRequest();
|
||||
|
||||
while (!webRequest.isDone)
|
||||
{
|
||||
float progress = webRequest.downloadProgress;
|
||||
EditorUtility.DisplayProgressBar("Downloading required iOS DLL...",
|
||||
"Please wait for the download to finish and do not close Unity. " + (int)(progress * 100) + "%", progress);
|
||||
}
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
|
||||
if (webRequest.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.ProtocolError)
|
||||
{
|
||||
ConvaiLogger.Error($"Error downloading file: {webRequest.error}", ConvaiLogger.LogCategory.Editor);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] results = webRequest.downloadHandler.data;
|
||||
string zipPath = Path.Combine(Path.GetTempPath(), "downloaded.zip");
|
||||
File.WriteAllBytes(zipPath, results);
|
||||
ExtractZipFile(zipPath, outputPath);
|
||||
File.Delete(zipPath);
|
||||
ConvaiLogger.Info($"Downloaded and extracted to {outputPath}" + "/ios/libgrpc.a", ConvaiLogger.LogCategory.Editor);
|
||||
|
||||
// Refresh the asset database to make sure the new files are recognized
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConvaiLogger.Error(e.Message, ConvaiLogger.LogCategory.Editor);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the download URL from Convai API.
|
||||
/// </summary>
|
||||
/// <returns>The download URL or null.</returns>
|
||||
private static string GetDownloadURL()
|
||||
{
|
||||
if (!ConvaiAPIKeySetup.GetAPIKey(out string apiKey)) return null;
|
||||
|
||||
string body = @"{""service_name"": ""unity-builds"",""version"":""ios""}";
|
||||
|
||||
WebRequest request = WebRequest.Create(DOWNLOAD_ENDPOINT_URL);
|
||||
request.Method = "POST";
|
||||
request.ContentType = "application/json";
|
||||
request.Headers.Add("CONVAI-API-KEY", apiKey);
|
||||
|
||||
using (StreamWriter streamWriter = new(request.GetRequestStream()))
|
||||
{
|
||||
streamWriter.Write(body);
|
||||
}
|
||||
|
||||
using (WebResponse response = request.GetResponse())
|
||||
using (Stream dataStream = response.GetResponseStream())
|
||||
using (StreamReader reader = new(dataStream))
|
||||
{
|
||||
JObject responseJson = JObject.Parse(reader.ReadToEnd());
|
||||
return (string)responseJson["download_link"];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the contents of a ZIP file to the specified output folder.
|
||||
/// </summary>
|
||||
/// <param name="zipFilePath">Path to the ZIP file.</param>
|
||||
/// <param name="outputFolder">Directory to extract the contents to.</param>
|
||||
private static void ExtractZipFile(string zipFilePath, string outputFolder)
|
||||
{
|
||||
if (!Directory.Exists(outputFolder)) Directory.CreateDirectory(outputFolder);
|
||||
|
||||
using (ZipArchive archive = ZipFile.OpenRead(zipFilePath))
|
||||
{
|
||||
float totalEntries = archive.Entries.Count;
|
||||
float currentEntry = 0;
|
||||
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
string fullPath = Path.Combine(outputFolder, entry.FullName);
|
||||
|
||||
// Ensure the directory exists
|
||||
string directoryName = Path.GetDirectoryName(fullPath);
|
||||
if (!Directory.Exists(directoryName))
|
||||
if (directoryName != null)
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
// Extract the entry to the output directory
|
||||
entry.ExtractToFile(fullPath, true);
|
||||
|
||||
// Update the progress bar
|
||||
currentEntry++;
|
||||
float progress = currentEntry / totalEntries;
|
||||
EditorUtility.DisplayProgressBar("Extracting", $"Extracting file {entry.Name}...", progress);
|
||||
}
|
||||
}
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target directory for extracting the files.
|
||||
/// </summary>
|
||||
/// <returns>Target directory path.</returns>
|
||||
private static string GetTargetDirectory()
|
||||
{
|
||||
_targetDirectory = Path.Combine(Application.dataPath, RELATIVE_PATH);
|
||||
if (!Directory.Exists(_targetDirectory)) Directory.CreateDirectory(_targetDirectory);
|
||||
return _targetDirectory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the iOS DLL file already exists.
|
||||
/// </summary>
|
||||
/// <returns>True if the file exists, otherwise false.</returns>
|
||||
private static bool CheckFileExistence()
|
||||
{
|
||||
string fullPath = Path.Combine(Application.dataPath, RELATIVE_PATH + "/ios/libgrpc.a");
|
||||
bool fileExists = File.Exists(fullPath);
|
||||
if (fileExists) ConvaiLogger.DebugLog("iOS DLL already exists. No need to download.", ConvaiLogger.LogCategory.Editor);
|
||||
|
||||
return fileExists;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 526eafca5259e824591be577e0305054
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Convai/Scripts/Editor/Logger.meta
Normal file
8
Assets/Convai/Scripts/Editor/Logger.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f95efface998ad4190a0c4b87fe42d4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
147
Assets/Convai/Scripts/Editor/Logger/LoggerSettingsManager.cs
Normal file
147
Assets/Convai/Scripts/Editor/Logger/LoggerSettingsManager.cs
Normal file
@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Convai.Scripts.Runtime.LoggerSystem;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Convai.Scripts.Editor.Logger
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the settings for the ConvaiLogger, including loading, creating, and modifying LoggerSettings.
|
||||
/// </summary>
|
||||
public class LoggerSettingsManager
|
||||
{
|
||||
// Path to the LoggerSettings asset
|
||||
private const string SETTINGS_PATH = "Assets/Convai/Resources/LoggerSettings.asset";
|
||||
|
||||
/// <summary>
|
||||
/// The LoggerSettings instance.
|
||||
/// </summary>
|
||||
private LoggerSettings _loggerSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Property accessor for _loggerSettings. If _loggerSettings is null, it attempts to load it from the asset path.
|
||||
/// If the asset does not exist, it creates a new LoggerSettings instance.
|
||||
/// </summary>
|
||||
public LoggerSettings LoggerSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_loggerSettings == null)
|
||||
{
|
||||
_loggerSettings = AssetDatabase.LoadAssetAtPath<LoggerSettings>(SETTINGS_PATH);
|
||||
if (_loggerSettings == null)
|
||||
{
|
||||
CreateLoggerSettings();
|
||||
ConvaiLogger.Warn("LoggerSettings ScriptableObject not found. Creating one...",
|
||||
ConvaiLogger.LogCategory.Character);
|
||||
}
|
||||
}
|
||||
|
||||
return _loggerSettings;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new LoggerSettings instance with default values and saves it as an asset
|
||||
/// </summary>
|
||||
private void CreateLoggerSettings()
|
||||
{
|
||||
_loggerSettings = ScriptableObject.CreateInstance<LoggerSettings>();
|
||||
|
||||
_loggerSettings.Character = ConvaiLogger.LogLevel.None
|
||||
| ConvaiLogger.LogLevel.Debug
|
||||
| ConvaiLogger.LogLevel.Error
|
||||
| ConvaiLogger.LogLevel.Exception
|
||||
| ConvaiLogger.LogLevel.Info
|
||||
| ConvaiLogger.LogLevel.Warning;
|
||||
|
||||
_loggerSettings.LipSync = ConvaiLogger.LogLevel.None
|
||||
| ConvaiLogger.LogLevel.Debug
|
||||
| ConvaiLogger.LogLevel.Error
|
||||
| ConvaiLogger.LogLevel.Exception
|
||||
| ConvaiLogger.LogLevel.Info
|
||||
| ConvaiLogger.LogLevel.Warning;
|
||||
|
||||
_loggerSettings.Actions = ConvaiLogger.LogLevel.None
|
||||
| ConvaiLogger.LogLevel.Debug
|
||||
| ConvaiLogger.LogLevel.Error
|
||||
| ConvaiLogger.LogLevel.Exception
|
||||
| ConvaiLogger.LogLevel.Info
|
||||
| ConvaiLogger.LogLevel.Warning;
|
||||
|
||||
// Check if the Convai folder exists and create if not
|
||||
if (!AssetDatabase.IsValidFolder("Assets/Convai/Resources"))
|
||||
AssetDatabase.CreateFolder("Assets/Convai", "Resources");
|
||||
|
||||
// Check if the Settings folder exists and create if not
|
||||
if (!AssetDatabase.IsValidFolder("Assets/Convai/Resources/Settings"))
|
||||
AssetDatabase.CreateFolder("Assets/Convai/Resources", "Settings");
|
||||
|
||||
AssetDatabase.CreateAsset(_loggerSettings, SETTINGS_PATH);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if all flags for a given row are set.
|
||||
/// </summary>
|
||||
/// <returns>True if all flags for the given row are set, false otherwise.</returns>
|
||||
public bool GetAllFlagsForRow(FieldInfo fieldInfo)
|
||||
{
|
||||
if (fieldInfo == null) return false;
|
||||
ConvaiLogger.LogLevel allLevel = AllLevel();
|
||||
ConvaiLogger.LogLevel currentValue = (ConvaiLogger.LogLevel)fieldInfo.GetValue(_loggerSettings);
|
||||
return allLevel == currentValue;
|
||||
}
|
||||
|
||||
private static ConvaiLogger.LogLevel AllLevel()
|
||||
{
|
||||
ConvaiLogger.LogLevel allLevel = ConvaiLogger.LogLevel.None;
|
||||
foreach (ConvaiLogger.LogLevel logLevel in Enum.GetValues(typeof(ConvaiLogger.LogLevel))) allLevel |= logLevel;
|
||||
|
||||
return allLevel;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Renders a checkbox for a given log type and handles changes to its value.
|
||||
/// </summary>
|
||||
public void RenderAndHandleCheckbox(FieldInfo field)
|
||||
{
|
||||
if (field == null) return;
|
||||
foreach (ConvaiLogger.LogLevel enumValue in Enum.GetValues(typeof(ConvaiLogger.LogLevel)))
|
||||
{
|
||||
if (enumValue == ConvaiLogger.LogLevel.None) continue;
|
||||
ConvaiLogger.LogLevel rowFlag = (ConvaiLogger.LogLevel)field.GetValue(_loggerSettings);
|
||||
bool currentLevelValue = (rowFlag & enumValue) != 0;
|
||||
bool newValue = EditorGUILayout.Toggle(currentLevelValue, GUILayout.Width(100));
|
||||
if (newValue)
|
||||
rowFlag |= enumValue;
|
||||
else
|
||||
rowFlag &= ~enumValue;
|
||||
field.SetValue(_loggerSettings, rowFlag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets all flags for a given row to the provided value.
|
||||
/// </summary>
|
||||
/// <param name="rowName">The name of the row to set the flags for.</param>
|
||||
/// <param name="value">The value to set all flags to.</param>
|
||||
public void SetAllFlagsForRow(FieldInfo rowName, bool value)
|
||||
{
|
||||
rowName.SetValue(_loggerSettings, value ? 31 : 0);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets all flags to the provided value.
|
||||
/// </summary>
|
||||
/// <param name="value"> The value to set all flags to.</param>
|
||||
public void SetAllFlags(bool value)
|
||||
{
|
||||
foreach (FieldInfo fieldInfo in _loggerSettings.GetType().GetFields()) fieldInfo.SetValue(_loggerSettings, value ? 31 : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6aa5925780d46299eacd79ab8115332
|
||||
timeCreated: 1701853621
|
||||
3
Assets/Convai/Scripts/Editor/LongTermMemory.meta
Normal file
3
Assets/Convai/Scripts/Editor/LongTermMemory.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77e4710e230f443e990b443766e95604
|
||||
timeCreated: 1720535720
|
||||
@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using Convai.Scripts.Runtime.Features.LongTermMemory;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Convai.Scripts.Editor.LongTermMemory
|
||||
{
|
||||
[CustomEditor(typeof(ConvaiLTMController))]
|
||||
public class ConvaiLTMInspector : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
ConvaiLTMController controller = (ConvaiLTMController)target;
|
||||
SetHeader(controller);
|
||||
AddButtons(controller);
|
||||
}
|
||||
|
||||
private void AddButtons(ConvaiLTMController controller)
|
||||
{
|
||||
switch (controller.LTMStatus)
|
||||
{
|
||||
case LTMStatus.NotDefined:
|
||||
return;
|
||||
case LTMStatus.Disabled:
|
||||
EnableButton(controller);
|
||||
break;
|
||||
case LTMStatus.Enabled:
|
||||
DisableButton(controller);
|
||||
break;
|
||||
case LTMStatus.Failed:
|
||||
RetryButton(controller);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetHeader(ConvaiLTMController controller)
|
||||
{
|
||||
GUIStyle style = new()
|
||||
{
|
||||
richText = true,
|
||||
fontStyle = FontStyle.Bold
|
||||
};
|
||||
switch (controller.LTMStatus)
|
||||
{
|
||||
case LTMStatus.NotDefined:
|
||||
style.normal.textColor = Color.yellow;
|
||||
GUILayout.Label("Long Term Memory <b>status is getting Updated</b>", style);
|
||||
break;
|
||||
case LTMStatus.Enabled:
|
||||
style.normal.textColor = Color.green;
|
||||
GUILayout.Label("Long Term Memory <b>is Enabled</b>", style);
|
||||
break;
|
||||
case LTMStatus.Disabled:
|
||||
style.normal.textColor = Color.red;
|
||||
GUILayout.Label("Long Term Memory <b>is Disabled</b>", style);
|
||||
break;
|
||||
case LTMStatus.Failed:
|
||||
style.normal.textColor = Color.red;
|
||||
GUILayout.Label("Long Term Memory <b>could not be updated</b>", style);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private void RetryButton(ConvaiLTMController controller)
|
||||
{
|
||||
if (GUILayout.Button("Retry"))
|
||||
{
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
controller.GetLTMStatus();
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
}
|
||||
}
|
||||
|
||||
private void DisableButton(ConvaiLTMController controller)
|
||||
{
|
||||
if (GUILayout.Button("Disable LTM")) DisableLTMTask(controller);
|
||||
}
|
||||
|
||||
private void EnableButton(ConvaiLTMController controller)
|
||||
{
|
||||
if (GUILayout.Button("Enable LTM")) EnableLTMTask(controller);
|
||||
}
|
||||
|
||||
private void EnableLTMTask(ConvaiLTMController controller)
|
||||
{
|
||||
controller.StartCoroutine(controller.ToggleLTM(true));
|
||||
}
|
||||
|
||||
private void DisableLTMTask(ConvaiLTMController controller)
|
||||
{
|
||||
controller.StartCoroutine(controller.ToggleLTM(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdd0fe9ac6f544faab4f420feef62324
|
||||
timeCreated: 1720535756
|
||||
8
Assets/Convai/Scripts/Editor/NPC.meta
Normal file
8
Assets/Convai/Scripts/Editor/NPC.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b384a5a1424381d4483a96496fef88b1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,153 @@
|
||||
using Convai.Scripts.Runtime.Core;
|
||||
using Convai.Scripts.Runtime.Features;
|
||||
using Convai.Scripts.Runtime.Features.LongTermMemory;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using ConvaiLipSync = Convai.Scripts.Runtime.Features.LipSync.ConvaiLipSync;
|
||||
|
||||
namespace Convai.Scripts.Editor.NPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor window for managing Convai NPC components.
|
||||
/// </summary>
|
||||
public class ConvaiNPCComponentSettingsWindow : EditorWindow
|
||||
{
|
||||
private ConvaiNPC _convaiNPC;
|
||||
|
||||
/// <summary>
|
||||
/// Handles GUI events for the window.
|
||||
/// </summary>
|
||||
private void OnGUI()
|
||||
{
|
||||
titleContent = new GUIContent("Convai NPC Components");
|
||||
Vector2 windowSize = new(300, 250);
|
||||
minSize = windowSize;
|
||||
maxSize = windowSize;
|
||||
if (_convaiNPC == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("No ConvaiNPC selected");
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginVertical(GUI.skin.box);
|
||||
EditorGUIUtility.labelWidth = 200f; // Set a custom label width
|
||||
|
||||
_convaiNPC.IncludeActionsHandler = EditorGUILayout.Toggle(new GUIContent("NPC Actions", "Decides if Actions Handler is included"), _convaiNPC.IncludeActionsHandler);
|
||||
_convaiNPC.LipSync = EditorGUILayout.Toggle(new GUIContent("Lip Sync", "Decides if Lip Sync is enabled"), _convaiNPC.LipSync);
|
||||
_convaiNPC.HeadEyeTracking = EditorGUILayout.Toggle(new GUIContent("Head & Eye Tracking", "Decides if Head & Eye tracking is enabled"), _convaiNPC.HeadEyeTracking);
|
||||
_convaiNPC.EyeBlinking = EditorGUILayout.Toggle(new GUIContent("Eye Blinking", "Decides if Eye Blinking is enabled"), _convaiNPC.EyeBlinking);
|
||||
_convaiNPC.NarrativeDesignManager = EditorGUILayout.Toggle(new GUIContent("Narrative Design Manager", "Decides if Narrative Design Manager is enabled"),
|
||||
_convaiNPC.NarrativeDesignManager);
|
||||
_convaiNPC.ConvaiGroupNPCController =
|
||||
EditorGUILayout.Toggle(new GUIContent("Group NPC Controller", "Decides if this NPC can be a part of Convai NPC to NPC Conversation"),
|
||||
_convaiNPC.ConvaiGroupNPCController);
|
||||
_convaiNPC.LongTermMemoryController =
|
||||
EditorGUILayout.Toggle(new GUIContent("Long Term Memory", "Component to toggle Long term memory for this character"),
|
||||
_convaiNPC.LongTermMemoryController);
|
||||
_convaiNPC.NarrativeDesignKeyController =
|
||||
EditorGUILayout.Toggle(new GUIContent("Narrative Design Keys", "Adds handler for Narrative Design Keys for this character"),
|
||||
_convaiNPC.NarrativeDesignKeyController);
|
||||
_convaiNPC.DynamicInfoController =
|
||||
EditorGUILayout.Toggle(new GUIContent("Dynamic Info", "Component used to send dynamic info like your game states to the character"),
|
||||
_convaiNPC.DynamicInfoController);
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (GUILayout.Button("Apply Changes", GUILayout.Height(40)))
|
||||
{
|
||||
ApplyChanges();
|
||||
EditorUtility.SetDirty(_convaiNPC);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the component states when the window gains focus.
|
||||
/// </summary>
|
||||
private void OnFocus()
|
||||
{
|
||||
RefreshComponentStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Convai NPC Component Settings window.
|
||||
/// </summary>
|
||||
/// <param name="convaiNPC">The Convai NPC to manage.</param>
|
||||
public static void Open(ConvaiNPC convaiNPC)
|
||||
{
|
||||
ConvaiNPCComponentSettingsWindow window = GetWindow<ConvaiNPCComponentSettingsWindow>();
|
||||
window.titleContent = new GUIContent("Convai NPC Component Settings");
|
||||
window._convaiNPC = convaiNPC;
|
||||
window.RefreshComponentStates();
|
||||
window.Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the states of the components.
|
||||
/// </summary>
|
||||
private void RefreshComponentStates()
|
||||
{
|
||||
if (_convaiNPC != null)
|
||||
{
|
||||
_convaiNPC.IncludeActionsHandler = _convaiNPC.GetComponent<ConvaiActionsHandler>() is not null;
|
||||
_convaiNPC.LipSync = _convaiNPC.GetComponent<ConvaiLipSync>() is not null;
|
||||
_convaiNPC.HeadEyeTracking = _convaiNPC.GetComponent<ConvaiHeadTracking>() is not null;
|
||||
_convaiNPC.EyeBlinking = _convaiNPC.GetComponent<ConvaiBlinkingHandler>() is not null;
|
||||
_convaiNPC.NarrativeDesignManager = _convaiNPC.GetComponent<NarrativeDesignManager>() is not null;
|
||||
_convaiNPC.ConvaiGroupNPCController = _convaiNPC.GetComponent<ConvaiGroupNPCController>() is not null;
|
||||
_convaiNPC.LongTermMemoryController = _convaiNPC.GetComponent<ConvaiLTMController>() is not null;
|
||||
_convaiNPC.NarrativeDesignKeyController =
|
||||
_convaiNPC.GetComponent<NarrativeDesignKeyController>() is not null;
|
||||
_convaiNPC.DynamicInfoController = _convaiNPC.GetComponent<DynamicInfoController>() is not null;
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies changes based on the user's selection in the inspector.
|
||||
/// </summary>
|
||||
private void ApplyChanges()
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Confirm Apply Changes",
|
||||
"Do you want to apply the following changes?", "Yes", "No"))
|
||||
{
|
||||
ApplyComponent<ConvaiActionsHandler>(_convaiNPC.IncludeActionsHandler);
|
||||
ApplyComponent<ConvaiLipSync>(_convaiNPC.LipSync);
|
||||
ApplyComponent<ConvaiHeadTracking>(_convaiNPC.HeadEyeTracking);
|
||||
ApplyComponent<ConvaiBlinkingHandler>(_convaiNPC.EyeBlinking);
|
||||
ApplyComponent<NarrativeDesignManager>(_convaiNPC.NarrativeDesignManager);
|
||||
ApplyComponent<ConvaiGroupNPCController>(_convaiNPC.ConvaiGroupNPCController);
|
||||
ApplyComponent<ConvaiLTMController>(_convaiNPC.LongTermMemoryController);
|
||||
ApplyComponent<NarrativeDesignKeyController>(_convaiNPC.NarrativeDesignKeyController);
|
||||
ApplyComponent<DynamicInfoController>(_convaiNPC.DynamicInfoController);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies or removes a component based on the specified condition.
|
||||
/// If the component is to be removed, its state is saved. If it's added, its state is restored if previously saved.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the component.</typeparam>
|
||||
/// <param name="includeComponent">Whether to include the component.</param>
|
||||
private void ApplyComponent<T>(bool includeComponent) where T : Component
|
||||
{
|
||||
T component = _convaiNPC.GetComponent<T>();
|
||||
|
||||
if (includeComponent)
|
||||
{
|
||||
if (component == null)
|
||||
{
|
||||
component = _convaiNPC.gameObject.AddComponentSafe<T>();
|
||||
}
|
||||
}
|
||||
else if (component != null)
|
||||
{
|
||||
DestroyImmediate(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f84be99651944b69262e8060b8ac451
|
||||
timeCreated: 1696842374
|
||||
107
Assets/Convai/Scripts/Editor/NPC/ConvaiNPCEditor.cs
Normal file
107
Assets/Convai/Scripts/Editor/NPC/ConvaiNPCEditor.cs
Normal file
@ -0,0 +1,107 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using Convai.Scripts.Runtime.Core;
|
||||
using Convai.Scripts.Runtime.LoggerSystem;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Convai.Scripts.Editor.NPC
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for the ConvaiNPC component.
|
||||
/// Provides functionalities to cache and restore states of all convai scripts whenever a scene is saved.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(ConvaiNPC))]
|
||||
[HelpURL("https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/scripts-overview")]
|
||||
public class ConvaiNPCEditor : UnityEditor.Editor
|
||||
{
|
||||
private ConvaiNPC _convaiNPC;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_convaiNPC = (ConvaiNPC)target;
|
||||
}
|
||||
/// <summary>
|
||||
/// Overrides the default inspector GUI to add custom buttons and functionality.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
// Add Components button to add necessary components and assign a random color to the character.
|
||||
if (GUILayout.Button(new GUIContent(
|
||||
"Add Components",
|
||||
"Adds necessary components to the NPC and assigns a random color to the character's text"
|
||||
),
|
||||
GUILayout.Width(120)
|
||||
)
|
||||
) AddComponentsToNPC();
|
||||
|
||||
if (GUILayout.Button(new GUIContent(
|
||||
"Copy Debug",
|
||||
"Copies the session id and other essential properties to clipboard for easier debugging"
|
||||
),
|
||||
GUILayout.Width(120)
|
||||
)
|
||||
) CopyToClipboard();
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void CopyToClipboard()
|
||||
{
|
||||
StringBuilder stringBuilder = new();
|
||||
stringBuilder.AppendLine($"Endpoint: {_convaiNPC.GetEndPointURL}");
|
||||
stringBuilder.AppendLine($"Character ID: {_convaiNPC.characterID}");
|
||||
stringBuilder.AppendLine($"Session ID: {_convaiNPC.sessionID}");
|
||||
|
||||
GUIUtility.systemCopyBuffer = stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds components to the NPC and assigns a random color to the character's text.
|
||||
/// </summary>
|
||||
private void AddComponentsToNPC()
|
||||
{
|
||||
try
|
||||
{
|
||||
ConvaiNPCComponentSettingsWindow.Open(_convaiNPC);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ConvaiLogger.Exception($"Unexpected error occurred when applying changes. Error: {ex}", ConvaiLogger.LogCategory.UI);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for Unity editor components.
|
||||
/// </summary>
|
||||
public static class EditorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a component to the GameObject safely, catching any exceptions that occur during the process.
|
||||
/// </summary>
|
||||
/// <param name="go">The GameObject to which the component will be added.</param>
|
||||
/// <typeparam name="T">The type of the component to be added, derived from UnityEngine.Component.</typeparam>
|
||||
/// <returns>The newly added component, or null if the operation failed.</returns>
|
||||
public static T AddComponentSafe<T>(this GameObject go) where T : Component
|
||||
{
|
||||
try
|
||||
{
|
||||
return go.AddComponent<T>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ConvaiLogger.Exception($"Failed to add component of type {typeof(T).Name}, Error: {ex}", ConvaiLogger.LogCategory.UI);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/Convai/Scripts/Editor/NPC/ConvaiNPCEditor.cs.meta
Normal file
11
Assets/Convai/Scripts/Editor/NPC/ConvaiNPCEditor.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f2ab5d11b144b3aa5d8c371ea664c44
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/Convai/Scripts/Editor/NarrativeDesign.meta
Normal file
3
Assets/Convai/Scripts/Editor/NarrativeDesign.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25397e28c8a1443e8ed01b939b22a538
|
||||
timeCreated: 1715597900
|
||||
@ -0,0 +1,133 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Convai.Scripts.Runtime.Features;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Convai.Scripts.Editor.NarrativeDesign
|
||||
{
|
||||
[CustomEditor(typeof(NarrativeDesignManager))]
|
||||
public class NarrativeDesignManagerEditor : UnityEditor.Editor
|
||||
{
|
||||
private NarrativeDesignManager _narrativeDesignManager;
|
||||
private SerializedProperty _sectionChangeEvents;
|
||||
private bool _sectionEventsExpanded = true;
|
||||
private Dictionary<string, bool> _sectionIdExpanded = new();
|
||||
private SerializedObject _serializedObject;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_serializedObject = new SerializedObject(target);
|
||||
_narrativeDesignManager = (NarrativeDesignManager)target;
|
||||
FindProperties();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
_serializedObject.Update();
|
||||
|
||||
DrawUpdateButton();
|
||||
DrawSectionEvents();
|
||||
|
||||
_serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawUpdateButton()
|
||||
{
|
||||
if (GUILayout.Button("Check for Updates")) OnUpdateNarrativeDesignButtonClicked();
|
||||
GUILayout.Space(10);
|
||||
}
|
||||
|
||||
private void DrawSectionEvents()
|
||||
{
|
||||
if (_narrativeDesignManager.sectionDataList.Count == 0) return;
|
||||
|
||||
_sectionEventsExpanded = EditorGUILayout.Foldout(_sectionEventsExpanded, "Section Events", true, EditorStyles.foldoutHeader);
|
||||
if (!_sectionEventsExpanded) return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
for (int i = 0; i < _narrativeDesignManager.sectionDataList.Count; i++) DrawSectionEvent(i);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
_serializedObject.ApplyModifiedProperties();
|
||||
_narrativeDesignManager.OnSectionEventListChange();
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
private void DrawSectionEvent(int index)
|
||||
{
|
||||
SectionData sectionData = _narrativeDesignManager.sectionDataList[index];
|
||||
string sectionId = sectionData.sectionId;
|
||||
|
||||
EnsureSectionChangeEventsDataExists(sectionId);
|
||||
|
||||
_sectionIdExpanded.TryAdd(sectionId, false);
|
||||
|
||||
GUIStyle sectionIdStyle = CreateSectionIdStyle();
|
||||
|
||||
string sectionIdText = $"{sectionData.sectionName} - {sectionId}";
|
||||
_sectionIdExpanded[sectionId] = EditorGUILayout.Foldout(_sectionIdExpanded[sectionId], sectionIdText, true, sectionIdStyle);
|
||||
|
||||
if (_sectionIdExpanded[sectionId]) DrawSectionChangeEvents(index);
|
||||
}
|
||||
|
||||
private void EnsureSectionChangeEventsDataExists(string sectionId)
|
||||
{
|
||||
if (!_narrativeDesignManager.sectionChangeEventsDataList.Exists(x => x.id == sectionId))
|
||||
_narrativeDesignManager.sectionChangeEventsDataList.Add(new SectionChangeEventsData { id = sectionId });
|
||||
}
|
||||
|
||||
private GUIStyle CreateSectionIdStyle()
|
||||
{
|
||||
return new GUIStyle(EditorStyles.foldoutHeader)
|
||||
{
|
||||
fontStyle = FontStyle.Bold,
|
||||
fontSize = 14
|
||||
};
|
||||
}
|
||||
|
||||
private void DrawSectionChangeEvents(int index)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
SerializedProperty sectionChangeEventsProperty = _sectionChangeEvents.GetArrayElementAtIndex(index);
|
||||
EditorGUILayout.PropertyField(sectionChangeEventsProperty, GUIContent.none, true);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
private async void OnUpdateNarrativeDesignButtonClicked()
|
||||
{
|
||||
await Task.WhenAll(_narrativeDesignManager.UpdateSectionListAsync(), _narrativeDesignManager.UpdateTriggerListAsync());
|
||||
|
||||
// Remove section change events for deleted sections
|
||||
_narrativeDesignManager.sectionChangeEventsDataList = _narrativeDesignManager.sectionChangeEventsDataList
|
||||
.Where(changeEvent => _narrativeDesignManager.sectionDataList.Any(section => section.sectionId == changeEvent.id))
|
||||
.ToList();
|
||||
|
||||
// Remove expanded states for deleted sections
|
||||
_sectionIdExpanded = _sectionIdExpanded
|
||||
.Where(kvp => _narrativeDesignManager.sectionDataList.Any(section => section.sectionId == kvp.Key))
|
||||
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
|
||||
_serializedObject.Update(); // Update the serialized object
|
||||
_serializedObject.ApplyModifiedProperties();
|
||||
_narrativeDesignManager.OnSectionEventListChange();
|
||||
|
||||
// Force the inspector to repaint
|
||||
Repaint();
|
||||
}
|
||||
|
||||
|
||||
private void FindProperties()
|
||||
{
|
||||
_serializedObject.FindProperty(nameof(NarrativeDesignManager.sectionDataList));
|
||||
_serializedObject.FindProperty(nameof(NarrativeDesignManager.triggerDataList));
|
||||
_sectionChangeEvents = _serializedObject.FindProperty(nameof(NarrativeDesignManager.sectionChangeEventsDataList));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1537f32558f6e8042878f1a95c1fe984
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,43 @@
|
||||
using Convai.Scripts.Runtime.Features;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Convai.Scripts.Editor.NarrativeDesign
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(SectionChangeEventsData))]
|
||||
public class NarrativeDesignSectionChangeEventsDataPropertyDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
SerializedProperty sectionIdProperty = property.FindPropertyRelative("id");
|
||||
SerializedProperty onSectionStartProperty = property.FindPropertyRelative("onSectionStart");
|
||||
SerializedProperty onSectionEndProperty = property.FindPropertyRelative("onSectionEnd");
|
||||
|
||||
Rect sectionIdRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
|
||||
EditorGUI.LabelField(sectionIdRect, "Section ID", sectionIdProperty.stringValue);
|
||||
|
||||
position.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
|
||||
EditorGUI.PropertyField(position, onSectionStartProperty, true);
|
||||
position.y += EditorGUI.GetPropertyHeight(onSectionStartProperty) + EditorGUIUtility.standardVerticalSpacing;
|
||||
|
||||
EditorGUI.PropertyField(position, onSectionEndProperty, true);
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
SerializedProperty onSectionStartProperty = property.FindPropertyRelative("onSectionStart");
|
||||
SerializedProperty onSectionEndProperty = property.FindPropertyRelative("onSectionEnd");
|
||||
|
||||
float height = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
height += EditorGUI.GetPropertyHeight(onSectionStartProperty) + EditorGUIUtility.standardVerticalSpacing;
|
||||
height += EditorGUI.GetPropertyHeight(onSectionEndProperty);
|
||||
|
||||
return height;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23fcdcc9acc44804e991455dcd85953d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,34 @@
|
||||
using System.Threading.Tasks;
|
||||
using Convai.Scripts.Runtime.Features;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Convai.Scripts.Editor.NarrativeDesign
|
||||
{
|
||||
[CustomEditor(typeof(NarrativeDesignTrigger))]
|
||||
public class NarrativeDesignTriggerEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
NarrativeDesignTrigger narrativeDesignTrigger = (NarrativeDesignTrigger)target;
|
||||
|
||||
if (GUILayout.Button("Update Triggers"))
|
||||
if (narrativeDesignTrigger.convaiNPC != null)
|
||||
{
|
||||
NarrativeDesignManager manager = narrativeDesignTrigger.convaiNPC.GetComponent<NarrativeDesignManager>();
|
||||
if (manager != null)
|
||||
manager.UpdateTriggerListAsync().ContinueWith(_ =>
|
||||
{
|
||||
narrativeDesignTrigger.UpdateAvailableTriggers();
|
||||
EditorUtility.SetDirty(narrativeDesignTrigger);
|
||||
}, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
DrawDefaultInspector();
|
||||
if (narrativeDesignTrigger.availableTriggers is { Count: > 0 })
|
||||
narrativeDesignTrigger.selectedTriggerIndex =
|
||||
EditorGUILayout.Popup("Trigger", narrativeDesignTrigger.selectedTriggerIndex, narrativeDesignTrigger.availableTriggers.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdf68c1817bd4102879052a18d773fec
|
||||
timeCreated: 1706873993
|
||||
8
Assets/Convai/Scripts/Editor/ScriptableObjects.meta
Normal file
8
Assets/Convai/Scripts/Editor/ScriptableObjects.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7933a9f8b84f8d4083197f8006f7c02
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,76 @@
|
||||
using System.Text;
|
||||
using Convai.Scripts.Runtime.PlayerStats;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Convai.Scripts.Editor.ScriptableObjects
|
||||
{
|
||||
[CustomEditor(typeof(ConvaiPlayerDataSO))]
|
||||
public class ConvaiPlayerDataSOInspector : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
ConvaiPlayerDataSO dataSO = (ConvaiPlayerDataSO)target;
|
||||
ResetData(dataSO);
|
||||
CopyData(dataSO);
|
||||
GUILayout.Space(10);
|
||||
GUILayout.Label("Player Pref Settings");
|
||||
GUILayout.BeginHorizontal();
|
||||
LoadFromPlayerPref(dataSO);
|
||||
SaveDataToPlayerPrefs(dataSO);
|
||||
DeleteFromPlayerPrefs(dataSO);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private static void SaveDataToPlayerPrefs(ConvaiPlayerDataSO dataSO)
|
||||
{
|
||||
if (GUILayout.Button("Save"))
|
||||
{
|
||||
PlayerPrefs.SetString(ConvaiPlayerDataHandler.PLAYER_NAME_SAVE_KEY, dataSO.PlayerName);
|
||||
PlayerPrefs.SetString(ConvaiPlayerDataHandler.SPEAKER_ID_SAVE_KEY, dataSO.SpeakerID);
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadFromPlayerPref(ConvaiPlayerDataSO dataSO)
|
||||
{
|
||||
if (GUILayout.Button("Load"))
|
||||
{
|
||||
dataSO.PlayerName = PlayerPrefs.GetString(ConvaiPlayerDataHandler.PLAYER_NAME_SAVE_KEY, dataSO.DefaultPlayerName);
|
||||
dataSO.SpeakerID = PlayerPrefs.GetString(ConvaiPlayerDataHandler.SPEAKER_ID_SAVE_KEY, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteFromPlayerPrefs(ConvaiPlayerDataSO dataSO)
|
||||
{
|
||||
if (GUILayout.Button("Delete"))
|
||||
{
|
||||
dataSO.PlayerName = string.Empty;
|
||||
dataSO.SpeakerID = string.Empty;
|
||||
|
||||
PlayerPrefs.DeleteKey(ConvaiPlayerDataHandler.PLAYER_NAME_SAVE_KEY);
|
||||
PlayerPrefs.DeleteKey(ConvaiPlayerDataHandler.SPEAKER_ID_SAVE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyData(ConvaiPlayerDataSO dataSO)
|
||||
{
|
||||
if (GUILayout.Button("Copy Data"))
|
||||
{
|
||||
StringBuilder stringBuilder = new();
|
||||
stringBuilder.AppendLine($"PlayerName: {dataSO.PlayerName}");
|
||||
stringBuilder.AppendLine($"Speaker ID: {dataSO.SpeakerID}");
|
||||
GUIUtility.systemCopyBuffer = stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetData(ConvaiPlayerDataSO dataSO)
|
||||
{
|
||||
if (GUILayout.Button("Reset Data"))
|
||||
{
|
||||
dataSO.PlayerName = string.Empty;
|
||||
dataSO.SpeakerID = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e6e21a36dbb83240b8a58bd402f3e01
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Convai/Scripts/Editor/Setup.meta
Normal file
8
Assets/Convai/Scripts/Editor/Setup.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2ef013e23cfeea43972daeb3a881509
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/Convai/Scripts/Editor/Setup/AccountsSection.meta
Normal file
3
Assets/Convai/Scripts/Editor/Setup/AccountsSection.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91fa1040db584e95b2fd142549dbd268
|
||||
timeCreated: 1722957380
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d295dcb12d9e445dbf269ab6e3e299b0
|
||||
timeCreated: 1722960791
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fc713f380f7468884e4a104009e7458
|
||||
timeCreated: 1722956898
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d11cc95c0c14a60b1bf1b440ae94766
|
||||
timeCreated: 1722954432
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93617c4578954fb5b11b00d6b666c67a
|
||||
timeCreated: 1722956705
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1dcb3be6b2394712b3af09c8b688f3ab
|
||||
timeCreated: 1722963570
|
||||
@ -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"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1f34d1d84db4af6b62212a1ae6876e9
|
||||
timeCreated: 1722970221
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cd221b6dafe4c1794021b46231db3b6
|
||||
timeCreated: 1722971255
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 12a663852e0448c9a654350280989401
|
||||
timeCreated: 1722963605
|
||||
172
Assets/Convai/Scripts/Editor/Setup/ConvaiSDKEditorWindow.cs
Normal file
172
Assets/Convai/Scripts/Editor/Setup/ConvaiSDKEditorWindow.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d123a78f8d9744a4ca944a1474396511
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/Convai/Scripts/Editor/Setup/Documentation.meta
Normal file
3
Assets/Convai/Scripts/Editor/Setup/Documentation.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b9e16bfe94040548214ec275f408efb
|
||||
timeCreated: 1723460414
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abea5fdbf367417d9a4d3cefdc402a51
|
||||
timeCreated: 1723460429
|
||||
3
Assets/Convai/Scripts/Editor/Setup/LoggerSettings.meta
Normal file
3
Assets/Convai/Scripts/Editor/Setup/LoggerSettings.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c25aa8ea78d429ab1c7716dcb052391
|
||||
timeCreated: 1723280701
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89de643989fd4cfea9cfea6be828d4ae
|
||||
timeCreated: 1723360157
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 061d492629d1a5b4f818e1b2d7fd5997
|
||||
timeCreated: 1723197447
|
||||
8
Assets/Convai/Scripts/Editor/Setup/LongTermMemory.meta
Normal file
8
Assets/Convai/Scripts/Editor/Setup/LongTermMemory.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bac50816875334f4cad40d61ee56ddb1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10caae4e93e9db74b96844fcecd6313d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Convai/Scripts/Editor/Setup/ServerAnimation.meta
Normal file
8
Assets/Convai/Scripts/Editor/Setup/ServerAnimation.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe7081e953bfb724cbf6676c2fb5236b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3e21c62030a49848f0630c7404e2d7f
|
||||
timeCreated: 1729858716
|
||||
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5430fefd15c7f04d81ae43d70bf5768
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cde2cc1e43134111918c555b5cb19ae6
|
||||
timeCreated: 1729858864
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 972b84739ae24eee9b6c60299a9f554e
|
||||
timeCreated: 1729858708
|
||||
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5059b3cdb59a8274ab89a0a0c3877a1e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b5384b14e205e34e98769b8d2e38030
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3163b8ff3c034b878e606e4924a02172
|
||||
timeCreated: 1734419624
|
||||
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36e144b353be7034ab9b83f184c24288
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1d8ed8a29394a5a8d502a4e32314afd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: effbae7421a30734d9dea85f5bda3927
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff9e296b4c9673e4abf0b96166a17fef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/Convai/Scripts/Editor/Setup/Updates.meta
Normal file
3
Assets/Convai/Scripts/Editor/Setup/Updates.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a8fc4b643dc44b69ca160965fdbf478
|
||||
timeCreated: 1723460878
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6519e8d66ce94102bedf6862d8d18f5d
|
||||
timeCreated: 1723460891
|
||||
8
Assets/Convai/Scripts/Editor/Tutorial.meta
Normal file
8
Assets/Convai/Scripts/Editor/Tutorial.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87108192e81b561468407969a263b958
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1 @@
|
||||
TutorialInitialized
|
||||
@ -0,0 +1,28 @@
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEditor.PackageManager;
|
||||
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
|
||||
|
||||
namespace Convai.Scripts.Editor.Tutorial
|
||||
{
|
||||
public class RemoveTutorialAssets : EditorWindow
|
||||
{
|
||||
private const string TUTORIAL_ASSETS_PATH = "Assets/Convai/Tutorials";
|
||||
private const string TUTORIAL_PACKAGE_NAME = "com.unity.learn.iet-framework";
|
||||
|
||||
[MenuItem("Convai/Remove Tutorial Assets")]
|
||||
private static void StartUninstallProcess()
|
||||
{
|
||||
if (Directory.Exists(TUTORIAL_ASSETS_PATH))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(TUTORIAL_ASSETS_PATH);
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
foreach (PackageInfo packageInfo in PackageInfo.GetAllRegisteredPackages())
|
||||
// Check if the package name matches
|
||||
if (packageInfo.name == TUTORIAL_PACKAGE_NAME)
|
||||
Client.Remove(TUTORIAL_PACKAGE_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 884304efde7585442b24b38748460394
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Convai/Scripts/Editor/UI.meta
Normal file
8
Assets/Convai/Scripts/Editor/UI.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b52a71c671cc4147939ccc623dc577d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/Convai/Scripts/Editor/UI/ConvaiImagesDirectory.cs
Normal file
7
Assets/Convai/Scripts/Editor/UI/ConvaiImagesDirectory.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Convai.Scripts.Editor.UI
|
||||
{
|
||||
public struct ConvaiImagesDirectory
|
||||
{
|
||||
public const string CONVAI_LOGO_PATH = "Assets/Convai/Art/UI/Logos/Convai Logo.png";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0e07e10d99ea164ca7da91a9aaa1a4b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
17
Assets/Convai/Scripts/Editor/UI/ReadOnlyDrawer.cs
Normal file
17
Assets/Convai/Scripts/Editor/UI/ReadOnlyDrawer.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Convai.Scripts.Runtime.Attributes;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Convai.Scripts.Editor.UI
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
|
||||
public class ReadOnlyDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
GUI.enabled = false; // Disable the property field
|
||||
EditorGUI.PropertyField(position, property, label, true);
|
||||
GUI.enabled = true; // Re-enable the property field
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Convai/Scripts/Editor/UI/ReadOnlyDrawer.cs.meta
Normal file
3
Assets/Convai/Scripts/Editor/UI/ReadOnlyDrawer.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b829967cd7e4a4880a40bb6653e26d9
|
||||
timeCreated: 1701082874
|
||||
3
Assets/Convai/Scripts/Editor/Utils.meta
Normal file
3
Assets/Convai/Scripts/Editor/Utils.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4fd939cd5f243f1a5b45f752d1ac976
|
||||
timeCreated: 1723619502
|
||||
@ -0,0 +1,20 @@
|
||||
using Convai.Scripts.Editor.Setup;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Convai.Scripts.Editor.Utils
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class ConvaiAPIKeySetupEditor
|
||||
{
|
||||
static ConvaiAPIKeySetupEditor()
|
||||
{
|
||||
ConvaiAPIKeySetup.OnAPIKeyNotFound += ShowAPIKeyNotFoundDialog;
|
||||
}
|
||||
|
||||
private static void ShowAPIKeyNotFoundDialog()
|
||||
{
|
||||
ConvaiSDKSetupEditorWindow window = EditorWindow.GetWindow<ConvaiSDKSetupEditorWindow>();
|
||||
ConvaiSDKSetupEditorWindow.ShowSection("account");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc9e6155cc984bbbbba406560b411797
|
||||
timeCreated: 1723619519
|
||||
Reference in New Issue
Block a user