├── Preview.gif ├── README.md.meta ├── package.json.meta ├── Editor.meta ├── Editor ├── ChatGPT.meta └── ChatGPT │ ├── ChatGPTForUnity.Editor.asmdef.meta │ ├── ChatGPT.cs.meta │ ├── ChatGPTWindow.cs.meta │ ├── ChatGPTForUnity.Editor.asmdef │ ├── ChatGPTWindow.cs │ └── ChatGPT.cs ├── README.md ├── package.json └── Preview.gif.meta /Preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunsvip/ChatGPTForUnity/HEAD/Preview.gif -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a8cb8f77f4f0ee2459b05ca49e0bee76 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 324d3a94d85fe294b8670f2620598f57 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bb910b327c81c344f8c9d227e09d59d0 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/ChatGPT.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: df527f59b34f11b47a071bf2200c88f7 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/ChatGPT/ChatGPTForUnity.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 299193176c4b787418edf2cd4363a0ce 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatGPTForUnity 2 | Blog:https://blog.csdn.net/final5788 3 | ChatGPT for unity 4 | 1. Open Unity PackageManager. 5 | 2. Add package from git URL: https://github.com/sunsvip/ChatGPTForUnity.git 6 | ![image](Preview.gif) 7 | 8 | -------------------------------------------------------------------------------- /Editor/ChatGPT/ChatGPT.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5bfc3d076f0cef741a906c118d9d3dc7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ChatGPT/ChatGPTWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 241d50f77f7a30645840b102a3e1b638 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ChatGPT/ChatGPTForUnity.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ChatGPTForUnity.Editor", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:6546d7765b4165b40850b3667f981c26" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": true, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [], 17 | "noEngineReferences": false 18 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.sunsvip.chatgpt", 3 | "displayName": "ChatGPT for Unity", 4 | "version": "1.0.1", 5 | "description": "A ChatGPT plugin for unity.", 6 | "unity": "2021.3", 7 | "dependencies": { 8 | "com.unity.nuget.newtonsoft-json": "3.0.2", 9 | "com.unity.textmeshpro": "3.0.6" 10 | }, 11 | "keywords": [ 12 | "Editor", 13 | "OpenAI", 14 | "ChatGPT" 15 | ], 16 | "author": { 17 | "name": "Eddie", 18 | "email": "sunmmvip@gmail.com" 19 | }, 20 | "license": "MIT", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/sunsvip/ChatGPTForUnity.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/sunsvip/ChatGPTForUnity/issues" 27 | } 28 | } -------------------------------------------------------------------------------- /Preview.gif.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3d6da022951e02d42bd76b64b4bd87f5 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 12 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 1 38 | mipBias: 0 39 | wrapU: 0 40 | wrapV: 0 41 | wrapW: 0 42 | nPOTScale: 1 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 0 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 0 55 | spriteTessellationDetail: -1 56 | textureType: 0 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 0 66 | cookieLightType: 0 67 | platformSettings: 68 | - serializedVersion: 3 69 | buildTarget: DefaultTexturePlatform 70 | maxTextureSize: 2048 71 | resizeAlgorithm: 0 72 | textureFormat: -1 73 | textureCompression: 1 74 | compressionQuality: 50 75 | crunchedCompression: 0 76 | allowsAlphaSplitting: 0 77 | overridden: 0 78 | androidETC2FallbackOverride: 0 79 | forceMaximumCompressionQuality_BC6H_BC7: 0 80 | - serializedVersion: 3 81 | buildTarget: Standalone 82 | maxTextureSize: 2048 83 | resizeAlgorithm: 0 84 | textureFormat: -1 85 | textureCompression: 1 86 | compressionQuality: 50 87 | crunchedCompression: 0 88 | allowsAlphaSplitting: 0 89 | overridden: 0 90 | androidETC2FallbackOverride: 0 91 | forceMaximumCompressionQuality_BC6H_BC7: 0 92 | - serializedVersion: 3 93 | buildTarget: Server 94 | maxTextureSize: 2048 95 | resizeAlgorithm: 0 96 | textureFormat: -1 97 | textureCompression: 1 98 | compressionQuality: 50 99 | crunchedCompression: 0 100 | allowsAlphaSplitting: 0 101 | overridden: 0 102 | androidETC2FallbackOverride: 0 103 | forceMaximumCompressionQuality_BC6H_BC7: 0 104 | - serializedVersion: 3 105 | buildTarget: Android 106 | maxTextureSize: 2048 107 | resizeAlgorithm: 0 108 | textureFormat: -1 109 | textureCompression: 1 110 | compressionQuality: 50 111 | crunchedCompression: 0 112 | allowsAlphaSplitting: 0 113 | overridden: 0 114 | androidETC2FallbackOverride: 0 115 | forceMaximumCompressionQuality_BC6H_BC7: 0 116 | spriteSheet: 117 | serializedVersion: 2 118 | sprites: [] 119 | outline: [] 120 | physicsShape: [] 121 | bones: [] 122 | spriteID: 123 | internalID: 0 124 | vertices: [] 125 | indices: 126 | edges: [] 127 | weights: [] 128 | secondaryTextures: [] 129 | nameFileIdTable: {} 130 | spritePackingTag: 131 | pSDRemoveMatte: 0 132 | pSDShowRemoveMatteOption: 0 133 | userData: 134 | assetBundleName: 135 | assetBundleVariant: 136 | -------------------------------------------------------------------------------- /Editor/ChatGPT/ChatGPTWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace ChatGPT.Editor 7 | { 8 | public class ChatGPTWindow : EditorWindow 9 | { 10 | Vector2 scrollPos = Vector2.zero; 11 | 12 | ChatGPT ai; 13 | private bool settingFoldout = false; 14 | string message; 15 | const string aiRoleName = "AI"; 16 | private float chatBoxWidthRatio = 0.85f; 17 | private float iconSizeRatio = 0.6f; 18 | private float iconMaxSize = 100f; 19 | private float chatBoxPadding = 20; 20 | private float chatBoxEdgePadding = 10; 21 | 22 | GUIStyle myChatStyle; 23 | GUIStyle aiChatStyle; 24 | 25 | GUIStyle aiIconStyle; 26 | GUIStyle myIconStyle; 27 | GUIStyle txtAreaStyle; 28 | 29 | GUIContent chatContent; 30 | 31 | bool isEditorInitialized = false; 32 | private float scrollViewHeight; 33 | 34 | string myApiKey; 35 | 36 | [MenuItem("ChatGPT/ChatGPT Window")] 37 | static void ChatGPTMenu() 38 | { 39 | var win = GetWindow("ChatGPT"); 40 | win.Show(); 41 | } 42 | private void OnEnable() 43 | { 44 | EditorApplication.update += OnEditorUpdate; 45 | myApiKey = EditorPrefs.GetString("ChatGPT.Settings.APIKey", null); 46 | ai = new ChatGPT(myApiKey); 47 | ai.ChatGPTRandomness = EditorPrefs.GetFloat("ChatGPT.Settings.Temperature", 0f); 48 | chatContent = new GUIContent(); 49 | ai.RestoreChatHistory(); 50 | } 51 | 52 | private void OnEditorUpdate() 53 | { 54 | if (EditorApplication.isCompiling || EditorApplication.isUpdating) 55 | { 56 | return; 57 | } 58 | try 59 | { 60 | InitGUIStyles(); 61 | isEditorInitialized = true; 62 | EditorApplication.update -= OnEditorUpdate; 63 | } 64 | catch (Exception) 65 | { 66 | 67 | } 68 | } 69 | 70 | private void InitGUIStyles() 71 | { 72 | 73 | aiChatStyle = new GUIStyle( 74 | #if UNITY_2021_1_OR_NEWER 75 | EditorStyles.selectionRect 76 | #else 77 | EditorStyles.textArea 78 | #endif 79 | ); 80 | aiChatStyle.wordWrap = true; 81 | aiChatStyle.normal.textColor = Color.white; 82 | aiChatStyle.fontSize = 18; 83 | aiChatStyle.alignment = TextAnchor.MiddleLeft; 84 | 85 | myChatStyle = new GUIStyle(EditorStyles.helpBox); 86 | myChatStyle.wordWrap = true; 87 | myChatStyle.normal.textColor = Color.white; 88 | myChatStyle.fontSize = 18; 89 | myChatStyle.alignment = TextAnchor.MiddleLeft; 90 | 91 | 92 | txtAreaStyle = new GUIStyle(EditorStyles.textArea); 93 | txtAreaStyle.fontSize = 18; 94 | 95 | aiIconStyle = new GUIStyle(); 96 | aiIconStyle.wordWrap = true; 97 | aiIconStyle.alignment = TextAnchor.MiddleCenter; 98 | aiIconStyle.fontSize = 18; 99 | aiIconStyle.fontStyle = FontStyle.Bold; 100 | aiIconStyle.normal.textColor = Color.black; 101 | aiIconStyle.normal.background = EditorGUIUtility.FindTexture("sv_icon_dot5_pix16_gizmo"); 102 | 103 | myIconStyle = new GUIStyle(aiIconStyle); 104 | myIconStyle.normal.background = EditorGUIUtility.FindTexture("sv_icon_dot2_pix16_gizmo"); 105 | } 106 | 107 | private void OnDisable() 108 | { 109 | ai.SaveChatHistory(); 110 | SaveSettings(); 111 | } 112 | 113 | 114 | private void OnGUI() 115 | { 116 | if (!isEditorInitialized) return; 117 | EditorGUILayout.BeginVertical(); 118 | { 119 | scrollPos = EditorGUILayout.BeginScrollView(scrollPos); 120 | { 121 | scrollViewHeight = 0; 122 | for (int i = 0; i < ai.MessageHistory.Count; i++) 123 | { 124 | var msg = ai.MessageHistory[i]; 125 | var msgRect = EditorGUILayout.BeginVertical(); 126 | { 127 | EditorGUILayout.BeginHorizontal(); 128 | { 129 | bool isMyMsg = ai.IsSelfMessage(msg); 130 | var labelStyle = isMyMsg ? myChatStyle : aiChatStyle; 131 | chatContent.text = msg.content; 132 | float chatBoxWidth = this.position.width * chatBoxWidthRatio; 133 | float iconSize = Mathf.Min(iconMaxSize, (this.position.width - chatBoxWidth) * iconSizeRatio); 134 | float chatBoxHeight = Mathf.Max(iconSize, chatBoxEdgePadding + labelStyle.CalcHeight(chatContent, chatBoxWidth - chatBoxEdgePadding)); 135 | ChatGPTCodeBlock[] codeBlocks = null; 136 | if (isMyMsg) { GUILayout.FlexibleSpace(); } 137 | else 138 | { 139 | codeBlocks = ai.GetCodeBlocksByIdx(i); 140 | if (codeBlocks != null) 141 | { 142 | chatBoxWidth -= 50; 143 | } 144 | EditorGUILayout.LabelField(aiRoleName, aiIconStyle, GUILayout.Width(iconSize), GUILayout.Height(iconSize)); 145 | } 146 | 147 | EditorGUILayout.SelectableLabel(msg.content, labelStyle, GUILayout.Width(chatBoxWidth), GUILayout.Height(chatBoxHeight)); 148 | if (!isMyMsg) 149 | { 150 | if (codeBlocks != null) 151 | { 152 | for (int blockIdx = 0; blockIdx < codeBlocks.Length; blockIdx++) 153 | { 154 | var cBlock = codeBlocks[blockIdx]; 155 | EditorGUILayout.BeginVertical("box"); 156 | { 157 | if (GUILayout.Button($"Save {cBlock.FileExtension} File({blockIdx})")) 158 | { 159 | var fileName = EditorUtility.SaveFilePanel("Save File", EditorPrefs.GetString("LAST_SELECT_PATH"), null, cBlock.FileExtension); 160 | if (!string.IsNullOrWhiteSpace(fileName)) 161 | { 162 | try 163 | { 164 | System.IO.File.WriteAllText(fileName, cBlock.Content, System.Text.Encoding.UTF8); 165 | EditorPrefs.SetString("LAST_SELECT_PATH", Path.GetFullPath(fileName)); 166 | AssetDatabase.Refresh(); 167 | } 168 | catch (Exception e) 169 | { 170 | Debug.LogError($"Save {cBlock.FileExtension} file failed:{e.Message}"); 171 | } 172 | } 173 | } 174 | EditorGUILayout.EndVertical(); 175 | } 176 | } 177 | } 178 | GUILayout.FlexibleSpace(); 179 | } 180 | else 181 | { 182 | EditorGUILayout.LabelField(msg.role, myIconStyle, GUILayout.Width(iconSize), GUILayout.Height(iconSize)); 183 | } 184 | EditorGUILayout.EndHorizontal(); 185 | } 186 | EditorGUILayout.EndVertical(); 187 | } 188 | EditorGUILayout.Space(chatBoxPadding); 189 | scrollViewHeight += msgRect.height; 190 | } 191 | EditorGUILayout.EndScrollView(); 192 | } 193 | 194 | if (ai.IsRequesting) 195 | { 196 | var barWidth = position.width * 0.8f; 197 | var pBarRect = new Rect((position.width - barWidth) * 0.5f, (position.height - 30f) * 0.5f, barWidth, 30f); 198 | EditorGUI.ProgressBar(pBarRect, ai.RequestProgress, $"Progress:{ai.RequestProgress:P2}"); 199 | } 200 | GUILayout.FlexibleSpace(); 201 | if (string.IsNullOrWhiteSpace(myApiKey)) EditorGUILayout.HelpBox("Please fill in the API Key in ChatGPT Settings first.", MessageType.Error); 202 | if (settingFoldout = EditorGUILayout.Foldout(settingFoldout, "ChatGPT Settings:")) 203 | { 204 | EditorGUILayout.BeginVertical("box"); 205 | { 206 | EditorGUILayout.BeginHorizontal(); 207 | { 208 | #if UNITY_2021_1_OR_NEWER 209 | if (EditorGUILayout.LinkButton("Get API Key:", GUILayout.Width(170))) 210 | #else 211 | if (GUILayout.Button("Get API Key:", GUILayout.Width(170))) 212 | #endif 213 | { 214 | Application.OpenURL("https://platform.openai.com/account/api-keys"); 215 | } 216 | EditorGUI.BeginChangeCheck(); 217 | { 218 | myApiKey = EditorGUILayout.PasswordField(myApiKey); 219 | if (EditorGUI.EndChangeCheck()) 220 | { 221 | ai.SetAPIKey(myApiKey); 222 | } 223 | } 224 | EditorGUILayout.EndHorizontal(); 225 | } 226 | 227 | EditorGUILayout.BeginHorizontal(); 228 | { 229 | EditorGUILayout.LabelField("Randomness", GUILayout.Width(170)); 230 | ai.ChatGPTRandomness = EditorGUILayout.Slider(ai.ChatGPTRandomness, 0, 2); 231 | EditorGUILayout.EndHorizontal(); 232 | } 233 | EditorGUILayout.BeginHorizontal(); 234 | { 235 | EditorGUILayout.LabelField("WebRequest Timeout:", GUILayout.Width(170)); 236 | ai.RequestTimeout = EditorGUILayout.IntSlider(ai.RequestTimeout, 30, 120); 237 | EditorGUILayout.EndHorizontal(); 238 | } 239 | EditorGUILayout.EndVertical(); 240 | 241 | } 242 | 243 | } 244 | EditorGUILayout.BeginHorizontal(); 245 | { 246 | message = EditorGUILayout.TextArea(message, txtAreaStyle, GUILayout.MinHeight(80)); 247 | 248 | EditorGUI.BeginDisabledGroup(ai.IsRequesting); 249 | { 250 | if (GUILayout.Button("Send", GUILayout.MaxWidth(120), GUILayout.Height(80))) 251 | { 252 | if (!string.IsNullOrWhiteSpace(message)) 253 | { 254 | ai.Send(message, OnChatGPTMessage); 255 | } 256 | } 257 | if (GUILayout.Button("New Chat", GUILayout.MaxWidth(80), GUILayout.Height(80))) 258 | { 259 | ai.NewChat(); 260 | } 261 | EditorGUI.EndDisabledGroup(); 262 | } 263 | 264 | EditorGUILayout.EndHorizontal(); 265 | } 266 | EditorGUILayout.EndVertical(); 267 | } 268 | } 269 | 270 | private void OnChatGPTMessage(bool arg1, string arg2) 271 | { 272 | scrollPos.y = scrollViewHeight; 273 | if (arg1) 274 | { 275 | message = string.Empty; 276 | } 277 | Repaint(); 278 | } 279 | 280 | private void SaveSettings() 281 | { 282 | EditorPrefs.SetFloat("ChatGPT.Settings.Temperature", ai.ChatGPTRandomness); 283 | EditorPrefs.SetString("ChatGPT.Settings.APIKey", myApiKey); 284 | } 285 | } 286 | } 287 | 288 | -------------------------------------------------------------------------------- /Editor/ChatGPT/ChatGPT.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | using TMPro.EditorUtilities; 9 | using UnityEditor; 10 | using UnityEngine; 11 | using UnityEngine.Networking; 12 | 13 | namespace ChatGPT.Editor 14 | { 15 | public class ChatGPT 16 | { 17 | private static readonly Dictionary SupportCodeLanguages = new Dictionary 18 | { 19 | ["python"] = "py", 20 | ["csharp"] = "cs", 21 | ["json"] = "json", 22 | ["cpp"] = "cpp", 23 | ["c++"] = "cpp", 24 | ["java"] = "java", 25 | ["javascript"] = "js", 26 | ["html"] = "html", 27 | ["css"] = "css", 28 | ["xml"] = "xml", 29 | ["markdown"] = "md", 30 | ["typescript"] = "ts" 31 | }; 32 | const string ChatgptUrl = "https://api.openai.com/v1/chat/completions"; 33 | const string DefaultModel = "gpt-3.5-turbo"; 34 | const float DefaultTemperature = 0; 35 | const string DefaultUserId = "user"; 36 | string ApiKey = "your chatgpt api key"; 37 | string UserId; 38 | List messageHistory; 39 | 40 | Dictionary codeBlocksDic; 41 | public List MessageHistory => messageHistory; 42 | ChatGPTRequestData requestData; 43 | UnityWebRequest webRequest; 44 | 45 | int mRequestTimeout = 60; 46 | public int RequestTimeout { get => mRequestTimeout; set => mRequestTimeout = Mathf.Max(value, 30); } 47 | public float ChatGPTRandomness { get => requestData.temperature; set { requestData.temperature = Mathf.Clamp(value, 0, 2); } } 48 | public bool IsRequesting => webRequest != null && !webRequest.isDone; 49 | public float RequestProgress => IsRequesting ? (webRequest.uploadProgress + webRequest.downloadProgress) / 2f : 0f; 50 | public ChatGPT(string apiKey, string userId = DefaultUserId, string model = DefaultModel, float temperature = DefaultTemperature) 51 | { 52 | this.ApiKey = apiKey; 53 | this.UserId = string.IsNullOrWhiteSpace(userId) ? DefaultUserId : userId; 54 | messageHistory = new List(); 55 | requestData = new ChatGPTRequestData(model, temperature); 56 | codeBlocksDic = new Dictionary(); 57 | } 58 | public void SetAPIKey(string str) 59 | { 60 | this.ApiKey = str; 61 | } 62 | /// 63 | /// 接着上次的话题 64 | /// 65 | public void RestoreChatHistory() 66 | { 67 | var chatHistoryJson = EditorPrefs.GetString("ChatGPT.Settings.ChatHistory", string.Empty); 68 | var requestDataJson = EditorPrefs.GetString("ChatGPT.Settings.RequestData", string.Empty); 69 | if (!string.IsNullOrEmpty(requestDataJson)) 70 | { 71 | var jsonObj = JsonConvert.DeserializeObject(requestDataJson); 72 | if (jsonObj != null) 73 | { 74 | requestData.messages = jsonObj.messages; 75 | } 76 | } 77 | if (!string.IsNullOrEmpty(chatHistoryJson)) 78 | { 79 | var jsonObj = JsonConvert.DeserializeObject>(chatHistoryJson); 80 | if (jsonObj != null) 81 | { 82 | messageHistory = jsonObj; 83 | ParseAllMessageCodeBlocks(messageHistory); 84 | } 85 | } 86 | } 87 | 88 | private void ParseAllMessageCodeBlocks(List messageHistory, bool forceAll = false) 89 | { 90 | if (messageHistory == null || messageHistory.Count < 1) 91 | { 92 | return; 93 | } 94 | if (forceAll) 95 | { 96 | codeBlocksDic.Clear(); 97 | for (int i = 0; i < messageHistory.Count; i++) 98 | { 99 | var msg = messageHistory[i]; 100 | if (IsSelfMessage(msg)) continue; 101 | 102 | var codeBlocks = ParseCodeBlocks(msg.content); 103 | if (codeBlocks != null) 104 | { 105 | codeBlocksDic.Add(i, codeBlocks); 106 | } 107 | } 108 | } 109 | else 110 | { 111 | for (int i = 0; i < messageHistory.Count; i++) 112 | { 113 | var msg = messageHistory[i]; 114 | if (IsSelfMessage(msg)) continue; 115 | if (codeBlocksDic.ContainsKey(i)) continue; 116 | 117 | var codeBlocks = ParseCodeBlocks(msg.content); 118 | if (codeBlocks != null) 119 | { 120 | codeBlocksDic.Add(i, codeBlocks); 121 | } 122 | } 123 | } 124 | } 125 | private ChatGPTCodeBlock[] ParseCodeBlocks(string message) 126 | { 127 | if (string.IsNullOrWhiteSpace(message)) 128 | { 129 | return null; 130 | } 131 | 132 | Regex regex = new Regex(@"```(?[^\n]+)?\n(?.*?)\n```", RegexOptions.Singleline); 133 | MatchCollection matches = regex.Matches(message); 134 | List codeBlocks = new List(); 135 | foreach (Match match in matches) 136 | { 137 | string codeBlock = match.Groups["code"].Value; 138 | string codeTag = match.Groups["language"].Value.ToLower(); 139 | if (!SupportCodeLanguages.ContainsKey(codeTag)) 140 | { 141 | continue; 142 | } 143 | if (!string.IsNullOrWhiteSpace(codeBlock) && !string.IsNullOrWhiteSpace(codeTag)) 144 | { 145 | var cBlock = new ChatGPTCodeBlock() 146 | { 147 | Tag = codeTag, 148 | Content = codeBlock, 149 | FileExtension = SupportCodeLanguages[codeTag] 150 | }; 151 | codeBlocks.Add(cBlock); 152 | } 153 | } 154 | return codeBlocks.Count > 0 ? codeBlocks.ToArray() : null; 155 | } 156 | public void SaveChatHistory() 157 | { 158 | if (messageHistory != null && messageHistory.Count > 0) 159 | { 160 | var chatHistoryJson = JsonConvert.SerializeObject(messageHistory); 161 | EditorPrefs.SetString("ChatGPT.Settings.ChatHistory", chatHistoryJson); 162 | } 163 | if (requestData != null) 164 | { 165 | var requestDataJson = JsonConvert.SerializeObject(requestData); 166 | EditorPrefs.SetString("ChatGPT.Settings.RequestData", requestDataJson); 167 | } 168 | } 169 | public void Send(string message, Action onComplete = null, Action onProgressUpdate = null) 170 | { 171 | TMP_EditorCoroutine.StartCoroutine(Request(message, onComplete, onProgressUpdate)); 172 | } 173 | 174 | public async Task SendAsync(string message) 175 | { 176 | bool isCompleted = false; 177 | string result = string.Empty; 178 | Action onComplete = (success, str) => 179 | { 180 | isCompleted = true; 181 | if (success) result = str; 182 | }; 183 | 184 | TMP_EditorCoroutine.StartCoroutine(Request(message, onComplete, null)); 185 | while (!isCompleted) 186 | { 187 | await Task.Delay(10); 188 | } 189 | return result; 190 | } 191 | private IEnumerator Request(string input, Action onComplete, Action onProgressUpdate) 192 | { 193 | var msg = new ChatGPTMessage() 194 | { 195 | role = UserId, 196 | content = input, 197 | }; 198 | requestData.AppendChat(msg); 199 | messageHistory.Add(msg); 200 | 201 | using (webRequest = new UnityWebRequest(ChatgptUrl, "POST")) 202 | { 203 | var jsonDt = JsonConvert.SerializeObject(requestData); 204 | Debug.Log(jsonDt); 205 | byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonDt); 206 | webRequest.uploadHandler = new UploadHandlerRaw(bodyRaw); 207 | webRequest.downloadHandler = new DownloadHandlerBuffer(); 208 | webRequest.SetRequestHeader("Content-Type", "application/json"); 209 | webRequest.SetRequestHeader("Authorization", $"Bearer {this.ApiKey}"); 210 | webRequest.certificateHandler = new WebRequestCertNoValidate(); 211 | webRequest.timeout = RequestTimeout; 212 | var req = webRequest.SendWebRequest(); 213 | while (!webRequest.isDone) 214 | { 215 | onProgressUpdate?.Invoke((webRequest.downloadProgress + webRequest.uploadProgress) / 2f); 216 | yield return null; 217 | } 218 | 219 | if (webRequest.result != UnityWebRequest.Result.Success) 220 | { 221 | Debug.LogError($"---------ChatGPT请求失败:{webRequest.error}---------"); 222 | onComplete?.Invoke(false, string.Empty); 223 | } 224 | else 225 | { 226 | var json = webRequest.downloadHandler.text; 227 | Debug.Log(json); 228 | try 229 | { 230 | ChatCompletion result = JsonConvert.DeserializeObject(json); 231 | int lastChoiceIdx = result.choices.Count - 1; 232 | var replyMsg = result.choices[lastChoiceIdx].message; 233 | replyMsg.content = replyMsg.content.Trim(); 234 | messageHistory.Add(replyMsg); 235 | var codeBlock = ParseCodeBlocks(replyMsg.content); 236 | if (codeBlock != null) 237 | { 238 | codeBlocksDic.Add(messageHistory.Count - 1, codeBlock); 239 | } 240 | onComplete?.Invoke(true, replyMsg.content); 241 | } 242 | catch (System.Exception e) 243 | { 244 | Debug.LogError($"---------ChatGPT返回数据解析失败:{e.Message}---------"); 245 | onComplete?.Invoke(false, e.Message); 246 | } 247 | } 248 | webRequest.Dispose(); 249 | webRequest = null; 250 | } 251 | } 252 | public ChatGPTCodeBlock[] GetCodeBlocksByIdx(int msgIdx) 253 | { 254 | if (codeBlocksDic.TryGetValue(msgIdx, out var codeBlocks)) return codeBlocks; 255 | return null; 256 | } 257 | public void NewChat() 258 | { 259 | requestData.ClearChat(); 260 | messageHistory.Clear(); 261 | codeBlocksDic.Clear(); 262 | } 263 | public bool IsSelfMessage(ChatGPTMessage msg) 264 | { 265 | return this.UserId.CompareTo(msg.role) == 0; 266 | } 267 | } 268 | 269 | class ChatGPTRequestData 270 | { 271 | public List messages; 272 | public string model; 273 | public float temperature; 274 | 275 | public ChatGPTRequestData(string model, float temper) 276 | { 277 | this.model = model; 278 | this.temperature = temper; 279 | this.messages = new List(); 280 | } 281 | 282 | /// 283 | /// 同一话题追加会话内容 284 | /// 285 | /// 286 | /// 287 | public ChatGPTRequestData AppendChat(ChatGPTMessage msg) 288 | { 289 | this.messages.Add(msg); 290 | return this; 291 | } 292 | /// 293 | /// 清除聊天历史(结束一个话题), 相当于新建一个聊天话题 294 | /// 295 | public void ClearChat() 296 | { 297 | this.messages.Clear(); 298 | } 299 | } 300 | public class ChatGPTCodeBlock 301 | { 302 | public string Tag; 303 | public string Content; 304 | 305 | public string FileExtension; 306 | } 307 | class ChatGPTUsage 308 | { 309 | public int prompt_tokens; 310 | public int completion_tokens; 311 | public int total_tokens; 312 | } 313 | 314 | public class ChatGPTMessage 315 | { 316 | public string role; 317 | public string content; 318 | } 319 | 320 | class Choice 321 | { 322 | public ChatGPTMessage message; 323 | public string finish_reason; 324 | public int index; 325 | } 326 | 327 | class ChatCompletion 328 | { 329 | public string id; 330 | public string @object; 331 | public int created; 332 | public string model; 333 | public ChatGPTUsage usage; 334 | public List choices; 335 | } 336 | 337 | 338 | class WebRequestCertNoValidate : UnityEngine.Networking.CertificateHandler 339 | { 340 | protected override bool ValidateCertificate(byte[] certificateData) 341 | { 342 | //bool validation = base.ValidateCertificate(certificateData); 343 | 344 | //if (!validation) 345 | //{ 346 | // X509Certificate2 certificate = new X509Certificate2(certificateData); 347 | // // Do custom validation that puts it's result into the validation boolean. 348 | //} 349 | return true; 350 | } 351 | } 352 | 353 | } 354 | 355 | --------------------------------------------------------------------------------