From d30dc95ad679c1ddafa045c95bbecd0529b71ac1 Mon Sep 17 00:00:00 2001 From: yaochunhui Date: Mon, 4 Mar 2024 21:09:43 +0800 Subject: [PATCH 1/9] project code --- Editor.meta | 8 + Editor/LocalizeGptWindow.meta | 8 + .../LocalizeGptWindow.GUI.cs | 291 ++++++++++ .../LocalizeGptWindow.GUI.cs.meta | 3 + .../LocalizeGptWindow.Progress.cs | 88 +++ .../LocalizeGptWindow.Progress.cs.meta | 3 + .../LocalizeGptWindow.Record.cs | 70 +++ .../LocalizeGptWindow.Record.cs.meta | 3 + .../LocalizeGptWindow.StringTable.cs | 62 +++ .../LocalizeGptWindow.StringTable.cs.meta | 3 + .../LocalizeGptWindow.Translate.cs | 266 +++++++++ .../LocalizeGptWindow.Translate.cs.meta | 3 + Editor/LocalizeGptWindow/LocalizeGptWindow.cs | 90 +++ .../LocalizeGptWindow.cs.meta | 3 + Editor/OpenAI-Unity.meta | 3 + Editor/OpenAI-Unity/Configuration.cs | 50 ++ Editor/OpenAI-Unity/Configuration.cs.meta | 11 + Editor/OpenAI-Unity/DataTypes.cs | 425 +++++++++++++++ Editor/OpenAI-Unity/DataTypes.cs.meta | 11 + Editor/OpenAI-Unity/Interfaces.meta | 8 + Editor/OpenAI-Unity/Interfaces/IResponse.cs | 8 + .../OpenAI-Unity/Interfaces/IResponse.cs.meta | 11 + Editor/OpenAI-Unity/OpenAIApi.cs | 515 ++++++++++++++++++ Editor/OpenAI-Unity/OpenAIApi.cs.meta | 11 + Editor/OpenAI-Unity/Utils.meta | 8 + .../Utils/CustomNamingStrategy.cs | 14 + .../Utils/CustomNamingStrategy.cs.meta | 11 + Editor/OpenAI-Unity/Utils/ExtensionMethods.cs | 75 +++ .../Utils/ExtensionMethods.cs.meta | 11 + Editor/Utils.meta | 8 + Editor/Utils/SimpleEditorTableView.cs | 181 ++++++ Editor/Utils/SimpleEditorTableView.cs.meta | 11 + LICENSE.md | 21 + LICENSE.md.meta | 7 + README.md | 2 + README.md.meta | 7 + package.json | 23 + package.json.meta | 7 + 38 files changed, 2340 insertions(+) create mode 100644 Editor.meta create mode 100644 Editor/LocalizeGptWindow.meta create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs.meta create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs.meta create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.Record.cs create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.Record.cs.meta create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.StringTable.cs create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.StringTable.cs.meta create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs.meta create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.cs create mode 100644 Editor/LocalizeGptWindow/LocalizeGptWindow.cs.meta create mode 100644 Editor/OpenAI-Unity.meta create mode 100644 Editor/OpenAI-Unity/Configuration.cs create mode 100644 Editor/OpenAI-Unity/Configuration.cs.meta create mode 100644 Editor/OpenAI-Unity/DataTypes.cs create mode 100644 Editor/OpenAI-Unity/DataTypes.cs.meta create mode 100644 Editor/OpenAI-Unity/Interfaces.meta create mode 100644 Editor/OpenAI-Unity/Interfaces/IResponse.cs create mode 100644 Editor/OpenAI-Unity/Interfaces/IResponse.cs.meta create mode 100644 Editor/OpenAI-Unity/OpenAIApi.cs create mode 100644 Editor/OpenAI-Unity/OpenAIApi.cs.meta create mode 100644 Editor/OpenAI-Unity/Utils.meta create mode 100644 Editor/OpenAI-Unity/Utils/CustomNamingStrategy.cs create mode 100644 Editor/OpenAI-Unity/Utils/CustomNamingStrategy.cs.meta create mode 100644 Editor/OpenAI-Unity/Utils/ExtensionMethods.cs create mode 100644 Editor/OpenAI-Unity/Utils/ExtensionMethods.cs.meta create mode 100644 Editor/Utils.meta create mode 100644 Editor/Utils/SimpleEditorTableView.cs create mode 100644 Editor/Utils/SimpleEditorTableView.cs.meta create mode 100644 LICENSE.md create mode 100644 LICENSE.md.meta create mode 100644 README.md create mode 100644 README.md.meta create mode 100644 package.json create mode 100644 package.json.meta diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..2f45e75 --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 81cc623dd179e4e699c718fd7f3f6e68 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/LocalizeGptWindow.meta b/Editor/LocalizeGptWindow.meta new file mode 100644 index 0000000..4036463 --- /dev/null +++ b/Editor/LocalizeGptWindow.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6ca6ba5b0a2b94694b2162eaf52027aa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs b/Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs new file mode 100644 index 0000000..2b5efac --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs @@ -0,0 +1,291 @@ +using System; +using UnityEditor; +using UnityEditor.Localization.UI; +using UnityEngine; + +namespace RedGame.Framework.EditorTools +{ + public partial class LocalizeGptWindow + { + enum OutputType + { + None, Prompt, Error, + } + + private SimpleEditorTableView _tableView; + private Vector2 _scrollPosition; + private string _outputStr; + private OutputType _outputType; + private bool _gptFoldout = true; + private bool _collectionFoldout = true; + + private void OnGUI() + { + if (IsBusy()) + { + OnBusyGUI(); + return; + } + + OnGptModelGUI(); + EditorGUILayout.Space(); + OnSelectCollectionGUI(); + EditorGUILayout.Space(); + if (!_curCollection) + { + return; + } + + OnEntryListGUI(); + OnOutputGUI(); + } + + private SimpleEditorTableView CreateTable() + { + SimpleEditorTableView tableView = new SimpleEditorTableView(); + + GUIStyle labelGUIStyle = new GUIStyle(GUI.skin.label) + { + padding = new RectOffset(left: 10, right: 10, top: 2, bottom: 2) + }; + + tableView.AddColumn("", 30, (rect, rec) => + { + rec.selected = EditorGUI.Toggle( + position: rect, + value: rec.selected + ); + }).SetMaxWidth(40).SetSorting((a, b) => a.selected.CompareTo(b.selected)); + + tableView.AddColumn("Key", 80, (rect, rec) => + { + EditorGUI.LabelField( + position: rect, + label: rec.key, + style: labelGUIStyle + ); + }).SetAutoResize(true).SetSorting((a, b) => String.Compare(a.key, b.key, StringComparison.Ordinal)); + + tableView.AddColumn("Src Locales", 100, (rect, rec) => + { + EditorGUI.LabelField( + position: rect, + label: rec.srcLangNames, + style: labelGUIStyle + ); + }).SetAllowToggleVisibility(true); + + tableView.AddColumn("Dst Locales", 100, (rect, rec) => + { + EditorGUI.LabelField( + position: rect, + label: string.Join(',', rec.dstLangNames), + style: labelGUIStyle + ); + }).SetAllowToggleVisibility(true); + + tableView.AddColumn("Operation", 180, (rect, rec) => + { + Rect rt1 = new Rect(rect.x, rect.y, rect.width / 2, rect.height); + + if (GUI.Button(rt1, "Show Prompt")) + { + RefreshRecord(rec.key); + Output("System Prompt: \n" + rec.systemPrompt + "\n User Prompt:\n" + rec.prompt, OutputType.Prompt); + } + + Rect rt2 = new Rect(rect.x + rect.width / 2, rect.y, rect.width / 2, rect.height); + + if (GUI.Button(rt2, "Translate")) + { + TranslateSingleRec(rec); + } + }); + return tableView; + } + + private void OnSelectCollectionGUI() + { + if (_collections == null || _recs == null) + { + RefreshStringTableCollection(); + } + + if (_collections == null || _collections.Length == 0) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.HelpBox("No StringTableCollection found.\n"+ + "Please create at least one collection with Unity Localization", MessageType.Error); + EditorGUILayout.EndHorizontal(); + + if (GUILayout.Button("Create Collection")) + { + LocalizationTablesWindow.ShowWindow(); + } + + return; + } + + _collectionFoldout = EditorGUILayout.BeginFoldoutHeaderGroup(_collectionFoldout, "String Table Collection"); + + if (_collectionFoldout) + { + EditorGUI.indentLevel++; + EditorGUILayout.BeginVertical("Box"); + EditorGUILayout.BeginHorizontal(); + int index = Array.IndexOf(_collections, _curCollection); + index = EditorGUILayout.Popup("Select Collection", index, _collectionNames); + if (index >= 0 && index < _collections.Length) + { + _curCollection = _collections[index]; + } + + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.EndVertical(); + EditorGUI.indentLevel--; + } + EditorGUILayout.EndFoldoutHeaderGroup(); + } + + private void OnGptModelGUI() + { + _gptFoldout = EditorGUILayout.BeginFoldoutHeaderGroup(_gptFoldout, "GPT Model Parameters"); + + if (_gptFoldout) + { + EditorGUI.indentLevel++; + EditorGUI.BeginChangeCheck(); + EditorGUILayout.BeginVertical("Box"); + _baseUrl = EditorGUILayout.TextField("Base URL", _baseUrl); + if (string.IsNullOrEmpty(_baseUrl)) + _baseUrl = DEFAULT_BASE_URL; + + _apiKey = EditorGUILayout.PasswordField("API Key", _apiKey); + + int index = Array.IndexOf(_validModels, _model); + index = EditorGUILayout.Popup("Model", index, _validModels); + if (index >= 0 && index < _validModels.Length) + { + _model = _validModels[index]; + } + + _temperature = EditorGUILayout.Slider("Temperature", _temperature, 0, 1); + EditorGUILayout.EndVertical(); + + if (EditorGUI.EndChangeCheck()) + { + SaveSettings(); + } + + EditorGUI.indentLevel--; + } + + EditorGUILayout.EndFoldoutHeaderGroup(); + + } + + private void OnBusyGUI() + { + TranslateRec rec = _pendingRecs[_currentPendingRecIndex]; + EditorGUILayout.Space(); + + EditorGUILayout.LabelField("Translating ", EditorStyles.boldLabel); + + EditorGUILayout.Space(); + + EditorGUILayout.BeginHorizontal(); + Rect progressRect = GUILayoutUtility.GetRect(100, EditorGUIUtility.singleLineHeight, GUILayout.ExpandWidth(true)); + EditorGUI.ProgressBar(progressRect, GetProgress(), + $"{rec.key}({_currentPendingRecIndex + 1}/{_pendingRecs.Length})"); + if (GUILayout.Button("Cancel", GUILayout.Width(100))) + { + CancelTask(); + } + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginVertical("Box"); + EditorGUILayout.LabelField("System Prompt: ", EditorStyles.boldLabel); + EditorGUI.indentLevel++; + EditorGUILayout.LabelField(rec.systemPrompt, EditorStyles.wordWrappedLabel); + EditorGUI.indentLevel--; + EditorGUILayout.Space(); + + EditorGUILayout.LabelField("User Prompt: ", EditorStyles.boldLabel); + EditorGUI.indentLevel++; + EditorGUILayout.LabelField(rec.prompt, EditorStyles.wordWrappedLabel); + EditorGUI.indentLevel--; + EditorGUILayout.Space(); + EditorGUILayout.EndVertical(); + } + + private void OnOutputGUI() + { + if (!string.IsNullOrEmpty(_outputStr) && _outputType != OutputType.None) + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField(_outputType.ToString(), EditorStyles.boldLabel); + EditorGUILayout.Space(); + if (_outputType == OutputType.Error) + { + EditorGUILayout.HelpBox(_outputStr, MessageType.Error); + } else + { + _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, GUILayout.Height(100)); + EditorGUILayout.TextArea(_outputStr, GUILayout.ExpandHeight(true)); + EditorGUILayout.EndScrollView(); + } + } + } + + private void OnEntryListGUI() + { + EditorGUILayout.LabelField("Entries to be localized", EditorStyles.boldLabel); + if (GUILayout.Button("Open Table Editor")) + { + LocalizationTablesWindow.ShowWindow(_curCollection); + } + if (_recs == null) + RefreshRecords(); + + if (_recs == null || _recs.Length == 0) + { + EditorGUILayout.HelpBox("No Entry to translate.\n" + + "Only entries with partial language translations are displayed here.\n" + + "Edit entries in Localization Table Editor and press [Refresh List] button", + MessageType.Warning); + return; + } + + EditorGUILayout.BeginHorizontal(); + + if (GUILayout.Button("Select All", GUILayout.Width(100))) + { + foreach (var rec in _recs) + { + rec.selected = true; + } + } + + if (GUILayout.Button("Deselect All", GUILayout.Width(100))) + { + foreach (var rec in _recs) + { + rec.selected = false; + } + } + + if (GUILayout.Button("Translate Selected", GUILayout.ExpandWidth(true))) + { + TranslateSelectedRecs(); + } + + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + _tableView ??= CreateTable(); + _tableView.DrawTableGUI(_recs, (_recs.Length + 2) * EditorGUIUtility.singleLineHeight); + } + + } +} \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs.meta b/Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs.meta new file mode 100644 index 0000000..83560cf --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6b44b5306c77427d92dfe6e5f521e1f4 +timeCreated: 1709433510 \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs b/Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs new file mode 100644 index 0000000..ed53d77 --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs @@ -0,0 +1,88 @@ +using System.Linq; +using System.Threading.Tasks; +using RedGame.OpenAI; +using UnityEngine; + +namespace RedGame.Framework.EditorTools +{ + public partial class LocalizeGptWindow + { + private float _taskStartTime; + private float _taskDuration = 3; + private Task _task; + private TranslateRec[] _pendingRecs; + private int _currentPendingRecIndex; + + private void TranslateSingleRec(TranslateRec rec) + { + InitOpenAi(); + RefreshRecord(rec.key); + _pendingRecs = new[] {rec}; + _currentPendingRecIndex = 0; + AskGpt(rec); + _taskStartTime = Time.realtimeSinceStartup; + } + + private void TranslateSelectedRecs() + { + InitOpenAi(); + var recs = _recs.Where((rec) => rec.selected).ToArray(); + if (recs.Length == 0) + return; + + _pendingRecs = recs; + foreach (var rec in recs) + { + RefreshRecord(rec.key); + } + + _currentPendingRecIndex = 0; + AskGpt(_pendingRecs[0]); + _taskStartTime = Time.realtimeSinceStartup; + } + + private void CancelTask() + { + _task = null; + _pendingRecs = null; + _currentPendingRecIndex = 0; + } + + private void UpdateTaskProgress() + { + if (_task == null) + return; + + if (!_task.IsCompleted) + return; + + if (_pendingRecs != null && _currentPendingRecIndex < _pendingRecs.Length) + { + var rec = _pendingRecs[_currentPendingRecIndex]; + OnTaskCompleted(_task, rec); + _taskDuration = Time.realtimeSinceStartup - _taskStartTime; + + if (_currentPendingRecIndex < _pendingRecs.Length - 1) + { + _currentPendingRecIndex++; + AskGpt(_pendingRecs[_currentPendingRecIndex]); + _taskStartTime = Time.realtimeSinceStartup; + } else + { + _task = null; + _pendingRecs = null; + RefreshRecords(); + } + } + } + + private float GetProgress() + { + if (_task == null) + return 0; + + float curTaskProgress = Mathf.Clamp01((Time.realtimeSinceStartup - _taskStartTime) / _taskDuration); + return (_currentPendingRecIndex + curTaskProgress) / _pendingRecs.Length; + } + } +} \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs.meta b/Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs.meta new file mode 100644 index 0000000..a008606 --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 87d00eadc0d34bcf80f86a98e53b2877 +timeCreated: 1709472802 \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.Record.cs b/Editor/LocalizeGptWindow/LocalizeGptWindow.Record.cs new file mode 100644 index 0000000..00effe1 --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.Record.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using UnityEngine.Localization.Tables; + +namespace RedGame.Framework.EditorTools +{ + public partial class LocalizeGptWindow + { + private class TranslateRec + { + public bool selected; + public string key; + public List srcTables; + public List dstTables; + public string srcLangNames; + public string dstLangNames; + public string prompt; + public string systemPrompt; + } + + private TranslateRec[] _recs; + + private void RefreshRecord(string key) + { + int index = Array.FindIndex(_recs, rec => rec.key == key); + if (index >= 0) + { + _recs[index] = GeneratePrompt(_curCollection, key); + } + } + + private void RefreshRecords() + { + if (!_curCollection) + { + if (_recs == null || _recs.Length > 0) + _recs = Array.Empty(); + return; + } + + Dictionary selectedKeys = new Dictionary(); + if (_recs != null) + { + foreach (var rec in _recs) + { + selectedKeys[rec.key] = rec.selected; + } + } + + List recs = new List(); + foreach (var entry in _curCollection.SharedData.Entries) + { + var rec = GeneratePrompt(_curCollection, entry.Key); + if (rec == null) + continue; + if (selectedKeys.TryGetValue(entry.Key, out bool selected)) + { + rec.selected = selected; + } else + { + rec.selected = true; + } + + recs.Add(rec); + } + + _recs = recs.ToArray(); + } + } +} \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.Record.cs.meta b/Editor/LocalizeGptWindow/LocalizeGptWindow.Record.cs.meta new file mode 100644 index 0000000..080556c --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.Record.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4fe0a8c81de44bcfb99e616e730b09da +timeCreated: 1709433861 \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.StringTable.cs b/Editor/LocalizeGptWindow/LocalizeGptWindow.StringTable.cs new file mode 100644 index 0000000..dec91f2 --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.StringTable.cs @@ -0,0 +1,62 @@ +using System; +using System.Reflection; +using UnityEditor; +using UnityEditor.Localization; +using UnityEngine; + +namespace RedGame.Framework.EditorTools +{ + public partial class LocalizeGptWindow + { + private StringTableCollection _curCollection; + private StringTableCollection[] _collections; + private string[] _collectionNames; + + private void RefreshStringTableCollection() + { + _curCollection = null; + CancelTask(); + + string[] guids = AssetDatabase.FindAssets("t:StringTableCollection"); + _collections = new StringTableCollection[guids.Length]; + _collectionNames = new string[guids.Length]; + for (int i = 0; i < guids.Length; i++) + { + var path = AssetDatabase.GUIDToAssetPath(guids[i]); + _collections[i] = AssetDatabase.LoadAssetAtPath(path); + _collectionNames[i] = _collections[i].name; + } + + if (_collections.Length > 0) + { + _curCollection = _collections[0]; + } else + { + _curCollection = null; + } + + _recs = null; + } + + // Notify Localization Table Editor to refresh by calling + // internal method LocalizationEditorSettings.EditorEvents.RaiseCollectionModified + private void NotifyStringTableEditorRefresh() + { + Type classType = typeof(LocalizationEditorEvents); + MethodInfo methodInfo = classType.GetMethod("RaiseCollectionModified", + BindingFlags.Instance | BindingFlags.NonPublic); + + if (methodInfo != null) + { + try + { + methodInfo.Invoke(LocalizationEditorSettings.EditorEvents, new object[] { this, _curCollection }); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + } +} \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.StringTable.cs.meta b/Editor/LocalizeGptWindow/LocalizeGptWindow.StringTable.cs.meta new file mode 100644 index 0000000..747e6c9 --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.StringTable.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7260781d6a8e48bcba6537bd58d51d62 +timeCreated: 1709433734 \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs b/Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs new file mode 100644 index 0000000..edc33fc --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using RedGame.OpenAI; +using UnityEditor; +using UnityEditor.Localization; +using UnityEngine; +using UnityEngine.Localization.Metadata; +using UnityEngine.Localization.Tables; + +namespace RedGame.Framework.EditorTools +{ + public partial class LocalizeGptWindow + { + private OpenAIApi _openAi; + + private const string SYSTEM_PROMPT = + "You are a multilingual translation AI. " + + "Your response should be in JSON format, " + + "with each language name as a key and the translated text as the corresponding value.\n\n"; + + private void InitOpenAi() + { + _openAi = new OpenAIApi( + apiKey:_apiKey, + baseUrl:_baseUrl); + } + + private TranslateRec GeneratePrompt(StringTableCollection collection, string key) + { + StringBuilder sb = new StringBuilder(); + TranslateRec rec = new TranslateRec + { + key = key, selected = true + }; + + sb.Append("Translate the following texts:\n"); + var tables = collection.StringTables; + rec.srcTables = new List(); + rec.dstTables = new List(); + foreach (var table in tables) + { + StringTableEntry entry = table.GetEntry(key); + + if (entry == null || string.IsNullOrWhiteSpace(entry.Value)) + { + rec.dstTables.Add(table); + continue; + } + + string langName = table.LocaleIdentifier.CultureInfo.EnglishName; + rec.srcTables.Add(table); + if (rec.srcTables.Count <= 2) + { + sb.Append("## From " + langName + ":\n" + entry.Value + "\n"); + } + } + + sb.Append("\n"); + + if (rec.dstTables.Count == 0 || rec.srcTables.Count == 0) + { + return null; + } + + rec.srcLangNames = string.Join(",", rec.srcTables.Select(t => t.LocaleIdentifier.CultureInfo.EnglishName)); + rec.dstLangNames = string.Join(",", rec.dstTables.Select(t => t.LocaleIdentifier.CultureInfo.EnglishName)); + + sb.Append("\n# Translate to: \n"); + sb.Append(rec.dstLangNames); + sb.Append("\n\n"); + + StringBuilder requirements = new StringBuilder(); + Comment comment = collection.SharedData.GetEntry(key).Metadata.GetMetadata(); + + if (comment != null && !string.IsNullOrWhiteSpace(comment.CommentText)) + { + requirements.Append("* "); + requirements.Append(comment.CommentText); + requirements.Append("\n"); + } + + foreach (var table in rec.dstTables) + { + var entry = table.GetEntry(key); + if (entry == null) + continue; + var langComment = entry.GetMetadata(); + if (langComment != null && !string.IsNullOrWhiteSpace(langComment.CommentText)) + { + string langName = table.LocaleIdentifier.CultureInfo.EnglishName; + requirements.Append("* Specifically for " + langName + ": " + + langComment.CommentText); + requirements.Append("\n"); + } + } + + if (requirements.Length > 0) + { + rec.systemPrompt = SYSTEM_PROMPT + + "# Translation Instructions:\n" + + requirements; + } else + { + rec.systemPrompt = SYSTEM_PROMPT; + } + + rec.prompt = sb.ToString(); + + return rec; + } + + private void AskGpt(TranslateRec rec) + { + _task = _openAi.CreateChatCompletion(new CreateChatCompletionRequest() + { + Model = _model, + Messages = new List() + { + new() { Role = "system", Content = rec.systemPrompt }, + new() { Role = "user", Content = rec.prompt } + }, + Temperature = _temperature, + ResponseFormat = ResponseFormat.JsonObject + }); + } + + private void OnTaskCompleted(Task task, TranslateRec rec) + { + if (task.IsFaulted) + { + if (task.Exception != null) + { + Output(task.Exception.Message, OutputType.Error); + Debug.LogError(task.Exception); + } + return; + } + + var response = task.Result; + if (response.Error != null) + { + Output(response.Error.Message, OutputType.Error); + Debug.LogError(response.Error.Message); + return; + } + + string answer = response.Choices[0].Message.Content; + + JToken jToken = JToken.Parse(answer); + Dictionary dict = TryParseJson(jToken); + + if (dict != null) + { + ApplyTranslation(rec, dict); + } else + { + Output("Failed to parse response as json object:\n" + answer, OutputType.Error); + Debug.LogError("Failed to parse response as json object"); + } + + Output("", OutputType.None); + } + + private string TryParseText(JToken json) + { + if (json.Type == JTokenType.String) + { + return json.ToString(); + } + + if (json.Type == JTokenType.Object) + { + JObject jObject = (JObject)json; + if (jObject.ContainsKey("text")) + return jObject["text"].ToString(); + if (jObject.ContainsKey("result")) + return jObject["result"].ToString(); + } + + return string.Empty; + } + + private string TryParseKey(JObject jObject) + { + if (jObject.ContainsKey("name")) + return jObject["name"].ToString(); + + if (jObject.ContainsKey("translation")) + return jObject["translation"].ToString(); + + return null; + } + + private Dictionary TryParseJson(JToken json) + { + // 三种可能: + // 1. 一个json对象 language: translation + // 2. 一个json数组,每个元素是一个json对象 + // 3. 有一个根节点的,根节点为"translations",下层才是 + + if (json.Type == JTokenType.Object) + { + JObject jObject = (JObject)json; + + JProperty rootProp = jObject.Properties().FirstOrDefault(p => + { + return p.Name.Equals("translations", StringComparison.OrdinalIgnoreCase) || + p.Name.Equals("translation", StringComparison.OrdinalIgnoreCase); + }); + + if (rootProp != null) + { + return TryParseJson(rootProp.Value); + } + + return jObject.Properties() + .ToDictionary( + p => p.Name, + p => TryParseText(p.Value)); + } + + if (json.Type == JTokenType.Array) + { + JArray jArray = (JArray)json; + + Dictionary dict = new Dictionary(); + foreach (var item in jArray) + { + if (item.Type == JTokenType.Object) + { + JObject jObject = (JObject)item; + string key = TryParseKey(jObject); + if (key != null) + { + dict[key] = TryParseText(jObject); + } + } + } + + return dict; + } + + return null; + } + + private void ApplyTranslation(TranslateRec rec, Dictionary result) + { + foreach (var table in rec.dstTables) + { + string langName = table.LocaleIdentifier.CultureInfo.EnglishName; + if (result.ContainsKey(langName)) + { + string translated = result[langName]; + table.AddEntry(rec.key, translated); + } + EditorUtility.SetDirty(table); + } + + NotifyStringTableEditorRefresh(); + } + } +} \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs.meta b/Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs.meta new file mode 100644 index 0000000..6ab4a5a --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0877a0ed5a644c8a85eb01a048091d5a +timeCreated: 1709433631 \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.cs b/Editor/LocalizeGptWindow/LocalizeGptWindow.cs new file mode 100644 index 0000000..4f9aa58 --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.cs @@ -0,0 +1,90 @@ +using UnityEditor; +using UnityEngine; + +namespace RedGame.Framework.EditorTools +{ + public partial class LocalizeGptWindow : EditorWindow + { + private string _model = "gpt-3.5-turbo"; + private readonly string[] _validModels = + { + "gpt-3.5-turbo", + "gpt-4-turbo-preview", + "gpt-4-0125-preview", + "gpt-4-1106-preview", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-1106" + }; + + private float _temperature; + private string _apiKey; + private const string DEFAULT_BASE_URL = "https://api.openai.com/v1"; + private string _baseUrl = DEFAULT_BASE_URL; + + [MenuItem("Tools/GPT Localization")] + private static void ShowWindow() + { + var window = GetWindow(); + window.titleContent = new GUIContent("GPT Localization"); + window.Show(); + } + + private void OnEnable() + { + LoadSettings(); + EditorApplication.update += UpdateFrame; + CancelTask(); + } + + private void OnDisable() + { + EditorApplication.update -= UpdateFrame; + CancelTask(); + } + + private void OnFocus() + { + if (!_curCollection) + { + RefreshStringTableCollection(); + } + if (!IsBusy()) + { + RefreshRecords(); + } + } + + private void Output(string str, OutputType type) + { + _outputStr = str; + _outputType = type; + } + + private void UpdateFrame() + { + if (IsBusy()) + { + UpdateTaskProgress(); + Repaint(); + } + } + + private bool IsBusy() => _task != null; + + private void LoadSettings() + { + _model = EditorPrefs.GetString("LocalizeGptWindow.Model", _model); + _temperature = EditorPrefs.GetFloat("LocalizeGptWindow.Temperature", _temperature); + _baseUrl = EditorPrefs.GetString("LocalizeGptWindow.BaseUrl", _baseUrl); + _apiKey = EditorPrefs.GetString("LocalizeGptWindow.ApiKey", _apiKey); + } + + private void SaveSettings() + { + EditorPrefs.SetString("LocalizeGptWindow.Model", _model); + EditorPrefs.SetFloat("LocalizeGptWindow.Temperature", _temperature); + EditorPrefs.SetString("LocalizeGptWindow.BaseUrl", _baseUrl); + EditorPrefs.SetString("LocalizeGptWindow.ApiKey", _apiKey); + } + } +} \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.cs.meta b/Editor/LocalizeGptWindow/LocalizeGptWindow.cs.meta new file mode 100644 index 0000000..be8d0ba --- /dev/null +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e55a8c9dd58f450db0a091ce816391a3 +timeCreated: 1709208374 \ No newline at end of file diff --git a/Editor/OpenAI-Unity.meta b/Editor/OpenAI-Unity.meta new file mode 100644 index 0000000..7195549 --- /dev/null +++ b/Editor/OpenAI-Unity.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 915014c54e0340edb746cf5e6aff31b2 +timeCreated: 1709556403 \ No newline at end of file diff --git a/Editor/OpenAI-Unity/Configuration.cs b/Editor/OpenAI-Unity/Configuration.cs new file mode 100644 index 0000000..cbb4b7b --- /dev/null +++ b/Editor/OpenAI-Unity/Configuration.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; +using UnityEngine; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace RedGame.OpenAI +{ + public class Configuration + { + public Auth Auth { get; } + + /// Used for serializing and deserializing PascalCase request object fields into snake_case format for JSON. Ignores null fields when creating JSON strings. + private readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() + { + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new DefaultContractResolver() + { + NamingStrategy = new CustomNamingStrategy() + } + }; + + public Configuration(string apiKey = null, string organization = null) + { + if (apiKey == null) + { + var userPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var authPath = $"{userPath}/.openai/auth.json"; + + if (File.Exists(authPath)) + { + var json = File.ReadAllText(authPath); + Auth = JsonConvert.DeserializeObject(json, jsonSerializerSettings); + } + else + { + Debug.LogError("API Key is null and auth.json does not exist. Please check https://github.com/srcnalt/OpenAI-Unity#saving-your-credentials"); + } + } + else + { + Auth = new Auth() + { + ApiKey = apiKey, + Organization = organization + }; + } + } + } +} diff --git a/Editor/OpenAI-Unity/Configuration.cs.meta b/Editor/OpenAI-Unity/Configuration.cs.meta new file mode 100644 index 0000000..59f244a --- /dev/null +++ b/Editor/OpenAI-Unity/Configuration.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8740e1fbd17f6444ea542aadc60b2c1f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/OpenAI-Unity/DataTypes.cs b/Editor/OpenAI-Unity/DataTypes.cs new file mode 100644 index 0000000..512755c --- /dev/null +++ b/Editor/OpenAI-Unity/DataTypes.cs @@ -0,0 +1,425 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace RedGame.OpenAI +{ + #region Common Data Types + public struct Choice + { + public string Text { get; set; } + public int? Index { get; set; } + public int? Logprobs { get; set; } + public string FinishReason { get; set; } + } + + public struct Usage + { + public string PromptTokens { get; set; } + public string CompletionTokens { get; set; } + public string TotalTokens { get; set; } + } + + public class OpenAIFile + { + public string Prompt { get; set; } + public object Completion { get; set; } + public string Id { get; set; } + public string Object { get; set; } + public long Bytes { get; set; } + public long CreatedAt { get; set; } + public string Filename { get; set; } + public string Purpose { get; set; } + public object StatusDetails { get; set; } + public string Status { get; set; } + } + + public class OpenAIFileResponse : OpenAIFile, IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + } + + public class ApiError + { + public string Message; + public string Type; + public object Param; + public object Code; + } + + public struct Auth + { + [JsonRequired] + public string ApiKey { get; set; } + public string Organization { get; set; } + } + #endregion + + #region Models API Data Types + public struct ListModelsResponse: IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + public string Object { get; set; } + public List Data { get; set; } + } + + public class OpenAIModel + { + public string Id { get; set; } + public string Object { get; set; } + public string OwnedBy { get; set; } + public long Created { get; set; } + public string Root { get; set; } + public string Parent { get; set; } + public List> Permission { get; set; } + } + + public class OpenAIModelResponse : OpenAIModel, IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + } + #endregion + + #region Chat API Data Types + + public enum ResponseFormat + { + Text, + JsonObject + } + + public sealed class CreateChatCompletionRequest + { + public string Model { get; set; } + public List Messages { get; set; } + public float? Temperature { get; set; } = 1; + public int N { get; set; } = 1; + public bool Stream { get; set; } = false; + public string Stop { get; set; } + public int? MaxTokens { get; set; } + public float? PresencePenalty { get; set; } = 0; + public float? FrequencyPenalty { get; set; } = 0; + public Dictionary LogitBias { get; set; } + public string User { get; set; } + public string SystemFingerprint { get; set; } + + [JsonConverter(typeof(ResponseFormatJsonConverter))] + public ResponseFormat? ResponseFormat { get; set; } + } + + public class ResponseFormatJsonConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, ResponseFormat value, JsonSerializer serializer) + { + if (value == ResponseFormat.JsonObject) + { + writer.WriteStartObject(); + writer.WritePropertyName("type"); + writer.WriteValue("json_object"); + writer.WriteEndObject(); + } else + { + writer.WriteNull(); + } + } + + public override ResponseFormat ReadJson(JsonReader reader, System.Type objectType, ResponseFormat existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.StartObject) + { + JObject obj = JObject.Load(reader); + if (obj.TryGetValue("type", out JToken typeToken) && typeToken.ToString() == "json_object") + { + return ResponseFormat.JsonObject; + } + } + + return ResponseFormat.Text; + } + } + + public struct CreateChatCompletionResponse : IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + public string Model { get; set; } + public string Id { get; set; } + public string Object { get; set; } + public long Created { get; set; } + public List Choices { get; set; } + public Usage Usage { get; set; } + public string SystemFingerprint { get; set; } + } + + public struct ChatChoice + { + public ChatMessage Message { get; set; } + public ChatMessage Delta { get; set; } + public int? Index { get; set; } + public string FinishReason { get; set; } + public string Logprobs { get; set; } + } + + public struct ChatMessage + { + public string Role { get; set; } + public string Content { get; set; } + } + + #endregion + + #region Audio Transcriptions Data Types + + public struct FileData + { + public byte[] Data; + public string Name; + } + + public class CreateAudioRequestBase + { + public string File { get; set; } + public FileData FileData { get; set; } + public string Model { get; set; } + public string Prompt { get; set; } + public string ResponseFormat { get; set; } = AudioResponseFormat.Json; + public float? Temperature { get; set; } = 0; + } + + public class CreateAudioTranscriptionsRequest: CreateAudioRequestBase + { + public string Language { get; set; } + } + + public class CreateAudioTranslationRequest: CreateAudioRequestBase { } + + public struct CreateAudioResponse: IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + public string Text { get; set; } + } + #endregion + + #region Images API Data Types + public class CreateImageRequestBase + { + public int? N { get; set; } = 1; + public string Size { get; set; } = ImageSize.Size1024; + public string ResponseFormat { get; set; } = ImageResponseFormat.Url; + public string User { get; set; } + } + + public sealed class CreateImageRequest: CreateImageRequestBase + { + public string Prompt { get; set; } + } + + public sealed class CreateImageEditRequest: CreateImageRequestBase + { + public string Image { get; set; } + public string Mask { get; set; } + public string Prompt { get; set; } + } + + public sealed class CreateImageVariationRequest: CreateImageRequestBase + { + public string Image { get; set; } + } + + public struct CreateImageResponse: IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + public long Created { get; set; } + public List Data { get; set; } + } + + public struct ImageData + { + public string Url { get; set; } + public string B64Json { get; set; } + } + #endregion + + #region Embeddins API Data Types + public struct CreateEmbeddingsRequest + { + public string Model { get; set; } + public string Input { get; set; } + public string User { get; set; } + } + + public struct CreateEmbeddingsResponse: IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + public string Object { get; set; } + public List Data; + public string Model { get; set; } + public Usage Usage { get; set; } + } + + public struct EmbeddingData + { + public string Object { get; set; } + public List Embedding { get; set; } + public int Index { get; set; } + } + #endregion + + #region Files API Data Types + public struct ListFilesResponse: IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + public string Object { get; set; } + public List Data { get; set; } + public bool HasMore { get; set; } + } + + public struct DeleteResponse: IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + public string Id { get; set; } + public string Object { get; set; } + public bool Deleted { get; set; } + } + + public struct CreateFileRequest + { + public string File { get; set; } + public string Purpose { get; set; } + } + #endregion + + #region FineTunes API Data Types + public class CreateFineTuneRequest + { + public string TrainingFile { get; set; } + public string ValidationFile { get; set; } + public string Model { get; set; } + public int NEpochs { get; set; } = 4; + public int? BatchSize { get; set; } = null; + public float? LearningRateMultiplier { get; set; } = null; + public float PromptLossWeight { get; set; } = 0.01f; + public bool ComputeClassificationMetrics { get; set; } = false; + public int? ClassificationNClasses { get; set; } = null; + public string ClassificationPositiveClass { get; set; } + public List ClassificationBetas { get; set; } + public string Suffix { get; set; } + } + + public struct ListFineTunesResponse: IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + public string Object { get; set; } + public List Data { get; set; } + public object NextStartingAfter { get; set; } + } + + public struct ListFineTuneEventsResponse: IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + public string Object { get; set; } + public List Data { get; set; } + } + + public class FineTune + { + public string Id { get; set; } + public string Object { get; set; } + public long CreatedAt { get; set; } + public long UpdatedAt { get; set; } + public string Model { get; set; } + public string FineTunedModel { get; set; } + public string OrganizationId { get; set; } + public string Status { get; set; } + public Dictionary Hyperparams { get; set; } + public List TrainingFiles { get; set; } + public List ValidationFiles { get; set; } + public List ResultFiles { get; set; } + public List Events { get; set; } + } + + public class FineTuneResponse : FineTune, IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + } + + public struct FineTuneEvent + { + public string Object { get; set; } + public long CreatedAt { get; set; } + public string Level { get; set; } + public string Message { get; set; } + } + #endregion + + #region Moderations API Data Types + public class CreateModerationRequest + { + public string Input { get; set; } + public string Model { get; set; } = ModerationModel.Latest; + } + + public struct CreateModerationResponse: IResponse + { + public ApiError Error { get; set; } + public string Warning { get; set; } + public string Id { get; set; } + public string Model { get; set; } + public List Results { get; set; } + } + + public struct ModerationResult + { + public bool Flagged { get; set; } + public Dictionary Categories { get; set; } + public Dictionary CategoryScores { get; set; } + } + #endregion + + #region Static String Types + public static class ContentType + { + public const string MultipartFormData = "multipart/form-data"; + public const string ApplicationJson = "application/json"; + } + + public static class ImageSize + { + public const string Size256 = "256x256"; + public const string Size512 = "512x512"; + public const string Size1024 = "1024x1024"; + } + + public static class ImageResponseFormat + { + public const string Url = "url"; + public const string Base64Json = "b64_json"; + } + + public static class AudioResponseFormat + { + public const string Json = "json"; + public const string Text = "text"; + public const string Srt = "srt"; + public const string VerboseJson = "verbose_json"; + public const string Vtt = "vtt"; + } + + public static class ModerationModel + { + public const string Stable = "text-moderation-stable"; + public const string Latest = "text-moderation-latest"; + } + #endregion +} diff --git a/Editor/OpenAI-Unity/DataTypes.cs.meta b/Editor/OpenAI-Unity/DataTypes.cs.meta new file mode 100644 index 0000000..e9c1894 --- /dev/null +++ b/Editor/OpenAI-Unity/DataTypes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e435220a42c5c6342b3e084524a64024 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/OpenAI-Unity/Interfaces.meta b/Editor/OpenAI-Unity/Interfaces.meta new file mode 100644 index 0000000..6aa7005 --- /dev/null +++ b/Editor/OpenAI-Unity/Interfaces.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ea72b8184a896df428adb943bf478aea +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/OpenAI-Unity/Interfaces/IResponse.cs b/Editor/OpenAI-Unity/Interfaces/IResponse.cs new file mode 100644 index 0000000..6542770 --- /dev/null +++ b/Editor/OpenAI-Unity/Interfaces/IResponse.cs @@ -0,0 +1,8 @@ +namespace RedGame.OpenAI +{ + public interface IResponse + { + ApiError Error { get; set; } + public string Warning { get; set; } + } +} diff --git a/Editor/OpenAI-Unity/Interfaces/IResponse.cs.meta b/Editor/OpenAI-Unity/Interfaces/IResponse.cs.meta new file mode 100644 index 0000000..3f51448 --- /dev/null +++ b/Editor/OpenAI-Unity/Interfaces/IResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3985268b4cc61e43ba87332f281fb2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/OpenAI-Unity/OpenAIApi.cs b/Editor/OpenAI-Unity/OpenAIApi.cs new file mode 100644 index 0000000..30561b0 --- /dev/null +++ b/Editor/OpenAI-Unity/OpenAIApi.cs @@ -0,0 +1,515 @@ +using System; +using System.IO; +using UnityEngine; +using System.Text; +using Newtonsoft.Json; +using System.Threading; +using System.Globalization; +using System.Threading.Tasks; +using UnityEngine.Networking; +using System.Collections.Generic; +using Newtonsoft.Json.Serialization; +using System.Linq; + +namespace RedGame.OpenAI +{ + public class OpenAIApi + { + /// + /// Reads and sets user credentials from %User%/.openai/auth.json + /// Remember that your API key is a secret! Do not share it with others or expose it in any client-side code (browsers, apps). + /// Production requests must be routed through your own backend server where your API key can be securely loaded from an environment variable or key management service. + /// + private Configuration configuration; + + private Configuration Configuration + { + get + { + if (configuration == null) + { + configuration = new Configuration(); + } + + return configuration; + } + } + + private const string OFFICIAL_BASE_PATH = "https://api.openai.com/v1"; + + /// OpenAI API base path for requests. + private readonly string _basePath; + + public OpenAIApi(string apiKey = null, string organization = null, string baseUrl = null) + { + if (string.IsNullOrEmpty(baseUrl)) + _basePath = OFFICIAL_BASE_PATH; + else + _basePath = baseUrl; + + if (apiKey != null) + { + configuration = new Configuration(apiKey, organization); + } + } + + /// Used for serializing and deserializing PascalCase request object fields into snake_case format for JSON. Ignores null fields when creating JSON strings. + private readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() + { + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new DefaultContractResolver() + { + NamingStrategy = new CustomNamingStrategy() + }, + Culture = CultureInfo.InvariantCulture + }; + + /// + /// Dispatches an HTTP request to the specified path with the specified method and optional payload. + /// + /// The path to send the request to. + /// The HTTP method to use for the request. + /// An optional byte array of json payload to include in the request. + /// Response type of the request. + /// A Task containing the response from the request as the specified type. + private async Task DispatchRequest(string path, string method, byte[] payload = null) where T: IResponse + { + T data; + + using (var request = UnityWebRequest.Put(path, payload)) + { + request.method = method; + request.SetHeaders(Configuration, ContentType.ApplicationJson); + + var asyncOperation = request.SendWebRequest(); + + while (!asyncOperation.isDone) await Task.Yield(); + + data = JsonConvert.DeserializeObject(request.downloadHandler.text, jsonSerializerSettings); + } + + if (data?.Error != null) + { + ApiError error = data.Error; + Debug.LogError($"Error Message: {error.Message}\nError Type: {error.Type}\n"); + } + + if (data?.Warning != null) + { + Debug.LogWarning(data.Warning); + } + + return data; + } + + /// + /// Dispatches an HTTP request to the specified path with the specified method and optional payload. + /// + /// The path to send the request to. + /// The HTTP method to use for the request. + /// A callback function to be called when a response is updated. + /// A callback function to be called when the request is complete. + /// A cancellation token to cancel the request. + /// An optional byte array of json payload to include in the request. + private async void DispatchRequest(string path, string method, Action> onResponse, Action onComplete, CancellationTokenSource token, byte[] payload = null) where T: IResponse + { + using (var request = UnityWebRequest.Put(path, payload)) + { + request.method = method; + request.SetHeaders(Configuration, ContentType.ApplicationJson); + + var asyncOperation = request.SendWebRequest(); + + do + { + List dataList = new List(); + string[] lines = request.downloadHandler.text.Split('\n').Where(line => line != "").ToArray(); + + foreach (string line in lines) + { + var value = line.Replace("data: ", ""); + + if (value.Contains("[DONE]")) + { + onComplete?.Invoke(); + break; + } + + var data = JsonConvert.DeserializeObject(value, jsonSerializerSettings); + + if (data?.Error != null) + { + ApiError error = data.Error; + Debug.LogError($"Error Message: {error.Message}\nError Type: {error.Type}\n"); + } + else + { + dataList.Add(data); + } + } + onResponse?.Invoke(dataList); + + await Task.Yield(); + } + while (!asyncOperation.isDone && !token.IsCancellationRequested); + + onComplete?.Invoke(); + } + } + + /// + /// Dispatches an HTTP request to the specified path with a multi-part data form. + /// + /// The path to send the request to. + /// A multi-part data form to upload with the request. + /// Response type of the request. + /// A Task containing the response from the request as the specified type. + private async Task DispatchRequest(string path, List form) where T: IResponse + { + T data; + + using (var request = new UnityWebRequest(path, "POST")) + { + request.SetHeaders(Configuration); + var boundary = UnityWebRequest.GenerateBoundary(); + var formSections = UnityWebRequest.SerializeFormSections(form, boundary); + var contentType = $"{ContentType.MultipartFormData}; boundary={Encoding.UTF8.GetString(boundary)}"; + request.uploadHandler = new UploadHandlerRaw(formSections) {contentType = contentType}; + request.downloadHandler = new DownloadHandlerBuffer(); + var asyncOperation = request.SendWebRequest(); + + while (!asyncOperation.isDone) await Task.Yield(); + + data = JsonConvert.DeserializeObject(request.downloadHandler.text, jsonSerializerSettings); + } + + if (data != null && data.Error != null) + { + ApiError error = data.Error; + Debug.LogError($"Error Message: {error.Message}\nError Type: {error.Type}\n"); + } + + return data; + } + + /// + /// Create byte array payload from the given request object that contains the parameters. + /// + /// The request object that contains the parameters of the payload. + /// type of the request object. + /// Byte array payload. + private byte[] CreatePayload(T request) + { + var json = JsonConvert.SerializeObject(request, jsonSerializerSettings); + return Encoding.UTF8.GetBytes(json); + } + + /// + /// Lists the currently available models, and provides basic information about each one such as the owner and availability. + /// + public async Task ListModels() + { + var path = $"{_basePath}/models"; + return await DispatchRequest(path, UnityWebRequest.kHttpVerbGET); + } + + /// + /// Retrieves a model instance, providing basic information about the model such as the owner and permissioning. + /// + /// The ID of the model to use for this request + /// See + public async Task RetrieveModel(string id) + { + var path = $"{_basePath}/models/{id}"; + return await DispatchRequest(path, UnityWebRequest.kHttpVerbGET); + } + + /// + /// Creates a chat completion request as in ChatGPT. + /// + /// See + /// See + public async Task CreateChatCompletion(CreateChatCompletionRequest request) + { + var path = $"{_basePath}/chat/completions"; + var payload = CreatePayload(request); + return await DispatchRequest(path, UnityWebRequest.kHttpVerbPOST, payload); + } + + /// + /// Creates a chat completion request as in ChatGPT. + /// + /// See + /// Callback function that will be called when stream response is updated. + /// Callback function that will be called when stream response is completed. + /// Cancellation token to cancel the request. + public void CreateChatCompletionAsync(CreateChatCompletionRequest request, Action> onResponse, Action onComplete, CancellationTokenSource token) + { + request.Stream = true; + var path = $"{_basePath}/chat/completions"; + var payload = CreatePayload(request); + + DispatchRequest(path, UnityWebRequest.kHttpVerbPOST, onResponse, onComplete, token, payload); + } + + /// + /// Creates an image given a prompt. + /// + /// See + /// See + public async Task CreateImage(CreateImageRequest request) + { + var path = $"{_basePath}/images/generations"; + var payload = CreatePayload(request); + return await DispatchRequest(path, UnityWebRequest.kHttpVerbPOST, payload); + } + + /// + /// Creates an edited or extended image given an original image and a prompt. + /// + /// See + /// See + public async Task CreateImageEdit(CreateImageEditRequest request) + { + var path = $"{_basePath}/images/edits"; + + var form = new List(); + form.AddFile(request.Image, "image", "image/png"); + form.AddFile(request.Mask, "mask", "image/png"); + form.AddValue(request.Prompt, "prompt"); + form.AddValue(request.N, "n"); + form.AddValue(request.Size, "size"); + form.AddValue(request.ResponseFormat, "response_format"); + + return await DispatchRequest(path, form); + } + + /// + /// Creates a variation of a given image. + /// + /// See + /// See + public async Task CreateImageVariation(CreateImageVariationRequest request) + { + var path = $"{_basePath}/images/variations"; + + var form = new List(); + form.AddFile(request.Image, "image", "image/png"); + form.AddValue(request.N, "n"); + form.AddValue(request.Size, "size"); + form.AddValue(request.ResponseFormat, "response_format"); + form.AddValue(request.User, "user"); + + return await DispatchRequest(path, form); + } + + /// + /// Creates an embedding vector representing the input text. + /// + /// See + /// See + public async Task CreateEmbeddings(CreateEmbeddingsRequest request) + { + var path = $"{_basePath}/embeddings"; + var payload = CreatePayload(request); + return await DispatchRequest(path, UnityWebRequest.kHttpVerbPOST, payload); + } + + /// + /// Transcribes audio into the input language. + /// + /// See + /// See + public async Task CreateAudioTranscription(CreateAudioTranscriptionsRequest request) + { + var path = $"{_basePath}/audio/transcriptions"; + + var form = new List(); + if (string.IsNullOrEmpty(request.File)) + { + form.AddData(request.FileData, "file", $"audio/{Path.GetExtension(request.File)}"); + } + else + { + form.AddFile(request.File, "file", $"audio/{Path.GetExtension(request.File)}"); + } + form.AddValue(request.Model, "model"); + form.AddValue(request.Prompt, "prompt"); + form.AddValue(request.ResponseFormat, "response_format"); + form.AddValue(request.Temperature, "temperature"); + form.AddValue(request.Language, "language"); + + return await DispatchRequest(path, form); + } + + /// + /// Translates audio into into English. + /// + /// See + /// See + public async Task CreateAudioTranslation(CreateAudioTranslationRequest request) + { + var path = $"{_basePath}/audio/translations"; + + var form = new List(); + if (string.IsNullOrEmpty(request.File)) + { + form.AddData(request.FileData, "file", $"audio/{Path.GetExtension(request.File)}"); + } + else + { + form.AddFile(request.File, "file", $"audio/{Path.GetExtension(request.File)}"); + } + form.AddValue(request.Model, "model"); + form.AddValue(request.Prompt, "prompt"); + form.AddValue(request.ResponseFormat, "response_format"); + form.AddValue(request.Temperature, "temperature"); + + return await DispatchRequest(path, form); + } + + /// + /// Returns a list of files that belong to the user's organization. + /// + /// See + public async Task ListFiles() + { + var path = $"{_basePath}/files"; + return await DispatchRequest(path, UnityWebRequest.kHttpVerbGET); + } + + /// + /// Upload a file that contains document(s) to be used across various endpoints/features. + /// Currently, the size of all the files uploaded by one organization can be up to 1 GB. + /// Please contact us if you need to increase the storage limit. + /// + /// See + /// See + public async Task CreateFile(CreateFileRequest request) + { + var path = $"{_basePath}/files"; + + var form = new List(); + form.AddFile(request.File, "file", "application/json"); + form.AddValue(request.Purpose, "purpose"); + + return await DispatchRequest(path, form); + } + + /// + /// Delete a file. + /// + /// The ID of the file to use for this request + /// See + public async Task DeleteFile(string id) + { + var path = $"{_basePath}/files/{id}"; + return await DispatchRequest(path, UnityWebRequest.kHttpVerbDELETE); + } + + /// + /// Returns information about a specific file. + /// + /// The ID of the file to use for this request + /// See + public async Task RetrieveFile(string id) + { + var path = $"{_basePath}/files/{id}"; + return await DispatchRequest(path, UnityWebRequest.kHttpVerbGET); + } + + /// + /// Returns the contents of the specified file + /// + /// The ID of the file to use for this request + /// See + public async Task DownloadFile(string id) + { + var path = $"{_basePath}/files/{id}/content"; + return await DispatchRequest(path, UnityWebRequest.kHttpVerbGET); + } + + /// + /// Manage fine-tuning jobs to tailor a model to your specific training data. + /// Related guide: Fine-tune models + /// + /// See + /// See + public async Task CreateFineTune(CreateFineTuneRequest request) + { + var path = $"{_basePath}/fine-tunes"; + var payload = CreatePayload(request); + return await DispatchRequest(path, UnityWebRequest.kHttpVerbPOST, payload); + } + + /// + /// List your organization's fine-tuning jobs + /// + /// See + public async Task ListFineTunes() + { + var path = $"{_basePath}/fine-tunes"; + return await DispatchRequest(path, UnityWebRequest.kHttpVerbGET); + } + + /// + /// Gets info about the fine-tune job. + /// + /// The ID of the fine-tune job + /// See + public async Task RetrieveFineTune(string id) + { + var path = $"{_basePath}/fine-tunes/{id}"; + return await DispatchRequest(path, UnityWebRequest.kHttpVerbGET); + } + + /// + /// Immediately cancel a fine-tune job. + /// + /// The ID of the fine-tune job to cancel + /// See + public async Task CancelFineTune(string id) + { + var path = $"{_basePath}/fine-tunes/{id}/cancel"; + return await DispatchRequest(path, UnityWebRequest.kHttpVerbPOST); + } + + /// + /// Get fine-grained status updates for a fine-tune job. + /// + /// The ID of the fine-tune job to get events for. + /// Whether to stream events for the fine-tune job. + /// If set to true, events will be sent as data-only server-sent events as they become available. + /// The stream will terminate with a data: [DONE] message when the job is finished (succeeded, cancelled, or failed). + /// If set to false, only events generated so far will be returned. + /// See + public async Task ListFineTuneEvents(string id, bool stream = false) + { + var path = $"{_basePath}/fine-tunes/{id}/events?stream={stream}"; + return await DispatchRequest(path, UnityWebRequest.kHttpVerbGET); + } + + /// + /// Delete a fine-tuned model. You must have the Owner role in your organization. + /// + /// The model to delete + /// See + public async Task DeleteFineTunedModel(string model) + { + var path = $"{_basePath}/models/{model}"; + return await DispatchRequest(path, UnityWebRequest.kHttpVerbDELETE); + } + + /// + /// Classifies if text violates OpenAI's Content Policy + /// + /// See + /// See + public async Task CreateModeration(CreateModerationRequest request) + { + var path = $"{_basePath}/moderations"; + var payload = CreatePayload(request); + return await DispatchRequest(path, UnityWebRequest.kHttpVerbPOST, payload); + } + } +} diff --git a/Editor/OpenAI-Unity/OpenAIApi.cs.meta b/Editor/OpenAI-Unity/OpenAIApi.cs.meta new file mode 100644 index 0000000..836ebd9 --- /dev/null +++ b/Editor/OpenAI-Unity/OpenAIApi.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5fff9490e0c13184fa95c338c139d45d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/OpenAI-Unity/Utils.meta b/Editor/OpenAI-Unity/Utils.meta new file mode 100644 index 0000000..d47ab24 --- /dev/null +++ b/Editor/OpenAI-Unity/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8e81eb4397231344f80ac5fc542979c1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/OpenAI-Unity/Utils/CustomNamingStrategy.cs b/Editor/OpenAI-Unity/Utils/CustomNamingStrategy.cs new file mode 100644 index 0000000..482db78 --- /dev/null +++ b/Editor/OpenAI-Unity/Utils/CustomNamingStrategy.cs @@ -0,0 +1,14 @@ +using System.Text.RegularExpressions; +using Newtonsoft.Json.Serialization; + +namespace RedGame.OpenAI +{ + public class CustomNamingStrategy : NamingStrategy + { + protected override string ResolvePropertyName(string name) + { + var result = Regex.Replace(name, "([A-Z])", m => (m.Index > 0 ? "_" : "") + m.Value[0].ToString().ToLowerInvariant()); + return result; + } + } +} diff --git a/Editor/OpenAI-Unity/Utils/CustomNamingStrategy.cs.meta b/Editor/OpenAI-Unity/Utils/CustomNamingStrategy.cs.meta new file mode 100644 index 0000000..00e9b9d --- /dev/null +++ b/Editor/OpenAI-Unity/Utils/CustomNamingStrategy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 25db7a33aa10bdf4a85f66fb47f74b89 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/OpenAI-Unity/Utils/ExtensionMethods.cs b/Editor/OpenAI-Unity/Utils/ExtensionMethods.cs new file mode 100644 index 0000000..9425eb1 --- /dev/null +++ b/Editor/OpenAI-Unity/Utils/ExtensionMethods.cs @@ -0,0 +1,75 @@ +using System.IO; +using UnityEngine.Networking; +using System.Collections.Generic; + +namespace RedGame.OpenAI +{ + public static class ExtensionMethods + { + /// + /// Read a file and add it to this form. + /// + /// List of multipart form sections. + /// Path of the file to read. + /// Name of the form field. + /// Content type of the file. + public static void AddFile(this List form, string path, string name, string contentType) + { + if (path != null) + { + var data = File.ReadAllBytes(path); + var fileName = Path.GetFileName(path); + form.Add(new MultipartFormFileSection(name, data, fileName, contentType)); + } + } + + /// + /// Read a file and add it to this form. + /// + /// List of multipart form sections. + /// Byte array data of the file to attach. + /// Name of the form field. + /// Content type of the file. + public static void AddData(this List form, FileData data, string name, string contentType) + { + if (data.Data != null) + { + var fileName = Path.GetFileName(data.Name); + form.Add(new MultipartFormFileSection(name, data.Data, fileName, contentType)); + } + } + + /// + /// Add a primitive value to the form. + /// + /// List of multipart form sections. + /// Value of the form field. + /// Name of the form field. + public static void AddValue(this List form, object value, string name) + { + if (value != null) + { + form.Add(new MultipartFormDataSection(name, value.ToString())); + } + } + + /// + /// Set headers of the HTTP request with user credentials. + /// + /// this UnityWebRequest + /// Configuration file that contains user credentials. + /// The value of the Accept header for an HTTP request. + public static void SetHeaders(this UnityWebRequest request, Configuration configuration, string type = null) + { + if (configuration.Auth.Organization != null) + { + request.SetRequestHeader("OpenAI-Organization", configuration.Auth.Organization); + } + if (type != null) + { + request.SetRequestHeader("Content-Type", type); + } + request.SetRequestHeader("Authorization", "Bearer " + configuration.Auth.ApiKey); + } + } +} diff --git a/Editor/OpenAI-Unity/Utils/ExtensionMethods.cs.meta b/Editor/OpenAI-Unity/Utils/ExtensionMethods.cs.meta new file mode 100644 index 0000000..b38d3e2 --- /dev/null +++ b/Editor/OpenAI-Unity/Utils/ExtensionMethods.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a08c27193387de640b389d816ef35cfc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Utils.meta b/Editor/Utils.meta new file mode 100644 index 0000000..2181da4 --- /dev/null +++ b/Editor/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: baec6a9eb314448739ff9ccb674a61ad +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Utils/SimpleEditorTableView.cs b/Editor/Utils/SimpleEditorTableView.cs new file mode 100644 index 0000000..df2d676 --- /dev/null +++ b/Editor/Utils/SimpleEditorTableView.cs @@ -0,0 +1,181 @@ +// ================================================== +// Copyright (c) Red Games. All rights reserved. +// @Author: Yao Chunhui +// @Description: 方便创建TableView GUI +// ================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEngine; + +namespace RedGame.Framework.EditorTools +{ + public class SimpleEditorTableView + { + private MultiColumnHeaderState _multiColumnHeaderState; + private MultiColumnHeader _multiColumnHeader; + private MultiColumnHeaderState.Column[] _columns; + private readonly Color _lighterColor = Color.white * 0.3f; + private readonly Color _darkerColor = Color.white * 0.1f; + + private Vector2 _scrollPosition; + private bool _columnResized; + private bool _sortingDirty; + + public delegate void DrawItem(Rect rect, TData item); + + public class ColumnDef + { + internal MultiColumnHeaderState.Column column; + internal DrawItem onDraw; + internal Comparison onSort; + + public ColumnDef SetMaxWidth(float maxWidth) + { + column.maxWidth = maxWidth; + return this; + } + + public ColumnDef SetTooltip(string tooltip) + { + column.headerContent.tooltip = tooltip; + return this; + } + + public ColumnDef SetAutoResize(bool autoResize) + { + column.autoResize = autoResize; + return this; + } + + public ColumnDef SetAllowToggleVisibility(bool allow) + { + column.allowToggleVisibility = allow; + return this; + } + + public ColumnDef SetSorting(Comparison onSort) + { + this.onSort = onSort; + column.canSort = true; + return this; + } + } + + private readonly List _columnDefs = new List(); + + public void ClearColumns() + { + _columnDefs.Clear(); + _columnResized = true; + } + + public ColumnDef AddColumn(string title, int minWidth, DrawItem onDrawItem) + { + ColumnDef columnDef = new ColumnDef() + { + column = new MultiColumnHeaderState.Column() + { + allowToggleVisibility = false, + autoResize = true, + minWidth = minWidth, + canSort = false, + sortingArrowAlignment = TextAlignment.Right, + headerContent = new GUIContent(title), + headerTextAlignment = TextAlignment.Left, + }, + onDraw = onDrawItem + }; + + _columnDefs.Add(columnDef); + _columnResized = true; + return columnDef; + } + + private void ReBuild() + { + _columns = _columnDefs.Select((def) => def.column).ToArray(); + _multiColumnHeaderState = new MultiColumnHeaderState(_columns); + _multiColumnHeader = new MultiColumnHeader(_multiColumnHeaderState); + _multiColumnHeader.visibleColumnsChanged += (multiColumnHeader) => multiColumnHeader.ResizeToFit(); + _multiColumnHeader.sortingChanged += (multiColumnHeader) => _sortingDirty = true; + _multiColumnHeader.ResizeToFit(); + _columnResized = false; + } + + public void DrawTableGUI(TData[] data, float maxHeight = float.MaxValue, float rowHeight = -1) + { + if (_multiColumnHeader == null || _columnResized) + ReBuild(); + + float rowWidth = _multiColumnHeaderState.widthOfAllVisibleColumns; + if (rowHeight < 0) + rowHeight = EditorGUIUtility.singleLineHeight; + + Rect headerRect = GUILayoutUtility.GetRect(rowWidth, rowHeight); + _multiColumnHeader!.OnGUI(headerRect, xScroll: 0.0f); + + float sumWidth = rowWidth; + float sumHeight = rowHeight * data.Length + GUI.skin.horizontalScrollbar.fixedHeight; + + UpdateSorting(data); + + Rect scrollViewPos = GUILayoutUtility.GetRect(0, sumWidth, 0, maxHeight); + Rect viewRect = new Rect(0, 0, sumWidth, sumHeight); + + _scrollPosition = GUI.BeginScrollView( + position: scrollViewPos, + scrollPosition: _scrollPosition, + viewRect: viewRect, + alwaysShowHorizontal: false, + alwaysShowVertical: false + ); + + EditorGUILayout.BeginVertical(); + + for (int row = 0; row < data.Length; row++) + { + Rect rowRect = new Rect(0, rowHeight * row, rowWidth, rowHeight); + + EditorGUI.DrawRect(rect: rowRect, color: row % 2 == 0 ? _darkerColor : _lighterColor); + + for (int col = 0; col < _columns.Length; col++) + { + if (_multiColumnHeader.IsColumnVisible(col)) + { + int visibleColumnIndex = _multiColumnHeader.GetVisibleColumnIndex(col); + Rect cellRect = _multiColumnHeader.GetCellRect(visibleColumnIndex, rowRect); + _columnDefs[col].onDraw(cellRect, data[row]); + } + } + } + + EditorGUILayout.EndVertical(); + GUI.EndScrollView(handleScrollWheel: true); + } + + private void UpdateSorting(TData[] data) + { + if (_sortingDirty) + { + int sortIndex = _multiColumnHeader.sortedColumnIndex; + if (sortIndex >= 0) + { + var sortCompare = _columnDefs[sortIndex].onSort; + bool ascending = _multiColumnHeader.IsSortedAscending(sortIndex); + + Array.Sort(data, ((a, b) => + { + int r = sortCompare(a, b); + return ascending ? r : -r; + })); + } + + _sortingDirty = false; + } + } + } +} \ No newline at end of file diff --git a/Editor/Utils/SimpleEditorTableView.cs.meta b/Editor/Utils/SimpleEditorTableView.cs.meta new file mode 100644 index 0000000..4df2e30 --- /dev/null +++ b/Editor/Utils/SimpleEditorTableView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 153b3a8d1bec849b0b43f88b7554284e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..62154a2 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Yao Chunhui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE.md.meta b/LICENSE.md.meta new file mode 100644 index 0000000..19ca295 --- /dev/null +++ b/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8d53b78d428f048678c0cb2e7ab21fbf +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc37021 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +## Unity GPT Localization +A Unity tool combining Localization and ChatGPT for easy multilingual translation using OpenAI API. Control translation quality with Localization comments. diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..2e7a372 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3c4072be5d32140089cd9ae48301708b +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..ddfc850 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "com.redgame.gpt-localization", + "version": "0.1.0", + "displayName": "GPT Localization", + "description": "A Unity tool combining Localization and ChatGPT for easy multilingual translation using OpenAI API. Control translation quality with Localization comments.", + "unity": "2020.3", + "unityRelease": "0f1", + "dependencies": { + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.localization": "1.4.5" + }, + "keywords": [ + "openai", + "chatgpt", + "localization", + "ai", + "translate" + ], + "author": { + "name": "Yao Chunhui", + "url": "https://github.com/redclock" + } +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..f7a5403 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 518f87b17f93043589a7fa6c7747da26 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 9531d02cf084c3a99eaf181a588736b25bd61c3f Mon Sep 17 00:00:00 2001 From: yaochunhui Date: Mon, 4 Mar 2024 21:22:43 +0800 Subject: [PATCH 2/9] README --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index fc37021..7a0623a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ ## Unity GPT Localization A Unity tool combining Localization and ChatGPT for easy multilingual translation using OpenAI API. Control translation quality with Localization comments. + +### Features + +### Installation + +### Usage + +### Example + +### License + +### Contributing + +### Credits From 91a05c8e55deb37bcfbf1f46d19f995da49cc2bf Mon Sep 17 00:00:00 2001 From: yaochunhui Date: Sun, 10 Mar 2024 22:02:55 +0800 Subject: [PATCH 3/9] improve samples --- Editor/GptLocalization.asmdef | 19 +++ Editor/GptLocalization.asmdef.meta | 7 + .../LocalizeGptWindow.GUI.cs | 32 +++- .../LocalizeGptWindow.Progress.cs | 9 +- .../LocalizeGptWindow.Translate.cs | 28 ++-- Editor/LocalizeGptWindow/LocalizeGptWindow.cs | 2 + Intro.gif | Bin 0 -> 1284651 bytes Intro.gif.meta | 153 ++++++++++++++++++ README.md | 11 +- README_CN.md | 25 +++ README_CN.md.meta | 3 + 11 files changed, 272 insertions(+), 17 deletions(-) create mode 100644 Editor/GptLocalization.asmdef create mode 100644 Editor/GptLocalization.asmdef.meta create mode 100644 Intro.gif create mode 100644 Intro.gif.meta create mode 100644 README_CN.md create mode 100644 README_CN.md.meta diff --git a/Editor/GptLocalization.asmdef b/Editor/GptLocalization.asmdef new file mode 100644 index 0000000..6408e0b --- /dev/null +++ b/Editor/GptLocalization.asmdef @@ -0,0 +1,19 @@ +{ + "name": "GptLocalization", + "rootNamespace": "", + "references": [ + "Unity.Localization", + "Unity.Localization.Editor" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Editor/GptLocalization.asmdef.meta b/Editor/GptLocalization.asmdef.meta new file mode 100644 index 0000000..f1dbe3e --- /dev/null +++ b/Editor/GptLocalization.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 67175f829d999413589faec43acbcd56 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs b/Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs index 2b5efac..ea106a2 100644 --- a/Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.GUI.cs @@ -9,7 +9,7 @@ public partial class LocalizeGptWindow { enum OutputType { - None, Prompt, Error, + None, Prompt, Error, Info } private SimpleEditorTableView _tableView; @@ -155,14 +155,25 @@ private void OnGptModelGUI() if (_gptFoldout) { EditorGUI.indentLevel++; - EditorGUI.BeginChangeCheck(); EditorGUILayout.BeginVertical("Box"); _baseUrl = EditorGUILayout.TextField("Base URL", _baseUrl); if (string.IsNullOrEmpty(_baseUrl)) _baseUrl = DEFAULT_BASE_URL; _apiKey = EditorGUILayout.PasswordField("API Key", _apiKey); - + if (!IsValidOpenAIKey(_apiKey)) + { + GUIStyle style = new GUIStyle(EditorStyles.helpBox) + { + richText = true + }; + EditorGUILayout.BeginHorizontal(); + EditorGUI.indentLevel--; + GUILayout.Space(EditorGUIUtility.labelWidth); + EditorGUILayout.LabelField("Please enter a valid OpenAI API Key.", style); + EditorGUI.indentLevel++; + EditorGUILayout.EndHorizontal(); + } int index = Array.IndexOf(_validModels, _model); index = EditorGUILayout.Popup("Model", index, _validModels); if (index >= 0 && index < _validModels.Length) @@ -224,7 +235,14 @@ private void OnOutputGUI() if (!string.IsNullOrEmpty(_outputStr) && _outputType != OutputType.None) { EditorGUILayout.Space(); + EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(_outputType.ToString(), EditorStyles.boldLabel); + if (GUILayout.Button("Clear", GUILayout.Width(100))) + { + _outputStr = string.Empty; + _outputType = OutputType.None; + } + EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); if (_outputType == OutputType.Error) { @@ -286,6 +304,12 @@ private void OnEntryListGUI() _tableView ??= CreateTable(); _tableView.DrawTableGUI(_recs, (_recs.Length + 2) * EditorGUIUtility.singleLineHeight); } - + + + private bool IsValidOpenAIKey(string key) + { + var apiKeyPattern = @"^sk-\w{32,}$"; + return System.Text.RegularExpressions.Regex.IsMatch(key, apiKeyPattern); + } } } \ No newline at end of file diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs b/Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs index ed53d77..8631dd2 100644 --- a/Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.Progress.cs @@ -59,7 +59,11 @@ private void UpdateTaskProgress() if (_pendingRecs != null && _currentPendingRecIndex < _pendingRecs.Length) { var rec = _pendingRecs[_currentPendingRecIndex]; - OnTaskCompleted(_task, rec); + if (!OnTaskCompleted(_task, rec)) + { + CancelTask(); + return; + } _taskDuration = Time.realtimeSinceStartup - _taskStartTime; if (_currentPendingRecIndex < _pendingRecs.Length - 1) @@ -69,9 +73,12 @@ private void UpdateTaskProgress() _taskStartTime = Time.realtimeSinceStartup; } else { + Output("Translation completed:\n" + + string.Join("\n", _pendingRecs.Select((rec) => rec.key)), OutputType.Info); _task = null; _pendingRecs = null; RefreshRecords(); + } } } diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs b/Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs index edc33fc..a6b1295 100644 --- a/Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.Translate.cs @@ -37,7 +37,6 @@ private TranslateRec GeneratePrompt(StringTableCollection collection, string key key = key, selected = true }; - sb.Append("Translate the following texts:\n"); var tables = collection.StringTables; rec.srcTables = new List(); rec.dstTables = new List(); @@ -50,12 +49,15 @@ private TranslateRec GeneratePrompt(StringTableCollection collection, string key rec.dstTables.Add(table); continue; } - - string langName = table.LocaleIdentifier.CultureInfo.EnglishName; + rec.srcTables.Add(table); - if (rec.srcTables.Count <= 2) + if (rec.srcTables.Count <= 1) { - sb.Append("## From " + langName + ":\n" + entry.Value + "\n"); + sb.Append("Translate from "); + sb.Append(table.LocaleIdentifier.CultureInfo.EnglishName); + sb.Append(":\n"); + sb.Append(entry.Value); + sb.Append("\n"); } } @@ -128,7 +130,7 @@ private void AskGpt(TranslateRec rec) }); } - private void OnTaskCompleted(Task task, TranslateRec rec) + private bool OnTaskCompleted(Task task, TranslateRec rec) { if (task.IsFaulted) { @@ -136,16 +138,18 @@ private void OnTaskCompleted(Task task, TranslateR { Output(task.Exception.Message, OutputType.Error); Debug.LogError(task.Exception); + } else + { + Output("Task Unknown Error", OutputType.Error); } - return; + return false; } var response = task.Result; if (response.Error != null) { Output(response.Error.Message, OutputType.Error); - Debug.LogError(response.Error.Message); - return; + return false; } string answer = response.Choices[0].Message.Content; @@ -159,10 +163,12 @@ private void OnTaskCompleted(Task task, TranslateR } else { Output("Failed to parse response as json object:\n" + answer, OutputType.Error); - Debug.LogError("Failed to parse response as json object"); + return false; } - Output("", OutputType.None); + //Output(answer, OutputType.Info); + + return true; } private string TryParseText(JToken json) diff --git a/Editor/LocalizeGptWindow/LocalizeGptWindow.cs b/Editor/LocalizeGptWindow/LocalizeGptWindow.cs index 4f9aa58..0c684f5 100644 --- a/Editor/LocalizeGptWindow/LocalizeGptWindow.cs +++ b/Editor/LocalizeGptWindow/LocalizeGptWindow.cs @@ -58,6 +58,8 @@ private void Output(string str, OutputType type) { _outputStr = str; _outputType = type; + if (type == OutputType.Error) + Debug.LogError(str); } private void UpdateFrame() diff --git a/Intro.gif b/Intro.gif new file mode 100644 index 0000000000000000000000000000000000000000..d8a7b4ca16b8e640d323266ce159d318a7f4b76e GIT binary patch literal 1284651 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

DOxJ<>IR$@RdL0X} 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�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