diff --git a/CHANGELOG.md b/CHANGELOG.md index d03e6612..657b9912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## v1.2.1 +#### 🐛 Fixes + +- Kill server after Unity crash (PR: #101) +- Persist chat template on remote servers (PR: #103) + + ## v1.2.0 #### 🚀 Features diff --git a/CHANGELOG.release.md b/CHANGELOG.release.md index de42835b..8c34aca6 100644 --- a/CHANGELOG.release.md +++ b/CHANGELOG.release.md @@ -1,16 +1,5 @@ -### 🚀 Features - -- LLM server unit tests (PR: #90) -- Implement chat templates (PR: #92) -- Stop chat functionality (PR: #95) -- Keep only the llamafile binary (PR: #97) - ### 🐛 Fixes -- Fix remote server functionality (PR: #96) -- Fix Max issue needing to run llamafile manually the first time (PR: #98) - -### 📦 General - -- Async startup support (PR: #89) +- Kill server after Unity crash (PR: #101) +- Persist chat template on remote servers (PR: #103) diff --git a/README.md b/README.md index 214a5603..c13ca295 100644 --- a/README.md +++ b/README.md @@ -306,8 +306,10 @@ If it is not selected, the full reply from the model is received in one go - `Parallel Prompts` number of prompts that can happen in parallel (default: -1 = number of LLM/LLMClient objects) - `Debug` select to log the output of the model in the Unity Editor + - `Asynchronous Startup` allows to start the server asynchronously - `Remote` select to allow remote access to the server - `Port` port to run the server + - `Kill Existing Servers On Start` kills existing servers by the Unity project on startup to handle Unity crashes diff --git a/Runtime/LLM.cs b/Runtime/LLM.cs index af5f54ed..fc1e512d 100644 --- a/Runtime/LLM.cs +++ b/Runtime/LLM.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.IO; using System.IO.Compression; -using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using UnityEditor; @@ -23,6 +22,7 @@ public class LLM : LLMClient [ServerAdvanced] public bool debug = false; [ServerAdvanced] public bool asynchronousStartup = false; [ServerAdvanced] public bool remote = false; + [ServerAdvanced] public bool killExistingServersOnStart = true; [Model] public string model = ""; [ModelAddonAdvanced] public string lora = ""; @@ -52,6 +52,8 @@ public class LLM : LLMClient private bool mmapCrash = false; public bool serverListening { get; private set; } = false; private ManualResetEvent serverBlock = new ManualResetEvent(false); + static object crashKillLock = new object(); + static bool crashKill = false; #if UNITY_EDITOR [InitializeOnLoadMethod] @@ -162,11 +164,19 @@ public List GetListeningClients() return clients; } + void KillServersAfterUnityCrash() + { + lock (crashKillLock) { + if (crashKill) return; + LLMUnitySetup.KillServerAfterUnityCrash(server); + crashKill = true; + } + } + new public async void Awake() { - // start the llm server and run the Awake of the client + if (killExistingServersOnStart) KillServersAfterUnityCrash(); await StartLLMServer(); - base.Awake(); } @@ -189,20 +199,6 @@ private string SelectApeBinary() return apeExe; } - public bool IsPortInUse() - { - try - { - using (TcpClient c = new TcpClient()) - { - c.Connect(host, port); - } - return true; - } - catch {} - return false; - } - private void DebugLog(string message, bool logError = false) { // Debug log if debug is enabled @@ -282,7 +278,8 @@ private void RunServerCommand(string exe, string args) private async Task StartLLMServer() { - if (IsPortInUse()) throw new Exception($"Port {port} is already in use, please use another port or kill all llamafile processes using it!"); + bool portInUse = asynchronousStartup ? await IsServerReachableAsync() : IsServerReachable(); + if (portInUse) throw new Exception($"Port {port} is already in use, please use another port or kill all llamafile processes using it!"); // Start the LLM server in a cross-platform way if (model == "") throw new Exception("No model file provided!"); @@ -322,15 +319,21 @@ private async Task StartLLMServer() } if (process.HasExited) throw new Exception("Server could not be started!"); + else LLMUnitySetup.SaveServerPID(process.Id); } public void StopProcess() { // kill the llm server - if (process != null && !process.HasExited) + if (process != null) { - process.Kill(); - process.WaitForExit(); + int pid = process.Id; + if (!process.HasExited) + { + process.Kill(); + process.WaitForExit(); + } + LLMUnitySetup.DeleteServerPID(pid); } } diff --git a/Runtime/LLMClient.cs b/Runtime/LLMClient.cs index aaf4f446..0dc0abe0 100644 --- a/Runtime/LLMClient.cs +++ b/Runtime/LLMClient.cs @@ -83,7 +83,7 @@ public class LLMClient : MonoBehaviour [TextArea(5, 10), Chat] public string prompt = "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions."; protected List chat; - public string chatTemplate; + public string chatTemplate = ChatTemplate.DefaultTemplate; public ChatTemplate template; private List<(string, string)> requestHeaders = new List<(string, string)> { ("Content-Type", "application/json") }; private string previousEndpoint; @@ -95,7 +95,6 @@ public class LLMClient : MonoBehaviour public async void Awake() { - // initialise the prompt and set the keep tokens based on its length InitGrammar(); await InitPrompt(); LoadTemplate(); @@ -131,18 +130,13 @@ private void OnValidate() string newEndpoint = host + ":" + port; if (newEndpoint != previousEndpoint) { - string template = ChatTemplate.DefaultTemplate; + string templateToSet = chatTemplate; if (GetType() == typeof(LLMClient)) { LLM server = GetServer(); - if (server != null) template = server.chatTemplate; + if (server != null) templateToSet = server.chatTemplate; } - else - { - if (chatTemplate != null && chatTemplate != "") - template = chatTemplate; - } - SetTemplate(template); + SetTemplate(templateToSet); previousEndpoint = newEndpoint; } } @@ -375,6 +369,39 @@ public void CancelRequests() WIPRequests.Clear(); } + public bool IsServerReachable(int timeout = 5) + { + using (UnityWebRequest webRequest = UnityWebRequest.Head($"{host}:{port}/tokenize")) + { + webRequest.timeout = timeout; + webRequest.SendWebRequest(); + while (!webRequest.isDone) {} + if (webRequest.result == UnityWebRequest.Result.ConnectionError) + { + return false; + } + return true; + } + } + + public async Task IsServerReachableAsync(int timeout = 5) + { + using (UnityWebRequest webRequest = UnityWebRequest.Head($"{host}:{port}/tokenize")) + { + webRequest.timeout = timeout; + webRequest.SendWebRequest(); + while (!webRequest.isDone) + { + await Task.Yield(); + } + if (webRequest.result == UnityWebRequest.Result.ConnectionError) + { + return false; + } + return true; + } + } + public async Task PostRequest(string json, string endpoint, ContentCallback getContent, Callback callback = null) { // send a post request to the server and call the relevant callbacks to convert the received content and handle it diff --git a/Runtime/LLMUnitySetup.cs b/Runtime/LLMUnitySetup.cs index 7a01ca3c..95488425 100644 --- a/Runtime/LLMUnitySetup.cs +++ b/Runtime/LLMUnitySetup.cs @@ -5,8 +5,8 @@ using Debug = UnityEngine.Debug; using System.Threading.Tasks; using System.Collections.Generic; -using System.IO.Compression; using System.Net; +using System; namespace LLMUnity { @@ -98,7 +98,7 @@ public void DownloadProgressChanged(object sender, DownloadProgressChangedEventA public static async Task DownloadFile( string fileUrl, string savePath, bool overwrite = false, bool executable = false, TaskCallback callback = null, Callback progresscallback = null, - bool async=true + bool async = true ) { // download a file to the specified path @@ -117,7 +117,9 @@ public static async Task DownloadFile( if (async) { await client.DownloadFileTaskAsync(fileUrl, tmpPath); - } else { + } + else + { client.DownloadFile(fileUrl, tmpPath); } if (executable) makeExecutable(tmpPath); @@ -164,6 +166,112 @@ await Task.Run(() => } return fullPath.Substring(basePathSlash.Length + 1); } + #endif + + static string GetPIDFile() + { + string persistDir = Path.Combine(Application.persistentDataPath, "LLMUnity"); + if (!Directory.Exists(persistDir)) + { + Directory.CreateDirectory(persistDir); + } + return Path.Combine(persistDir, "server_process.txt"); + } + + public static void SaveServerPID(int pid) + { + try + { + using (StreamWriter writer = new StreamWriter(GetPIDFile(), true)) + { + writer.WriteLine(pid); + } + } + catch (Exception e) + { + Debug.LogError("Error saving PID to file: " + e.Message); + } + } + + static List ReadServerPIDs() + { + List pids = new List(); + string pidfile = GetPIDFile(); + if (!File.Exists(pidfile)) return pids; + + try + { + using (StreamReader reader = new StreamReader(pidfile)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + if (int.TryParse(line, out int pid)) + { + pids.Add(pid); + } + else + { + Debug.LogError("Invalid file entry: " + line); + } + } + } + } + catch (Exception e) + { + Debug.LogError("Error reading from file: " + e.Message); + } + return pids; + } + + public static string GetCommandLineArguments(Process process) + { + if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.WindowsPlayer) + { + return process.MainModule.FileName.Replace('\\', '/'); + } + else + { + return RunProcess("ps", $"-o command -p {process.Id}").Replace("COMMAND\n", ""); + } + } + + public static void KillServerAfterUnityCrash(string serverBinary) + { + foreach (int pid in ReadServerPIDs()) + { + try + { + Process process = Process.GetProcessById(pid); + string command = GetCommandLineArguments(process); + if (command.Contains(serverBinary)) + { + Debug.Log($"killing existing server with {pid}: {command}"); + process.Kill(); + process.WaitForExit(); + } + } + catch (Exception) {} + } + + string pidfile = GetPIDFile(); + if (File.Exists(pidfile)) File.Delete(pidfile); + } + + public static void DeleteServerPID(int pid) + { + string pidfile = GetPIDFile(); + if (!File.Exists(pidfile)) return; + + List pidEntries = ReadServerPIDs(); + pidEntries.Remove(pid); + + File.Delete(pidfile); + foreach (int pidEntry in pidEntries) + { + SaveServerPID(pidEntry); + } + } } } diff --git a/Samples~/ChatBot/ChatBot.cs b/Samples~/ChatBot/ChatBot.cs index 917c3cda..07db2c6d 100644 --- a/Samples~/ChatBot/ChatBot.cs +++ b/Samples~/ChatBot/ChatBot.cs @@ -153,5 +153,11 @@ void Update() lastBubbleOutsideFOV = -1; } } + + public void ExitGame() + { + Debug.Log("Exit button clicked"); + Application.Quit(); + } } } diff --git a/Samples~/ChatBot/ExitButton.cs b/Samples~/ChatBot/ExitButton.cs deleted file mode 100644 index 326adbbc..00000000 --- a/Samples~/ChatBot/ExitButton.cs +++ /dev/null @@ -1,14 +0,0 @@ -using UnityEngine; - -namespace LLMUnitySamples -{ - public class ExitButton : MonoBehaviour - { - public void ExitGame() - { - // This method will be called when the button is clicked - Debug.Log("Exit button clicked"); - Application.Quit(); - } - } -} diff --git a/Samples~/ChatBot/ExitButton.cs.meta b/Samples~/ChatBot/ExitButton.cs.meta deleted file mode 100644 index c49d6220..00000000 --- a/Samples~/ChatBot/ExitButton.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: da33e8a729588434a81b3973cf7d2113 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/ChatBot/Scene.unity b/Samples~/ChatBot/Scene.unity index 379e2bc1..dba42778 100644 --- a/Samples~/ChatBot/Scene.unity +++ b/Samples~/ChatBot/Scene.unity @@ -697,6 +697,8 @@ MonoBehaviour: parallelPrompts: -1 debug: 0 asynchronousStartup: 0 + remote: 0 + killExistingServersOnStart: 1 model: lora: contextSize: 512 @@ -889,7 +891,6 @@ GameObject: - component: {fileID: 1788474018} - component: {fileID: 1788474017} - component: {fileID: 1788474016} - - component: {fileID: 1788474019} m_Layer: 5 m_Name: ExitButton m_TagString: Untagged @@ -961,8 +962,8 @@ MonoBehaviour: m_OnClick: m_PersistentCalls: m_Calls: - - m_Target: {fileID: 1788474019} - m_TargetAssemblyTypeName: ExitButton, Assembly-CSharp + - m_Target: {fileID: 180020066} + m_TargetAssemblyTypeName: LLMUnitySamples.ChatBot, Assembly-CSharp m_MethodName: ExitGame m_Mode: 1 m_Arguments: @@ -1011,18 +1012,6 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1788474014} m_CullTransparentMesh: 1 ---- !u!114 &1788474019 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1788474014} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: da33e8a729588434a81b3973cf7d2113, type: 3} - m_Name: - m_EditorClassIdentifier: --- !u!1 &1833883732 GameObject: m_ObjectHideFlags: 0 diff --git a/Samples~/ServerClient/Scene.unity b/Samples~/ServerClient/Scene.unity index 6287e9c4..bbd6090c 100644 --- a/Samples~/ServerClient/Scene.unity +++ b/Samples~/ServerClient/Scene.unity @@ -226,6 +226,7 @@ RectTransform: - {fileID: 1342801405} - {fileID: 1448762425} - {fileID: 1116321462} + - {fileID: 834540939} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} @@ -233,6 +234,85 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0, y: 0} +--- !u!1 &232793937 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 232793938} + - component: {fileID: 232793940} + - component: {fileID: 232793939} + m_Layer: 5 + m_Name: Text (Legacy) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &232793938 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 232793937} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 834540939} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 2} + m_SizeDelta: {x: 25, y: 25} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &232793939 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 232793937} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: "\xD7" +--- !u!222 &232793940 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 232793937} + m_CullTransparentMesh: 1 --- !u!1 &261091095 GameObject: m_ObjectHideFlags: 0 @@ -650,6 +730,139 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: -20, y: -20} m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &834540938 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 834540939} + - component: {fileID: 834540942} + - component: {fileID: 834540941} + - component: {fileID: 834540940} + m_Layer: 5 + m_Name: ExitButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &834540939 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 834540938} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 232793938} + m_Father: {fileID: 158550917} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 25, y: 25} + m_Pivot: {x: 1, y: 1} +--- !u!114 &834540940 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 834540938} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 834540941} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 978410682} + m_TargetAssemblyTypeName: ServerClient, Assembly-CSharp + m_MethodName: ExitGame + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 1 +--- !u!114 &834540941 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 834540938} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.24722636, g: 0.24722636, b: 0.24722636, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10913, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &834540942 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 834540938} + m_CullTransparentMesh: 1 --- !u!1 &856480601 GameObject: m_ObjectHideFlags: 0 @@ -1258,6 +1471,8 @@ MonoBehaviour: parallelPrompts: -1 debug: 0 asynchronousStartup: 0 + remote: 0 + killExistingServersOnStart: 1 model: lora: contextSize: 512 diff --git a/Samples~/ServerClient/ServerClient.cs b/Samples~/ServerClient/ServerClient.cs index 5554f6ff..e2679827 100644 --- a/Samples~/ServerClient/ServerClient.cs +++ b/Samples~/ServerClient/ServerClient.cs @@ -69,4 +69,10 @@ public void CancelRequests() interaction1.AIReplyComplete(); interaction2.AIReplyComplete(); } + + public void ExitGame() + { + Debug.Log("Exit button clicked"); + Application.Quit(); + } } diff --git a/Samples~/SimpleInteraction/Scene.unity b/Samples~/SimpleInteraction/Scene.unity index 2c9d6fe1..9c893df7 100644 --- a/Samples~/SimpleInteraction/Scene.unity +++ b/Samples~/SimpleInteraction/Scene.unity @@ -134,7 +134,7 @@ GameObject: - component: {fileID: 107963746} - component: {fileID: 107963745} m_Layer: 0 - m_Name: GameObject + m_Name: SimpleInteraction m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -270,6 +270,7 @@ RectTransform: - {fileID: 856480602} - {fileID: 1342801405} - {fileID: 724531320} + - {fileID: 1308766010} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} @@ -993,6 +994,8 @@ MonoBehaviour: parallelPrompts: -1 debug: 0 asynchronousStartup: 0 + remote: 0 + killExistingServersOnStart: 1 model: lora: contextSize: 512 @@ -1094,6 +1097,218 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1059033619} m_CullTransparentMesh: 1 +--- !u!1 &1171567417 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1171567418} + - component: {fileID: 1171567420} + - component: {fileID: 1171567419} + m_Layer: 5 + m_Name: Text (Legacy) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1171567418 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1171567417} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1308766010} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 2} + m_SizeDelta: {x: 25, y: 25} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1171567419 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1171567417} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: "\xD7" +--- !u!222 &1171567420 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1171567417} + m_CullTransparentMesh: 1 +--- !u!1 &1308766009 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1308766010} + - component: {fileID: 1308766013} + - component: {fileID: 1308766012} + - component: {fileID: 1308766011} + m_Layer: 5 + m_Name: ExitButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1308766010 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1308766009} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1171567418} + m_Father: {fileID: 158550917} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 25, y: 25} + m_Pivot: {x: 1, y: 1} +--- !u!114 &1308766011 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1308766009} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1308766012} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 107963745} + m_TargetAssemblyTypeName: SimpleInteraction, Assembly-CSharp + m_MethodName: ExitGame + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 1 +--- !u!114 &1308766012 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1308766009} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.24722636, g: 0.24722636, b: 0.24722636, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10913, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1308766013 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1308766009} + m_CullTransparentMesh: 1 --- !u!1 &1342801404 GameObject: m_ObjectHideFlags: 0 diff --git a/Samples~/SimpleInteraction/SimpleInteraction.cs b/Samples~/SimpleInteraction/SimpleInteraction.cs index c64bfe56..12ffc5e8 100644 --- a/Samples~/SimpleInteraction/SimpleInteraction.cs +++ b/Samples~/SimpleInteraction/SimpleInteraction.cs @@ -38,4 +38,10 @@ public void CancelRequests() llm.CancelRequests(); AIReplyComplete(); } + + public void ExitGame() + { + Debug.Log("Exit button clicked"); + Application.Quit(); + } } diff --git a/Tests/Runtime/TestLLM.cs b/Tests/Runtime/TestLLM.cs index 3c923544..b1c5357c 100644 --- a/Tests/Runtime/TestLLM.cs +++ b/Tests/Runtime/TestLLM.cs @@ -14,9 +14,13 @@ public class LLMNoAwake : LLM public new void Awake() {} public new void OnDestroy() {} - public void CallAwake() + public async Task CallAwake() { base.Awake(); + while (!serverListening) + { + await Task.Delay(100); + } } public void CallOnDestroy() @@ -36,7 +40,7 @@ public class TestLLM GameObject gameObject; LLMNoAwake llm; int port = 15555; - string AIReply = "antantantantantantantantantantantantantantantantantantantantant"; + string AIReply = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; Exception error = null; public TestLLM() @@ -75,8 +79,7 @@ public async Task RunTests() error = null; try { - Assert.That(!llm.IsPortInUse()); - llm.CallAwake(); + await llm.CallAwake(); TestAlive(); await llm.Tokenize("I", TestTokens); await llm.Warmup(); @@ -109,7 +112,6 @@ public IEnumerator RunTestsWait() public void TestAlive() { Assert.That(llm.serverListening); - Assert.That(llm.IsPortInUse()); } public async void TestInitParameters() diff --git a/VERSION b/VERSION index 79127d85..6a5e98a7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.2.0 +v1.2.1 diff --git a/package.json b/package.json index e41de7e8..3b3d24dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ai.undream.llmunity", - "version": "1.2.0", + "version": "1.2.1", "displayName": "LLMUnity", "description": "LLMUnity allows to run and distribute LLM models in the Unity engine.", "unity": "2022.3",