initial upload

This commit is contained in:
tom.hempel
2025-09-21 22:42:26 +02:00
commit d03bcd4ba5
6231 changed files with 351582 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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