diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Extensions/TextureExtensions.cs b/OpenAI/Packages/com.openai.unity/Runtime/Extensions/TextureExtensions.cs index 6ccbf2b8..2657c377 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Extensions/TextureExtensions.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Extensions/TextureExtensions.cs @@ -1,27 +1,39 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. -using System; -using System.IO; using System.Threading; using System.Threading.Tasks; +using Unity.Collections; using UnityEngine; +using Utilities.Extensions; +using OpenAI.Images; +using Utilities.Async; +using System; + +#if !PLATFORM_WEBGL +using System.IO; using Utilities.WebRequestRest; +#endif namespace OpenAI.Extensions { - internal static class TextureExtensions + public static class TextureExtensions { - public static async Task<(Texture2D, string)> ConvertFromBase64Async(string b64, bool debug, CancellationToken cancellationToken) + internal static async Task<(Texture2D, string)> ConvertFromBase64Async(string b64, bool debug, CancellationToken cancellationToken) { - var imageData = Convert.FromBase64String(b64); + using var imageData = NativeArrayExtensions.FromBase64String(b64, Allocator.Persistent); #if PLATFORM_WEBGL var texture = new Texture2D(2, 2); +#if UNITY_6000_0_OR_NEWER texture.LoadImage(imageData); +#else + texture.LoadImage(imageData.ToArray()); +#endif return await Task.FromResult((texture, string.Empty)); #else if (!Rest.TryGetDownloadCacheItem(b64, out var localFilePath)) { - await File.WriteAllBytesAsync(localFilePath, imageData, cancellationToken).ConfigureAwait(true); + await using var fs = new FileStream(localFilePath, FileMode.Create, FileAccess.Write); + await fs.WriteAsync(imageData, cancellationToken: cancellationToken); localFilePath = $"file://{localFilePath}"; } @@ -30,5 +42,54 @@ internal static class TextureExtensions return (texture, cachedPath); #endif } + + /// + /// Loads a Texture2D from an ImageResult, handling base64, cached path, or URL. + /// + /// The to load the texture for. + /// Optional, debug flag. + /// Optional, . + /// + /// A tuple containing the converted and the cached file path as a string. + /// + public static async Task<(Texture2D, Uri)> LoadTextureAsync(this ImageResult imageResult, bool debug = false, CancellationToken cancellationToken = default) + { + await Awaiters.UnityMainThread; + + if (imageResult.Texture.IsNull()) + { + if (!string.IsNullOrWhiteSpace(imageResult.B64_Json)) + { + var (texture, cachedPath) = await ConvertFromBase64Async(imageResult.B64_Json, debug, cancellationToken); + imageResult.Texture = texture; + imageResult.CachedPathUri = new Uri(cachedPath); + } + else + { + Texture2D texture; + Uri cachedPath; + + if (imageResult.CachedPathUri != null) + { + texture = await Rest.DownloadTextureAsync(imageResult.CachedPathUri, parameters: new RestParameters(debug: debug), cancellationToken: cancellationToken); + cachedPath = imageResult.CachedPathUri; + } + else if (!string.IsNullOrWhiteSpace(imageResult.Url)) + { + texture = await Rest.DownloadTextureAsync(imageResult.Url, parameters: new RestParameters(debug: debug), cancellationToken: cancellationToken); + cachedPath = Rest.TryGetDownloadCacheItem(imageResult.Url, out var path) ? new Uri(path) : null; + } + else + { + throw new System.Exception("ImageResult does not contain valid image data."); + } + + imageResult.Texture = texture; + imageResult.CachedPathUri = cachedPath; + } + } + + return (imageResult.Texture, imageResult.CachedPathUri); + } } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Images/ImageResult.cs b/OpenAI/Packages/com.openai.unity/Runtime/Images/ImageResult.cs index 255df3cd..3a3395cd 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Images/ImageResult.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Images/ImageResult.cs @@ -56,7 +56,12 @@ internal ImageResult( [Preserve] [JsonIgnore] - public string CachedPath { get; internal set; } + [Obsolete("use CachedPathUri")] + public string CachedPath => CachedPathUri?.ToString(); + + [Preserve] + [JsonIgnore] + public Uri CachedPathUri { get; internal set; } [Preserve] [JsonIgnore] @@ -75,9 +80,9 @@ internal ImageResult( [Preserve] public override string ToString() { - if (!string.IsNullOrWhiteSpace(CachedPath)) + if (CachedPathUri != null) { - return CachedPath; + return CachedPathUri.ToString(); } if (!string.IsNullOrWhiteSpace(B64_Json)) diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Images/ImagesEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/Images/ImagesEndpoint.cs index 156b459a..1ffa6645 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Images/ImagesEndpoint.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Images/ImagesEndpoint.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using UnityEngine; -using Utilities.Async; using Utilities.WebRequestRest; namespace OpenAI.Images @@ -180,33 +179,15 @@ private async Task> DeserializeResponseAsync(Response } await Rest.ValidateCacheDirectoryAsync(); - var downloads = imagesResponse.Results.Select(DownloadAsync).ToList(); - async Task DownloadAsync(ImageResult result) - { - await Awaiters.UnityMainThread; - - if (string.IsNullOrWhiteSpace(result.Url)) - { - var (texture, cachePath) = await TextureExtensions.ConvertFromBase64Async(result.B64_Json, EnableDebug, cancellationToken); - result.Texture = texture; - result.CachedPath = cachePath; - } - else - { - result.Texture = await Rest.DownloadTextureAsync(result.Url, parameters: new RestParameters(debug: EnableDebug), cancellationToken: cancellationToken); - - if (Rest.TryGetDownloadCacheItem(result.Url, out var cachedPath)) - { - result.CachedPath = cachedPath; - } - } - } + Task<(Texture2D, Uri)> DownloadAsync(ImageResult result) + => result.LoadTextureAsync(debug: EnableDebug, cancellationToken); - await Task.WhenAll(downloads).ConfigureAwait(true); + await Task.WhenAll(imagesResponse.Results.Select(DownloadAsync).ToList()).ConfigureAwait(true); - foreach (var result in imagesResponse.Results) + for (var i = 0; i < imagesResponse.Results.Count; i++) { + var result = imagesResponse.Results[i]; result.CreatedAt = DateTimeOffset.FromUnixTimeSeconds(imagesResponse.CreatedAtUnixSeconds).UtcDateTime; result.Background = imagesResponse.Background; result.OutputFormat = imagesResponse.OutputFormat; diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Responses/Message.cs b/OpenAI/Packages/com.openai.unity/Runtime/Responses/Message.cs index 19870a4d..1c5e8b3b 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Responses/Message.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Responses/Message.cs @@ -41,7 +41,7 @@ internal Message( [Preserve] public Message(Role role, string text) - : this(role, new TextContent(text)) + : this(role, new TextContent(text, role == Role.Assistant ? ResponseContentType.OutputText : ResponseContentType.InputText)) { } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Responses/TextContent.cs b/OpenAI/Packages/com.openai.unity/Runtime/Responses/TextContent.cs index 4d83acd2..66de38a3 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Responses/TextContent.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Responses/TextContent.cs @@ -29,9 +29,9 @@ internal TextContent( } [Preserve] - public TextContent(string text) + public TextContent(string text, ResponseContentType type = ResponseContentType.InputText) { - Type = ResponseContentType.InputText; + Type = type; Text = text; } diff --git a/OpenAI/Packages/com.openai.unity/Samples~/Chat/ChatBehaviour.cs b/OpenAI/Packages/com.openai.unity/Samples~/Chat/ChatBehaviour.cs index e9cd8f4e..574bfddb 100644 --- a/OpenAI/Packages/com.openai.unity/Samples~/Chat/ChatBehaviour.cs +++ b/OpenAI/Packages/com.openai.unity/Samples~/Chat/ChatBehaviour.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using TMPro; -using Unity.Collections; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_05_Images.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_05_Images.cs index 3c1914d1..7967d63f 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_05_Images.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_05_Images.cs @@ -44,12 +44,10 @@ public async Task Test_01_01_GenerateImages() var result = imageResults[i]; Assert.IsNotNull(result); Assert.IsNotNull(result.Texture); + Assert.IsNotNull(result.CachedPathUri); Assert.IsFalse(string.IsNullOrWhiteSpace(result.B64_Json)); - var imageBytes = Convert.FromBase64String(result.B64_Json); - Assert.IsNotNull(imageBytes); var path = Path.Combine(testDirectory, $"{nameof(Test_01_01_GenerateImages)}-{i}-{DateTime.UtcNow:yyyyMMddHHmmss}.jpeg"); - await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write); - await fileStream.WriteAsync(imageBytes, 0, imageBytes.Length); + File.Copy(result.CachedPathUri.LocalPath, path, true); } } catch (Exception e) @@ -82,12 +80,9 @@ public async Task Test_02_01_CreateImageEdit_Path() var result = imageResults[i]; Assert.IsNotNull(result); Assert.IsNotNull(result.Texture); - Assert.IsFalse(string.IsNullOrWhiteSpace(result.B64_Json)); - var imageBytes = Convert.FromBase64String(result.B64_Json); - Assert.IsNotNull(imageBytes); + Assert.IsNotNull(result.CachedPathUri); var path = Path.Combine(testDirectory, $"{nameof(Test_02_01_CreateImageEdit_Path)}-{i}-{DateTime.UtcNow:yyyyMMddHHmmss}.png"); - await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write); - await fileStream.WriteAsync(imageBytes, 0, imageBytes.Length); + File.Copy(result.CachedPathUri.LocalPath, path, true); } } catch (Exception e) @@ -122,12 +117,10 @@ public async Task Test_02_02_CreateImageEdit_Texture() var result = imageResults[i]; Assert.IsNotNull(result); Assert.IsNotNull(result.Texture); + Assert.IsNotNull(result.CachedPathUri); Assert.IsFalse(string.IsNullOrWhiteSpace(result.B64_Json)); - var imageBytes = Convert.FromBase64String(result.B64_Json); - Assert.IsNotNull(imageBytes); var path = Path.Combine(testDirectory, $"{nameof(Test_02_02_CreateImageEdit_Texture)}-{i}-{DateTime.UtcNow:yyyyMMddHHmmss}.png"); - await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write); - await fileStream.WriteAsync(imageBytes, 0, imageBytes.Length); + File.Copy(result.CachedPathUri.LocalPath, path, true); } } catch (Exception e) @@ -159,12 +152,10 @@ public async Task Test_02_03_CreateImageEdit_MaskAsTransparency() var result = imageResults[i]; Assert.IsNotNull(result); Assert.IsNotNull(result.Texture); + Assert.IsNotNull(result.CachedPathUri); Assert.IsFalse(string.IsNullOrWhiteSpace(result.B64_Json)); - var imageBytes = Convert.FromBase64String(result.B64_Json); - Assert.IsNotNull(imageBytes); var path = Path.Combine(testDirectory, $"{nameof(Test_02_03_CreateImageEdit_MaskAsTransparency)}-{i}-{DateTime.UtcNow:yyyyMMddHHmmss}.png"); - await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write); - await fileStream.WriteAsync(imageBytes, 0, imageBytes.Length); + File.Copy(result.CachedPathUri.LocalPath, path, true); } } catch (Exception e) @@ -202,12 +193,10 @@ public async Task Test_02_04_CreateImageEdit_MultipleFiles() var result = imageResults[i]; Assert.IsNotNull(result); Assert.IsNotNull(result.Texture); + Assert.IsNotNull(result.CachedPathUri); Assert.IsFalse(string.IsNullOrWhiteSpace(result.B64_Json)); - var imageBytes = Convert.FromBase64String(result.B64_Json); - Assert.IsNotNull(imageBytes); var path = Path.Combine(testDirectory, $"{nameof(Test_02_04_CreateImageEdit_MultipleFiles)}-{i}-{DateTime.UtcNow:yyyyMMddHHmmss}.png"); - await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write); - await fileStream.WriteAsync(imageBytes, 0, imageBytes.Length); + File.Copy(result.CachedPathUri.LocalPath, path, true); } } catch (Exception e) @@ -232,8 +221,9 @@ public async Task Test_03_01_CreateImageVariation_Path() foreach (var result in imageResults) { - Assert.IsNotNull(result.Texture); Debug.Log(result.ToString()); + Assert.IsNotNull(result.Texture); + Assert.IsNull(result.CachedPathUri); } } catch (Exception e) @@ -261,6 +251,7 @@ public async Task Test_03_02_CreateImageVariation_Texture() { Debug.Log(result.ToString()); Assert.IsNotNull(result.Texture); + Assert.IsNull(result.CachedPathUri); } } catch (Exception e) @@ -289,12 +280,10 @@ public async Task Test_03_03_CreateImageVariation_Texture_B64_Json() var result = imageResults[i]; Assert.IsNotNull(result); Assert.IsNotNull(result.Texture); + Assert.IsNotNull(result.CachedPathUri); Assert.IsFalse(string.IsNullOrWhiteSpace(result.B64_Json)); - var imageBytes = Convert.FromBase64String(result.B64_Json); - Assert.IsNotNull(imageBytes); var path = Path.Combine(testDirectory, $"{nameof(Test_03_03_CreateImageVariation_Texture_B64_Json)}-{i}-{DateTime.UtcNow:yyyyMMddHHmmss}.png"); - await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write); - await fileStream.WriteAsync(imageBytes, 0, imageBytes.Length); + File.Copy(result.CachedPathUri.LocalPath, path, true); } } catch (Exception e) diff --git a/OpenAI/Packages/com.openai.unity/package.json b/OpenAI/Packages/com.openai.unity/package.json index 28de899e..7bfc8e30 100644 --- a/OpenAI/Packages/com.openai.unity/package.json +++ b/OpenAI/Packages/com.openai.unity/package.json @@ -3,7 +3,7 @@ "displayName": "OpenAI", "description": "A OpenAI package for the Unity to use though their RESTful API.\n\nIndependently developed, this is not an official library and I am not affiliated with OpenAI.\n\nAn OpenAI API account is required.", "keywords": [], - "version": "8.8.7", + "version": "8.8.8", "unity": "2021.3", "documentationUrl": "https://github.com/RageAgainstThePixel/com.openai.unity#documentation", "changelogUrl": "https://github.com/RageAgainstThePixel/com.openai.unity/releases", @@ -18,7 +18,7 @@ }, "dependencies": { "com.utilities.encoder.wav": "3.0.2", - "com.utilities.rest": "5.0.4", + "com.utilities.rest": "5.1.0", "com.utilities.websockets": "2.0.0" }, "samples": [