├── .gitattributes ├── Config ├── FilterPlugin.ini └── DefaultHttpGPT.ini ├── .github └── FUNDING.yml ├── Resources └── Icon128.png ├── .gitignore ├── Source ├── HttpGPTCommonModule │ ├── Private │ │ ├── LogHttpGPT.cpp │ │ ├── HttpGPTCommonModule.cpp │ │ ├── Structures │ │ │ ├── HttpGPTImageTypes.cpp │ │ │ ├── HttpGPTCommonTypes.cpp │ │ │ └── HttpGPTChatTypes.cpp │ │ ├── Management │ │ │ └── HttpGPTSettings.cpp │ │ ├── Utils │ │ │ └── HttpGPTHelper.cpp │ │ └── Tasks │ │ │ └── HttpGPTBaseTask.cpp │ ├── Public │ │ ├── HttpGPTCommonModule.h │ │ ├── LogHttpGPT.h │ │ ├── HttpGPTInternalFuncs.h │ │ ├── Structures │ │ │ ├── HttpGPTCommonTypes.h │ │ │ ├── HttpGPTImageTypes.h │ │ │ └── HttpGPTChatTypes.h │ │ ├── Tasks │ │ │ └── HttpGPTBaseTask.h │ │ ├── Utils │ │ │ └── HttpGPTHelper.h │ │ └── Management │ │ │ └── HttpGPTSettings.h │ └── HttpGPTCommonModule.Build.cs ├── HttpGPTChatModule │ ├── Public │ │ ├── HttpGPTChatModule.h │ │ └── Tasks │ │ │ └── HttpGPTChatRequest.h │ ├── Private │ │ ├── HttpGPTChatModule.cpp │ │ └── Tasks │ │ │ └── HttpGPTChatRequest.cpp │ └── HttpGPTChatModule.Build.cs ├── HttpGPTImageModule │ ├── Public │ │ ├── HttpGPTImageModule.h │ │ └── Tasks │ │ │ └── HttpGPTImageRequest.h │ ├── Private │ │ ├── HttpGPTImageModule.cpp │ │ └── Tasks │ │ │ └── HttpGPTImageRequest.cpp │ └── HttpGPTImageModule.Build.cs └── HttpGPTEditorModule │ ├── Public │ └── HttpGPTEditorModule.h │ ├── Private │ ├── ImageGen │ │ ├── SHttpGPTImageGenItemData.h │ │ ├── SHttpGPTImageGenItem.h │ │ ├── SHttpGPTImageGenView.h │ │ ├── HttpGPTImageGetter.h │ │ ├── HttpGPTImageGetter.cpp │ │ ├── SHttpGPTImageGenItemData.cpp │ │ ├── SHttpGPTImageGenItem.cpp │ │ └── SHttpGPTImageGenView.cpp │ ├── Chat │ │ ├── HttpGPTMessagingHandler.h │ │ ├── SHttpGPTChatItem.h │ │ ├── SHttpGPTChatShell.h │ │ ├── SHttpGPTChatView.h │ │ ├── HttpGPTMessagingHandler.cpp │ │ ├── SHttpGPTChatItem.cpp │ │ ├── SHttpGPTChatShell.cpp │ │ └── SHttpGPTChatView.cpp │ └── HttpGPTEditorModule.cpp │ └── HttpGPTEditorModule.Build.cs ├── LICENSE ├── README.md └── HttpGPT.uplugin /.gitattributes: -------------------------------------------------------------------------------- 1 | Content/** filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | Config/DefaultHttpGPT.ini 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [lucoiso] 4 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucoiso/UEHttpGPT/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Binaries 2 | DerivedDataCache 3 | Intermediate 4 | Saved 5 | enc_temp_folder 6 | .vscode 7 | .vs 8 | .idea 9 | *.VC.db 10 | *.opensdf 11 | *.opendb 12 | *.sdf 13 | *.sln 14 | *.suo 15 | *.xcodeproj 16 | *.xcworkspace 17 | *.ipch 18 | *.TMP -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Private/LogHttpGPT.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "LogHttpGPT.h" 6 | 7 | DEFINE_LOG_CATEGORY(LogHttpGPT); 8 | DEFINE_LOG_CATEGORY(LogHttpGPT_Internal); 9 | -------------------------------------------------------------------------------- /Source/HttpGPTChatModule/Public/HttpGPTChatModule.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | class FHttpGPTChatModule : public IModuleInterface 11 | { 12 | public: 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; 16 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Public/HttpGPTCommonModule.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | class FHttpGPTCommonModule : public IModuleInterface 11 | { 12 | public: 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; 16 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Public/LogHttpGPT.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | /** 10 | * 11 | */ 12 | 13 | HTTPGPTCOMMONMODULE_API DECLARE_LOG_CATEGORY_EXTERN(LogHttpGPT, Display, All); 14 | 15 | HTTPGPTCOMMONMODULE_API DECLARE_LOG_CATEGORY_EXTERN(LogHttpGPT_Internal, NoLogging, All); 16 | -------------------------------------------------------------------------------- /Source/HttpGPTImageModule/Public/HttpGPTImageModule.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | class FHttpGPTImageModule : public IModuleInterface 11 | { 12 | public: 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; 16 | -------------------------------------------------------------------------------- /Source/HttpGPTChatModule/Private/HttpGPTChatModule.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "HttpGPTChatModule.h" 6 | 7 | #define LOCTEXT_NAMESPACE "FHttpGPTChatModule" 8 | 9 | void FHttpGPTChatModule::StartupModule() 10 | { 11 | } 12 | 13 | void FHttpGPTChatModule::ShutdownModule() 14 | { 15 | } 16 | 17 | #undef LOCTEXT_NAMESPACE 18 | 19 | IMPLEMENT_MODULE(FHttpGPTChatModule, HttpGPTChatModule) 20 | -------------------------------------------------------------------------------- /Source/HttpGPTImageModule/Private/HttpGPTImageModule.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "HttpGPTImageModule.h" 6 | 7 | #define LOCTEXT_NAMESPACE "FHttpGPTImageModule" 8 | 9 | void FHttpGPTImageModule::StartupModule() 10 | { 11 | } 12 | 13 | void FHttpGPTImageModule::ShutdownModule() 14 | { 15 | } 16 | 17 | #undef LOCTEXT_NAMESPACE 18 | 19 | IMPLEMENT_MODULE(FHttpGPTImageModule, HttpGPTImageModule) 20 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Private/HttpGPTCommonModule.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "HttpGPTCommonModule.h" 6 | 7 | #define LOCTEXT_NAMESPACE "FHttpGPTCommonModule" 8 | 9 | void FHttpGPTCommonModule::StartupModule() 10 | { 11 | } 12 | 13 | void FHttpGPTCommonModule::ShutdownModule() 14 | { 15 | } 16 | 17 | #undef LOCTEXT_NAMESPACE 18 | 19 | IMPLEMENT_MODULE(FHttpGPTCommonModule, HttpGPTCommonModule) 20 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Public/HttpGPTEditorModule.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | class FHttpGPTEditorModule : public IModuleInterface 10 | { 11 | protected: 12 | virtual void StartupModule() override; 13 | virtual void ShutdownModule() override; 14 | 15 | TSharedRef OnSpawnTab(const FSpawnTabArgs& SpawnTabArgs) const; 16 | 17 | private: 18 | void RegisterMenus(); 19 | FPropertyEditorModule* PropertyEditorModule = nullptr; 20 | }; 21 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Private/Structures/HttpGPTImageTypes.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "Structures/HttpGPTImageTypes.h" 6 | #include "Management/HttpGPTSettings.h" 7 | 8 | FHttpGPTImageOptions::FHttpGPTImageOptions() 9 | { 10 | SetDefaults(); 11 | } 12 | 13 | void FHttpGPTImageOptions::SetDefaults() 14 | { 15 | if (const UHttpGPTSettings* const Settings = GetDefault()) 16 | { 17 | ImagesNum = Settings->ImageOptions.ImagesNum; 18 | Size = Settings->ImageOptions.Size; 19 | Format = Settings->ImageOptions.Format; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Source/HttpGPTChatModule/HttpGPTChatModule.Build.cs: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2022 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | using UnrealBuildTool; 6 | 7 | public class HttpGPTChatModule : ModuleRules 8 | { 9 | public HttpGPTChatModule(ReadOnlyTargetRules Target) : base(Target) 10 | { 11 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 12 | CppStandard = CppStandardVersion.Cpp17; 13 | 14 | PublicDependencyModuleNames.AddRange(new[] 15 | { 16 | "Core", 17 | "HTTP", 18 | "Json", 19 | "HttpGPTCommonModule" 20 | }); 21 | 22 | PrivateDependencyModuleNames.AddRange(new[] 23 | { 24 | "Engine", 25 | "CoreUObject" 26 | }); 27 | 28 | if (Target.bBuildEditor) PrivateDependencyModuleNames.Add("UnrealEd"); 29 | } 30 | } -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/HttpGPTCommonModule.Build.cs: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2022 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | using UnrealBuildTool; 6 | 7 | public class HttpGPTCommonModule : ModuleRules 8 | { 9 | public HttpGPTCommonModule(ReadOnlyTargetRules Target) : base(Target) 10 | { 11 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 12 | CppStandard = CppStandardVersion.Cpp17; 13 | 14 | PublicDependencyModuleNames.AddRange(new[] 15 | { 16 | "Core", 17 | "HTTP", 18 | "Json" 19 | }); 20 | 21 | PrivateDependencyModuleNames.AddRange(new[] 22 | { 23 | "Engine", 24 | "CoreUObject", 25 | "DeveloperSettings" 26 | }); 27 | 28 | if (Target.bBuildEditor) PrivateDependencyModuleNames.Add("UnrealEd"); 29 | } 30 | } -------------------------------------------------------------------------------- /Source/HttpGPTImageModule/HttpGPTImageModule.Build.cs: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2022 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | using UnrealBuildTool; 6 | 7 | public class HttpGPTImageModule : ModuleRules 8 | { 9 | public HttpGPTImageModule(ReadOnlyTargetRules Target) : base(Target) 10 | { 11 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 12 | CppStandard = CppStandardVersion.Cpp17; 13 | 14 | PublicDependencyModuleNames.AddRange(new[] 15 | { 16 | "Core", 17 | "HTTP", 18 | "Json", 19 | "HttpGPTCommonModule" 20 | }); 21 | 22 | PrivateDependencyModuleNames.AddRange(new[] 23 | { 24 | "Engine", 25 | "CoreUObject" 26 | }); 27 | 28 | if (Target.bBuildEditor) PrivateDependencyModuleNames.Add("UnrealEd"); 29 | } 30 | } -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/ImageGen/SHttpGPTImageGenItemData.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | class SHttpGPTImageGenItemData final : public SCompoundWidget 11 | { 12 | public: 13 | SLATE_BEGIN_ARGS(SHttpGPTImageGenItemData) : _Texture() 14 | { 15 | } 16 | 17 | SLATE_ARGUMENT(class UTexture2D*, Texture) 18 | SLATE_END_ARGS() 19 | 20 | void Construct(const FArguments& InArgs); 21 | 22 | FReply HandleSaveButton(); 23 | bool IsSaveEnabled() const; 24 | 25 | private: 26 | TSharedRef ConstructContent(); 27 | 28 | TWeakObjectPtr Texture; 29 | }; 30 | 31 | using SHttpGPTImageGenItemDataPtr = TSharedPtr; 32 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Private/Structures/HttpGPTCommonTypes.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "Structures/HttpGPTCommonTypes.h" 6 | #include 7 | 8 | #ifdef UE_INLINE_GENERATED_CPP_BY_NAME 9 | #include UE_INLINE_GENERATED_CPP_BY_NAME(HttpGPTCommonTypes) 10 | #endif 11 | 12 | FHttpGPTCommonOptions::FHttpGPTCommonOptions() 13 | { 14 | SetDefaults(); 15 | } 16 | 17 | void FHttpGPTCommonOptions::SetDefaults() 18 | { 19 | if (const UHttpGPTSettings* const Settings = GetDefault()) 20 | { 21 | APIKey = Settings->CommonOptions.APIKey; 22 | User = Settings->CommonOptions.User; 23 | bIsAzureOpenAI = Settings->CommonOptions.bIsAzureOpenAI; 24 | Endpoint = Settings->CommonOptions.Endpoint; 25 | AzureOpenAIAPIVersion = Settings->CommonOptions.AzureOpenAIAPIVersion; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/HttpGPTEditorModule.Build.cs: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | using UnrealBuildTool; 6 | 7 | public class HttpGPTEditorModule : ModuleRules 8 | { 9 | public HttpGPTEditorModule(ReadOnlyTargetRules Target) : base(Target) 10 | { 11 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 12 | CppStandard = CppStandardVersion.Cpp17; 13 | 14 | PublicDependencyModuleNames.AddRange(new[] 15 | { 16 | "Core" 17 | }); 18 | 19 | PrivateDependencyModuleNames.AddRange(new[] 20 | { 21 | "Engine", 22 | "CoreUObject", 23 | "InputCore", 24 | "Slate", 25 | "SlateCore", 26 | "UnrealEd", 27 | "ToolMenus", 28 | "EditorStyle", 29 | "WorkspaceMenuStructure", 30 | "Projects", 31 | "AssetRegistry", 32 | "HttpGPTCommonModule", 33 | "HttpGPTChatModule", 34 | "HttpGPTImageModule", 35 | "Json" 36 | }); 37 | } 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Lucas Vilas-Bôas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/ImageGen/SHttpGPTImageGenItem.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | class SHttpGPTImageGenItem final : public SCompoundWidget 11 | { 12 | public: 13 | SLATE_BEGIN_ARGS(SHttpGPTImageGenItem) : _OutScrollBox(), _Prompt(), _Num(), _Size() 14 | { 15 | } 16 | 17 | SLATE_ARGUMENT(TSharedPtr, OutScrollBox) 18 | SLATE_ARGUMENT(FString, Prompt) 19 | SLATE_ARGUMENT(FString, Num) 20 | SLATE_ARGUMENT(FString, Size) 21 | SLATE_END_ARGS() 22 | 23 | void Construct(const FArguments& InArgs); 24 | virtual ~SHttpGPTImageGenItem() override; 25 | 26 | TWeakObjectPtr HttpGPTImageGetterObject; 27 | 28 | private: 29 | TSharedRef ConstructContent(); 30 | 31 | FString Prompt; 32 | 33 | TSharedPtr Status; 34 | TSharedPtr ItemViewBox; 35 | 36 | TWeakObjectPtr RequestReference; 37 | }; 38 | 39 | using SHttpGPTImageGenItemPtr = TSharedPtr; 40 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/ImageGen/SHttpGPTImageGenView.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | class SHttpGPTImageGenView final : public SCompoundWidget 11 | { 12 | public: 13 | SLATE_USER_ARGS(SHttpGPTImageGenView) 14 | { 15 | } 16 | 17 | SLATE_END_ARGS() 18 | 19 | void Construct(const FArguments& InArgs); 20 | 21 | bool IsSendRequestEnabled() const; 22 | bool IsClearViewEnabled() const; 23 | 24 | private: 25 | TSharedRef ConstructContent(); 26 | 27 | FReply HandleSendRequestButton(); 28 | FReply HandleClearViewButton(); 29 | 30 | void InitializeImageNumOptions(); 31 | void InitializeImageSizeOptions(); 32 | 33 | TSharedPtr ViewBox; 34 | TSharedPtr ViewScrollBox; 35 | 36 | TSharedPtr InputTextBox; 37 | 38 | TSharedPtr ImageNumComboBox; 39 | TArray> ImageNum; 40 | 41 | TSharedPtr ImageSizeComboBox; 42 | TArray> ImageSize; 43 | }; 44 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/Chat/HttpGPTMessagingHandler.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include "HttpGPTMessagingHandler.generated.h" 11 | 12 | DECLARE_DELEGATE_OneParam(FMessageContentUpdated, FString); 13 | 14 | UCLASS(MinimalAPI, NotBlueprintable, NotPlaceable, Category = "Implementation") 15 | class UHttpGPTMessagingHandler : public UObject 16 | { 17 | GENERATED_BODY() 18 | 19 | public: 20 | explicit UHttpGPTMessagingHandler(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); 21 | 22 | FMessageContentUpdated OnMessageContentUpdated; 23 | 24 | UFUNCTION() 25 | void RequestSent(); 26 | 27 | UFUNCTION() 28 | void RequestFailed(); 29 | 30 | UFUNCTION() 31 | void ProcessUpdated(const FHttpGPTChatResponse& Response); 32 | 33 | UFUNCTION() 34 | void ProcessCompleted(const FHttpGPTChatResponse& Response); 35 | 36 | TSharedPtr ScrollBoxReference; 37 | 38 | void Destroy(); 39 | 40 | private: 41 | void ProcessResponse(const FHttpGPTChatResponse& Response); 42 | }; 43 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/Chat/SHttpGPTChatItem.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | static const FName NewSessionName = TEXT("New Session"); 12 | 13 | class SHttpGPTChatItem final : public SCompoundWidget 14 | { 15 | public: 16 | SLATE_BEGIN_ARGS(SHttpGPTChatItem) : _MessageRole(), _InputText(), _ScrollBox() 17 | { 18 | } 19 | 20 | SLATE_ARGUMENT(EHttpGPTChatRole, MessageRole) 21 | SLATE_ARGUMENT(FString, InputText) 22 | SLATE_ARGUMENT(TSharedPtr, ScrollBox) 23 | SLATE_END_ARGS() 24 | 25 | void Construct(const FArguments& InArgs); 26 | 27 | FString GetRoleText() const; 28 | FString GetMessageText() const; 29 | 30 | TWeakObjectPtr MessagingHandlerObject; 31 | 32 | private: 33 | TSharedRef ConstructContent(); 34 | 35 | EHttpGPTChatRole MessageRole; 36 | FString InputText; 37 | 38 | TSharedPtr Role; 39 | TSharedPtr Message; 40 | }; 41 | 42 | using SHttpGPTChatItemPtr = TSharedPtr; 43 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Public/HttpGPTInternalFuncs.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | /** 12 | * 13 | */ 14 | 15 | namespace HttpGPT::Internal 16 | { 17 | template 18 | constexpr const bool HasEmptyParam(const Ty& Arg1) 19 | { 20 | if constexpr (std::is_base_of()) 21 | { 22 | return Arg1.IsEmpty(); 23 | } 24 | else if constexpr (std::is_base_of()) 25 | { 26 | return Arg1.IsNone(); 27 | } 28 | else if constexpr (std::is_base_of()) 29 | { 30 | return Arg1.IsEmptyOrWhitespace(); 31 | } 32 | else if constexpr (std::is_base_of()) 33 | { 34 | return Arg1.empty(); 35 | } 36 | else 37 | { 38 | #if ENGINE_MAJOR_VERSION >= 5 39 | return Arg1.IsEmpty(); 40 | #else 41 | return Arg1.Num() == 0; 42 | #endif 43 | } 44 | } 45 | 46 | template 47 | constexpr const bool HasEmptyParam(const Ty& Arg1, Args&&... args) 48 | { 49 | return HasEmptyParam(Arg1) || HasEmptyParam(std::forward(args)...); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unreal Engine Plugin: HttpGPT 2 | 3 | ![Banner1](https://user-images.githubusercontent.com/77353979/226219720-150abd31-487c-474f-ab14-576ff2efbdde.png) 4 | 5 | HttpGPT is an Unreal Engine plugin that facilitates integration with OpenAI's GPT based services (ChatGPT and DALL-E) through asynchronous REST requests, making it easy for developers to communicate with these services. 6 | 7 | HttpGPT also includes Editor Tools to integrate Chat GPT and DALL-E image generation directly in the Engine. 8 | 9 | ## Editor Tool: HttpGPT Chat 10 | ![Editor1](https://user-images.githubusercontent.com/77353979/229308552-941f4def-fe92-48bd-8931-b8198c341749.png) 11 | 12 | ## Editor Tool: HttpGPT Image Generator 13 | ![Editor2](https://user-images.githubusercontent.com/77353979/229308557-8ff8008f-9b96-4ff6-a0f2-3b26fd104632.png) 14 | 15 | ## Links 16 | * [Documentation](https://github.com/lucoiso/UEHttpGPT/wiki) 17 | * [Unreal Engine Marketplace](https://www.unrealengine.com/marketplace/en-US/product/433c180835184aeca0172680a69497ee) 18 | * [Unreal Engine Forum](https://forums.unrealengine.com/t/free-httpgpt-gpt-integration-chatgpt-and-dall-e/765168?u=lucoiso) 19 | * [OpenAI Documentation](https://platform.openai.com/docs/introduction) 20 | * [Example Project: SpeechGPT](https://github.com/lucoiso/UESpeechGPT) 21 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/ImageGen/HttpGPTImageGetter.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include "HttpGPTImageGetter.generated.h" 11 | 12 | DECLARE_DELEGATE_OneParam(FImageGenerated, UTexture2D*); 13 | DECLARE_DELEGATE_OneParam(FImageStatusChanged, FString); 14 | 15 | UCLASS(MinimalAPI, NotBlueprintable, NotPlaceable, Category = "Implementation") 16 | class UHttpGPTImageGetter : public UObject 17 | { 18 | GENERATED_BODY() 19 | 20 | public: 21 | explicit UHttpGPTImageGetter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); 22 | 23 | FImageGenerated OnImageGenerated; 24 | FImageStatusChanged OnStatusChanged; 25 | 26 | UFUNCTION() 27 | void RequestSent(); 28 | 29 | UFUNCTION() 30 | void RequestFailed(); 31 | 32 | UFUNCTION() 33 | void ProcessCompleted(const FHttpGPTImageResponse& Response); 34 | 35 | void Destroy(); 36 | 37 | TSharedPtr OutScrollBox; 38 | 39 | private: 40 | void ProcessImage(const FHttpGPTImageData& Data); 41 | 42 | FHttpGPTImageGenerate OnImageGenerated_Internal; 43 | 44 | UFUNCTION() 45 | void ImageGenerated(UTexture2D* const Texture); 46 | 47 | uint8 GeneratedImages; 48 | uint8 DataSize; 49 | }; 50 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/Chat/SHttpGPTChatShell.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | using FNamePtr = TSharedPtr; 11 | 12 | class SHttpGPTChatShell final : public SCompoundWidget 13 | { 14 | public: 15 | SLATE_USER_ARGS(SHttpGPTChatShell) 16 | { 17 | } 18 | 19 | SLATE_END_ARGS() 20 | 21 | void Construct(const FArguments& InArgs); 22 | virtual ~SHttpGPTChatShell() override; 23 | 24 | private: 25 | TSharedRef ConstructContent(); 26 | 27 | void InitializeChatSessionOptions(); 28 | void InitializeChatSession(const FNamePtr& InItem); 29 | 30 | TSharedPtr ShellBox; 31 | TSharedPtr CurrentView; 32 | 33 | TSharedPtr> ChatSessionListView; 34 | TArray ChatSessions; 35 | 36 | TSharedRef OnGenerateChatSessionRow(FNamePtr InItem, const TSharedRef& OwnerTable); 37 | 38 | void OnChatSessionSelectionChanged(const FNamePtr InItem, ESelectInfo::Type SelectInfo); 39 | void OnChatSessionNameChanged(const FNamePtr InItem, const FName& NewName); 40 | void OnChatSessionDoubleClicked(const FNamePtr InItem); 41 | FReply OnChatSessionKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent); 42 | }; 43 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/Chat/SHttpGPTChatView.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include "SHttpGPTChatItem.h" 11 | 12 | class SHttpGPTChatView final : public SCompoundWidget 13 | { 14 | public: 15 | SLATE_USER_ARGS(SHttpGPTChatView) 16 | : _SessionID(NAME_None) 17 | { 18 | } 19 | 20 | SLATE_ARGUMENT(FName, SessionID) 21 | SLATE_END_ARGS() 22 | 23 | void Construct(const FArguments& InArgs); 24 | virtual ~SHttpGPTChatView() override; 25 | 26 | bool IsSendMessageEnabled() const; 27 | bool IsClearChatEnabled() const; 28 | FString GetHistoryPath() const; 29 | 30 | void SetSessionID(const FName& NewSessionID); 31 | FName GetSessionID() const; 32 | 33 | void ClearChat(); 34 | 35 | private: 36 | TSharedRef ConstructContent(); 37 | 38 | FReply HandleSendMessageButton(const EHttpGPTChatRole Role); 39 | FReply HandleClearChatButton(); 40 | 41 | TArray GetChatHistory() const; 42 | FString GetDefaultSystemContext() const; 43 | 44 | void LoadChatHistory(); 45 | void SaveChatHistory() const; 46 | 47 | FName SessionID; 48 | 49 | TSharedPtr ChatBox; 50 | TArray ChatItems; 51 | TSharedPtr ChatScrollBox; 52 | 53 | TSharedPtr InputTextBox; 54 | 55 | TSharedPtr ModelsComboBox; 56 | TArray> AvailableModels; 57 | 58 | TWeakObjectPtr RequestReference; 59 | }; 60 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Public/Structures/HttpGPTCommonTypes.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "HttpGPTCommonTypes.generated.h" 9 | 10 | USTRUCT(BlueprintType, Category = "HttpGPT | Common", Meta = (DisplayName = "HttpGPT Common Error")) 11 | struct HTTPGPTCOMMONMODULE_API FHttpGPTCommonError 12 | { 13 | GENERATED_BODY() 14 | 15 | FHttpGPTCommonError() = default; 16 | 17 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Common") 18 | FName Type; 19 | 20 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Common") 21 | FName Code; 22 | 23 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Common") 24 | FString Message; 25 | }; 26 | 27 | USTRUCT(BlueprintType, Category = "HttpGPT | Common", Meta = (DisplayName = "HttpGPT Common Options")) 28 | struct HTTPGPTCOMMONMODULE_API FHttpGPTCommonOptions 29 | { 30 | GENERATED_BODY() 31 | 32 | FHttpGPTCommonOptions(); 33 | 34 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Common", Meta = (DisplayName = "API Key")) 35 | FName APIKey; 36 | 37 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Common", Meta = (DisplayName = "User")) 38 | FName User; 39 | 40 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Common", Meta = (DisplayName = "Is Azure OpenAI")) 41 | bool bIsAzureOpenAI; 42 | 43 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Common", Meta = (DisplayName = "Endpoint", EditCondition = "bIsAzureOpenAI")) 44 | FString Endpoint; 45 | 46 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Common", 47 | Meta = (DisplayName = "Azure OpenAI API Version", EditCondition = "bIsAzureOpenAI")) 48 | FString AzureOpenAIAPIVersion; 49 | 50 | private: 51 | void SetDefaults(); 52 | }; 53 | -------------------------------------------------------------------------------- /HttpGPT.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 23, 4 | "VersionName": "1.5.10", 5 | "FriendlyName": "HttpGPT - GPT Integration", 6 | "Description": "HttpGPT is an Unreal Engine plugin that facilitates integration with OpenAI's GPT based services (ChatGPT and DALL-E) through asynchronous REST requests, making it easy for developers to communicate with these services. HttpGPT also includes new Editor Tools to integrate Chat GPT and DALL-E image generation directly in the Engine.", 7 | "Category": "Game Features", 8 | "CreatedBy": "Lucas Vilas-Boas", 9 | "CreatedByURL": "https://github.com/lucoiso", 10 | "DocsURL": "https://github.com/lucoiso/UEHttpGPT/wiki", 11 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/433c180835184aeca0172680a69497ee", 12 | "SupportURL": "https://github.com/lucoiso/UEHttpGPT/issues", 13 | "CanContainContent": false, 14 | "IsBetaVersion": true, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "HttpGPTChatModule", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default", 22 | "PlatformAllowList": [ 23 | "Win64", 24 | "Mac", 25 | "Linux", 26 | "IOS", 27 | "Android" 28 | ] 29 | }, 30 | { 31 | "Name": "HttpGPTImageModule", 32 | "Type": "Runtime", 33 | "LoadingPhase": "Default", 34 | "PlatformAllowList": [ 35 | "Win64", 36 | "Mac", 37 | "Linux", 38 | "IOS", 39 | "Android" 40 | ] 41 | }, 42 | { 43 | "Name": "HttpGPTCommonModule", 44 | "Type": "Runtime", 45 | "LoadingPhase": "Default", 46 | "PlatformAllowList": [ 47 | "Win64", 48 | "Mac", 49 | "Linux", 50 | "IOS", 51 | "Android" 52 | ] 53 | }, 54 | { 55 | "Name": "HttpGPTEditorModule", 56 | "Type": "Editor", 57 | "PlatformAllowList": [ 58 | "Win64", 59 | "Mac", 60 | "Linux" 61 | ], 62 | "LoadingPhase": "Default" 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/ImageGen/HttpGPTImageGetter.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "HttpGPTImageGetter.h" 6 | #include 7 | #include 8 | 9 | #ifdef UE_INLINE_GENERATED_CPP_BY_NAME 10 | #include UE_INLINE_GENERATED_CPP_BY_NAME(HttpGPTImageGetter) 11 | #endif 12 | 13 | UHttpGPTImageGetter::UHttpGPTImageGetter(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer), GeneratedImages(0u), DataSize(0u) 14 | { 15 | } 16 | 17 | void UHttpGPTImageGetter::RequestSent() 18 | { 19 | OnStatusChanged.ExecuteIfBound("Request Sent. Waiting for the response..."); 20 | } 21 | 22 | void UHttpGPTImageGetter::RequestFailed() 23 | { 24 | OnStatusChanged.ExecuteIfBound("Request Failed. Check the Logs."); 25 | Destroy(); 26 | } 27 | 28 | void UHttpGPTImageGetter::ProcessCompleted(const FHttpGPTImageResponse& Response) 29 | { 30 | if (!Response.bSuccess) 31 | { 32 | const FStringFormatOrderedArguments Arguments_ErrorDetails{ 33 | FString("Request Failed."), FString("Please check the logs. (Enable internal logs in Project Settings -> Plugins -> HttpGPT)."), 34 | FString("Error Details: "), FString("\tError Code: ") + Response.Error.Code.ToString(), 35 | FString("\tError Type: ") + Response.Error.Type.ToString(), FString("\tError Message: ") + Response.Error.Message 36 | }; 37 | 38 | OnStatusChanged.ExecuteIfBound(FString::Format(TEXT("{0}\n{1}\n\n{2}\n{3}\n{4}\n{5}"), Arguments_ErrorDetails)); 39 | Destroy(); 40 | return; 41 | } 42 | 43 | DataSize = Response.Data.Num(); 44 | OnStatusChanged.ExecuteIfBound("Request Completed."); 45 | 46 | OnImageGenerated_Internal.BindUFunction(this, TEXT("ImageGenerated")); 47 | 48 | for (const FHttpGPTImageData& Data : Response.Data) 49 | { 50 | ProcessImage(Data); 51 | } 52 | } 53 | 54 | void UHttpGPTImageGetter::ProcessImage(const FHttpGPTImageData& Data) 55 | { 56 | UHttpGPTImageHelper::GenerateImage(Data, OnImageGenerated_Internal); 57 | } 58 | 59 | void UHttpGPTImageGetter::ImageGenerated(UTexture2D* const Texture) 60 | { 61 | OnImageGenerated.ExecuteIfBound(Texture); 62 | 63 | ++GeneratedImages; 64 | if (GeneratedImages >= DataSize) 65 | { 66 | if (OutScrollBox.IsValid()) 67 | { 68 | OutScrollBox->ScrollToEnd(); 69 | } 70 | 71 | Destroy(); 72 | } 73 | } 74 | 75 | void UHttpGPTImageGetter::Destroy() 76 | { 77 | ClearFlags(RF_Standalone); 78 | 79 | #if ENGINE_MAJOR_VERSION >= 5 80 | MarkAsGarbage(); 81 | #else 82 | MarkPendingKill(); 83 | #endif 84 | } 85 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/Chat/HttpGPTMessagingHandler.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "HttpGPTMessagingHandler.h" 6 | #include 7 | #include 8 | 9 | #ifdef UE_INLINE_GENERATED_CPP_BY_NAME 10 | #include UE_INLINE_GENERATED_CPP_BY_NAME(HttpGPTMessagingHandler) 11 | #endif 12 | 13 | UHttpGPTMessagingHandler::UHttpGPTMessagingHandler(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) 14 | { 15 | } 16 | 17 | void UHttpGPTMessagingHandler::RequestSent() 18 | { 19 | OnMessageContentUpdated.ExecuteIfBound("Waiting for response..."); 20 | } 21 | 22 | void UHttpGPTMessagingHandler::RequestFailed() 23 | { 24 | OnMessageContentUpdated.ExecuteIfBound( 25 | "Request Failed.\nPlease check the logs. (Enable internal logs in Project Settings -> Plugins -> HttpGPT)."); 26 | Destroy(); 27 | } 28 | 29 | void UHttpGPTMessagingHandler::ProcessUpdated(const FHttpGPTChatResponse& Response) 30 | { 31 | ProcessResponse(Response); 32 | } 33 | 34 | void UHttpGPTMessagingHandler::ProcessCompleted(const FHttpGPTChatResponse& Response) 35 | { 36 | ProcessResponse(Response); 37 | Destroy(); 38 | } 39 | 40 | void UHttpGPTMessagingHandler::ProcessResponse(const FHttpGPTChatResponse& Response) 41 | { 42 | bool bScrollToEnd = false; 43 | if (ScrollBoxReference.IsValid()) 44 | { 45 | bScrollToEnd = FMath::Abs(ScrollBoxReference->GetScrollOffsetOfEnd() - ScrollBoxReference->GetScrollOffset()) <= 8.f; 46 | } 47 | 48 | if (!Response.bSuccess) 49 | { 50 | const FStringFormatOrderedArguments Arguments_ErrorDetails{ 51 | FString("Request Failed."), FString("Please check the logs. (Enable internal logs in Project Settings -> Plugins -> HttpGPT)."), 52 | FString("Error Details: "), FString("\tError Code: ") + Response.Error.Code.ToString(), 53 | FString("\tError Type: ") + Response.Error.Type.ToString(), FString("\tError Message: ") + Response.Error.Message 54 | }; 55 | 56 | OnMessageContentUpdated.ExecuteIfBound(FString::Format(TEXT("{0}\n{1}\n\n{2}\n{3}\n{4}\n{5}"), Arguments_ErrorDetails)); 57 | } 58 | else if (Response.bSuccess && !HttpGPT::Internal::HasEmptyParam(Response.Choices)) 59 | { 60 | OnMessageContentUpdated.ExecuteIfBound(Response.Choices[0].Message.Content); 61 | } 62 | else 63 | { 64 | return; 65 | } 66 | 67 | if (ScrollBoxReference.IsValid() && bScrollToEnd) 68 | { 69 | ScrollBoxReference->ScrollToEnd(); 70 | } 71 | } 72 | 73 | void UHttpGPTMessagingHandler::Destroy() 74 | { 75 | ClearFlags(RF_Standalone); 76 | 77 | #if ENGINE_MAJOR_VERSION >= 5 78 | MarkAsGarbage(); 79 | #else 80 | MarkPendingKill(); 81 | #endif 82 | } 83 | -------------------------------------------------------------------------------- /Config/DefaultHttpGPT.ini: -------------------------------------------------------------------------------- 1 | [CoreRedirects] 2 | ;v1.5.0 3 | ; Classes 4 | +ClassRedirects = (OldName="/Script/HttpGPT.HttpGPTRequest", NewName="/Script/HttpGPTChatModule.HttpGPTChatRequest") 5 | +ClassRedirects = (OldName="/Script/HttpGPT.HttpGPTHelper", NewName="/Script/HttpGPTCommonModule.HttpGPTHelper") 6 | +ClassRedirects = (OldName="/Script/HttpGPT.HttpGPTSettings", NewName="/Script/HttpGPTCommonModule.HttpGPTSettings") 7 | 8 | ; Delegates 9 | +ClassRedirects = (OldName="/Script/HttpGPT.HttpGPTResponseDelegate", NewName="/Script/HttpGPTChatModule.HttpGPTChatResponseDelegate") 10 | +ClassRedirects = (OldName="/Script/HttpGPT.HttpGPTGenericDelegate", NewName="/Script/HttpGPTCommonModule.HttpGPTGenericDelegate") 11 | 12 | ; Enumerations 13 | +EnumRedirects = (OldName="/Script/HttpGPT.EHttpGPTRole",NewName="/Script/HttpGPTCommonModule.EHttpGPTChatRole") 14 | +EnumRedirects = (OldName="/Script/HttpGPT.EHttpGPTModel",NewName="/Script/HttpGPTCommonModule.EHttpGPTChatModel") 15 | 16 | ; Structures 17 | +StructRedirects = (OldName="/Script/HttpGPT.HttpGPTError",NewName="/Script/HttpGPTCommonModule.HttpGPTCommonError") 18 | +StructRedirects = (OldName="/Script/HttpGPT.HttpGPTResponse",NewName="/Script/HttpGPTCommonModule.HttpGPTChatResponse") 19 | +StructRedirects = (OldName="/Script/HttpGPT.HttpGPTMessage",NewName="/Script/HttpGPTCommonModule.HttpGPTChatMessage") 20 | +StructRedirects = (OldName="/Script/HttpGPT.HttpGPTChoice",NewName="/Script/HttpGPTCommonModule.HttpGPTChatChoice") 21 | +StructRedirects = (OldName="/Script/HttpGPT.HttpGPTUsage",NewName="/Script/HttpGPTCommonModule.HttpGPTChatUsage") 22 | +StructRedirects = (OldName="/Script/HttpGPT.HttpGPTOptions",NewName="/Script/HttpGPTCommonModule.HttpGPTChatOptions") 23 | 24 | ; Properties 25 | +PropertyRedirects = (OldName="/Script/HttpGPTCommonModule.HttpGPTChatOptions.APIKey", NewName="/Script/HttpGPTCommonModule.HttpGPTCommonOptions.APIKey") 26 | +PropertyRedirects = (OldName="/Script/HttpGPTCommonModule.HttpGPTChatOptions.User", NewName="/Script/HttpGPTCommonModule.HttpGPTCommonOptions.User") 27 | +PropertyRedirects = (OldName="/Script/HttpGPTChatModule.HttpGPTChatRequest.SendMessage_CustomOptions.Options", NewName="/Script/HttpGPTChatModule.HttpGPTChatRequest.SendMessage_CustomOptions.ChatOptions") 28 | +PropertyRedirects = (OldName="/Script/HttpGPTChatModule.HttpGPTChatRequest.SendMessages_CustomOptions.Options", NewName="/Script/HttpGPTChatModule.HttpGPTChatRequest.SendMessages_CustomOptions.ChatOptions") 29 | 30 | ; Functions 31 | +FunctionRedirects = (OldName="/Script/HttpGPTChatModule.HttpGPTChatRequest.SendMessage", NewName="/Script/HttpGPTChatModule.HttpGPTChatRequest.SendMessage_DefaultOptions") 32 | +FunctionRedirects = (OldName="/Script/HttpGPTChatModule.HttpGPTChatRequest.SendMessages", NewName="/Script/HttpGPTChatModule.HttpGPTChatRequest.SendMessages_DefaultOptions") 33 | +FunctionRedirects = (OldName="/Script/HttpGPTChatModule.HttpGPTChatRequest.GetTaskOptions", NewName="/Script/HttpGPTChatModule.HttpGPTChatRequest.GetChatOptions") -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Public/Tasks/HttpGPTBaseTask.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "Structures/HttpGPTCommonTypes.h" 12 | #include "HttpGPTBaseTask.generated.h" 13 | 14 | DECLARE_DYNAMIC_MULTICAST_DELEGATE(FHttpGPTGenericDelegate); 15 | 16 | /** 17 | * 18 | */ 19 | UCLASS(NotPlaceable, Category = "HttpGPT", meta = (ExposedAsyncProxy = AsyncTask)) 20 | class HTTPGPTCOMMONMODULE_API UHttpGPTBaseTask : public UBlueprintAsyncActionBase 21 | { 22 | GENERATED_BODY() 23 | 24 | friend class UHttpGPTTaskStatus; 25 | 26 | public: 27 | UPROPERTY(BlueprintAssignable, Category = "HttpGPT") 28 | FHttpGPTGenericDelegate RequestFailed; 29 | 30 | UPROPERTY(BlueprintAssignable, Category = "HttpGPT") 31 | FHttpGPTGenericDelegate RequestSent; 32 | 33 | virtual void Activate() override; 34 | 35 | UFUNCTION(BlueprintCallable, Category = "HttpGPT", meta = (DisplayName = "Stop HttpGPT Task")) 36 | void StopHttpGPTTask(); 37 | 38 | virtual void SetReadyToDestroy() override; 39 | 40 | UFUNCTION(BlueprintPure, Category = "HttpGPT", Meta = (DisplayName = "Get API Key")) 41 | const FHttpGPTCommonOptions GetCommonOptions() const; 42 | 43 | protected: 44 | TSharedPtr HttpRequest; 45 | FHttpGPTCommonOptions CommonOptions; 46 | 47 | mutable FCriticalSection Mutex; 48 | 49 | virtual bool CanActivateTask() const; 50 | virtual bool CanBindProgress() const; 51 | virtual FString GetEndpointURL() const; 52 | 53 | void SendRequest(); 54 | 55 | /* Return true if contains error */ 56 | const bool CheckError(const TSharedPtr& JsonObject, FHttpGPTCommonError& OutputError) const; 57 | 58 | void InitializeRequest(); 59 | void BindRequestCallbacks(); 60 | 61 | virtual FString SetRequestContent() { return FString(); }; 62 | 63 | virtual void OnProgressUpdated(const FString& Content, int32 BytesSent, int32 BytesReceived) 64 | { 65 | }; 66 | 67 | virtual void OnProgressCompleted(const FString& Content, const bool bWasSuccessful) 68 | { 69 | }; 70 | 71 | bool bInitialized = false; 72 | bool bIsReadyToDestroy = false; 73 | bool bIsTaskActive = false; 74 | 75 | #if WITH_EDITOR 76 | bool bIsEditorTask = false; 77 | bool bEndingPIE = false; 78 | 79 | virtual void PrePIEEnded(bool bIsSimulating); 80 | #endif 81 | }; 82 | 83 | UCLASS(NotPlaceable, Category = "HttpGPT") 84 | class HTTPGPTCOMMONMODULE_API UHttpGPTTaskStatus final : public UBlueprintFunctionLibrary 85 | { 86 | GENERATED_BODY() 87 | 88 | public: 89 | UFUNCTION(BlueprintPure, Category = "HttpGPT") 90 | static bool IsTaskActive(const UHttpGPTBaseTask* Test); 91 | 92 | UFUNCTION(BlueprintPure, Category = "HttpGPT") 93 | static bool IsTaskReadyToDestroy(const UHttpGPTBaseTask* Test); 94 | 95 | UFUNCTION(BlueprintPure, Category = "HttpGPT") 96 | static bool IsTaskStillValid(const UHttpGPTBaseTask* Test); 97 | }; 98 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Public/Utils/HttpGPTHelper.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "Structures/HttpGPTChatTypes.h" 10 | #include "Structures/HttpGPTImageTypes.h" 11 | #include "HttpGPTHelper.generated.h" 12 | 13 | /** 14 | * 15 | */ 16 | UCLASS(NotPlaceable, Category = "HttpGPT") 17 | class HTTPGPTCOMMONMODULE_API UHttpGPTHelper final : public UBlueprintFunctionLibrary 18 | { 19 | GENERATED_BODY() 20 | 21 | public: 22 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat", meta = (DisplayName = "Convert HttpGPT Model to Name")) 23 | static const FName ModelToName(const EHttpGPTChatModel Model); 24 | 25 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat", meta = (DisplayName = "Convert Name to HttpGPT Model")) 26 | static const EHttpGPTChatModel NameToModel(const FName Model); 27 | 28 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat", meta = (DisplayName = "Convert HttpGPT Role to Name")) 29 | static const FName RoleToName(const EHttpGPTChatRole Role); 30 | 31 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat", meta = (DisplayName = "Convert Name to HttpGPT Role")) 32 | static const EHttpGPTChatRole NameToRole(const FName Role); 33 | 34 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat", meta = (DisplayName = "Convert HttpGPT Param Type to Name")) 35 | static const FName PropertyTypeToName(const EHttpGPTPropertyType Type); 36 | 37 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat", meta = (DisplayName = "Convert Name to HttpGPT Param Type")) 38 | static const EHttpGPTPropertyType NameToPropertyType(const FName Type); 39 | 40 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat", meta = (DisplayName = "Get Available GPT Models")) 41 | static const TArray GetAvailableGPTModels(); 42 | 43 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat", meta = (DisplayName = "Get Endpoint for Model")) 44 | static const FString GetEndpointForModel(const EHttpGPTChatModel Model, const bool bIsAzureOpenAI = false, 45 | const FString& AzureOpenAIAPIVersion = TEXT("None")); 46 | 47 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat", meta = (DisplayName = "Model Supports Chat")) 48 | static const bool ModelSupportsChat(const EHttpGPTChatModel Model); 49 | 50 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Image", meta = (DisplayName = "Convert HttpGPT Size to Name")) 51 | static const FName SizeToName(const EHttpGPTImageSize Size); 52 | 53 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Image", meta = (DisplayName = "Convert Name to HttpGPT Size")) 54 | static const EHttpGPTImageSize NameToSize(const FName Size); 55 | 56 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Image", meta = (DisplayName = "Convert HttpGPT Format to Name")) 57 | static const FName FormatToName(const EHttpGPTResponseFormat Format); 58 | 59 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Image", meta = (DisplayName = "Convert Name to HttpGPT Format")) 60 | static const EHttpGPTResponseFormat NameToFormat(const FName Format); 61 | }; 62 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Public/Structures/HttpGPTImageTypes.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | // These structures are defined in the common module due to being used in both modules, to avoid cyclic dependencies. 6 | 7 | #pragma once 8 | 9 | #include 10 | #include "Structures/HttpGPTCommonTypes.h" 11 | #include "HttpGPTImageTypes.generated.h" 12 | 13 | UENUM(BlueprintType, Category = "HttpGPT | Image", Meta = (DisplayName = "HttpGPT Image Size")) 14 | enum class EHttpGPTImageSize : uint8 15 | { 16 | x256 UMETA(DisplayName = "256x256"), 17 | x512 UMETA(DisplayName = "512x512"), 18 | x1024 UMETA(DisplayName = "1024x1024"), 19 | }; 20 | 21 | UENUM(BlueprintType, Category = "HttpGPT | Image", Meta = (DisplayName = "HttpGPT Image Response Format")) 22 | enum class EHttpGPTResponseFormat : uint8 23 | { 24 | url UMETA(DisplayName = "URL"), 25 | b64_json UMETA(DisplayName = "B64")}; 26 | 27 | USTRUCT(BlueprintType, Category = "HttpGPT | Image", Meta = (DisplayName = "HttpGPT Image Data")) 28 | struct HTTPGPTCOMMONMODULE_API FHttpGPTImageData 29 | { 30 | GENERATED_BODY() 31 | 32 | FHttpGPTImageData() = default; 33 | 34 | FHttpGPTImageData(const FString& Data, const EHttpGPTResponseFormat& DataFormat) : Content(Data), Format(DataFormat) 35 | { 36 | } 37 | 38 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Image") 39 | FString Content; 40 | 41 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Image") 42 | EHttpGPTResponseFormat Format = EHttpGPTResponseFormat::b64_json; 43 | }; 44 | 45 | USTRUCT(BlueprintType, Category = "HttpGPT | Image", Meta = (DisplayName = "HttpGPT Image Response")) 46 | struct HTTPGPTCOMMONMODULE_API FHttpGPTImageResponse 47 | { 48 | GENERATED_BODY() 49 | 50 | FHttpGPTImageResponse() = default; 51 | 52 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Image") 53 | int32 Created = 0; 54 | 55 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Image") 56 | TArray Data; 57 | 58 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Image") 59 | bool bSuccess = false; 60 | 61 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Image") 62 | FHttpGPTCommonError Error; 63 | }; 64 | 65 | USTRUCT(BlueprintType, Category = "HttpGPT | Image", Meta = (DisplayName = "HttpGPT Image Options")) 66 | struct HTTPGPTCOMMONMODULE_API FHttpGPTImageOptions 67 | { 68 | GENERATED_BODY() 69 | 70 | FHttpGPTImageOptions(); 71 | 72 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Image", 73 | Meta = (DisplayName = "Number of Images", ClampMin = "1", UIMin = "1", ClampMax = "10", UIMax = "10")) 74 | int32 ImagesNum; 75 | 76 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Image", Meta = (DisplayName = "Image Size")) 77 | EHttpGPTImageSize Size = EHttpGPTImageSize::x256; 78 | 79 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Image", Meta = (DisplayName = "Response Format")) 80 | EHttpGPTResponseFormat Format = EHttpGPTResponseFormat::b64_json; 81 | 82 | private: 83 | void SetDefaults(); 84 | }; 85 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/ImageGen/SHttpGPTImageGenItemData.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "SHttpGPTImageGenItemData.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | void SHttpGPTImageGenItemData::Construct(const FArguments& InArgs) 12 | { 13 | Texture = InArgs._Texture; 14 | 15 | ChildSlot 16 | [ 17 | ConstructContent() 18 | ]; 19 | } 20 | 21 | TSharedRef SHttpGPTImageGenItemData::ConstructContent() 22 | { 23 | constexpr float SlotPadding = 4.0f; 24 | 25 | return SNew(SVerticalBox) 26 | + SVerticalBox::Slot().Padding(SlotPadding).FillHeight(1.f) 27 | [ 28 | SNew(SImage).Image(Texture.IsValid() ? new FSlateImageBrush(Texture.Get(), FVector2D(256.f, 256.f)) : nullptr) 29 | ] 30 | + SVerticalBox::Slot().Padding(SlotPadding).AutoHeight() 31 | [ 32 | SNew(SButton) 33 | .Text(FText::FromString(TEXT("Save"))) 34 | .HAlign(HAlign_Center) 35 | .OnClicked(this, &SHttpGPTImageGenItemData::HandleSaveButton) 36 | .IsEnabled(this, &SHttpGPTImageGenItemData::IsSaveEnabled) 37 | ]; 38 | } 39 | 40 | FReply SHttpGPTImageGenItemData::HandleSaveButton() 41 | { 42 | const FString AssetName = FString::FromInt(Texture->GetUniqueID()); 43 | FString TargetFilename = FPaths::Combine(TEXT("/Game/"), UHttpGPTSettings::Get()->GeneratedImagesDir, AssetName); 44 | FPaths::NormalizeFilename(TargetFilename); 45 | 46 | UPackage* const Package = CreatePackage(*TargetFilename); 47 | UTexture2D* const SavedTexture = NewObject(Package, *AssetName, RF_Public | RF_Standalone); 48 | 49 | #if ENGINE_MAJOR_VERSION >= 5 50 | SavedTexture->SetPlatformData(Texture->GetPlatformData()); 51 | #else 52 | SavedTexture->PlatformData = Texture->PlatformData; 53 | #endif 54 | 55 | SavedTexture->UpdateResource(); 56 | 57 | #if ENGINE_MAJOR_VERSION >= 5 58 | SavedTexture->Source.Init(Texture->GetSizeX(), Texture->GetSizeY(), 1, Texture->GetPlatformData()->Mips.Num(), TSF_BGRA8); 59 | #else 60 | SavedTexture->Source.Init(Texture->GetSizeX(), Texture->GetSizeY(), 1, Texture->PlatformData->Mips.Num(), ETextureSourceFormat::TSF_BGRA8); 61 | #endif 62 | 63 | SavedTexture->PostEditChange(); 64 | 65 | SavedTexture->MarkPackageDirty(); 66 | FAssetRegistryModule::AssetCreated(SavedTexture); 67 | 68 | const FString TempPackageFilename = FPackageName::LongPackageNameToFilename(Package->GetName(), FPackageName::GetAssetPackageExtension()); 69 | 70 | #if ENGINE_MAJOR_VERSION >= 5 71 | FSavePackageArgs SaveArgs; 72 | SaveArgs.SaveFlags = RF_Public | RF_Standalone; 73 | UPackage::SavePackage(Package, SavedTexture, *TempPackageFilename, SaveArgs); 74 | #else 75 | UPackage::SavePackage(Package, SavedTexture, RF_Public | RF_Standalone, *TempPackageFilename); 76 | #endif 77 | 78 | TArray SyncAssets; 79 | SyncAssets.Add(FAssetData(SavedTexture)); 80 | GEditor->SyncBrowserToObjects(SyncAssets); 81 | 82 | return FReply::Handled(); 83 | } 84 | 85 | bool SHttpGPTImageGenItemData::IsSaveEnabled() const 86 | { 87 | return Texture.IsValid(); 88 | } 89 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/HttpGPTEditorModule.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "HttpGPTEditorModule.h" 6 | #include "Chat/SHttpGPTChatShell.h" 7 | #include "ImageGen/SHttpGPTImageGenView.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | static const FName HttpGPTChatTabName("HttpGPTChat"); 14 | static const FName HttpGPTImageGeneratorTabName("HttpGPTImageGenerator"); 15 | 16 | #define LOCTEXT_NAMESPACE "FHttpGPTEditorModule" 17 | 18 | void FHttpGPTEditorModule::StartupModule() 19 | { 20 | const FSimpleDelegate RegisterDelegate = FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FHttpGPTEditorModule::RegisterMenus); 21 | UToolMenus::RegisterStartupCallback(RegisterDelegate); 22 | } 23 | 24 | void FHttpGPTEditorModule::ShutdownModule() 25 | { 26 | UToolMenus::UnRegisterStartupCallback(this); 27 | UToolMenus::UnregisterOwner(this); 28 | 29 | FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(HttpGPTChatTabName); 30 | } 31 | 32 | TSharedRef FHttpGPTEditorModule::OnSpawnTab(const FSpawnTabArgs& SpawnTabArgs) const 33 | { 34 | const FName TabId = *SpawnTabArgs.GetTabId().ToString(); 35 | 36 | TSharedPtr OutContent; 37 | if (TabId.IsEqual(HttpGPTChatTabName)) 38 | { 39 | OutContent = SNew(SHttpGPTChatShell); 40 | } 41 | else if (TabId.IsEqual(HttpGPTImageGeneratorTabName)) 42 | { 43 | OutContent = SNew(SHttpGPTImageGenView); 44 | } 45 | 46 | if (OutContent.IsValid()) 47 | { 48 | return SNew(SDockTab).TabRole(NomadTab) 49 | [ 50 | OutContent.ToSharedRef() 51 | ]; 52 | } 53 | 54 | return SNew(SDockTab); 55 | } 56 | 57 | void FHttpGPTEditorModule::RegisterMenus() 58 | { 59 | FToolMenuOwnerScoped OwnerScoped(this); 60 | const FOnSpawnTab EditorTabSpawnerDelegate = FOnSpawnTab::CreateRaw(this, &FHttpGPTEditorModule::OnSpawnTab); 61 | 62 | #if ENGINE_MAJOR_VERSION < 5 63 | const FName AppStyleName = FEditorStyle::GetStyleSetName(); 64 | #else 65 | const FName AppStyleName = FAppStyle::GetAppStyleSetName(); 66 | #endif 67 | 68 | const TSharedRef Menu = WorkspaceMenu::GetMenuStructure().GetToolsCategory()->AddGroup( 69 | LOCTEXT("HttpGPTCategory", "HttpGPT"), LOCTEXT("HttpGPTCategoryTooltip", "HttpGPT Plugin Tabs"), 70 | FSlateIcon(AppStyleName, "Icons.Documentation")); 71 | 72 | FGlobalTabmanager::Get()->RegisterNomadTabSpawner(HttpGPTChatTabName, EditorTabSpawnerDelegate). 73 | SetDisplayName(FText::FromString(TEXT("HttpGPT Chat"))).SetTooltipText(FText::FromString(TEXT("Open HttpGPT Chat"))). 74 | SetIcon(FSlateIcon(AppStyleName, "DerivedData.ResourceUsage")).SetGroup(Menu); 75 | 76 | FGlobalTabmanager::Get()->RegisterNomadTabSpawner(HttpGPTImageGeneratorTabName, EditorTabSpawnerDelegate). 77 | SetDisplayName(FText::FromString(TEXT("HttpGPT Image Generator"))).SetTooltipText( 78 | FText::FromString(TEXT("Open HttpGPT Image Generator"))).SetIcon( 79 | FSlateIcon(AppStyleName, "LevelEditor.Tabs.Viewports")).SetGroup(Menu); 80 | } 81 | 82 | #undef LOCTEXT_NAMESPACE 83 | 84 | IMPLEMENT_MODULE(FHttpGPTEditorModule, HttpGPTEditor) 85 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Private/Structures/HttpGPTChatTypes.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "Structures/HttpGPTChatTypes.h" 6 | #include "Utils/HttpGPTHelper.h" 7 | #include 8 | #include 9 | 10 | #ifdef UE_INLINE_GENERATED_CPP_BY_NAME 11 | #include UE_INLINE_GENERATED_CPP_BY_NAME(HttpGPTChatTypes) 12 | #endif 13 | 14 | TSharedPtr FHttpGPTFunction::GetFunction() const 15 | { 16 | TSharedPtr JsonObject = MakeShared(); 17 | JsonObject->SetStringField("name", Name.ToString()); 18 | JsonObject->SetStringField("description", Description); 19 | 20 | const TSharedPtr ParametersObject = MakeShared(); 21 | ParametersObject->SetStringField("type", "object"); 22 | 23 | const TSharedPtr PropertiesObject = MakeShared(); 24 | for (const FHttpGPTFunctionProperty& PropIt : Properties) 25 | { 26 | TSharedPtr PropertyObject = MakeShared(); 27 | PropertyObject->SetStringField("type", UHttpGPTHelper::PropertyTypeToName(PropIt.Type).ToString().ToLower()); 28 | PropertyObject->SetStringField("description", PropIt.Description); 29 | 30 | TArray> EnumArr; 31 | for (const FName& EnumIt : PropIt.Enum) 32 | { 33 | EnumArr.Emplace(MakeShared(EnumIt.ToString())); 34 | } 35 | PropertyObject->SetArrayField("enum", EnumArr); 36 | 37 | PropertiesObject->SetObjectField(PropIt.Name.ToString(), PropertyObject); 38 | } 39 | 40 | ParametersObject->SetObjectField("properties", PropertiesObject); 41 | 42 | TArray> RequiredParams; 43 | for (const FName& ReqIt : RequiredProperties) 44 | { 45 | RequiredParams.Emplace(MakeShared(ReqIt.ToString())); 46 | } 47 | 48 | ParametersObject->SetArrayField("required", RequiredParams); 49 | JsonObject->SetObjectField("parameters", ParametersObject); 50 | 51 | return MakeShared(JsonObject); 52 | } 53 | 54 | FHttpGPTChatMessage::FHttpGPTChatMessage(const FName& InRole, const FString& InContent) 55 | { 56 | Role = UHttpGPTHelper::NameToRole(InRole); 57 | Content = InContent; 58 | } 59 | 60 | TSharedPtr FHttpGPTChatMessage::GetMessage() const 61 | { 62 | TSharedPtr JsonObject = MakeShared(); 63 | JsonObject->SetStringField("role", UHttpGPTHelper::RoleToName(Role).ToString().ToLower()); 64 | 65 | if (Role == EHttpGPTChatRole::Function) 66 | { 67 | JsonObject->SetStringField("name", FunctionCall.Name.ToString()); 68 | JsonObject->SetStringField("content", FunctionCall.Arguments); 69 | } 70 | else 71 | { 72 | JsonObject->SetStringField("content", Content); 73 | } 74 | 75 | return MakeShared(JsonObject); 76 | } 77 | 78 | FHttpGPTChatOptions::FHttpGPTChatOptions() 79 | { 80 | SetDefaults(); 81 | } 82 | 83 | void FHttpGPTChatOptions::SetDefaults() 84 | { 85 | if (const UHttpGPTSettings* const Settings = GetDefault()) 86 | { 87 | Model = Settings->ChatOptions.Model; 88 | MaxTokens = Settings->ChatOptions.MaxTokens; 89 | Temperature = Settings->ChatOptions.Temperature; 90 | TopP = Settings->ChatOptions.TopP; 91 | Choices = Settings->ChatOptions.Choices; 92 | bStream = Settings->ChatOptions.bStream; 93 | Stop = Settings->ChatOptions.Stop; 94 | PresencePenalty = Settings->ChatOptions.PresencePenalty; 95 | FrequencyPenalty = Settings->ChatOptions.FrequencyPenalty; 96 | LogitBias = Settings->ChatOptions.LogitBias; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/ImageGen/SHttpGPTImageGenItem.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "SHttpGPTImageGenItem.h" 6 | #include "SHttpGPTImageGenItemData.h" 7 | #include "HttpGPTImageGetter.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void SHttpGPTImageGenItem::Construct(const FArguments& InArgs) 15 | { 16 | Prompt = InArgs._Prompt; 17 | 18 | HttpGPTImageGetterObject = NewObject(); 19 | HttpGPTImageGetterObject->SetFlags(RF_Standalone); 20 | HttpGPTImageGetterObject->OutScrollBox = InArgs._OutScrollBox; 21 | 22 | HttpGPTImageGetterObject->OnImageGenerated.BindLambda([this](UTexture2D* const Texture) 23 | { 24 | if (Texture && ItemViewBox.IsValid()) 25 | { 26 | ItemViewBox->AddSlot().AutoWidth()[SNew(SHttpGPTImageGenItemData).Texture(Texture)]; 27 | } 28 | }); 29 | 30 | HttpGPTImageGetterObject->OnStatusChanged.BindLambda([this](const FString& NewStatus) 31 | { 32 | if (HttpGPT::Internal::HasEmptyParam(NewStatus) || !Status.IsValid()) 33 | { 34 | return; 35 | } 36 | 37 | Status->SetText(FText::FromString(TEXT("Status: ") + NewStatus)); 38 | }); 39 | 40 | FHttpGPTImageOptions Options; 41 | Options.Format = EHttpGPTResponseFormat::b64_json; 42 | Options.Size = UHttpGPTHelper::NameToSize(*InArgs._Size); 43 | Options.ImagesNum = FCString::Atoi(*InArgs._Num); 44 | 45 | RequestReference = UHttpGPTImageRequest::EditorTask(Prompt, Options); 46 | 47 | RequestReference->ProcessCompleted.AddDynamic(HttpGPTImageGetterObject.Get(), &UHttpGPTImageGetter::ProcessCompleted); 48 | RequestReference->ErrorReceived.AddDynamic(HttpGPTImageGetterObject.Get(), &UHttpGPTImageGetter::ProcessCompleted); 49 | RequestReference->RequestFailed.AddDynamic(HttpGPTImageGetterObject.Get(), &UHttpGPTImageGetter::RequestFailed); 50 | RequestReference->RequestSent.AddDynamic(HttpGPTImageGetterObject.Get(), &UHttpGPTImageGetter::RequestSent); 51 | 52 | RequestReference->Activate(); 53 | 54 | ChildSlot 55 | [ 56 | ConstructContent() 57 | ]; 58 | } 59 | 60 | SHttpGPTImageGenItem::~SHttpGPTImageGenItem() 61 | { 62 | if (RequestReference.IsValid()) 63 | { 64 | RequestReference->StopHttpGPTTask(); 65 | } 66 | } 67 | 68 | TSharedRef SHttpGPTImageGenItem::ConstructContent() 69 | { 70 | constexpr float SlotPadding = 4.0f; 71 | 72 | #if ENGINE_MAJOR_VERSION < 5 73 | using FAppStyle = FEditorStyle; 74 | #endif 75 | 76 | return SNew(SBox).Padding(SlotPadding) 77 | [ 78 | SNew(SBorder).BorderImage(FAppStyle::Get().GetBrush("Menu.Background")) 79 | [ 80 | SNew(SVerticalBox) 81 | + SVerticalBox::Slot().Padding(SlotPadding).AutoHeight() 82 | [ 83 | SNew(SVerticalBox) 84 | + SVerticalBox::Slot().AutoHeight() 85 | [ 86 | SNew(STextBlock).Font(FCoreStyle::GetDefaultFontStyle("Bold", 10)).Text(FText::FromString(TEXT("Prompt: ") + Prompt)) 87 | ] 88 | + SVerticalBox::Slot().AutoHeight() 89 | [ 90 | SAssignNew(Status, STextBlock).Text(FText::FromString(TEXT("Status: Sending request..."))) 91 | ] 92 | ] 93 | + SVerticalBox::Slot().Padding(SlotPadding).FillHeight(1.f) 94 | [ 95 | SNew(SScrollBox).Orientation(Orient_Horizontal) 96 | + SScrollBox::Slot() 97 | [ 98 | SAssignNew(ItemViewBox, SHorizontalBox) 99 | ] 100 | ] 101 | ] 102 | ]; 103 | } 104 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/Chat/SHttpGPTChatItem.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "SHttpGPTChatItem.h" 6 | #include "HttpGPTMessagingHandler.h" 7 | #include 8 | 9 | void SHttpGPTChatItem::Construct(const FArguments& InArgs) 10 | { 11 | MessageRole = InArgs._MessageRole; 12 | InputText = InArgs._InputText; 13 | 14 | if (MessageRole == EHttpGPTChatRole::Assistant) 15 | { 16 | MessagingHandlerObject = NewObject(); 17 | MessagingHandlerObject->SetFlags(RF_Standalone); 18 | MessagingHandlerObject->ScrollBoxReference = InArgs._ScrollBox; 19 | 20 | MessagingHandlerObject->OnMessageContentUpdated.BindLambda([this](FString Content) 21 | { 22 | if (!Message.IsValid()) 23 | { 24 | return; 25 | } 26 | 27 | #if ENGINE_MAJOR_VERSION > 5 || (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1) 28 | const FTextSelection SelectedText = Message->GetSelection(); 29 | Message->SetText(FText::FromString(Content)); 30 | Message->SelectText(SelectedText.GetBeginning(), SelectedText.GetEnd()); 31 | #else 32 | Message->SetText(FText::FromString(Content)); 33 | #endif 34 | }); 35 | } 36 | 37 | ChildSlot 38 | [ 39 | ConstructContent() 40 | ]; 41 | } 42 | 43 | static FSlateColor& operator*=(FSlateColor& Lhs, const float Rhs) 44 | { 45 | FLinearColor NewColor = Lhs.GetSpecifiedColor() * Rhs; 46 | NewColor.A = 1.f; 47 | Lhs = FSlateColor(NewColor); 48 | 49 | return Lhs; 50 | } 51 | 52 | TSharedRef SHttpGPTChatItem::ConstructContent() 53 | { 54 | constexpr float SlotPadding = 4.0f; 55 | constexpr float PaddingMultiplier = 32.0f; 56 | 57 | FText RoleText = FText::FromString(TEXT("User:")); 58 | FMargin BoxMargin(SlotPadding * PaddingMultiplier, SlotPadding, SlotPadding, SlotPadding); 59 | FSlateColor MessageColor(FLinearColor::White); 60 | 61 | if (MessageRole == EHttpGPTChatRole::Assistant) 62 | { 63 | RoleText = FText::FromString(TEXT("Assistant:")); 64 | BoxMargin = FMargin(SlotPadding, SlotPadding, SlotPadding * PaddingMultiplier, SlotPadding); 65 | MessageColor *= 0.3f; 66 | } 67 | else if (MessageRole == EHttpGPTChatRole::System) 68 | { 69 | RoleText = FText::FromString(TEXT("System:")); 70 | BoxMargin = FMargin(SlotPadding * PaddingMultiplier * 0.5f, SlotPadding); 71 | MessageColor *= 0.f; 72 | } 73 | 74 | const FMargin MessageMargin(SlotPadding * 4.f, SlotPadding, SlotPadding, SlotPadding); 75 | 76 | #if ENGINE_MAJOR_VERSION < 5 77 | using FAppStyle = FEditorStyle; 78 | #endif 79 | 80 | return SNew(SBox).Padding(BoxMargin) 81 | [ 82 | SNew(SBorder).BorderImage(FAppStyle::Get().GetBrush("Menu.Background")).BorderBackgroundColor(MessageColor) 83 | [ 84 | SNew(SVerticalBox) 85 | + SVerticalBox::Slot().Padding(SlotPadding).AutoHeight() 86 | [ 87 | SAssignNew(Role, STextBlock).Font(FCoreStyle::GetDefaultFontStyle("Bold", 10)).Text(RoleText) 88 | ] 89 | + SVerticalBox::Slot().Padding(MessageMargin).FillHeight(1.f) 90 | [ 91 | SAssignNew(Message, SMultiLineEditableText).AllowMultiLine(true).AutoWrapText(true).IsReadOnly(true).AllowContextMenu(true).Text( 92 | FText::FromString(InputText)) 93 | ] 94 | ] 95 | ]; 96 | } 97 | 98 | FString SHttpGPTChatItem::GetRoleText() const 99 | { 100 | return Role.IsValid() ? Role->GetText().ToString() : FString(); 101 | } 102 | 103 | FString SHttpGPTChatItem::GetMessageText() const 104 | { 105 | return Message.IsValid() ? Message->GetText().ToString() : FString(); 106 | } 107 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Public/Management/HttpGPTSettings.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "Structures/HttpGPTCommonTypes.h" 10 | #include "Structures/HttpGPTChatTypes.h" 11 | #include "Structures/HttpGPTImageTypes.h" 12 | #include "HttpGPTSettings.generated.h" 13 | 14 | /** 15 | * 16 | */ 17 | UCLASS(Config = Plugins, DefaultConfig, meta = (DisplayName = "HttpGPT")) 18 | class HTTPGPTCOMMONMODULE_API UHttpGPTSettings : public UDeveloperSettings 19 | { 20 | GENERATED_BODY() 21 | 22 | public: 23 | explicit UHttpGPTSettings(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); 24 | 25 | static const UHttpGPTSettings* Get(); 26 | 27 | UPROPERTY(GlobalConfig, EditAnywhere, Category = "Default Options", Meta = (DisplayName = "Common Options")) 28 | FHttpGPTCommonOptions CommonOptions; 29 | 30 | UPROPERTY(GlobalConfig, EditAnywhere, Category = "Default Options", Meta = (DisplayName = "Chat Options")) 31 | FHttpGPTChatOptions ChatOptions; 32 | 33 | UPROPERTY(GlobalConfig, EditAnywhere, Category = "Default Options", Meta = (DisplayName = "Image Options")) 34 | FHttpGPTImageOptions ImageOptions; 35 | 36 | /* Enable custom system context in HttpGPT Chat Editor Tool */ 37 | UPROPERTY(GlobalConfig, EditAnywhere, Category = "Editor | HttpGPT Chat", Meta = (DisplayName = "Use Custom System Context")) 38 | bool bUseCustomSystemContext; 39 | 40 | /* Custom system context to use in HttpGPT Chat Editor Tool */ 41 | UPROPERTY(GlobalConfig, EditAnywhere, Category = "Editor | HttpGPT Chat", 42 | Meta = (DisplayName = "Custom System Context", EditCondition = "bUseCustomSystemContext")) 43 | FString CustomSystemContext; 44 | 45 | /* Directory to store images generated by HttpGPT Image Generator Editor Tool */ 46 | UPROPERTY(GlobalConfig, EditAnywhere, Category = "Editor | HttpGPT Image Generator", Meta = (DisplayName = "Generated Images Directory")) 47 | FString GeneratedImagesDir; 48 | 49 | /* Will print extra internal informations in log */ 50 | UPROPERTY(GlobalConfig, EditAnywhere, Category = "Logging", Meta = (DisplayName = "Enable Internal Logs")) 51 | bool bEnableInternalLogs; 52 | 53 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Settings", 54 | meta = (HidePin = "Self", DefaultToSelf = "Self", DisplayName = "Get Common Options", CompactNodeTitle = "HttpGPT Common Options")) 55 | static FHttpGPTCommonOptions GetCommonOptions(); 56 | 57 | UFUNCTION(BlueprintCallable, Category = "HttpGPT | Settings", 58 | meta = (HidePin = "Self", DefaultToSelf = "Self", DisplayName = "Set Common Options")) 59 | static void SetCommonOptions(const FHttpGPTCommonOptions& Value); 60 | 61 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Settings", 62 | meta = (HidePin = "Self", DefaultToSelf = "Self", DisplayName = "Get Chat Options", CompactNodeTitle = "HttpGPT Chat Options")) 63 | static FHttpGPTChatOptions GetChatOptions(); 64 | 65 | UFUNCTION(BlueprintCallable, Category = "HttpGPT | Settings", meta = (HidePin = "Self", DefaultToSelf = "Self", DisplayName = "Set Chat Options")) 66 | static void SetChatOptions(const FHttpGPTChatOptions& Value); 67 | 68 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Settings", 69 | meta = (HidePin = "Self", DefaultToSelf = "Self", DisplayName = "Get Image Options", CompactNodeTitle = "HttpGPT Image Options")) 70 | static FHttpGPTImageOptions GetImageOptions(); 71 | 72 | UFUNCTION(BlueprintCallable, Category = "HttpGPT | Settings", 73 | meta = (HidePin = "Self", DefaultToSelf = "Self", DisplayName = "Set Image Options")) 74 | static void SetImageOptions(const FHttpGPTImageOptions& Value); 75 | 76 | protected: 77 | #if WITH_EDITOR 78 | virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; 79 | #endif 80 | 81 | virtual void PostInitProperties() override; 82 | 83 | virtual void SetToDefaults(); 84 | 85 | void SaveAndReload(const FName& PropertyName); 86 | 87 | private: 88 | void ToggleInternalLogs(); 89 | }; 90 | -------------------------------------------------------------------------------- /Source/HttpGPTImageModule/Public/Tasks/HttpGPTImageRequest.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | // Author: Lucas Vilas-Boas 8 | // Year: 2023 9 | // Repo: https://github.com/lucoiso/UEHttpGPT 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "HttpGPTImageRequest.generated.h" 19 | 20 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FHttpGPTImageResponseDelegate, const FHttpGPTImageResponse&, Response); 21 | 22 | /** 23 | * 24 | */ 25 | UCLASS(NotPlaceable, Category = "HttpGPT | Image", meta = (ExposedAsyncProxy = AsyncTask)) 26 | class HTTPGPTIMAGEMODULE_API UHttpGPTImageRequest : public UHttpGPTBaseTask 27 | { 28 | GENERATED_BODY() 29 | 30 | public: 31 | UPROPERTY(BlueprintAssignable, Category = "HttpGPT | Image") 32 | FHttpGPTImageResponseDelegate ProcessCompleted; 33 | 34 | UPROPERTY(BlueprintAssignable, Category = "HttpGPT | Image") 35 | FHttpGPTImageResponseDelegate ProgressUpdated; 36 | 37 | UPROPERTY(BlueprintAssignable, Category = "HttpGPT | Image") 38 | FHttpGPTImageResponseDelegate ProgressStarted; 39 | 40 | UPROPERTY(BlueprintAssignable, Category = "HttpGPT | Image") 41 | FHttpGPTImageResponseDelegate ErrorReceived; 42 | 43 | #if WITH_EDITOR 44 | static UHttpGPTImageRequest* EditorTask(const FString& Prompt, const FHttpGPTImageOptions Options); 45 | #endif 46 | 47 | UFUNCTION(BlueprintCallable, Category = "HttpGPT | Image | Default", 48 | meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Request Images with Default Options")) 49 | static UHttpGPTImageRequest* RequestImages_DefaultOptions(UObject* const WorldContextObject, const FString& Prompt); 50 | 51 | UFUNCTION(BlueprintCallable, Category = "HttpGPT | Image | Custom", 52 | meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Request Images with Custom Options")) 53 | static UHttpGPTImageRequest* RequestImages_CustomOptions(UObject* const WorldContextObject, const FString& Prompt, 54 | const FHttpGPTCommonOptions CommonOptions, const FHttpGPTImageOptions ImageOptions); 55 | 56 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Image") 57 | const FHttpGPTImageOptions GetImageOptions() const; 58 | 59 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Image") 60 | const FString GetPrompt() const; 61 | 62 | protected: 63 | FString Prompt; 64 | FHttpGPTImageOptions ImageOptions; 65 | 66 | virtual bool CanActivateTask() const override; 67 | virtual bool CanBindProgress() const override; 68 | virtual FString GetEndpointURL() const override; 69 | 70 | virtual FString SetRequestContent() override; 71 | virtual void OnProgressCompleted(const FString& Content, const bool bWasSuccessful) override; 72 | 73 | void DeserializeResponse(const FString& Content); 74 | 75 | private: 76 | FHttpGPTImageResponse Response; 77 | }; 78 | 79 | DECLARE_DYNAMIC_DELEGATE_OneParam(FHttpGPTImageGenerate, class UTexture2D*, Image); 80 | 81 | UCLASS(NotPlaceable, Category = "HttpGPT | Image", Meta = (DisplayName = "HttpGPT Image Helper")) 82 | class HTTPGPTIMAGEMODULE_API UHttpGPTImageHelper final : public UBlueprintFunctionLibrary 83 | { 84 | GENERATED_BODY() 85 | 86 | public: 87 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Image", Meta = (DisplayName = "Cast to HttpGPT Image Request")) 88 | static UHttpGPTImageRequest* CastToHttpGPTImageRequest(UObject* const Object); 89 | 90 | UFUNCTION(BlueprintCallable, Category = "HttpGPT | Image") 91 | static void GenerateImage(const FHttpGPTImageData& ImageData, const FHttpGPTImageGenerate& Callback); 92 | 93 | private: 94 | static void GenerateImageFromURL(const FHttpGPTImageData& ImageData, const FHttpGPTImageGenerate& Callback); 95 | static void GenerateImageFromB64(const FHttpGPTImageData& ImageData, const FHttpGPTImageGenerate& Callback); 96 | }; 97 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/ImageGen/SHttpGPTImageGenView.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "SHttpGPTImageGenView.h" 6 | #include "SHttpGPTImageGenItem.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | void SHttpGPTImageGenView::Construct(const FArguments& InArgs) 13 | { 14 | InitializeImageNumOptions(); 15 | ImageNumComboBox = SNew(STextComboBox).OptionsSource(&ImageNum).InitiallySelectedItem(ImageNum[0]).ToolTipText( 16 | FText::FromString(TEXT("Number of Generated Images"))); 17 | 18 | InitializeImageSizeOptions(); 19 | ImageSizeComboBox = SNew(STextComboBox).OptionsSource(&ImageSize).InitiallySelectedItem(ImageSize[0]).ToolTipText( 20 | FText::FromString(TEXT("Size of Generated Images"))); 21 | 22 | ChildSlot 23 | [ 24 | ConstructContent() 25 | ]; 26 | } 27 | 28 | TSharedRef SHttpGPTImageGenView::ConstructContent() 29 | { 30 | constexpr float SlotPadding = 4.0f; 31 | 32 | return SNew(SVerticalBox) 33 | + SVerticalBox::Slot().Padding(SlotPadding).FillHeight(1.f) 34 | [ 35 | SAssignNew(ViewScrollBox, SScrollBox) 36 | + SScrollBox::Slot() 37 | [ 38 | SAssignNew(ViewBox, SVerticalBox) 39 | ] 40 | ] 41 | + SVerticalBox::Slot().Padding(SlotPadding).AutoHeight() 42 | [ 43 | SNew(SHorizontalBox) 44 | + SHorizontalBox::Slot().Padding(SlotPadding).FillWidth(1.f) 45 | [ 46 | SAssignNew(InputTextBox, SEditableTextBox).AllowContextMenu(true).IsReadOnly(false).OnTextCommitted_Lambda( 47 | [this]([[maybe_unused]] const FText& Text, ETextCommit::Type CommitType) 48 | { 49 | if (IsSendRequestEnabled() && CommitType == ETextCommit::OnEnter) 50 | { 51 | HandleSendRequestButton(); 52 | } 53 | }) 54 | ] 55 | + SHorizontalBox::Slot().Padding(SlotPadding).AutoWidth() 56 | [ 57 | SNew(SButton) 58 | .Text(FText::FromString(TEXT("Generate"))) 59 | .ToolTipText(FText::FromString(TEXT("Request Images Generation"))) 60 | .OnClicked(this, &SHttpGPTImageGenView::HandleSendRequestButton) 61 | .IsEnabled(this, &SHttpGPTImageGenView::IsSendRequestEnabled) 62 | ] 63 | + SHorizontalBox::Slot().Padding(SlotPadding).AutoWidth() 64 | [ 65 | ImageNumComboBox.ToSharedRef() 66 | ] 67 | + SHorizontalBox::Slot().Padding(SlotPadding).AutoWidth() 68 | [ 69 | ImageSizeComboBox.ToSharedRef() 70 | ] 71 | + SHorizontalBox::Slot().Padding(SlotPadding).AutoWidth() 72 | [ 73 | SNew(SButton) 74 | .Text(FText::FromString(TEXT("Clear"))) 75 | .ToolTipText(FText::FromString(TEXT("Clear Generation History"))) 76 | .OnClicked(this, &SHttpGPTImageGenView::HandleClearViewButton) 77 | .IsEnabled(this, &SHttpGPTImageGenView::IsClearViewEnabled) 78 | ] 79 | ]; 80 | } 81 | 82 | FReply SHttpGPTImageGenView::HandleSendRequestButton() 83 | { 84 | ViewBox->AddSlot().AutoHeight()[SNew(SHttpGPTImageGenItem) 85 | .OutScrollBox(ViewScrollBox) 86 | .Prompt(InputTextBox->GetText().ToString()) 87 | .Num(*ImageNumComboBox->GetSelectedItem().Get()) 88 | .Size(*ImageSizeComboBox->GetSelectedItem().Get())]; 89 | 90 | ViewScrollBox->ScrollToEnd(); 91 | InputTextBox->SetText(FText::GetEmpty()); 92 | 93 | return FReply::Handled(); 94 | } 95 | 96 | bool SHttpGPTImageGenView::IsSendRequestEnabled() const 97 | { 98 | return !HttpGPT::Internal::HasEmptyParam(InputTextBox->GetText()); 99 | } 100 | 101 | FReply SHttpGPTImageGenView::HandleClearViewButton() 102 | { 103 | ViewBox->ClearChildren(); 104 | return FReply::Handled(); 105 | } 106 | 107 | bool SHttpGPTImageGenView::IsClearViewEnabled() const 108 | { 109 | return ViewBox->NumSlots() > 0; 110 | } 111 | 112 | void SHttpGPTImageGenView::InitializeImageNumOptions() 113 | { 114 | constexpr uint8 MaxNum = 10u; 115 | for (uint8 Iterator = 1u; Iterator <= MaxNum; ++Iterator) 116 | { 117 | ImageNum.Add(MakeShared(FString::FromInt(Iterator))); 118 | } 119 | } 120 | 121 | void SHttpGPTImageGenView::InitializeImageSizeOptions() 122 | { 123 | ImageSize.Add(MakeShared(UHttpGPTHelper::SizeToName(EHttpGPTImageSize::x256).ToString())); 124 | ImageSize.Add(MakeShared(UHttpGPTHelper::SizeToName(EHttpGPTImageSize::x512).ToString())); 125 | ImageSize.Add(MakeShared(UHttpGPTHelper::SizeToName(EHttpGPTImageSize::x1024).ToString())); 126 | } 127 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Private/Management/HttpGPTSettings.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "Management/HttpGPTSettings.h" 6 | #include "LogHttpGPT.h" 7 | #include 8 | 9 | #ifdef UE_INLINE_GENERATED_CPP_BY_NAME 10 | #include UE_INLINE_GENERATED_CPP_BY_NAME(HttpGPTSettings) 11 | #endif 12 | 13 | UHttpGPTSettings::UHttpGPTSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer), bUseCustomSystemContext(false), 14 | CustomSystemContext(FString()), 15 | GeneratedImagesDir("HttpGPT_Generated"), bEnableInternalLogs(false) 16 | { 17 | CategoryName = TEXT("Plugins"); 18 | 19 | SetToDefaults(); 20 | } 21 | 22 | const UHttpGPTSettings* UHttpGPTSettings::Get() 23 | { 24 | const UHttpGPTSettings* const Instance = GetDefault(); 25 | return Instance; 26 | } 27 | 28 | FHttpGPTCommonOptions UHttpGPTSettings::GetCommonOptions() 29 | { 30 | return GetDefault()->CommonOptions; 31 | } 32 | 33 | void UHttpGPTSettings::SetCommonOptions(const FHttpGPTCommonOptions& Value) 34 | { 35 | UHttpGPTSettings* const Settings = GetMutableDefault(); 36 | Settings->CommonOptions = Value; 37 | 38 | Settings->SaveAndReload(GET_MEMBER_NAME_CHECKED(UHttpGPTSettings, CommonOptions)); 39 | } 40 | 41 | FHttpGPTChatOptions UHttpGPTSettings::GetChatOptions() 42 | { 43 | return GetDefault()->ChatOptions; 44 | } 45 | 46 | void UHttpGPTSettings::SetChatOptions(const FHttpGPTChatOptions& Value) 47 | { 48 | UHttpGPTSettings* const Settings = GetMutableDefault(); 49 | Settings->ChatOptions = Value; 50 | 51 | Settings->SaveAndReload(GET_MEMBER_NAME_CHECKED(UHttpGPTSettings, ChatOptions)); 52 | } 53 | 54 | FHttpGPTImageOptions UHttpGPTSettings::GetImageOptions() 55 | { 56 | return GetDefault()->ImageOptions; 57 | } 58 | 59 | void UHttpGPTSettings::SetImageOptions(const FHttpGPTImageOptions& Value) 60 | { 61 | UHttpGPTSettings* const Settings = GetMutableDefault(); 62 | Settings->ImageOptions = Value; 63 | 64 | Settings->SaveAndReload(GET_MEMBER_NAME_CHECKED(UHttpGPTSettings, ImageOptions)); 65 | } 66 | 67 | #if WITH_EDITOR 68 | void UHttpGPTSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) 69 | { 70 | Super::PostEditChangeProperty(PropertyChangedEvent); 71 | 72 | if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UHttpGPTSettings, bEnableInternalLogs)) 73 | { 74 | ToggleInternalLogs(); 75 | } 76 | } 77 | #endif 78 | 79 | void UHttpGPTSettings::PostInitProperties() 80 | { 81 | Super::PostInitProperties(); 82 | ToggleInternalLogs(); 83 | } 84 | 85 | void UHttpGPTSettings::SetToDefaults() 86 | { 87 | CommonOptions.APIKey = NAME_None; 88 | CommonOptions.User = NAME_None; 89 | CommonOptions.bIsAzureOpenAI = false; 90 | CommonOptions.Endpoint = TEXT("https://api.openai.com/"); 91 | CommonOptions.AzureOpenAIAPIVersion = TEXT("2023-05-15"); 92 | 93 | ChatOptions.Model = EHttpGPTChatModel::gpt35turbo; 94 | ChatOptions.MaxTokens = 2048; 95 | ChatOptions.Temperature = 1.f; 96 | ChatOptions.TopP = 1.f; 97 | ChatOptions.Choices = 1; 98 | ChatOptions.bStream = true; 99 | ChatOptions.Stop = TArray(); 100 | ChatOptions.PresencePenalty = 0.f; 101 | ChatOptions.FrequencyPenalty = 0.f; 102 | ChatOptions.LogitBias = TMap(); 103 | 104 | ImageOptions.ImagesNum = 1; 105 | ImageOptions.Size = EHttpGPTImageSize::x256; 106 | ImageOptions.Format = EHttpGPTResponseFormat::url; 107 | } 108 | 109 | void UHttpGPTSettings::SaveAndReload(const FName& PropertyName) 110 | { 111 | SaveConfig(); 112 | 113 | uint32 PropagationFlags = 0u; 114 | 115 | #if ENGINE_MAJOR_VERSION >= 5 116 | PropagationFlags = UE::ELoadConfigPropagationFlags::LCPF_PropagateToChildDefaultObjects; 117 | #else 118 | PropagationFlags = UE4::ELoadConfigPropagationFlags::LCPF_PropagateToChildDefaultObjects; 119 | #endif 120 | 121 | ReloadConfig(GetClass(), *GetDefaultConfigFilename(), PropagationFlags, GetClass()->FindPropertyByName(PropertyName)); 122 | } 123 | 124 | void UHttpGPTSettings::ToggleInternalLogs() 125 | { 126 | #if !UE_BUILD_SHIPPING 127 | LogHttpGPT_Internal.SetVerbosity(bEnableInternalLogs ? ELogVerbosity::Display : ELogVerbosity::NoLogging); 128 | #endif 129 | } 130 | -------------------------------------------------------------------------------- /Source/HttpGPTChatModule/Public/Tasks/HttpGPTChatRequest.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "HttpGPTChatRequest.generated.h" 13 | 14 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FHttpGPTChatResponseDelegate, const FHttpGPTChatResponse&, Response); 15 | 16 | /** 17 | * 18 | */ 19 | UCLASS(NotPlaceable, Category = "HttpGPT | Chat", meta = (ExposedAsyncProxy = AsyncTask)) 20 | class HTTPGPTCHATMODULE_API UHttpGPTChatRequest : public UHttpGPTBaseTask 21 | { 22 | GENERATED_BODY() 23 | 24 | public: 25 | UPROPERTY(BlueprintAssignable, Category = "HttpGPT | Chat") 26 | FHttpGPTChatResponseDelegate ProcessCompleted; 27 | 28 | UPROPERTY(BlueprintAssignable, Category = "HttpGPT | Chat") 29 | FHttpGPTChatResponseDelegate ProgressUpdated; 30 | 31 | UPROPERTY(BlueprintAssignable, Category = "HttpGPT | Chat") 32 | FHttpGPTChatResponseDelegate ProgressStarted; 33 | 34 | UPROPERTY(BlueprintAssignable, Category = "HttpGPT | Chat") 35 | FHttpGPTChatResponseDelegate ErrorReceived; 36 | 37 | #if WITH_EDITOR 38 | static UHttpGPTChatRequest* EditorTask(const TArray& Messages, const FHttpGPTChatOptions& Options); 39 | #endif 40 | 41 | UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Default", 42 | meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Message with Default Options", 43 | AutoCreateRefTerm = "Functions")) 44 | static UHttpGPTChatRequest* SendMessage_DefaultOptions(UObject* const WorldContextObject, const FString& Message, 45 | const TArray& Functions); 46 | 47 | UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Default", 48 | meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Messages with Default Options", 49 | AutoCreateRefTerm = "Functions")) 50 | static UHttpGPTChatRequest* SendMessages_DefaultOptions(UObject* const WorldContextObject, const TArray& Messages, 51 | const TArray& Functions); 52 | 53 | UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Custom", 54 | meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Message with Custom Options", 55 | AutoCreateRefTerm = "Functions")) 56 | static UHttpGPTChatRequest* SendMessage_CustomOptions(UObject* const WorldContextObject, const FString& Message, 57 | const TArray& Functions, const FHttpGPTCommonOptions CommonOptions, 58 | const FHttpGPTChatOptions ChatOptions); 59 | 60 | UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Custom", 61 | meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Messages with Custom Options", 62 | AutoCreateRefTerm = "Functions")) 63 | static UHttpGPTChatRequest* SendMessages_CustomOptions(UObject* const WorldContextObject, const TArray& Messages, 64 | const TArray& Functions, const FHttpGPTCommonOptions CommonOptions, 65 | const FHttpGPTChatOptions ChatOptions); 66 | 67 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat") 68 | const FHttpGPTChatOptions GetChatOptions() const; 69 | 70 | protected: 71 | TArray Messages; 72 | TArray Functions; 73 | FHttpGPTChatOptions ChatOptions; 74 | 75 | virtual bool CanActivateTask() const override; 76 | virtual bool CanBindProgress() const override; 77 | virtual FString GetEndpointURL() const override; 78 | 79 | virtual FString SetRequestContent() override; 80 | virtual void OnProgressUpdated(const FString& Content, int32 BytesSent, int32 BytesReceived) override; 81 | virtual void OnProgressCompleted(const FString& Content, const bool bWasSuccessful) override; 82 | 83 | TArray GetDeltasFromContent(const FString& Content) const; 84 | 85 | void DeserializeStreamedResponse(const TArray& Deltas); 86 | void DeserializeSingleResponse(const FString& Content); 87 | 88 | private: 89 | FHttpGPTChatResponse Response; 90 | }; 91 | 92 | UCLASS(NotPlaceable, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Chat Helper")) 93 | class HTTPGPTCHATMODULE_API UHttpGPTChatHelper final : public UBlueprintFunctionLibrary 94 | { 95 | GENERATED_BODY() 96 | 97 | public: 98 | UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat", Meta = (DisplayName = "Cast to HttpGPT Chat Request")) 99 | static UHttpGPTChatRequest* CastToHttpGPTChatRequest(UObject* const Object); 100 | }; 101 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Private/Utils/HttpGPTHelper.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "Utils/HttpGPTHelper.h" 6 | #include "HttpGPTInternalFuncs.h" 7 | 8 | #ifdef UE_INLINE_GENERATED_CPP_BY_NAME 9 | #include UE_INLINE_GENERATED_CPP_BY_NAME(HttpGPTHelper) 10 | #endif 11 | 12 | const FName UHttpGPTHelper::ModelToName(const EHttpGPTChatModel Model) 13 | { 14 | switch (Model) 15 | { 16 | case EHttpGPTChatModel::gpt4: 17 | return "gpt-4"; 18 | 19 | case EHttpGPTChatModel::gpt432k: 20 | return "gpt-4-32k"; 21 | 22 | case EHttpGPTChatModel::gpt35turbo: 23 | return "gpt-3.5-turbo"; 24 | 25 | case EHttpGPTChatModel::gpt35turbo16k: 26 | return "gpt-3.5-turbo-16k"; 27 | 28 | case EHttpGPTChatModel::textdavinci003: 29 | return "text-davinci-003"; 30 | 31 | case EHttpGPTChatModel::textdavinci002: 32 | return "text-davinci-002"; 33 | 34 | case EHttpGPTChatModel::codedavinci002: 35 | return "code-davinci-002"; 36 | 37 | default: break; 38 | } 39 | 40 | return NAME_None; 41 | } 42 | 43 | const EHttpGPTChatModel UHttpGPTHelper::NameToModel(const FName Model) 44 | { 45 | if (Model.IsEqual("gpt-4", ENameCase::IgnoreCase)) 46 | { 47 | return EHttpGPTChatModel::gpt4; 48 | } 49 | if (Model.IsEqual("gpt-4-32k", ENameCase::IgnoreCase)) 50 | { 51 | return EHttpGPTChatModel::gpt432k; 52 | } 53 | if (Model.IsEqual("gpt-3.5-turbo", ENameCase::IgnoreCase)) 54 | { 55 | return EHttpGPTChatModel::gpt35turbo; 56 | } 57 | if (Model.IsEqual("gpt-3.5-turbo-16k", ENameCase::IgnoreCase)) 58 | { 59 | return EHttpGPTChatModel::gpt35turbo16k; 60 | } 61 | if (Model.IsEqual("text-davinci-003", ENameCase::IgnoreCase)) 62 | { 63 | return EHttpGPTChatModel::textdavinci003; 64 | } 65 | if (Model.IsEqual("text-davinci-002", ENameCase::IgnoreCase)) 66 | { 67 | return EHttpGPTChatModel::textdavinci002; 68 | } 69 | if (Model.IsEqual("code-davinci-002", ENameCase::IgnoreCase)) 70 | { 71 | return EHttpGPTChatModel::codedavinci002; 72 | } 73 | 74 | return EHttpGPTChatModel::gpt35turbo; 75 | } 76 | 77 | const FName UHttpGPTHelper::RoleToName(const EHttpGPTChatRole Role) 78 | { 79 | switch (Role) 80 | { 81 | case EHttpGPTChatRole::Assistant: 82 | return "assistant"; 83 | 84 | case EHttpGPTChatRole::User: 85 | return "user"; 86 | 87 | case EHttpGPTChatRole::System: 88 | return "system"; 89 | 90 | case EHttpGPTChatRole::Function: 91 | return "function"; 92 | 93 | default: 94 | break; 95 | } 96 | 97 | return NAME_None; 98 | } 99 | 100 | const EHttpGPTChatRole UHttpGPTHelper::NameToRole(const FName Role) 101 | { 102 | if (Role.IsEqual("user", ENameCase::IgnoreCase)) 103 | { 104 | return EHttpGPTChatRole::User; 105 | } 106 | if (Role.IsEqual("assistant", ENameCase::IgnoreCase)) 107 | { 108 | return EHttpGPTChatRole::Assistant; 109 | } 110 | if (Role.IsEqual("system", ENameCase::IgnoreCase)) 111 | { 112 | return EHttpGPTChatRole::System; 113 | } 114 | if (Role.IsEqual("function", ENameCase::IgnoreCase)) 115 | { 116 | return EHttpGPTChatRole::Function; 117 | } 118 | 119 | return EHttpGPTChatRole::User; 120 | } 121 | 122 | const FName UHttpGPTHelper::PropertyTypeToName(const EHttpGPTPropertyType Type) 123 | { 124 | switch (Type) 125 | { 126 | case EHttpGPTPropertyType::Boolean: 127 | return "bool"; 128 | 129 | case EHttpGPTPropertyType::Number: 130 | return "number"; 131 | 132 | case EHttpGPTPropertyType::String: 133 | return "string"; 134 | 135 | default: 136 | break; 137 | } 138 | 139 | return NAME_None; 140 | } 141 | 142 | const EHttpGPTPropertyType UHttpGPTHelper::NameToPropertyType(const FName Type) 143 | { 144 | if (Type.IsEqual("bool", ENameCase::IgnoreCase)) 145 | { 146 | return EHttpGPTPropertyType::Boolean; 147 | } 148 | if (Type.IsEqual("number", ENameCase::IgnoreCase)) 149 | { 150 | return EHttpGPTPropertyType::Number; 151 | } 152 | if (Type.IsEqual("string", ENameCase::IgnoreCase)) 153 | { 154 | return EHttpGPTPropertyType::String; 155 | } 156 | 157 | return EHttpGPTPropertyType::Boolean; 158 | } 159 | 160 | const TArray UHttpGPTHelper::GetAvailableGPTModels() 161 | { 162 | TArray Output; 163 | 164 | for (uint8 Iterator = static_cast(EHttpGPTChatModel::gpt4); Iterator <= static_cast(EHttpGPTChatModel::codedavinci002); ++Iterator) 165 | { 166 | if (const FName ModelName = ModelToName(static_cast(Iterator)); !HttpGPT::Internal::HasEmptyParam(ModelName)) 167 | { 168 | Output.Add(ModelName); 169 | } 170 | } 171 | 172 | return Output; 173 | } 174 | 175 | const FString UHttpGPTHelper::GetEndpointForModel(const EHttpGPTChatModel Model, const bool bIsAzureOpenAI, const FString& AzureOpenAIAPIVersion) 176 | { 177 | switch (Model) 178 | { 179 | case EHttpGPTChatModel::gpt4: 180 | case EHttpGPTChatModel::gpt432k: 181 | case EHttpGPTChatModel::gpt35turbo: 182 | case EHttpGPTChatModel::gpt35turbo16k: 183 | { 184 | if (bIsAzureOpenAI) 185 | { 186 | return FString::Format( 187 | TEXT("/openai/deployments/{0}/chat/completions?api-version={1}"), {ModelToName(Model).ToString(), AzureOpenAIAPIVersion}); 188 | } 189 | return "v1/chat/completions"; 190 | } 191 | 192 | case EHttpGPTChatModel::textdavinci003: 193 | case EHttpGPTChatModel::textdavinci002: 194 | case EHttpGPTChatModel::codedavinci002: 195 | { 196 | if (bIsAzureOpenAI) 197 | { 198 | return FString::Format( 199 | TEXT("/openai/deployments/{0}/completions?api-version={1}"), {ModelToName(Model).ToString(), AzureOpenAIAPIVersion}); 200 | } 201 | return "v1/completions"; 202 | } 203 | 204 | default: break; 205 | } 206 | 207 | return FString(); 208 | } 209 | 210 | const bool UHttpGPTHelper::ModelSupportsChat(const EHttpGPTChatModel Model) 211 | { 212 | switch (Model) 213 | { 214 | case EHttpGPTChatModel::gpt4: 215 | case EHttpGPTChatModel::gpt432k: 216 | case EHttpGPTChatModel::gpt35turbo: 217 | case EHttpGPTChatModel::gpt35turbo16k: 218 | return true; 219 | 220 | case EHttpGPTChatModel::textdavinci003: 221 | case EHttpGPTChatModel::textdavinci002: 222 | case EHttpGPTChatModel::codedavinci002: 223 | return false; 224 | 225 | default: break; 226 | } 227 | 228 | return false; 229 | } 230 | 231 | const FName UHttpGPTHelper::SizeToName(const EHttpGPTImageSize Size) 232 | { 233 | switch (Size) 234 | { 235 | case EHttpGPTImageSize::x256: 236 | return "256x256"; 237 | 238 | case EHttpGPTImageSize::x512: 239 | return "512x512"; 240 | 241 | case EHttpGPTImageSize::x1024: 242 | return "1024x1024"; 243 | 244 | default: 245 | break; 246 | } 247 | 248 | return NAME_None; 249 | } 250 | 251 | const EHttpGPTImageSize UHttpGPTHelper::NameToSize(const FName Size) 252 | { 253 | if (Size.IsEqual("256x256", ENameCase::IgnoreCase)) 254 | { 255 | return EHttpGPTImageSize::x256; 256 | } 257 | if (Size.IsEqual("512x512", ENameCase::IgnoreCase)) 258 | { 259 | return EHttpGPTImageSize::x512; 260 | } 261 | if (Size.IsEqual("1024x1024", ENameCase::IgnoreCase)) 262 | { 263 | return EHttpGPTImageSize::x1024; 264 | } 265 | 266 | return EHttpGPTImageSize::x256; 267 | } 268 | 269 | const FName UHttpGPTHelper::FormatToName(const EHttpGPTResponseFormat Format) 270 | { 271 | switch (Format) 272 | { 273 | case EHttpGPTResponseFormat::url: 274 | return "url"; 275 | 276 | case EHttpGPTResponseFormat::b64_json: 277 | return "b64_json"; 278 | 279 | default: 280 | break; 281 | } 282 | 283 | return NAME_None; 284 | } 285 | 286 | const EHttpGPTResponseFormat UHttpGPTHelper::NameToFormat(const FName Format) 287 | { 288 | if (Format.IsEqual("url", ENameCase::IgnoreCase)) 289 | { 290 | return EHttpGPTResponseFormat::url; 291 | } 292 | if (Format.IsEqual("b64_json", ENameCase::IgnoreCase)) 293 | { 294 | return EHttpGPTResponseFormat::b64_json; 295 | } 296 | 297 | return EHttpGPTResponseFormat::url; 298 | } 299 | -------------------------------------------------------------------------------- /Source/HttpGPTImageModule/Private/Tasks/HttpGPTImageRequest.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "Tasks/HttpGPTImageRequest.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #if WITH_EDITOR 25 | #include 26 | #endif 27 | 28 | #ifdef UE_INLINE_GENERATED_CPP_BY_NAME 29 | #include UE_INLINE_GENERATED_CPP_BY_NAME(HttpGPTImageRequest) 30 | #endif 31 | 32 | #if WITH_EDITOR 33 | UHttpGPTImageRequest* UHttpGPTImageRequest::EditorTask(const FString& Prompt, const FHttpGPTImageOptions Options) 34 | { 35 | UHttpGPTImageRequest* const NewAsyncTask = RequestImages_CustomOptions(GEditor->GetEditorWorldContext().World(), Prompt, FHttpGPTCommonOptions(), 36 | Options); 37 | NewAsyncTask->bIsEditorTask = true; 38 | 39 | return NewAsyncTask; 40 | } 41 | #endif 42 | 43 | UHttpGPTImageRequest* UHttpGPTImageRequest::RequestImages_DefaultOptions(UObject* const WorldContextObject, const FString& Prompt) 44 | { 45 | return RequestImages_CustomOptions(WorldContextObject, Prompt, FHttpGPTCommonOptions(), FHttpGPTImageOptions()); 46 | } 47 | 48 | UHttpGPTImageRequest* UHttpGPTImageRequest::RequestImages_CustomOptions(UObject* const WorldContextObject, const FString& Prompt, 49 | const FHttpGPTCommonOptions CommonOptions, 50 | const FHttpGPTImageOptions ImageOptions) 51 | { 52 | UHttpGPTImageRequest* const NewAsyncTask = NewObject(); 53 | NewAsyncTask->Prompt = Prompt; 54 | NewAsyncTask->CommonOptions = CommonOptions; 55 | NewAsyncTask->ImageOptions = ImageOptions; 56 | 57 | NewAsyncTask->RegisterWithGameInstance(WorldContextObject); 58 | 59 | return NewAsyncTask; 60 | } 61 | 62 | const FHttpGPTImageOptions UHttpGPTImageRequest::GetImageOptions() const 63 | { 64 | return ImageOptions; 65 | } 66 | 67 | const FString UHttpGPTImageRequest::GetPrompt() const 68 | { 69 | return Prompt; 70 | } 71 | 72 | bool UHttpGPTImageRequest::CanActivateTask() const 73 | { 74 | if (!Super::CanActivateTask()) 75 | { 76 | return false; 77 | } 78 | 79 | if (HttpGPT::Internal::HasEmptyParam(Prompt)) 80 | { 81 | UE_LOG(LogHttpGPT, Error, TEXT("%s (%d): Can't activate task: Invalid Prompt."), *FString(__FUNCTION__), GetUniqueID()); 82 | return false; 83 | } 84 | 85 | return true; 86 | } 87 | 88 | bool UHttpGPTImageRequest::CanBindProgress() const 89 | { 90 | return false; 91 | } 92 | 93 | FString UHttpGPTImageRequest::GetEndpointURL() const 94 | { 95 | if (CommonOptions.bIsAzureOpenAI) 96 | { 97 | return FString::Format( 98 | TEXT("{0}/openai/images/generations:submit?api-version={1}"), {GetCommonOptions().Endpoint, GetCommonOptions().AzureOpenAIAPIVersion}); 99 | } 100 | return FString::Format(TEXT("{0}/v1/images/generations"), {GetCommonOptions().Endpoint}); 101 | } 102 | 103 | FString UHttpGPTImageRequest::SetRequestContent() 104 | { 105 | FScopeLock Lock(&Mutex); 106 | 107 | if (!HttpRequest.IsValid()) 108 | { 109 | return FString(); 110 | } 111 | 112 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Mounting content"), *FString(__FUNCTION__), GetUniqueID()); 113 | 114 | const TSharedPtr JsonRequest = MakeShared(); 115 | JsonRequest->SetStringField("prompt", Prompt); 116 | JsonRequest->SetNumberField("n", GetImageOptions().ImagesNum); 117 | JsonRequest->SetStringField("size", UHttpGPTHelper::SizeToName(GetImageOptions().Size).ToString()); 118 | JsonRequest->SetStringField("response_format", UHttpGPTHelper::FormatToName(GetImageOptions().Format).ToString().ToLower()); 119 | 120 | if (!HttpGPT::Internal::HasEmptyParam(GetCommonOptions().User)) 121 | { 122 | JsonRequest->SetStringField("user", GetCommonOptions().User.ToString()); 123 | } 124 | 125 | FString RequestContentString; 126 | const TSharedRef> Writer = TJsonWriterFactory<>::Create(&RequestContentString); 127 | FJsonSerializer::Serialize(JsonRequest.ToSharedRef(), Writer); 128 | 129 | HttpRequest->SetContentAsString(RequestContentString); 130 | 131 | return RequestContentString; 132 | } 133 | 134 | void UHttpGPTImageRequest::OnProgressCompleted(const FString& Content, const bool bWasSuccessful) 135 | { 136 | FScopeLock Lock(&Mutex); 137 | 138 | if (!bWasSuccessful || HttpGPT::Internal::HasEmptyParam(Content)) 139 | { 140 | UE_LOG(LogHttpGPT, Error, TEXT("%s (%d): Request failed"), *FString(__FUNCTION__), GetUniqueID()); 141 | AsyncTask(ENamedThreads::GameThread, [this] 142 | { 143 | RequestFailed.Broadcast(); 144 | }); 145 | 146 | return; 147 | } 148 | 149 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Process Completed"), *FString(__FUNCTION__), GetUniqueID()); 150 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Content: %s"), *FString(__FUNCTION__), GetUniqueID(), *Content); 151 | 152 | DeserializeResponse(Content); 153 | 154 | if (Response.bSuccess) 155 | { 156 | AsyncTask(ENamedThreads::GameThread, [this] 157 | { 158 | FScopeLock Lock(&Mutex); 159 | ProcessCompleted.Broadcast(Response); 160 | }); 161 | } 162 | else 163 | { 164 | UE_LOG(LogHttpGPT, Error, TEXT("%s (%d): Request failed"), *FString(__FUNCTION__), GetUniqueID()); 165 | AsyncTask(ENamedThreads::GameThread, [this] 166 | { 167 | FScopeLock Lock(&Mutex); 168 | ErrorReceived.Broadcast(Response); 169 | }); 170 | } 171 | } 172 | 173 | void UHttpGPTImageRequest::DeserializeResponse(const FString& Content) 174 | { 175 | FScopeLock Lock(&Mutex); 176 | 177 | if (HttpGPT::Internal::HasEmptyParam(Content)) 178 | { 179 | return; 180 | } 181 | 182 | const TSharedRef> Reader = TJsonReaderFactory<>::Create(Content); 183 | TSharedPtr JsonResponse = MakeShared(); 184 | FJsonSerializer::Deserialize(Reader, JsonResponse); 185 | 186 | if (CheckError(JsonResponse, Response.Error)) 187 | { 188 | Response.bSuccess = false; 189 | return; 190 | } 191 | 192 | Response.bSuccess = true; 193 | Response.Created = JsonResponse->GetNumberField(TEXT("created")); 194 | 195 | const TArray> DataArray = JsonResponse->GetArrayField(TEXT("data")); 196 | for (auto Iterator = DataArray.CreateConstIterator(); Iterator; ++Iterator) 197 | { 198 | Response.Data.Add(FHttpGPTImageData( 199 | (*Iterator)->AsObject()->GetStringField(UHttpGPTHelper::FormatToName(GetImageOptions().Format).ToString()), GetImageOptions().Format)); 200 | } 201 | } 202 | 203 | UHttpGPTImageRequest* UHttpGPTImageHelper::CastToHttpGPTImageRequest(UObject* const Object) 204 | { 205 | return Cast(Object); 206 | } 207 | 208 | void UHttpGPTImageHelper::GenerateImage(const FHttpGPTImageData& ImageData, const FHttpGPTImageGenerate& Callback) 209 | { 210 | switch (ImageData.Format) 211 | { 212 | case EHttpGPTResponseFormat::url: 213 | GenerateImageFromURL(ImageData, Callback); 214 | break; 215 | 216 | case EHttpGPTResponseFormat::b64_json: 217 | GenerateImageFromB64(ImageData, Callback); 218 | break; 219 | 220 | default: 221 | break; 222 | } 223 | } 224 | 225 | void UHttpGPTImageHelper::GenerateImageFromURL(const FHttpGPTImageData& ImageData, const FHttpGPTImageGenerate& Callback) 226 | { 227 | const TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); 228 | HttpRequest->SetURL(ImageData.Content); 229 | HttpRequest->SetVerb("GET"); 230 | HttpRequest->OnProcessRequestComplete().BindLambda([=](FHttpRequestPtr Request, const FHttpResponsePtr& Response, bool bSuccess) 231 | { 232 | if (bSuccess && Response.IsValid()) 233 | { 234 | Callback.ExecuteIfBound(FImageUtils::ImportBufferAsTexture2D(Response->GetContent())); 235 | } 236 | else 237 | { 238 | Callback.ExecuteIfBound(nullptr); 239 | } 240 | }); 241 | HttpRequest->ProcessRequest(); 242 | } 243 | 244 | void UHttpGPTImageHelper::GenerateImageFromB64(const FHttpGPTImageData& ImageData, const FHttpGPTImageGenerate& Callback) 245 | { 246 | TArray DecodedBytes; 247 | FBase64::Decode(ImageData.Content, DecodedBytes); 248 | Callback.ExecuteIfBound(FImageUtils::ImportBufferAsTexture2D(DecodedBytes)); 249 | } 250 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Private/Tasks/HttpGPTBaseTask.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "Tasks/HttpGPTBaseTask.h" 6 | #include "Management/HttpGPTSettings.h" 7 | #include "HttpGPTInternalFuncs.h" 8 | #include "LogHttpGPT.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #if WITH_EDITOR 21 | #include 22 | #endif 23 | 24 | #ifdef UE_INLINE_GENERATED_CPP_BY_NAME 25 | #include UE_INLINE_GENERATED_CPP_BY_NAME(HttpGPTBaseTask) 26 | #endif 27 | 28 | void UHttpGPTBaseTask::Activate() 29 | { 30 | Super::Activate(); 31 | 32 | UE_LOG(LogHttpGPT, Display, TEXT("%s (%d): Activating task"), *FString(__FUNCTION__), GetUniqueID()); 33 | 34 | bIsTaskActive = true; 35 | if (!CommonOptions.Endpoint.EndsWith(TEXT("/"))) 36 | { 37 | CommonOptions.Endpoint += TEXT("/"); 38 | } 39 | 40 | if (!CanActivateTask()) 41 | { 42 | UE_LOG(LogHttpGPT, Error, TEXT("%s (%d): Failed to activate task."), *FString(__FUNCTION__), GetUniqueID()); 43 | RequestFailed.Broadcast(); 44 | SetReadyToDestroy(); 45 | return; 46 | } 47 | 48 | AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this] 49 | { 50 | SendRequest(); 51 | }); 52 | 53 | #if WITH_EDITOR 54 | if (bIsEditorTask) 55 | { 56 | SetFlags(RF_Standalone); 57 | } 58 | else 59 | { 60 | FEditorDelegates::PrePIEEnded.AddUObject(this, &UHttpGPTBaseTask::PrePIEEnded); 61 | } 62 | #endif 63 | } 64 | 65 | void UHttpGPTBaseTask::StopHttpGPTTask() 66 | { 67 | FScopeLock Lock(&Mutex); 68 | 69 | if (!bIsTaskActive) 70 | { 71 | return; 72 | } 73 | 74 | UE_LOG(LogHttpGPT, Display, TEXT("%s (%d): Stopping task"), *FString(__FUNCTION__), GetUniqueID()); 75 | 76 | bIsTaskActive = false; 77 | 78 | if (HttpRequest.IsValid()) 79 | { 80 | HttpRequest->CancelRequest(); 81 | HttpRequest.Reset(); 82 | } 83 | 84 | SetReadyToDestroy(); 85 | } 86 | 87 | void UHttpGPTBaseTask::SetReadyToDestroy() 88 | { 89 | FScopeLock Lock(&Mutex); 90 | 91 | if (bIsReadyToDestroy) 92 | { 93 | return; 94 | } 95 | 96 | UE_LOG(LogHttpGPT, Display, TEXT("%s (%d): Setting task as Ready to Destroy"), *FString(__FUNCTION__), GetUniqueID()); 97 | 98 | #if WITH_EDITOR 99 | if (bIsEditorTask) 100 | { 101 | ClearFlags(RF_Standalone); 102 | 103 | #if ENGINE_MAJOR_VERSION >= 5 104 | MarkAsGarbage(); 105 | #else 106 | MarkPendingKill(); 107 | #endif 108 | } 109 | 110 | if (FEditorDelegates::PrePIEEnded.IsBoundToObject(this)) 111 | { 112 | FEditorDelegates::PrePIEEnded.RemoveAll(this); 113 | } 114 | #endif 115 | 116 | bIsReadyToDestroy = true; 117 | bIsTaskActive = false; 118 | 119 | Super::SetReadyToDestroy(); 120 | } 121 | 122 | const FHttpGPTCommonOptions UHttpGPTBaseTask::GetCommonOptions() const 123 | { 124 | return CommonOptions; 125 | } 126 | 127 | #if WITH_EDITOR 128 | void UHttpGPTBaseTask::PrePIEEnded(bool bIsSimulating) 129 | { 130 | if (!IsValid(this)) 131 | { 132 | return; 133 | } 134 | 135 | UE_LOG(LogHttpGPT, Display, TEXT("%s (%d): Trying to finish task due to PIE end"), *FString(__FUNCTION__), GetUniqueID()); 136 | 137 | bEndingPIE = true; 138 | StopHttpGPTTask(); 139 | } 140 | #endif 141 | 142 | bool UHttpGPTBaseTask::CanActivateTask() const 143 | { 144 | if (HttpGPT::Internal::HasEmptyParam(GetCommonOptions().APIKey)) 145 | { 146 | UE_LOG(LogHttpGPT, Error, TEXT("%s (%d): Can't activate task: Invalid API Key."), *FString(__FUNCTION__), GetUniqueID()); 147 | return false; 148 | } 149 | 150 | return true; 151 | } 152 | 153 | bool UHttpGPTBaseTask::CanBindProgress() const 154 | { 155 | return true; 156 | } 157 | 158 | FString UHttpGPTBaseTask::GetEndpointURL() const 159 | { 160 | return FString(); 161 | } 162 | 163 | void UHttpGPTBaseTask::InitializeRequest() 164 | { 165 | FScopeLock Lock(&Mutex); 166 | 167 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Initializing request object"), *FString(__FUNCTION__), GetUniqueID()); 168 | 169 | HttpRequest = FHttpModule::Get().CreateRequest(); 170 | HttpRequest->SetURL(GetEndpointURL()); 171 | HttpRequest->SetVerb("POST"); 172 | HttpRequest->SetHeader("Content-Type", "application/json"); 173 | HttpRequest->SetHeader("Authorization", FString::Format(TEXT("Bearer {0}"), {GetCommonOptions().APIKey.ToString()})); 174 | } 175 | 176 | void UHttpGPTBaseTask::BindRequestCallbacks() 177 | { 178 | FScopeLock Lock(&Mutex); 179 | 180 | if (!HttpRequest.IsValid()) 181 | { 182 | return; 183 | } 184 | 185 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Binding callbacks"), *FString(__FUNCTION__), GetUniqueID()); 186 | 187 | if (CanBindProgress()) 188 | { 189 | #if ENGINE_MAJOR_VERSION > 5 || (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 4) 190 | HttpRequest->OnRequestProgress64().BindLambda([this](const FHttpRequestPtr& Request, int32 BytesSent, int32 BytesReceived) 191 | #else 192 | HttpRequest->OnRequestProgress().BindLambda([this](const FHttpRequestPtr& Request, int32 BytesSent, int32 BytesReceived) 193 | #endif 194 | { 195 | const FScopeTryLock Lock(&Mutex); 196 | 197 | if (!Lock.IsLocked() || !IsValid(this) || !bIsTaskActive || !Request.IsValid()) 198 | { 199 | return; 200 | } 201 | 202 | if (const FHttpResponsePtr Response = Request->GetResponse(); Response.IsValid()) 203 | { 204 | OnProgressUpdated(Response->GetContentAsString(), BytesSent, BytesReceived); 205 | } 206 | }); 207 | } 208 | 209 | HttpRequest->OnProcessRequestComplete().BindLambda([this](FHttpRequestPtr Request, const FHttpResponsePtr& RequestResponse, bool bWasSuccessful) 210 | { 211 | const FScopeTryLock Lock(&Mutex); 212 | 213 | if (!Lock.IsLocked() || !IsValid(this) || !bIsTaskActive) 214 | { 215 | return; 216 | } 217 | 218 | OnProgressCompleted(RequestResponse->GetContentAsString(), bWasSuccessful); 219 | SetReadyToDestroy(); 220 | }); 221 | } 222 | 223 | void UHttpGPTBaseTask::SendRequest() 224 | { 225 | FScopeLock Lock(&Mutex); 226 | 227 | InitializeRequest(); 228 | const FString ContentString = SetRequestContent(); 229 | BindRequestCallbacks(); 230 | 231 | if (!HttpRequest.IsValid()) 232 | { 233 | UE_LOG(LogHttpGPT, Error, TEXT("%s (%d): Failed to send request: Request object is invalid"), *FString(__FUNCTION__), GetUniqueID()); 234 | 235 | AsyncTask(ENamedThreads::GameThread, [this] 236 | { 237 | RequestFailed.Broadcast(); 238 | SetReadyToDestroy(); 239 | }); 240 | 241 | return; 242 | } 243 | 244 | UE_LOG(LogHttpGPT, Display, TEXT("%s (%d): Sending request"), *FString(__FUNCTION__), GetUniqueID()); 245 | 246 | if (HttpRequest->ProcessRequest()) 247 | { 248 | UE_LOG(LogHttpGPT, Display, TEXT("%s (%d): Request sent"), *FString(__FUNCTION__), GetUniqueID()); 249 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Request content body:\n%s"), *FString(__FUNCTION__), GetUniqueID(), *ContentString); 250 | 251 | AsyncTask(ENamedThreads::GameThread, [this] 252 | { 253 | RequestSent.Broadcast(); 254 | }); 255 | } 256 | else 257 | { 258 | UE_LOG(LogHttpGPT, Error, TEXT("%s (%d): Failed to initialize the request process"), *FString(__FUNCTION__), GetUniqueID()); 259 | AsyncTask(ENamedThreads::GameThread, [this] 260 | { 261 | RequestFailed.Broadcast(); 262 | SetReadyToDestroy(); 263 | }); 264 | } 265 | } 266 | 267 | const bool UHttpGPTBaseTask::CheckError(const TSharedPtr& JsonObject, FHttpGPTCommonError& OutputError) const 268 | { 269 | if (JsonObject->HasField(TEXT("error"))) 270 | { 271 | const TSharedPtr ErrorObj = JsonObject->GetObjectField(TEXT("error")); 272 | if (FString ErrorMessage; ErrorObj->TryGetStringField(TEXT("message"), ErrorMessage)) 273 | { 274 | OutputError.Message = *ErrorMessage; 275 | } 276 | if (FString ErrorCode; ErrorObj->TryGetStringField(TEXT("code"), ErrorCode)) 277 | { 278 | OutputError.Code = *ErrorCode; 279 | } 280 | if (FString ErrorType; ErrorObj->TryGetStringField(TEXT("type"), ErrorType)) 281 | { 282 | OutputError.Type = *ErrorType; 283 | } 284 | 285 | return true; 286 | } 287 | 288 | return false; 289 | } 290 | 291 | bool UHttpGPTTaskStatus::IsTaskActive(const UHttpGPTBaseTask* Test) 292 | { 293 | return IsValid(Test) && Test->bIsTaskActive; 294 | } 295 | 296 | bool UHttpGPTTaskStatus::IsTaskReadyToDestroy(const UHttpGPTBaseTask* Test) 297 | { 298 | return IsValid(Test) && Test->bIsReadyToDestroy; 299 | } 300 | 301 | bool UHttpGPTTaskStatus::IsTaskStillValid(const UHttpGPTBaseTask* Test) 302 | { 303 | bool bOutput = IsValid(Test) && !IsTaskReadyToDestroy(Test); 304 | 305 | #if WITH_EDITOR 306 | bOutput = bOutput && !Test->bEndingPIE; 307 | #endif 308 | 309 | return bOutput; 310 | } 311 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/Chat/SHttpGPTChatShell.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "SHttpGPTChatShell.h" 6 | #include "SHttpGPTChatView.h" 7 | #include 8 | #include 9 | #include 10 | 11 | using FOnChatSessionNameChanged = TDelegate; 12 | 13 | class SHttpGPTChatSessionOption : public STableRow 14 | { 15 | public: 16 | SLATE_BEGIN_ARGS(SHttpGPTChatSessionOption) 17 | { 18 | } 19 | 20 | SLATE_EVENT(FOnChatSessionNameChanged, OnNameChanged) 21 | SLATE_END_ARGS() 22 | 23 | void Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView, const FNamePtr& InItem) 24 | { 25 | OnNameChanged = InArgs._OnNameChanged; 26 | Item = InItem; 27 | 28 | STableRow::Construct( 29 | STableRow::FArguments().Padding(8.f).Content()[SAssignNew(SessionName, STextBlock) 30 | .Text(this, &SHttpGPTChatSessionOption::GetName) 31 | .ToolTipText(this, &SHttpGPTChatSessionOption::GetName)], InOwnerTableView); 32 | } 33 | 34 | FText GetName() const 35 | { 36 | return FText::FromName(Item.IsValid() ? *Item : TEXT("Invalid")); 37 | } 38 | 39 | void EnableEditMode() 40 | { 41 | const TSharedRef TextEntry = SNew(STextEntryPopup).Label(FText::FromString("Rename Session")).OnTextCommitted( 42 | this, &SHttpGPTChatSessionOption::OnNameCommited); 43 | 44 | FSlateApplication& SlateApp = FSlateApplication::Get(); 45 | 46 | SlateApp.PushMenu(AsShared(), FWidgetPath(), TextEntry, SlateApp.GetCursorPos(), FPopupTransitionEffect::TypeInPopup); 47 | } 48 | 49 | private: 50 | FNamePtr Item; 51 | FOnChatSessionNameChanged OnNameChanged; 52 | 53 | TSharedPtr SessionName; 54 | 55 | void OnNameCommited(const FText& NewText, const ETextCommit::Type CommitInfo) 56 | { 57 | if (!SessionName.IsValid()) 58 | { 59 | return; 60 | } 61 | 62 | if (CommitInfo == ETextCommit::OnEnter) 63 | { 64 | OnNameChanged.ExecuteIfBound(Item, FName(*NewText.ToString())); 65 | *Item = *NewText.ToString(); 66 | 67 | FSlateApplication::Get().DismissAllMenus(); 68 | } 69 | else if (CommitInfo == ETextCommit::OnCleared) 70 | { 71 | FSlateApplication::Get().DismissAllMenus(); 72 | } 73 | } 74 | }; 75 | 76 | void SHttpGPTChatShell::Construct([[maybe_unused]] const FArguments&) 77 | { 78 | ChildSlot 79 | [ 80 | ConstructContent() 81 | ]; 82 | 83 | InitializeChatSessionOptions(); 84 | } 85 | 86 | SHttpGPTChatShell::~SHttpGPTChatShell() = default; 87 | 88 | TSharedRef SHttpGPTChatShell::ConstructContent() 89 | { 90 | return SNew(SHorizontalBox) 91 | + SHorizontalBox::Slot().FillWidth(0.2f) 92 | [ 93 | SAssignNew(ChatSessionListView, SListView) 94 | .ListItemsSource(&ChatSessions) 95 | .OnGenerateRow(this, &SHttpGPTChatShell::OnGenerateChatSessionRow) 96 | .OnSelectionChanged(this, &SHttpGPTChatShell::OnChatSessionSelectionChanged) 97 | .SelectionMode(ESelectionMode::Single) 98 | .ClearSelectionOnClick(false) 99 | .OnMouseButtonDoubleClick(this, &SHttpGPTChatShell::OnChatSessionDoubleClicked) 100 | .OnKeyDownHandler(this, &SHttpGPTChatShell::OnChatSessionKeyDown) 101 | 102 | ] 103 | + SHorizontalBox::Slot().FillWidth(0.8f) 104 | [ 105 | SAssignNew(ShellBox, SBox).HAlign(HAlign_Fill).VAlign(VAlign_Fill) 106 | ]; 107 | } 108 | 109 | void SHttpGPTChatShell::InitializeChatSessionOptions() 110 | { 111 | ChatSessions.Empty(); 112 | 113 | if (const FString SessionsPath = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("HttpGPT")); FPaths::DirectoryExists(SessionsPath)) 114 | { 115 | TArray FoundFiles; 116 | IFileManager::Get().FindFilesRecursive(FoundFiles, *SessionsPath, TEXT("*.json"), true, false, true); 117 | 118 | TArray FoundBaseFileNames; 119 | Algo::Transform(FoundFiles, FoundBaseFileNames, [](const FString& Iterator) 120 | { 121 | return FPaths::GetBaseFilename(Iterator); 122 | }); 123 | 124 | for (const FString& FileIt : FoundBaseFileNames) 125 | { 126 | ChatSessions.EmplaceAt(FileIt.Equals(NewSessionName.ToString()) ? 0 : ChatSessions.Num(), MakeShared(FileIt)); 127 | } 128 | 129 | if (FoundBaseFileNames.IsEmpty() || !FoundBaseFileNames.Contains(NewSessionName.ToString())) 130 | { 131 | ChatSessions.EmplaceAt(0, MakeShared(NewSessionName)); 132 | } 133 | 134 | InitializeChatSession(ChatSessions[0]); 135 | } 136 | else if (IFileManager::Get().MakeDirectory(*SessionsPath, true)) 137 | { 138 | InitializeChatSessionOptions(); 139 | return; 140 | } 141 | 142 | if (ChatSessionListView.IsValid()) 143 | { 144 | ChatSessionListView->RequestListRefresh(); 145 | } 146 | } 147 | 148 | void SHttpGPTChatShell::InitializeChatSession(const FNamePtr& InItem) 149 | { 150 | if (!ShellBox.IsValid()) 151 | { 152 | return; 153 | } 154 | 155 | ShellBox->SetContent(SAssignNew(CurrentView, SHttpGPTChatView).SessionID(*InItem)); 156 | 157 | if (ChatSessionListView.IsValid()) 158 | { 159 | if (!ChatSessionListView->IsItemSelected(InItem)) 160 | { 161 | ChatSessionListView->SetSelection(InItem); 162 | } 163 | 164 | ChatSessionListView->RequestListRefresh(); 165 | } 166 | } 167 | 168 | TSharedRef SHttpGPTChatShell::OnGenerateChatSessionRow(FNamePtr InItem, const TSharedRef& OwnerTable) 169 | { 170 | return SNew(SHttpGPTChatSessionOption, OwnerTable, InItem).OnNameChanged(this, &SHttpGPTChatShell::OnChatSessionNameChanged); 171 | } 172 | 173 | void SHttpGPTChatShell::OnChatSessionSelectionChanged(const FNamePtr InItem, [[maybe_unused]] ESelectInfo::Type SelectInfo) 174 | { 175 | if (!InItem.IsValid()) 176 | { 177 | return; 178 | } 179 | 180 | InitializeChatSession(InItem); 181 | } 182 | 183 | void SHttpGPTChatShell::OnChatSessionNameChanged(const FNamePtr InItem, const FName& NewName) 184 | { 185 | if (!InItem.IsValid()) 186 | { 187 | return; 188 | } 189 | 190 | if (InItem->IsEqual(NewSessionName)) 191 | { 192 | ChatSessions.EmplaceAt(0, MakeShared(NewSessionName)); 193 | } 194 | 195 | if (const FString SessionPath = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("HttpGPT"), InItem->ToString()); FPaths::FileExists(SessionPath)) 196 | { 197 | const FString NewPath = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("HttpGPT"), NewName.ToString()); 198 | IFileManager::Get().Move(*SessionPath, *NewPath, true, true, false, false); 199 | } 200 | 201 | if (!CurrentView.IsValid()) 202 | { 203 | return; 204 | } 205 | 206 | if (CurrentView.IsValid() && CurrentView->GetSessionID().IsEqual(*InItem)) 207 | { 208 | CurrentView->SetSessionID(NewName); 209 | } 210 | 211 | *InItem = NewName; 212 | 213 | if (ChatSessionListView.IsValid()) 214 | { 215 | ChatSessionListView->RequestListRefresh(); 216 | } 217 | } 218 | 219 | void SHttpGPTChatShell::OnChatSessionDoubleClicked(const FNamePtr InItem) 220 | { 221 | if (!InItem.IsValid() || !ChatSessionListView.IsValid()) 222 | { 223 | return; 224 | } 225 | 226 | if (const TSharedPtr Row = ChatSessionListView->WidgetFromItem(InItem); Row.IsValid()) 227 | { 228 | if (const TSharedPtr Session = StaticCastSharedPtr(Row); Session.IsValid()) 229 | { 230 | Session->EnableEditMode(); 231 | ChatSessionListView->RequestListRefresh(); 232 | } 233 | } 234 | } 235 | 236 | FReply SHttpGPTChatShell::OnChatSessionKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) 237 | { 238 | if (!ChatSessionListView.IsValid()) 239 | { 240 | return FReply::Unhandled(); 241 | } 242 | 243 | if (InKeyEvent.GetKey() != EKeys::Delete) 244 | { 245 | return FReply::Unhandled(); 246 | } 247 | 248 | if (FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString("Are you sure you want to delete this session?")) == EAppReturnType::No) 249 | { 250 | return FReply::Unhandled(); 251 | } 252 | 253 | const FNamePtr SelectedItem = ChatSessionListView->GetNumItemsSelected() == 0 ? nullptr : ChatSessionListView->GetSelectedItems()[0]; 254 | if (!SelectedItem.IsValid()) 255 | { 256 | return FReply::Unhandled(); 257 | } 258 | 259 | if (CurrentView.IsValid()) 260 | { 261 | CurrentView->ClearChat(); 262 | } 263 | 264 | if (const FString SessionPath = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("HttpGPT"), SelectedItem->ToString() + TEXT(".json")); 265 | FPaths::FileExists(SessionPath)) 266 | { 267 | IFileManager::Get().Delete(*SessionPath, true, true); 268 | } 269 | 270 | if (SelectedItem->IsEqual(NewSessionName) || !ChatSessions.ContainsByPredicate([](const FNamePtr& Item) 271 | { 272 | return Item->IsEqual(NewSessionName); 273 | })) 274 | { 275 | ChatSessions.EmplaceAt(0, MakeShared(NewSessionName)); 276 | } 277 | 278 | ChatSessions.Remove(SelectedItem); 279 | 280 | if (ChatSessionListView.IsValid()) 281 | { 282 | ChatSessionListView->RequestListRefresh(); 283 | ChatSessionListView->SetSelection(ChatSessions[0]); 284 | } 285 | 286 | return FReply::Handled(); 287 | } 288 | -------------------------------------------------------------------------------- /Source/HttpGPTCommonModule/Public/Structures/HttpGPTChatTypes.h: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPTChat 4 | 5 | // These structures are defined in the common module due to being used in both modules, to avoid cyclic dependencies. 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include "Structures/HttpGPTCommonTypes.h" 12 | #include "HttpGPTChatTypes.generated.h" 13 | 14 | UENUM(BlueprintType, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Property Type")) 15 | enum class EHttpGPTPropertyType : uint8 16 | { 17 | Number, 18 | Boolean, 19 | String 20 | }; 21 | 22 | USTRUCT(BlueprintType, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Function Parameter")) 23 | struct HTTPGPTCOMMONMODULE_API FHttpGPTFunctionProperty 24 | { 25 | GENERATED_BODY() 26 | 27 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Name")) 28 | FName Name = NAME_None; 29 | 30 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Type")) 31 | EHttpGPTPropertyType Type = EHttpGPTPropertyType::String; 32 | 33 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Description")) 34 | FString Description; 35 | 36 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Enum")) 37 | TArray Enum; 38 | }; 39 | 40 | USTRUCT(BlueprintType, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Function")) 41 | struct HTTPGPTCOMMONMODULE_API FHttpGPTFunction 42 | { 43 | GENERATED_BODY() 44 | 45 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Name")) 46 | FName Name = NAME_None; 47 | 48 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Description")) 49 | FString Description; 50 | 51 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Properties")) 52 | TArray Properties; 53 | 54 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Required Properties")) 55 | TArray RequiredProperties; 56 | 57 | TSharedPtr GetFunction() const; 58 | }; 59 | 60 | USTRUCT(BlueprintType, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Function Call")) 61 | struct HTTPGPTCOMMONMODULE_API FHttpGPTFunctionCall 62 | { 63 | GENERATED_BODY() 64 | 65 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Name")) 66 | FName Name = NAME_None; 67 | 68 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Arguments")) 69 | FString Arguments; 70 | }; 71 | 72 | UENUM(BlueprintType, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Chat Role")) 73 | enum class EHttpGPTChatRole : uint8 74 | { 75 | User, 76 | Assistant, 77 | System, 78 | Function 79 | }; 80 | 81 | USTRUCT(BlueprintType, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Chat Message")) 82 | struct HTTPGPTCOMMONMODULE_API FHttpGPTChatMessage 83 | { 84 | GENERATED_BODY() 85 | 86 | FHttpGPTChatMessage() = default; 87 | 88 | FHttpGPTChatMessage(const EHttpGPTChatRole& InRole, const FString& InContent) : Role(InRole), Content(InContent) 89 | { 90 | } 91 | 92 | FHttpGPTChatMessage(const FName& InRole, const FString& InContent); 93 | 94 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 95 | EHttpGPTChatRole Role = EHttpGPTChatRole::User; 96 | 97 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 98 | FString Content; 99 | 100 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (EditCondition = "Role == EHttpGPTChatRole::Function")) 101 | FHttpGPTFunctionCall FunctionCall; 102 | 103 | TSharedPtr GetMessage() const; 104 | }; 105 | 106 | USTRUCT(BlueprintType, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Chat Choice")) 107 | struct HTTPGPTCOMMONMODULE_API FHttpGPTChatChoice 108 | { 109 | GENERATED_BODY() 110 | 111 | FHttpGPTChatChoice() = default; 112 | 113 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 114 | int32 Index = 0; 115 | 116 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 117 | FHttpGPTChatMessage Message; 118 | 119 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 120 | FName FinishReason = NAME_None; 121 | }; 122 | 123 | USTRUCT(BlueprintType, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Chat Usage")) 124 | struct HTTPGPTCOMMONMODULE_API FHttpGPTChatUsage 125 | { 126 | GENERATED_BODY() 127 | 128 | FHttpGPTChatUsage() = default; 129 | 130 | FHttpGPTChatUsage(const int32& PromptTokens, const int32& CompletionTokens, const int32& TotalTokens) : PromptTokens(PromptTokens), 131 | CompletionTokens(CompletionTokens), TotalTokens(TotalTokens) 132 | { 133 | } 134 | 135 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 136 | int32 PromptTokens = 0; 137 | 138 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 139 | int32 CompletionTokens = 0; 140 | 141 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 142 | int32 TotalTokens = 0; 143 | }; 144 | 145 | USTRUCT(BlueprintType, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Chat Response")) 146 | struct HTTPGPTCOMMONMODULE_API FHttpGPTChatResponse 147 | { 148 | GENERATED_BODY() 149 | 150 | FHttpGPTChatResponse() = default; 151 | 152 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 153 | FName ID = NAME_None; 154 | 155 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 156 | FName Object = NAME_None; 157 | 158 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 159 | int32 Created = 0; 160 | 161 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 162 | TArray Choices; 163 | 164 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 165 | FHttpGPTChatUsage Usage; 166 | 167 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 168 | bool bSuccess = false; 169 | 170 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat") 171 | FHttpGPTCommonError Error; 172 | }; 173 | 174 | UENUM(BlueprintType, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Chat Model")) 175 | enum class EHttpGPTChatModel : uint8 176 | { 177 | gpt4 UMETA(DisplayName = "gpt-4"), 178 | gpt432k UMETA(DisplayName = "gpt-4-32k"), 179 | gpt35turbo UMETA(DisplayName = "gpt-3.5-turbo"), 180 | gpt35turbo16k UMETA(DisplayName = "gpt-3.5-turbo-16k"), 181 | textdavinci003 UMETA(DisplayName = "text-davinci-003"), 182 | textdavinci002 UMETA(DisplayName = "text-davinci-002"), 183 | codedavinci002 UMETA(DisplayName = "code-davinci-002"), 184 | }; 185 | 186 | USTRUCT(BlueprintType, Category = "HttpGPT | Chat", Meta = (DisplayName = "HttpGPT Chat Options")) 187 | struct HTTPGPTCOMMONMODULE_API FHttpGPTChatOptions 188 | { 189 | GENERATED_BODY() 190 | 191 | FHttpGPTChatOptions(); 192 | 193 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Model")) 194 | EHttpGPTChatModel Model; 195 | 196 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", 197 | Meta = (DisplayName = "Temperature", ClampMin = "0.0", UIMin = "0.0", ClampMax = "2.0", UIMax = "2.0")) 198 | float Temperature; 199 | 200 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", 201 | Meta = (DisplayName = "TopP", ClampMin = "0.01", UIMin = "0.01", ClampMax = "1.0", UIMax = "1.0")) 202 | float TopP; 203 | 204 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Choices", ClampMin = "1", UIMin = "1")) 205 | int32 Choices; 206 | 207 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Stream")) 208 | bool bStream; 209 | 210 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Stop")) 211 | TArray Stop; 212 | 213 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", 214 | Meta = (DisplayName = "Presence Penalty", ClampMin = "-2.0", UIMin = "-2.0", ClampMax = "2.0", UIMax = "2.0")) 215 | float PresencePenalty; 216 | 217 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", 218 | Meta = (DisplayName = "Frequency Penalty", ClampMin = "-2.0", UIMin = "-2.0", ClampMax = "2.0", UIMax = "2.0")) 219 | float FrequencyPenalty; 220 | 221 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", 222 | Meta = (DisplayName = "Max Tokens", ClampMin = "1", UIMin = "1", ClampMax = "32768", UIMax = "32768")) 223 | int32 MaxTokens; 224 | 225 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "HttpGPT | Chat", Meta = (DisplayName = "Logit Bias")) 226 | TMap LogitBias; 227 | 228 | private: 229 | void SetDefaults(); 230 | }; 231 | -------------------------------------------------------------------------------- /Source/HttpGPTEditorModule/Private/Chat/SHttpGPTChatView.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "SHttpGPTChatView.h" 6 | #include "HttpGPTMessagingHandler.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | void SHttpGPTChatView::Construct(const FArguments& InArgs) 21 | { 22 | SetSessionID(InArgs._SessionID); 23 | 24 | SAssignNew(ModelsComboBox, STextComboBox).OptionsSource(&AvailableModels).ToolTipText(FText::FromString(TEXT("GPT Model"))); 25 | 26 | for (const FName& ModelName : UHttpGPTHelper::GetAvailableGPTModels()) 27 | { 28 | AvailableModels.Add(MakeShared(ModelName.ToString())); 29 | 30 | if (ModelsComboBox.IsValid() && ModelName.IsEqual(UHttpGPTHelper::ModelToName(EHttpGPTChatModel::gpt35turbo))) 31 | { 32 | ModelsComboBox->SetSelectedItem(AvailableModels.Top()); 33 | } 34 | } 35 | 36 | ChildSlot 37 | [ 38 | ConstructContent() 39 | ]; 40 | 41 | LoadChatHistory(); 42 | } 43 | 44 | SHttpGPTChatView::~SHttpGPTChatView() 45 | { 46 | SaveChatHistory(); 47 | } 48 | 49 | bool SHttpGPTChatView::IsSendMessageEnabled() const 50 | { 51 | return (!RequestReference.IsValid() || !UHttpGPTTaskStatus::IsTaskActive(RequestReference.Get())) && !HttpGPT::Internal::HasEmptyParam( 52 | InputTextBox->GetText()); 53 | } 54 | 55 | bool SHttpGPTChatView::IsClearChatEnabled() const 56 | { 57 | return !HttpGPT::Internal::HasEmptyParam(ChatItems); 58 | } 59 | 60 | FString SHttpGPTChatView::GetHistoryPath() const 61 | { 62 | return FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("HttpGPT"), SessionID.ToString() + TEXT(".json")); 63 | } 64 | 65 | void SHttpGPTChatView::SetSessionID(const FName& NewSessionID) 66 | { 67 | const FName NewValidSessionID = *FPaths::MakeValidFileName(NewSessionID.ToString()); 68 | if (SessionID == NewValidSessionID) 69 | { 70 | return; 71 | } 72 | 73 | if (SessionID.IsNone()) 74 | { 75 | SessionID = NewValidSessionID; 76 | return; 77 | } 78 | 79 | if (const FString OldPath = GetHistoryPath(); FPaths::FileExists(OldPath)) 80 | { 81 | IFileManager::Get().Delete(*OldPath, true, true, true); 82 | } 83 | 84 | SessionID = NewValidSessionID; 85 | SaveChatHistory(); 86 | } 87 | 88 | FName SHttpGPTChatView::GetSessionID() const 89 | { 90 | return SessionID; 91 | } 92 | 93 | void SHttpGPTChatView::ClearChat() 94 | { 95 | ChatItems.Empty(); 96 | if (ChatBox.IsValid()) 97 | { 98 | ChatBox->ClearChildren(); 99 | } 100 | 101 | if (RequestReference.IsValid()) 102 | { 103 | RequestReference->StopHttpGPTTask(); 104 | RequestReference.Reset(); 105 | } 106 | } 107 | 108 | TSharedRef SHttpGPTChatView::ConstructContent() 109 | { 110 | constexpr float SlotPadding = 4.0f; 111 | 112 | return SNew(SVerticalBox) 113 | + SVerticalBox::Slot().Padding(SlotPadding).FillHeight(1.f) 114 | [ 115 | SAssignNew(ChatScrollBox, SScrollBox) 116 | + SScrollBox::Slot() 117 | [ 118 | SAssignNew(ChatBox, SVerticalBox) 119 | ] 120 | ] 121 | + SVerticalBox::Slot().Padding(SlotPadding).AutoHeight() 122 | [ 123 | SNew(SHorizontalBox) 124 | + SHorizontalBox::Slot().Padding(SlotPadding).FillWidth(1.f) 125 | [ 126 | SAssignNew(InputTextBox, SEditableTextBox).AllowContextMenu(true).IsReadOnly(false).OnTextCommitted_Lambda( 127 | [this]([[maybe_unused]] const FText& Text, ETextCommit::Type CommitType) 128 | { 129 | if (IsSendMessageEnabled() && CommitType == ETextCommit::OnEnter) 130 | { 131 | HandleSendMessageButton(EHttpGPTChatRole::User); 132 | } 133 | }) 134 | ] 135 | + SHorizontalBox::Slot().Padding(SlotPadding).AutoWidth() 136 | [ 137 | SNew(SButton) 138 | .Text(FText::FromString(TEXT("Send"))) 139 | .ToolTipText(FText::FromString(TEXT("Send Message"))) 140 | .OnClicked(this, &SHttpGPTChatView::HandleSendMessageButton, EHttpGPTChatRole::User) 141 | .IsEnabled(this, &SHttpGPTChatView::IsSendMessageEnabled) 142 | ] 143 | + SHorizontalBox::Slot().Padding(SlotPadding).AutoWidth() 144 | [ 145 | SNew(SButton) 146 | .Text(FText::FromString(TEXT("System"))) 147 | .ToolTipText(FText::FromString(TEXT("Send Message as System Context"))) 148 | .OnClicked(this, &SHttpGPTChatView::HandleSendMessageButton, EHttpGPTChatRole::System) 149 | .IsEnabled(this, &SHttpGPTChatView::IsSendMessageEnabled) 150 | ] 151 | + SHorizontalBox::Slot().Padding(SlotPadding).AutoWidth() 152 | [ 153 | ModelsComboBox.ToSharedRef() 154 | ] 155 | + SHorizontalBox::Slot().Padding(SlotPadding).AutoWidth() 156 | [ 157 | SNew(SButton) 158 | .Text(FText::FromString(TEXT("Clear"))) 159 | .ToolTipText(FText::FromString(TEXT("Clear Chat History"))) 160 | .OnClicked(this, &SHttpGPTChatView::HandleClearChatButton) 161 | .IsEnabled(this, &SHttpGPTChatView::IsClearChatEnabled) 162 | ] 163 | ]; 164 | } 165 | 166 | FReply SHttpGPTChatView::HandleSendMessageButton(const EHttpGPTChatRole Role) 167 | { 168 | const SHttpGPTChatItemPtr NewMessage = SNew(SHttpGPTChatItem).MessageRole(Role).InputText(InputTextBox->GetText().ToString()); 169 | 170 | ChatBox->AddSlot().AutoHeight()[NewMessage.ToSharedRef()]; 171 | ChatItems.Add(NewMessage); 172 | 173 | if (Role == EHttpGPTChatRole::System) 174 | { 175 | ChatScrollBox->ScrollToEnd(); 176 | InputTextBox->SetText(FText::GetEmpty()); 177 | 178 | return FReply::Handled(); 179 | } 180 | 181 | const SHttpGPTChatItemPtr AssistantMessage = SNew(SHttpGPTChatItem).MessageRole(EHttpGPTChatRole::Assistant).ScrollBox(ChatScrollBox); 182 | 183 | FHttpGPTChatOptions Options; 184 | Options.Model = UHttpGPTHelper::NameToModel(*(*ModelsComboBox->GetSelectedItem().Get())); 185 | Options.bStream = true; 186 | 187 | RequestReference = UHttpGPTChatRequest::EditorTask(GetChatHistory(), Options); 188 | RequestReference->ProgressStarted.AddDynamic(AssistantMessage->MessagingHandlerObject.Get(), &UHttpGPTMessagingHandler::ProcessUpdated); 189 | RequestReference->ProgressUpdated.AddDynamic(AssistantMessage->MessagingHandlerObject.Get(), &UHttpGPTMessagingHandler::ProcessUpdated); 190 | RequestReference->ProcessCompleted.AddDynamic(AssistantMessage->MessagingHandlerObject.Get(), &UHttpGPTMessagingHandler::ProcessCompleted); 191 | RequestReference->ErrorReceived.AddDynamic(AssistantMessage->MessagingHandlerObject.Get(), &UHttpGPTMessagingHandler::ProcessCompleted); 192 | RequestReference->RequestFailed.AddDynamic(AssistantMessage->MessagingHandlerObject.Get(), &UHttpGPTMessagingHandler::RequestFailed); 193 | RequestReference->RequestSent.AddDynamic(AssistantMessage->MessagingHandlerObject.Get(), &UHttpGPTMessagingHandler::RequestSent); 194 | RequestReference->Activate(); 195 | 196 | ChatBox->AddSlot().AutoHeight()[AssistantMessage.ToSharedRef()]; 197 | ChatItems.Add(AssistantMessage); 198 | 199 | ChatScrollBox->ScrollToEnd(); 200 | InputTextBox->SetText(FText::GetEmpty()); 201 | 202 | return FReply::Handled(); 203 | } 204 | 205 | FReply SHttpGPTChatView::HandleClearChatButton() 206 | { 207 | ClearChat(); 208 | return FReply::Handled(); 209 | } 210 | 211 | TArray SHttpGPTChatView::GetChatHistory() const 212 | { 213 | TArray Output{FHttpGPTChatMessage(EHttpGPTChatRole::System, GetDefaultSystemContext())}; 214 | 215 | for (const SHttpGPTChatItemPtr& Item : ChatItems) 216 | { 217 | FString RoleText = Item->GetRoleText(); 218 | RoleText.RemoveFromEnd(TEXT(":")); 219 | Output.Add(FHttpGPTChatMessage(*RoleText, Item->GetMessageText())); 220 | } 221 | 222 | return Output; 223 | } 224 | 225 | FString SHttpGPTChatView::GetDefaultSystemContext() const 226 | { 227 | if (const UHttpGPTSettings* const Settings = UHttpGPTSettings::Get(); Settings->bUseCustomSystemContext) 228 | { 229 | return Settings->CustomSystemContext; 230 | } 231 | 232 | FString SupportedModels; 233 | for (const TSharedPtr& Model : AvailableModels) 234 | { 235 | SupportedModels.Append(*Model.Get() + ", "); 236 | } 237 | SupportedModels.RemoveFromEnd(", "); 238 | 239 | const TSharedPtr PluginInterface = IPluginManager::Get().FindPlugin("HttpGPT"); 240 | 241 | const FString PluginShortName = "HttpGPT"; 242 | const FString EngineVersion = FString::Printf(TEXT("%d.%d"), ENGINE_MAJOR_VERSION, ENGINE_MINOR_VERSION); 243 | 244 | const FStringFormatOrderedArguments Arguments_PluginInfo{ 245 | EngineVersion, PluginShortName, PluginInterface->GetDescriptor().VersionName, PluginInterface->GetDescriptor().CreatedBy, 246 | PluginInterface->GetDescriptor().Description 247 | }; 248 | 249 | const FString PluginInformation = FString::Format( 250 | TEXT("You are in the Unreal Engine {0} plugin {1} version {2}, which was developed by {3}. The description of HttpGPT is: \"{4}\""), 251 | Arguments_PluginInfo); 252 | 253 | const FStringFormatOrderedArguments Arguments_SupportInfo{PluginInterface->GetDescriptor().DocsURL, PluginInterface->GetDescriptor().SupportURL,}; 254 | 255 | const FString PluginSupport = FString::Format(TEXT("You can find the HttpGPT documentation at {0} and support at {1}."), Arguments_SupportInfo); 256 | 257 | const FStringFormatOrderedArguments Arguments_Models{*ModelsComboBox->GetSelectedItem().Get(), SupportedModels}; 258 | 259 | const FString ModelsInformation = FString::Format( 260 | TEXT("You're using the model {0} and HttpGPT currently supports all these OpenAI Models: {1}."), Arguments_Models); 261 | 262 | const FStringFormatOrderedArguments Arguments_EngineDocumentation{EngineVersion}; 263 | 264 | const FString EngineDocumentation_General = FString::Format( 265 | TEXT("You can find the Unreal Engine {0} general documentation at https://docs.unrealengine.com/{0}/en-US/."), Arguments_EngineDocumentation); 266 | const FString EngineDocumentation_CPP = FString::Format( 267 | TEXT("You can find the Unreal Engine {0} API documentation for C++ at https://docs.unrealengine.com/{0}/en-US/API/."), 268 | Arguments_EngineDocumentation); 269 | const FString EngineDocumentation_BP = FString::Format( 270 | TEXT("You can find the Unreal Engine {0} API documentation for Blueprints at https://docs.unrealengine.com/{0}/en-US/BlueprintAPI/."), 271 | Arguments_EngineDocumentation); 272 | 273 | const FStringFormatOrderedArguments Arguments_SystemContext{ 274 | PluginInformation, PluginSupport, ModelsInformation, EngineDocumentation_General, EngineDocumentation_CPP, EngineDocumentation_BP 275 | }; 276 | 277 | return FString::Format( 278 | TEXT("You are an assistant that will help with the development of projects in Unreal Engine in general.\n{0}\n{1}\n{2}\n{3}\n{4}\n{5}"), 279 | Arguments_SystemContext); 280 | } 281 | 282 | void SHttpGPTChatView::LoadChatHistory() 283 | { 284 | if (SessionID.IsNone()) 285 | { 286 | return; 287 | } 288 | 289 | if (const FString LoadPath = GetHistoryPath(); FPaths::FileExists(LoadPath)) 290 | { 291 | FString FileContent; 292 | if (!FFileHelper::LoadFileToString(FileContent, *LoadPath)) 293 | { 294 | return; 295 | } 296 | 297 | TSharedPtr JsonParsed; 298 | const TSharedRef> Reader = TJsonReaderFactory<>::Create(FileContent); 299 | if (FJsonSerializer::Deserialize(Reader, JsonParsed)) 300 | { 301 | const TArray> SessionData = JsonParsed->GetArrayField(TEXT("Data")); 302 | for (const TSharedPtr& Item : SessionData) 303 | { 304 | if (const TSharedPtr MessageItObj = Item->AsObject()) 305 | { 306 | if (FString RoleString; MessageItObj->TryGetStringField(TEXT("role"), RoleString)) 307 | { 308 | const EHttpGPTChatRole Role = UHttpGPTHelper::NameToRole(*RoleString); 309 | 310 | if (FString Message; MessageItObj->TryGetStringField(TEXT("content"), Message)) 311 | { 312 | if (Role == EHttpGPTChatRole::System && Message == GetDefaultSystemContext()) 313 | { 314 | continue; 315 | } 316 | 317 | ChatItems.Emplace(SNew(SHttpGPTChatItem).MessageRole(Role).InputText(Message).ScrollBox(ChatScrollBox)); 318 | } 319 | } 320 | } 321 | } 322 | } 323 | } 324 | 325 | for (const SHttpGPTChatItemPtr& Item : ChatItems) 326 | { 327 | ChatBox->AddSlot().AutoHeight()[Item.ToSharedRef()]; 328 | } 329 | } 330 | 331 | void SHttpGPTChatView::SaveChatHistory() const 332 | { 333 | if (SessionID.IsNone() || ChatItems.IsEmpty()) 334 | { 335 | return; 336 | } 337 | 338 | const TSharedPtr JsonRequest = MakeShared(); 339 | 340 | TArray> Data; 341 | for (const FHttpGPTChatMessage& Item : GetChatHistory()) 342 | { 343 | Data.Add(Item.GetMessage()); 344 | } 345 | 346 | JsonRequest->SetArrayField("Data", Data); 347 | 348 | FString RequestContentString; 349 | const TSharedRef> Writer = TJsonWriterFactory<>::Create(&RequestContentString); 350 | 351 | if (FJsonSerializer::Serialize(JsonRequest.ToSharedRef(), Writer)) 352 | { 353 | FFileHelper::SaveStringToFile(RequestContentString, *GetHistoryPath()); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /Source/HttpGPTChatModule/Private/Tasks/HttpGPTChatRequest.cpp: -------------------------------------------------------------------------------- 1 | // Author: Lucas Vilas-Boas 2 | // Year: 2023 3 | // Repo: https://github.com/lucoiso/UEHttpGPT 4 | 5 | #include "Tasks/HttpGPTChatRequest.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #if WITH_EDITOR 22 | #include 23 | #endif 24 | 25 | #ifdef UE_INLINE_GENERATED_CPP_BY_NAME 26 | #include UE_INLINE_GENERATED_CPP_BY_NAME(HttpGPTChatRequest) 27 | #endif 28 | 29 | #if WITH_EDITOR 30 | UHttpGPTChatRequest* UHttpGPTChatRequest::EditorTask(const TArray& Messages, const FHttpGPTChatOptions& Options) 31 | { 32 | UHttpGPTChatRequest* const NewAsyncTask = SendMessages_CustomOptions(GEditor->GetEditorWorldContext().World(), Messages, 33 | TArray(), FHttpGPTCommonOptions(), Options); 34 | NewAsyncTask->bIsEditorTask = true; 35 | 36 | return NewAsyncTask; 37 | } 38 | #endif 39 | 40 | UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessage_DefaultOptions(UObject* const WorldContextObject, const FString& Message, 41 | const TArray& Functions) 42 | { 43 | return SendMessage_CustomOptions(WorldContextObject, Message, Functions, FHttpGPTCommonOptions(), FHttpGPTChatOptions()); 44 | } 45 | 46 | UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessages_DefaultOptions(UObject* const WorldContextObject, const TArray& Messages, 47 | const TArray& Functions) 48 | { 49 | return SendMessages_CustomOptions(WorldContextObject, Messages, Functions, FHttpGPTCommonOptions(), FHttpGPTChatOptions()); 50 | } 51 | 52 | UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessage_CustomOptions(UObject* const WorldContextObject, const FString& Message, 53 | const TArray& Functions, 54 | const FHttpGPTCommonOptions CommonOptions, const FHttpGPTChatOptions ChatOptions) 55 | { 56 | return SendMessages_CustomOptions(WorldContextObject, {FHttpGPTChatMessage(EHttpGPTChatRole::User, Message)}, Functions, CommonOptions, 57 | ChatOptions); 58 | } 59 | 60 | UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessages_CustomOptions(UObject* const WorldContextObject, const TArray& Messages, 61 | const TArray& Functions, 62 | const FHttpGPTCommonOptions CommonOptions, const FHttpGPTChatOptions ChatOptions) 63 | { 64 | UHttpGPTChatRequest* const NewAsyncTask = NewObject(); 65 | NewAsyncTask->Messages = Messages; 66 | NewAsyncTask->CommonOptions = CommonOptions; 67 | NewAsyncTask->ChatOptions = ChatOptions; 68 | NewAsyncTask->Functions = Functions; 69 | 70 | NewAsyncTask->RegisterWithGameInstance(WorldContextObject); 71 | 72 | return NewAsyncTask; 73 | } 74 | 75 | bool UHttpGPTChatRequest::CanActivateTask() const 76 | { 77 | if (!Super::CanActivateTask()) 78 | { 79 | return false; 80 | } 81 | 82 | if (HttpGPT::Internal::HasEmptyParam(Messages)) 83 | { 84 | UE_LOG(LogHttpGPT, Error, TEXT("%s (%d): Can't activate task: Invalid Messages."), *FString(__FUNCTION__), GetUniqueID()); 85 | return false; 86 | } 87 | 88 | return true; 89 | } 90 | 91 | bool UHttpGPTChatRequest::CanBindProgress() const 92 | { 93 | return GetChatOptions().bStream; 94 | } 95 | 96 | FString UHttpGPTChatRequest::GetEndpointURL() const 97 | { 98 | return FString::Format(TEXT("{0}/{1}"), { 99 | GetCommonOptions().Endpoint, 100 | UHttpGPTHelper::GetEndpointForModel(GetChatOptions().Model, GetCommonOptions().bIsAzureOpenAI, 101 | GetCommonOptions().AzureOpenAIAPIVersion) 102 | }); 103 | } 104 | 105 | const FHttpGPTChatOptions UHttpGPTChatRequest::GetChatOptions() const 106 | { 107 | return ChatOptions; 108 | } 109 | 110 | FString UHttpGPTChatRequest::SetRequestContent() 111 | { 112 | FScopeLock Lock(&Mutex); 113 | 114 | if (!HttpRequest.IsValid()) 115 | { 116 | return FString(); 117 | } 118 | 119 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Mounting content"), *FString(__FUNCTION__), GetUniqueID()); 120 | 121 | const TSharedPtr JsonRequest = MakeShared(); 122 | JsonRequest->SetStringField("model", UHttpGPTHelper::ModelToName(GetChatOptions().Model).ToString().ToLower()); 123 | JsonRequest->SetNumberField("max_tokens", GetChatOptions().MaxTokens); 124 | JsonRequest->SetNumberField("temperature", GetChatOptions().Temperature); 125 | JsonRequest->SetNumberField("top_p", GetChatOptions().TopP); 126 | JsonRequest->SetNumberField("n", GetChatOptions().Choices); 127 | JsonRequest->SetNumberField("presence_penalty", GetChatOptions().PresencePenalty); 128 | JsonRequest->SetNumberField("frequency_penalty", GetChatOptions().FrequencyPenalty); 129 | JsonRequest->SetBoolField("stream", GetChatOptions().bStream); 130 | 131 | if (!HttpGPT::Internal::HasEmptyParam(GetCommonOptions().User)) 132 | { 133 | JsonRequest->SetStringField("user", GetCommonOptions().User.ToString()); 134 | } 135 | 136 | if (!HttpGPT::Internal::HasEmptyParam(GetChatOptions().Stop)) 137 | { 138 | TArray> StopJson; 139 | for (const FName& Iterator : GetChatOptions().Stop) 140 | { 141 | StopJson.Add(MakeShared(Iterator.ToString())); 142 | } 143 | 144 | JsonRequest->SetArrayField("stop", StopJson); 145 | } 146 | 147 | if (!HttpGPT::Internal::HasEmptyParam(GetChatOptions().LogitBias)) 148 | { 149 | const TSharedPtr LogitBiasJson = MakeShared(); 150 | for (const TPair& Iterator : GetChatOptions().LogitBias) 151 | { 152 | LogitBiasJson->SetNumberField(FString::FromInt(Iterator.Key), Iterator.Value); 153 | } 154 | 155 | JsonRequest->SetObjectField("logit_bias", LogitBiasJson); 156 | } 157 | 158 | if (UHttpGPTHelper::ModelSupportsChat(GetChatOptions().Model)) 159 | { 160 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Selected model supports Chat API. Mounting section history."), *FString(__FUNCTION__), 161 | GetUniqueID()); 162 | 163 | TArray> MessagesJson; 164 | for (const FHttpGPTChatMessage& Iterator : Messages) 165 | { 166 | MessagesJson.Add(Iterator.GetMessage()); 167 | } 168 | 169 | JsonRequest->SetArrayField("messages", MessagesJson); 170 | 171 | if (!Functions.IsEmpty()) 172 | { 173 | TArray> FunctionsJson; 174 | for (const FHttpGPTFunction& Iterator : Functions) 175 | { 176 | FunctionsJson.Add(Iterator.GetFunction()); 177 | } 178 | 179 | JsonRequest->SetArrayField("functions", FunctionsJson); 180 | } 181 | } 182 | else 183 | { 184 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Selected model does not supports Chat API. Using last message as prompt content."), 185 | *FString(__FUNCTION__), GetUniqueID()); 186 | JsonRequest->SetStringField("prompt", Messages.Top().Content); 187 | } 188 | 189 | FString RequestContentString; 190 | const TSharedRef> Writer = TJsonWriterFactory<>::Create(&RequestContentString); 191 | FJsonSerializer::Serialize(JsonRequest.ToSharedRef(), Writer); 192 | 193 | HttpRequest->SetContentAsString(RequestContentString); 194 | 195 | return RequestContentString; 196 | } 197 | 198 | void UHttpGPTChatRequest::OnProgressUpdated(const FString& Content, int32 BytesSent, int32 BytesReceived) 199 | { 200 | FScopeLock Lock(&Mutex); 201 | 202 | if (HttpGPT::Internal::HasEmptyParam(Content)) 203 | { 204 | return; 205 | } 206 | 207 | TArray Deltas = GetDeltasFromContent(Content); 208 | 209 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Progress Updated"), *FString(__FUNCTION__), GetUniqueID()); 210 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Content: %s; Bytes Sent: %d; Bytes Received: %d"), *FString(__FUNCTION__), GetUniqueID(), 211 | *Deltas.Top(), BytesSent, BytesReceived); 212 | 213 | DeserializeStreamedResponse(Deltas); 214 | 215 | if (!Response.bSuccess) 216 | { 217 | return; 218 | } 219 | 220 | if (!bInitialized) 221 | { 222 | bInitialized = true; 223 | 224 | AsyncTask(ENamedThreads::GameThread, [this] 225 | { 226 | FScopeLock Lock(&Mutex); 227 | ProgressStarted.Broadcast(Response); 228 | }); 229 | } 230 | 231 | AsyncTask(ENamedThreads::GameThread, [this] 232 | { 233 | const FScopeTryLock Lock(&Mutex); 234 | 235 | if (Lock.IsLocked()) 236 | { 237 | ProgressUpdated.Broadcast(Response); 238 | } 239 | }); 240 | } 241 | 242 | void UHttpGPTChatRequest::OnProgressCompleted(const FString& Content, const bool bWasSuccessful) 243 | { 244 | FScopeLock Lock(&Mutex); 245 | 246 | if (!bWasSuccessful || HttpGPT::Internal::HasEmptyParam(Content)) 247 | { 248 | UE_LOG(LogHttpGPT, Error, TEXT("%s (%d): Request failed"), *FString(__FUNCTION__), GetUniqueID()); 249 | AsyncTask(ENamedThreads::GameThread, [this] 250 | { 251 | RequestFailed.Broadcast(); 252 | }); 253 | 254 | return; 255 | } 256 | 257 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Process Completed"), *FString(__FUNCTION__), GetUniqueID()); 258 | UE_LOG(LogHttpGPT_Internal, Display, TEXT("%s (%d): Content: %s"), *FString(__FUNCTION__), GetUniqueID(), *Content); 259 | 260 | if (!GetChatOptions().bStream) 261 | { 262 | DeserializeSingleResponse(Content); 263 | } 264 | else 265 | { 266 | const TArray Deltas = GetDeltasFromContent(Content); 267 | DeserializeStreamedResponse(Deltas); 268 | } 269 | 270 | if (Response.bSuccess) 271 | { 272 | AsyncTask(ENamedThreads::GameThread, [this] 273 | { 274 | FScopeLock Lock(&Mutex); 275 | 276 | if (!GetChatOptions().bStream) 277 | { 278 | ProgressStarted.Broadcast(Response); 279 | } 280 | 281 | ProcessCompleted.Broadcast(Response); 282 | }); 283 | } 284 | else 285 | { 286 | UE_LOG(LogHttpGPT, Error, TEXT("%s (%d): Request failed"), *FString(__FUNCTION__), GetUniqueID()); 287 | AsyncTask(ENamedThreads::GameThread, [this] 288 | { 289 | FScopeLock Lock(&Mutex); 290 | ErrorReceived.Broadcast(Response); 291 | }); 292 | } 293 | } 294 | 295 | TArray UHttpGPTChatRequest::GetDeltasFromContent(const FString& Content) const 296 | { 297 | TArray Deltas; 298 | Content.ParseIntoArray(Deltas, TEXT("data: ")); 299 | 300 | if (Deltas.Top().Contains("[done]", ESearchCase::IgnoreCase)) 301 | { 302 | Deltas.Pop(); 303 | } 304 | 305 | if (HttpGPT::Internal::HasEmptyParam(Deltas)) 306 | { 307 | Deltas.Add(Content); 308 | } 309 | 310 | return Deltas; 311 | } 312 | 313 | void UHttpGPTChatRequest::DeserializeStreamedResponse(const TArray& Deltas) 314 | { 315 | FScopeLock Lock(&Mutex); 316 | 317 | Response.Choices.Empty(Deltas.Num()); 318 | for (const FString& Delta : Deltas) 319 | { 320 | DeserializeSingleResponse(Delta); 321 | } 322 | } 323 | 324 | void UHttpGPTChatRequest::DeserializeSingleResponse(const FString& Content) 325 | { 326 | FScopeLock Lock(&Mutex); 327 | 328 | if (HttpGPT::Internal::HasEmptyParam(Content)) 329 | { 330 | return; 331 | } 332 | 333 | const TSharedRef> Reader = TJsonReaderFactory<>::Create(Content); 334 | TSharedPtr JsonResponse = MakeShared(); 335 | FJsonSerializer::Deserialize(Reader, JsonResponse); 336 | 337 | if (CheckError(JsonResponse, Response.Error)) 338 | { 339 | Response.bSuccess = false; 340 | return; 341 | } 342 | 343 | Response.bSuccess = true; 344 | 345 | Response.ID = *JsonResponse->GetStringField(TEXT("id")); 346 | Response.Object = *JsonResponse->GetStringField(TEXT("object")); 347 | Response.Created = JsonResponse->GetNumberField(TEXT("created")); 348 | 349 | const TArray> ChoicesArr = JsonResponse->GetArrayField(TEXT("choices")); 350 | 351 | for (auto Iterator = ChoicesArr.CreateConstIterator(); Iterator; ++Iterator) 352 | { 353 | const TSharedPtr ChoiceObj = (*Iterator)->AsObject(); 354 | const int32 ChoiceIndex = ChoiceObj->GetIntegerField(TEXT("index")); 355 | 356 | FHttpGPTChatChoice* Choice = Response.Choices.FindByPredicate([this, ChoiceIndex](const FHttpGPTChatChoice& Element) 357 | { 358 | return Element.Index == ChoiceIndex; 359 | }); 360 | 361 | if (!Choice) 362 | { 363 | FHttpGPTChatChoice NewChoice; 364 | NewChoice.Index = ChoiceIndex; 365 | Choice = &Response.Choices.Add_GetRef(NewChoice); 366 | } 367 | 368 | if (const TSharedPtr* MessageObj; ChoiceObj->TryGetObjectField(TEXT("message"), MessageObj)) 369 | { 370 | if (FString RoleStr; (*MessageObj)->TryGetStringField(TEXT("role"), RoleStr)) 371 | { 372 | Choice->Message.Role = RoleStr == "user" ? EHttpGPTChatRole::User : EHttpGPTChatRole::Assistant; 373 | } 374 | 375 | if (FString ContentStr; (*MessageObj)->TryGetStringField(TEXT("content"), ContentStr)) 376 | { 377 | Choice->Message.Content = ContentStr; 378 | } 379 | 380 | if (const TSharedPtr* FunctionObj; (*MessageObj)->TryGetObjectField(TEXT("function_call"), FunctionObj)) 381 | { 382 | if (FString FunctionNameStr; (*FunctionObj)->TryGetStringField(TEXT("name"), FunctionNameStr)) 383 | { 384 | Choice->Message.FunctionCall.Name = *FunctionNameStr; 385 | } 386 | if (FString FunctionArgumentsStr; (*FunctionObj)->TryGetStringField(TEXT("arguments"), FunctionArgumentsStr)) 387 | { 388 | Choice->Message.FunctionCall.Arguments = FunctionArgumentsStr; 389 | } 390 | } 391 | } 392 | else if (const TSharedPtr* DeltaObj; ChoiceObj->TryGetObjectField(TEXT("delta"), DeltaObj)) 393 | { 394 | if (FString RoleStr; (*DeltaObj)->TryGetStringField(TEXT("role"), RoleStr)) 395 | { 396 | Choice->Message.Role = UHttpGPTHelper::NameToRole(*RoleStr); 397 | } 398 | else if (FString ContentStr; (*DeltaObj)->TryGetStringField(TEXT("content"), ContentStr)) 399 | { 400 | Choice->Message.Content += ContentStr; 401 | } 402 | 403 | if (const TSharedPtr* FunctionObj; (*DeltaObj)->TryGetObjectField(TEXT("function_call"), FunctionObj)) 404 | { 405 | if (FString FunctionNameStr; (*FunctionObj)->TryGetStringField(TEXT("name"), FunctionNameStr)) 406 | { 407 | Choice->Message.FunctionCall.Name = *FunctionNameStr; 408 | } 409 | if (FString FunctionArgumentsStr; (*FunctionObj)->TryGetStringField(TEXT("arguments"), FunctionArgumentsStr)) 410 | { 411 | Choice->Message.FunctionCall.Arguments += FunctionArgumentsStr; 412 | } 413 | } 414 | } 415 | else if (FString MessageText; ChoiceObj->TryGetStringField(TEXT("text"), MessageText)) 416 | { 417 | Choice->Message.Role = EHttpGPTChatRole::Assistant; 418 | Choice->Message.Content += MessageText; 419 | } 420 | 421 | while (Choice->Message.Content.StartsWith("\n")) 422 | { 423 | Choice->Message.Content.RemoveAt(0); 424 | } 425 | 426 | if (FString FinishReasonStr; ChoiceObj->TryGetStringField(TEXT("finish_reason"), FinishReasonStr)) 427 | { 428 | Choice->FinishReason = *FinishReasonStr; 429 | } 430 | } 431 | 432 | if (const TSharedPtr* UsageObj; JsonResponse->TryGetObjectField(TEXT("usage"), UsageObj)) 433 | { 434 | Response.Usage = FHttpGPTChatUsage((*UsageObj)->GetNumberField(TEXT("prompt_tokens")), (*UsageObj)->GetNumberField(TEXT("completion_tokens")), 435 | (*UsageObj)->GetNumberField(TEXT("total_tokens"))); 436 | } 437 | } 438 | 439 | UHttpGPTChatRequest* UHttpGPTChatHelper::CastToHttpGPTChatRequest(UObject* const Object) 440 | { 441 | return Cast(Object); 442 | } 443 | --------------------------------------------------------------------------------