383 lines
21 KiB
C#
383 lines
21 KiB
C#
#if UNITY_EDITOR
|
|
using System;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace AiToolbox {
|
|
[CustomPropertyDrawer(typeof(Parameters))]
|
|
public class ParametersPropertyDrawer : PropertyDrawer {
|
|
private float _height;
|
|
|
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
|
label.text = "ChatGPT Parameters";
|
|
label.tooltip = "Settings for the ChatGPT API.";
|
|
EditorGUI.BeginProperty(position, label, property);
|
|
|
|
property.isExpanded =
|
|
EditorGUI.Foldout(new Rect(position.x, position.y, position.width - 20, EditorGUIUtility.singleLineHeight),
|
|
property.isExpanded, label, true);
|
|
_height = EditorGUIUtility.singleLineHeight;
|
|
|
|
// Question mark button
|
|
{
|
|
var buttonRect = new Rect(position.x + position.width - 20, position.y + 2, 20,
|
|
EditorGUIUtility.singleLineHeight);
|
|
var icon = EditorGUIUtility.IconContent("_Help");
|
|
icon.tooltip = "Open documentation";
|
|
if (GUI.Button(buttonRect, icon, EditorStyles.label)) {
|
|
Application.OpenURL("https://ai-toolbox.dustyroom.com/runtime");
|
|
}
|
|
}
|
|
|
|
if (property.isExpanded) {
|
|
EditorGUI.indentLevel++;
|
|
var width = position.width - EditorGUIUtility.standardVerticalSpacing * 2f;
|
|
|
|
// Space
|
|
{
|
|
var rect = new Rect(position.x, position.y + _height, width, EditorGUIUtility.standardVerticalSpacing);
|
|
EditorGUI.LabelField(rect, GUIContent.none);
|
|
_height += rect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
}
|
|
|
|
// API Key
|
|
{
|
|
var apiKeyProperty = property.FindPropertyRelative(nameof(Parameters.apiKey));
|
|
var apiKey = apiKeyProperty.stringValue;
|
|
if (string.IsNullOrEmpty(apiKey)) {
|
|
const string m = "API Key is required.";
|
|
var helpBoxHeight = EditorStyles.helpBox.CalcHeight(new GUIContent(m),
|
|
width - EditorGUIUtility.labelWidth - 24);
|
|
var helpBoxRect = new Rect(position.x + EditorGUIUtility.labelWidth, position.y + _height,
|
|
width - EditorGUIUtility.labelWidth, helpBoxHeight);
|
|
EditorGUI.HelpBox(helpBoxRect, m, MessageType.Error);
|
|
_height += helpBoxRect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
|
|
var buttonRect = new Rect(position.x + EditorGUIUtility.labelWidth, position.y + _height,
|
|
width - EditorGUIUtility.labelWidth, EditorGUIUtility.singleLineHeight);
|
|
_height += buttonRect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
if (GUI.Button(buttonRect, "Open API Keys page", EditorStyles.miniButton)) {
|
|
Application.OpenURL("https://platform.openai.com/account/api-keys");
|
|
}
|
|
}
|
|
|
|
var rect = new Rect(position.x, position.y + _height, width, EditorGUIUtility.singleLineHeight);
|
|
_height += rect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
var encryptionProperty = property.FindPropertyRelative(nameof(Parameters.apiKeyEncryption));
|
|
var isRemote = encryptionProperty.enumValueIndex == (int)ApiKeyEncryption.RemoteConfig;
|
|
var apiKeyTitle = isRemote ? "API Key (fallback)" : "API Key";
|
|
var apiKeyLabel = new GUIContent(apiKeyTitle, "Your API Key from the OpenAI platform.");
|
|
var isEncrypted = encryptionProperty.enumValueIndex == (int)ApiKeyEncryption.LocallyEncrypted;
|
|
var passwordProperty = property.FindPropertyRelative(nameof(Parameters.apiKeyEncryptionPassword));
|
|
if (isEncrypted && !string.IsNullOrEmpty(apiKey) && !apiKey.StartsWith("sk-") &&
|
|
!string.IsNullOrEmpty(passwordProperty.stringValue)) {
|
|
apiKey = Key.B(apiKey, passwordProperty.stringValue);
|
|
}
|
|
|
|
var apiKeyEdited = EditorGUI.TextField(rect, apiKeyLabel, apiKey);
|
|
if (apiKeyEdited != apiKey) {
|
|
apiKeyProperty.stringValue = isEncrypted && !string.IsNullOrEmpty(apiKeyEdited)
|
|
? Key.A(apiKeyEdited, passwordProperty.stringValue)
|
|
: apiKeyEdited;
|
|
}
|
|
}
|
|
|
|
// API Key encryption
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
var encryptionProperty = property.FindPropertyRelative(nameof(Parameters.apiKeyEncryption));
|
|
|
|
// Warning
|
|
if (encryptionProperty.enumValueIndex == (int)ApiKeyEncryption.None &&
|
|
!string.IsNullOrEmpty(property.FindPropertyRelative(nameof(Parameters.apiKey)).stringValue)) {
|
|
const string m = "API Key is not encrypted. A malicious actor could steal your API Key by " +
|
|
"decompiling your build.";
|
|
var helpBoxHeight = EditorStyles.helpBox.CalcHeight(new GUIContent(m),
|
|
width - EditorGUIUtility.labelWidth - 24);
|
|
var helpBoxRect = new Rect(position.x + EditorGUIUtility.labelWidth, position.y + _height,
|
|
width - EditorGUIUtility.labelWidth, helpBoxHeight);
|
|
EditorGUI.HelpBox(helpBoxRect, m, MessageType.Warning);
|
|
_height += helpBoxRect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
}
|
|
|
|
var rect = new Rect(position.x, position.y + _height, width, EditorGUIUtility.singleLineHeight);
|
|
_height += rect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
var encryptionLabel = new GUIContent("Encryption",
|
|
"Encrypt API Key to prevent malicious actors from stealing it.");
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUI.PropertyField(rect, encryptionProperty, encryptionLabel);
|
|
if (EditorGUI.EndChangeCheck()) {
|
|
var apiKeyProperty = property.FindPropertyRelative(nameof(Parameters.apiKey));
|
|
var passwordProperty = property.FindPropertyRelative(nameof(Parameters.apiKeyEncryptionPassword));
|
|
var isEncrypted = encryptionProperty.enumValueIndex == (int)ApiKeyEncryption.LocallyEncrypted;
|
|
if (isEncrypted) {
|
|
if (string.IsNullOrEmpty(passwordProperty.stringValue)) {
|
|
passwordProperty.stringValue = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
if (apiKeyProperty.stringValue.StartsWith("sk-") &&
|
|
!string.IsNullOrEmpty(apiKeyProperty.stringValue)) {
|
|
var encryptedApiKey = Key.A(apiKeyProperty.stringValue, passwordProperty.stringValue);
|
|
apiKeyProperty.stringValue = encryptedApiKey;
|
|
}
|
|
} else {
|
|
if (!apiKeyProperty.stringValue.StartsWith("sk-") &&
|
|
!string.IsNullOrEmpty(passwordProperty.stringValue) &&
|
|
!string.IsNullOrEmpty(apiKeyProperty.stringValue)) {
|
|
var decryptedApiKey = Key.B(apiKeyProperty.stringValue, passwordProperty.stringValue);
|
|
apiKeyProperty.stringValue = decryptedApiKey;
|
|
}
|
|
}
|
|
}
|
|
|
|
var showPasswordField = encryptionProperty.enumValueIndex == (int)ApiKeyEncryption.LocallyEncrypted;
|
|
if (showPasswordField) {
|
|
var passwordProperty = property.FindPropertyRelative(nameof(Parameters.apiKeyEncryptionPassword));
|
|
|
|
if (string.IsNullOrEmpty(passwordProperty.stringValue)) {
|
|
const string m = "Password is required.";
|
|
var helpBoxHeight = EditorStyles.helpBox.CalcHeight(new GUIContent(m),
|
|
width - EditorGUIUtility.labelWidth - 24);
|
|
var helpBoxRect = new Rect(position.x + EditorGUIUtility.labelWidth, position.y + _height,
|
|
width - EditorGUIUtility.labelWidth, helpBoxHeight);
|
|
EditorGUI.HelpBox(helpBoxRect, m, MessageType.Error);
|
|
_height += helpBoxRect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
}
|
|
|
|
var passwordRect = new Rect(position.x, position.y + _height, width,
|
|
EditorGUIUtility.singleLineHeight);
|
|
var passwordLabel = new GUIContent("Password", "Password used to encrypt API Key.");
|
|
var passwordEdited = EditorGUI.TextField(passwordRect, passwordLabel, passwordProperty.stringValue);
|
|
if (passwordEdited != passwordProperty.stringValue) {
|
|
var apiKeyProperty = property.FindPropertyRelative(nameof(Parameters.apiKey));
|
|
var decryptedApiKey = string.IsNullOrEmpty(passwordProperty.stringValue)
|
|
? apiKeyProperty.stringValue
|
|
: Key.B(apiKeyProperty.stringValue, passwordProperty.stringValue);
|
|
var encryptedApiKey = string.IsNullOrEmpty(passwordEdited)
|
|
? decryptedApiKey
|
|
: Key.A(decryptedApiKey, passwordEdited);
|
|
apiKeyProperty.stringValue = encryptedApiKey;
|
|
passwordProperty.stringValue = passwordEdited;
|
|
}
|
|
|
|
_height += passwordRect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
}
|
|
|
|
var showRemoteConfigField = encryptionProperty.enumValueIndex == (int)ApiKeyEncryption.RemoteConfig;
|
|
if (showRemoteConfigField) {
|
|
var remoteConfigKeyProperty =
|
|
property.FindPropertyRelative(nameof(Parameters.apiKeyRemoteConfigKey));
|
|
var remoteConfigKeyRect = new Rect(position.x, position.y + _height, width,
|
|
EditorGUIUtility.singleLineHeight);
|
|
EditorGUI.PropertyField(remoteConfigKeyRect, remoteConfigKeyProperty,
|
|
new GUIContent("Remote Config Key"));
|
|
_height += remoteConfigKeyRect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
}
|
|
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
// Space
|
|
{
|
|
var rect = new Rect(position.x, position.y + _height, width, EditorGUIUtility.standardVerticalSpacing);
|
|
EditorGUI.LabelField(rect, GUIContent.none);
|
|
_height += rect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
}
|
|
|
|
// Model
|
|
{
|
|
var modelProperty = property.FindPropertyRelative(nameof(Parameters.model));
|
|
var rect = new Rect(position.x, position.y + _height, width, EditorGUIUtility.singleLineHeight);
|
|
var modelLabel = new GUIContent("Model", "The ChatGPT model to use.");
|
|
EditorGUI.PropertyField(rect, modelProperty, modelLabel);
|
|
_height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
|
|
|
if (modelProperty.enumValueIndex == (int)Model.Gpt4) {
|
|
const string m = "GPT-4 is currently in a limited beta and only accessible to those who have " +
|
|
"been granted access.";
|
|
var helpBoxHeight = EditorStyles.helpBox.CalcHeight(new GUIContent(m),
|
|
width - EditorGUIUtility.labelWidth - 24);
|
|
var helpBoxRect = new Rect(position.x + EditorGUIUtility.labelWidth, position.y + _height,
|
|
width - EditorGUIUtility.labelWidth, helpBoxHeight);
|
|
EditorGUI.HelpBox(helpBoxRect, m, MessageType.Warning);
|
|
_height += helpBoxRect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
|
|
var buttonRect = new Rect(position.x + EditorGUIUtility.labelWidth, position.y + _height,
|
|
width - EditorGUIUtility.labelWidth, EditorGUIUtility.singleLineHeight);
|
|
if (GUI.Button(buttonRect, "Request access", EditorStyles.miniButton)) {
|
|
Application.OpenURL("https://openai.com/waitlist/gpt-4-api");
|
|
}
|
|
|
|
_height += buttonRect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
}
|
|
}
|
|
|
|
// Temperature
|
|
{
|
|
var rect = new Rect(position.x, position.y + _height, width, EditorGUIUtility.singleLineHeight);
|
|
var temperatureLabel = new GUIContent("Temperature",
|
|
"Controls the randomness of the model. Lower values make the " +
|
|
"model more predictable; higher values make the model more " +
|
|
"surprising.");
|
|
EditorGUI.Slider(rect, property.FindPropertyRelative("temperature"), 0f, 1f, temperatureLabel);
|
|
_height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
|
}
|
|
|
|
// Role
|
|
{
|
|
var roleProperty = property.FindPropertyRelative(nameof(Parameters.role));
|
|
var useRole = !string.IsNullOrEmpty(roleProperty.stringValue);
|
|
var toggleRect = new Rect(position.x, position.y + _height, EditorGUIUtility.labelWidth,
|
|
EditorGUIUtility.singleLineHeight);
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
var roleLabel = new GUIContent(" Role", "The character or entity the AI will assume the role of.");
|
|
useRole = EditorGUI.ToggleLeft(toggleRect, roleLabel, useRole);
|
|
if (EditorGUI.EndChangeCheck()) {
|
|
if (useRole) {
|
|
roleProperty.stringValue = "Character Description";
|
|
}
|
|
|
|
if (!useRole) {
|
|
roleProperty.stringValue = null;
|
|
}
|
|
}
|
|
|
|
var rect = new Rect(position.x + EditorGUIUtility.labelWidth - 15, position.y + _height,
|
|
width - EditorGUIUtility.labelWidth + 15, EditorGUIUtility.singleLineHeight);
|
|
|
|
EditorGUI.BeginDisabledGroup(!useRole);
|
|
|
|
if (useRole) {
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUI.PropertyField(rect, roleProperty, GUIContent.none);
|
|
if (EditorGUI.EndChangeCheck()) {
|
|
if (roleProperty.stringValue == string.Empty) {
|
|
roleProperty.stringValue = " ";
|
|
}
|
|
}
|
|
} else {
|
|
EditorGUI.LabelField(rect, "Character Description", EditorStyles.textField);
|
|
}
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
_height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
|
}
|
|
|
|
// Timeout
|
|
{
|
|
var timeoutProperty = property.FindPropertyRelative(nameof(Parameters.timeout));
|
|
var useTimeout = timeoutProperty.intValue > 0;
|
|
var toggleRect = new Rect(position.x, position.y + _height, EditorGUIUtility.labelWidth,
|
|
EditorGUIUtility.singleLineHeight);
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
var timeoutLabel = new GUIContent(" Timeout", "The maximum number of seconds to wait for a response.");
|
|
useTimeout = EditorGUI.ToggleLeft(toggleRect, timeoutLabel, useTimeout);
|
|
if (EditorGUI.EndChangeCheck()) {
|
|
if (useTimeout && timeoutProperty.intValue == 0) {
|
|
timeoutProperty.intValue = 60;
|
|
}
|
|
|
|
if (!useTimeout && timeoutProperty.intValue > 0) {
|
|
timeoutProperty.intValue = 0;
|
|
}
|
|
}
|
|
|
|
var rect = new Rect(position.x + EditorGUIUtility.labelWidth - 15, position.y + _height,
|
|
width - EditorGUIUtility.labelWidth + 15, EditorGUIUtility.singleLineHeight);
|
|
|
|
EditorGUI.BeginDisabledGroup(!useTimeout);
|
|
|
|
if (useTimeout) {
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUI.PropertyField(rect, timeoutProperty, GUIContent.none);
|
|
if (EditorGUI.EndChangeCheck()) {
|
|
if (timeoutProperty.intValue < 1) {
|
|
timeoutProperty.intValue = 1;
|
|
}
|
|
}
|
|
} else {
|
|
EditorGUI.LabelField(rect, "Disabled", EditorStyles.textField);
|
|
}
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
_height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
|
}
|
|
|
|
// Throttle
|
|
{
|
|
var throttleProperty = property.FindPropertyRelative(nameof(Parameters.throttle));
|
|
var useThrottle = throttleProperty.intValue > 0;
|
|
var toggleRect = new Rect(position.x, position.y + _height, EditorGUIUtility.labelWidth,
|
|
EditorGUIUtility.singleLineHeight);
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
var throttleLabel = new GUIContent(" Throttle", "The maximum number of concurrent requests.");
|
|
useThrottle = EditorGUI.ToggleLeft(toggleRect, throttleLabel, useThrottle);
|
|
if (EditorGUI.EndChangeCheck()) {
|
|
if (useThrottle && throttleProperty.intValue == 0) {
|
|
throttleProperty.intValue = 10;
|
|
}
|
|
|
|
if (!useThrottle && throttleProperty.intValue > 0) {
|
|
throttleProperty.intValue = 0;
|
|
}
|
|
}
|
|
|
|
var rect = new Rect(position.x + EditorGUIUtility.labelWidth - 15, position.y + _height,
|
|
width - EditorGUIUtility.labelWidth + 15, EditorGUIUtility.singleLineHeight);
|
|
|
|
EditorGUI.BeginDisabledGroup(!useThrottle);
|
|
|
|
if (useThrottle) {
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUI.PropertyField(rect, throttleProperty, GUIContent.none);
|
|
if (EditorGUI.EndChangeCheck()) {
|
|
if (throttleProperty.intValue < 1) {
|
|
throttleProperty.intValue = 1;
|
|
}
|
|
}
|
|
} else {
|
|
EditorGUI.LabelField(rect, "Disabled", EditorStyles.textField);
|
|
}
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
_height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
|
}
|
|
|
|
// Draw box around parameters
|
|
{
|
|
var indentWidth = EditorGUI.indentLevel * 15 - 5;
|
|
var rect = new Rect(position.x + indentWidth, position.y + EditorGUIUtility.singleLineHeight,
|
|
position.width - indentWidth,
|
|
_height - EditorGUIUtility.singleLineHeight +
|
|
EditorGUIUtility.standardVerticalSpacing);
|
|
GUI.backgroundColor = Color.gray;
|
|
EditorGUI.HelpBox(rect, null, MessageType.None);
|
|
}
|
|
|
|
// Space
|
|
{
|
|
var rect = new Rect(position.x, position.y + _height, width, EditorGUIUtility.standardVerticalSpacing);
|
|
EditorGUI.LabelField(rect, GUIContent.none);
|
|
_height += rect.height + EditorGUIUtility.standardVerticalSpacing * 2;
|
|
}
|
|
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
EditorGUI.EndProperty();
|
|
property.serializedObject.ApplyModifiedProperties();
|
|
}
|
|
|
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
|
|
return _height;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif |