// OpenAI Sample, Copyright LifeEXE. All Rights Reserved. #include "FuncLib/OpenAIFuncLib.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" #include "Internationalization/Regex.h" DEFINE_LOG_CATEGORY_STATIC(LogOpenAIFuncLib, All, All); FString UOpenAIFuncLib::OpenAIAllModelToString(EAllModelEnum Model) { switch (Model) { case EAllModelEnum::Whisper_1: return "whisper-1"; case EAllModelEnum::GPT_3_5_Turbo_0301: return "gpt-3.5-turbo-0301"; case EAllModelEnum::GPT_3_5_Turbo: return "gpt-3.5-turbo"; case EAllModelEnum::GPT_3_5_Turbo_16k_0613: return "gpt-3.5-turbo-16k-0613"; case EAllModelEnum::GPT_3_5_Turbo_16k: return "gpt-3.5-turbo-16k"; case EAllModelEnum::GPT_3_5_Turbo_0613: return "gpt-3.5-turbo-0613"; case EAllModelEnum::GPT_3_5_Turbo_Instruct_0914: return "gpt-3.5-turbo-instruct-0914"; case EAllModelEnum::GPT_3_5_Turbo_Instruct: return "gpt-3.5-turbo-instruct"; case EAllModelEnum::Text_Embedding_Ada_002: return "text-embedding-ada-002"; case EAllModelEnum::GPT_4: return "gpt-4"; case EAllModelEnum::GPT_4_0314: return "gpt-4-0314"; case EAllModelEnum::GPT_4_0613: return "gpt-4-0613"; case EAllModelEnum::DALL_E_2: return "dall-e-2"; case EAllModelEnum::DALL_E_3: return "dall-e-3"; case EAllModelEnum::GPT_4_1106_Preview: return "gpt-4-1106-preview"; case EAllModelEnum::GPT_4_Vision_Preview: return "gpt-4-vision-preview"; case EAllModelEnum::GPT_3_5_Turbo_1106: return "gpt-3.5-turbo-1106"; case EAllModelEnum::TTS_1: return "tts-1"; case EAllModelEnum::TTS_1_HD: return "tts-1-hd"; case EAllModelEnum::TTS_1_1106: return "tts-1-1106"; case EAllModelEnum::TTS_1_HD_1106: return "tts-1-hd-1106"; case EAllModelEnum::Text_Embedding_3_Large: return "text-embedding-3-large"; case EAllModelEnum::GPT_4_32K_0314: return "gpt-4-32k-0314"; case EAllModelEnum::GPT_3_5_Turbo_0125: return "gpt-3.5-turbo-0125"; case EAllModelEnum::Text_Embedding_3_Small: return "text-embedding-3-small"; case EAllModelEnum::GPT_4_0125_Preview: return "gpt-4-0125-preview"; case EAllModelEnum::GPT_4_Turbo_Preview: return "gpt-4-turbo-preview"; case EAllModelEnum::GPT_4O_2024_05_13: return "gpt-4o-2024-05-13"; case EAllModelEnum::GPT_4O: return "gpt-4o"; case EAllModelEnum::GPT_4_Turbo_2024_04_09: return "gpt-4-turbo-2024-04-09"; case EAllModelEnum::GPT_4_Turbo: return "gpt-4-turbo"; case EAllModelEnum::GPT_4_1106_Vision_Preview: return "gpt-4-1106-vision-preview"; } checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIMainModelToString(EMainModelEnum Model) { switch (Model) { case EMainModelEnum::GPT_4O: return "gpt-4o"; case EMainModelEnum::GPT_4: return "gpt-4"; case EMainModelEnum::GPT_4_0314: return "gpt-4-0314"; case EMainModelEnum::GPT_4_0613: return "gpt-4-0613"; case EMainModelEnum::GPT_4_1106_Preview: return "gpt-4-1106-preview"; case EMainModelEnum::GPT_4_Vision_Preview: return "gpt-4-vision-preview"; case EMainModelEnum::GPT_3_5_Turbo_0301: return "gpt-3.5-turbo-0301"; case EMainModelEnum::GPT_3_5_Turbo: return "gpt-3.5-turbo"; case EMainModelEnum::GPT_3_5_Turbo_Instruct: return "gpt-3.5-turbo-instruct"; } checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIModerationModelToString(EModerationsModelEnum Model) { switch (Model) { case EModerationsModelEnum::Text_Moderation_Latest: return "text-moderation-latest"; case EModerationsModelEnum::Text_Moderation_Stable: return "text-moderation-stable"; } checkNoEntry(); return {}; } bool UOpenAIFuncLib::ModelSupportsVision(const FString& Model) { return OpenAIAllModelToString(EAllModelEnum::GPT_4_Vision_Preview).Equals(Model) || OpenAIAllModelToString(EAllModelEnum::GPT_4_1106_Vision_Preview).Equals(Model) || OpenAIAllModelToString(EAllModelEnum::GPT_4O).Equals(Model); } FString UOpenAIFuncLib::OpenAIAudioModelToString(EAudioModel Model) { switch (Model) { case EAudioModel::Whisper_1: return "whisper-1"; } checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAITTSModelToString(ETTSModel Model) { switch (Model) { case ETTSModel::TTS_1: return "tts-1"; case ETTSModel::TTS_1_HD: return "tts-1-hd"; } checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIVoiceToString(EVoice Voice) { switch (Voice) { case EVoice::Alloy: return "alloy"; case EVoice::Echo: return "echo"; case EVoice::Fable: return "fable"; case EVoice::Nova: return "nova"; case EVoice::Onyx: return "onyx"; case EVoice::Shimmer: return "shimmer"; } checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAITTSAudioFormatToString(ETTSAudioFormat Format) { switch (Format) { case ETTSAudioFormat::AAC: return "aac"; case ETTSAudioFormat::FLAC: return "flac"; case ETTSAudioFormat::MP3: return "mp3"; case ETTSAudioFormat::OPUS: return "opus"; } checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIImageModelToString(EImageModelEnum Model) { switch (Model) { case EImageModelEnum::DALL_E_2: return "dall-e-2"; case EImageModelEnum::DALL_E_3: return "dall-e-3"; } checkNoEntry(); return {}; } EImageModelEnum UOpenAIFuncLib::StringToOpenAIImageModel(const FString& Model) { if (Model.Equals("dall-e-2")) return EImageModelEnum::DALL_E_2; if (Model.Equals("dall-e-3")) return EImageModelEnum::DALL_E_3; UE_LOG(LogOpenAIFuncLib, Error, TEXT("Unknown EImageModelEnum: %s"), *Model); checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIImageSizeDalle2ToString(EImageSizeDalle2 ImageSize) { switch (ImageSize) { case EImageSizeDalle2::Size_256x256: return "256x256"; case EImageSizeDalle2::Size_512x512: return "512x512"; case EImageSizeDalle2::Size_1024x1024: return "1024x1024"; } checkNoEntry(); return {}; } EImageSizeDalle2 UOpenAIFuncLib::StringToOpenAIImageSizeDalle2(const FString& ImageSize) { if (ImageSize.Equals("256x256")) return EImageSizeDalle2::Size_256x256; if (ImageSize.Equals("512x512")) return EImageSizeDalle2::Size_512x512; if (ImageSize.Equals("1024x1024")) return EImageSizeDalle2::Size_1024x1024; UE_LOG(LogOpenAIFuncLib, Error, TEXT("Unknown EImageSizeDalle2: %s"), *ImageSize); checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIImageSizeDalle3ToString(EImageSizeDalle3 ImageSize) { switch (ImageSize) { case EImageSizeDalle3::Size_1024x1024: return "1024x1024"; case EImageSizeDalle3::Size_1024x1792: return "1024x1792"; case EImageSizeDalle3::Size_1792x1024: return "1792x1024"; } checkNoEntry(); return {}; } EImageSizeDalle3 UOpenAIFuncLib::StringToOpenAIImageSizeDalle3(const FString& ImageSize) { if (ImageSize.Equals("1024x1024")) return EImageSizeDalle3::Size_1024x1024; if (ImageSize.Equals("1024x1792")) return EImageSizeDalle3::Size_1024x1792; if (ImageSize.Equals("1792x1024")) return EImageSizeDalle3::Size_1792x1024; UE_LOG(LogOpenAIFuncLib, Error, TEXT("Unknown EImageSizeDalle3: %s"), *ImageSize); checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIImageFormatToString(EOpenAIImageFormat ImageFormat) { switch (ImageFormat) { case EOpenAIImageFormat::URL: return "url"; case EOpenAIImageFormat::B64_JSON: return "b64_json"; } checkNoEntry(); return {}; } EOpenAIImageFormat UOpenAIFuncLib::StringToOpenAIImageFormat(const FString& ImageFormat) { if (ImageFormat.Equals("url")) return EOpenAIImageFormat::URL; if (ImageFormat.Equals("b64_json")) return EOpenAIImageFormat::B64_JSON; UE_LOG(LogOpenAIFuncLib, Error, TEXT("Unknown EOpenAIImageFormat: %s"), *ImageFormat); checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIImageQualityToString(EOpenAIImageQuality ImageQuality) { switch (ImageQuality) { case EOpenAIImageQuality::HD: return "hd"; case EOpenAIImageQuality::Standard: return "standard"; } checkNoEntry(); return {}; } EOpenAIImageQuality UOpenAIFuncLib::StringToOpenAIImageQuality(const FString& ImageQuality) { if (ImageQuality.Equals("hd")) return EOpenAIImageQuality::HD; if (ImageQuality.Equals("standard")) return EOpenAIImageQuality::Standard; UE_LOG(LogOpenAIFuncLib, Error, TEXT("Unknown EOpenAIImageQuality: %s"), *ImageQuality); checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIImageStyleToString(EOpenAIImageStyle ImageStyle) { switch (ImageStyle) { case EOpenAIImageStyle::Natural: return "natural"; case EOpenAIImageStyle::Vivid: return "vivid"; } checkNoEntry(); return {}; } EOpenAIImageStyle UOpenAIFuncLib::StringToOpenAIImageStyle(const FString& ImageStyle) { if (ImageStyle.Equals("natural")) return EOpenAIImageStyle::Natural; if (ImageStyle.Equals("vivid")) return EOpenAIImageStyle::Vivid; UE_LOG(LogOpenAIFuncLib, Error, TEXT("Unknown EOpenAIImageStyle: %s"), *ImageStyle); checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIRoleToString(ERole Role) { switch (Role) { case ERole::System: return "system"; case ERole::User: return "user"; case ERole::Assistant: return "assistant"; case ERole::Function: return "function"; case ERole::Tool: return "tool"; } checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIFinishReasonToString(EOpenAIFinishReason FinishReason) { switch (FinishReason) { case EOpenAIFinishReason::Stop: return "stop"; case EOpenAIFinishReason::Length: return "length"; case EOpenAIFinishReason::Content_Filter: return "content_filter"; case EOpenAIFinishReason::Tool_Calls: return "tool_calls"; case EOpenAIFinishReason::Null: return ""; } checkNoEntry(); return {}; } EOpenAIFinishReason UOpenAIFuncLib::StringToOpenAIFinishReason(const FString& FinishReason) { if (FinishReason.Equals("stop")) return EOpenAIFinishReason::Stop; if (FinishReason.Equals("length")) return EOpenAIFinishReason::Length; if (FinishReason.Equals("content_filter")) return EOpenAIFinishReason::Content_Filter; if (FinishReason.Equals("tool_calls")) return EOpenAIFinishReason::Tool_Calls; if (FinishReason.IsEmpty()) return EOpenAIFinishReason::Null; UE_LOG(LogOpenAIFuncLib, Error, TEXT("Unknown OpenAIFinishReason: %s"), *FinishReason); checkNoEntry(); return {}; } ERole UOpenAIFuncLib::StringToOpenAIRole(const FString& Role) { if (Role.ToLower().Equals("system")) return ERole::System; if (Role.ToLower().Equals("user")) return ERole::User; if (Role.ToLower().Equals("assistant")) return ERole::Assistant; if (Role.ToLower().Equals("function")) return ERole::Function; if (Role.ToLower().Equals("tool")) return ERole::Tool; UE_LOG(LogOpenAIFuncLib, Error, TEXT("Unknown OpenAIRole: %s"), *Role); checkNoEntry(); return {}; } FOpenAIAuth UOpenAIFuncLib::LoadAPITokensFromFile(const FString& FilePath) { TArray FileLines; if (!FFileHelper::LoadFileToStringArray(FileLines, *FilePath)) { UE_LOG(LogOpenAIFuncLib, Error, TEXT("Failed loading file: %s"), *FilePath); return {}; } else if (FileLines.Num() < 2) { UE_LOG(LogOpenAIFuncLib, Error, TEXT("Auth file might have 2 or 3 lines only")); return {}; } FOpenAIAuth Auth; FString ParamName, ParamValue; FileLines[0].Split("=", &ParamName, &ParamValue); Auth.APIKey = ParamValue; FileLines[1].Split("=", &ParamName, &ParamValue); Auth.OrganizationID = ParamValue; if (FileLines.Num() > 2) { FileLines[2].Split("=", &ParamName, &ParamValue); Auth.ProjectID = ParamValue; } return Auth; } FOpenAIAuth UOpenAIFuncLib::LoadAPITokensFromFileOnce(const FString& FilePath) { static FOpenAIAuth Auth; if (Auth.IsEmpty()) { Auth = LoadAPITokensFromFile(FilePath); } return Auth; } OpenAI::ServiceSecrets UOpenAIFuncLib::LoadServiceSecretsFromFile(const FString& FilePath) { TArray FileLines; if (!FFileHelper::LoadFileToStringArray(FileLines, *FilePath)) { UE_LOG(LogOpenAIFuncLib, Error, TEXT("Failed loading file: %s"), *FilePath); return {}; } OpenAI::ServiceSecrets Secrets; for (const auto& Line : FileLines) { FString SecretName, SecretValue; Line.Split("=", &SecretName, &SecretValue); Secrets.Add(MakeTuple(SecretName, SecretValue)); } return Secrets; } bool UOpenAIFuncLib::LoadSecretByName(const OpenAI::ServiceSecrets& Secrets, const FString& SecretName, FString& SecretValue) { const auto* Found = Secrets.FindByPredicate([&](const TTuple& SecretData) { return SecretData.Key.Equals(SecretName); }); if (Found) { SecretValue = *Found->Value; return true; } SecretValue = {}; return false; } FString UOpenAIFuncLib::OpenAIAudioTranscriptToString(ETranscriptFormat TranscriptFormat) { switch (TranscriptFormat) { case ETranscriptFormat::JSON: return "json"; case ETranscriptFormat::Text: return "text"; case ETranscriptFormat::Str: return "str"; case ETranscriptFormat::Verbose_JSON: return "verbose_json"; case ETranscriptFormat::Vtt: return "vtt"; } checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIEmbeddingsEncodingFormatToString(EEmbeddingsEncodingFormat EmbeddingsEncodingFormat) { switch (EmbeddingsEncodingFormat) { case EEmbeddingsEncodingFormat::Float: return "float"; case EEmbeddingsEncodingFormat::Base64: return "base64"; } checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIChatResponseFormatToString(EChatResponseFormat ChatResponseFormat) { switch (ChatResponseFormat) { case EChatResponseFormat::Text: return "text"; case EChatResponseFormat::JSON_Object: return "json_object"; } checkNoEntry(); return {}; } FString UOpenAIFuncLib::OpenAIModelToString(const FOpenAIModel& OpenAIModel) { FString Out = FString::Printf(TEXT("id: %s\n"), *OpenAIModel.ID); Out.Append(FString::Printf(TEXT("object: %s\n"), *OpenAIModel.Object)); Out.Append(FString::Printf(TEXT("created: %i\n"), OpenAIModel.Created)); Out.Append(FString::Printf(TEXT("owned_by: %s\n"), *OpenAIModel.Owned_By)); Out.Append(FString::Printf(TEXT("root: %s\n"), *OpenAIModel.Root)); Out.Append(FString::Printf(TEXT("parent: %s\n"), *OpenAIModel.Parent)); return Out; } FString UOpenAIFuncLib::OpenAIMessageContentTypeToString(EMessageContentType MessageContentType) { switch (MessageContentType) { case EMessageContentType::Text: return "text"; case EMessageContentType::Image_URL: return "image_url"; } checkNoEntry(); return {}; } FString UOpenAIFuncLib::BoolToString(bool Value) { return Value ? TEXT("true") : TEXT("false"); } FString UOpenAIFuncLib::RemoveWhiteSpaces(const FString& Input) { FString Result; const TSet Whitespaces{'\t', '\n', '\r'}; for (const TCHAR& Char : Input) { if (!Whitespaces.Contains(Char)) { Result += Char; } } return Result; } bool UOpenAIFuncLib::StringToJson(const FString& JsonString, TSharedPtr& JsonObject) { TSharedRef> JsonReader = TJsonReaderFactory<>::Create(JsonString); return FJsonSerializer::Deserialize(JsonReader, JsonObject); } namespace { void ConvertKeysToLowercaseRecursive(TSharedPtr Value); void ConvertObjectKeysToLowercase(TSharedPtr JsonObject) { TSharedPtr NewJsonObject = MakeShareable(new FJsonObject); for (const auto& Elem : JsonObject->Values) { const FString LowerKey = Elem.Key.ToLower(); ConvertKeysToLowercaseRecursive(Elem.Value); NewJsonObject->SetField(LowerKey, Elem.Value); } *JsonObject = *NewJsonObject; } void ConvertKeysToLowercaseRecursive(TSharedPtr Value) { if (Value->Type == EJson::Object) { ConvertObjectKeysToLowercase(Value->AsObject()); } else if (Value->Type == EJson::Array) { const TArray>& Array = Value->AsArray(); for (const auto& Item : Array) { ConvertKeysToLowercaseRecursive(Item); } } } } // namespace bool UOpenAIFuncLib::JsonToString(const TSharedPtr& JsonObject, FString& JsonString) { TSharedPtr NewJsonObject = JsonObject; ConvertObjectKeysToLowercase(NewJsonObject); TSharedRef> Writer = TJsonWriterFactory<>::Create(&JsonString); return FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer); } FString UOpenAIFuncLib::OpenAIModerationsToString(const FModerationResults& ModerationResults) { FString Out; Out.Append(FString::Printf(TEXT("hate: %s\n"), *BoolToString(ModerationResults.Categories.Hate))); Out.Append(FString::Printf(TEXT("hate/threatening: %s\n"), *BoolToString(ModerationResults.Categories.Hate_Threatening))); Out.Append(FString::Printf(TEXT("self-harm: %s\n"), *BoolToString(ModerationResults.Categories.Self_Harm))); Out.Append(FString::Printf(TEXT("sexual: %s\n"), *BoolToString(ModerationResults.Categories.Sexual))); Out.Append(FString::Printf(TEXT("sexual/minors: %s\n"), *BoolToString(ModerationResults.Categories.Sexual_Minors))); Out.Append(FString::Printf(TEXT("violence: %s\n"), *BoolToString(ModerationResults.Categories.Violence))); Out.Append(FString::Printf(TEXT("violence/graphic: %s\n\n"), *BoolToString(ModerationResults.Categories.Violence_Graphic))); Out.Append(FString::Printf(TEXT("hate: %f\n"), ModerationResults.Category_Scores.Hate)); Out.Append(FString::Printf(TEXT("hate/threatening: %f\n"), ModerationResults.Category_Scores.Hate_Threatening)); Out.Append(FString::Printf(TEXT("self-harm: %f\n"), ModerationResults.Category_Scores.Self_Harm)); Out.Append(FString::Printf(TEXT("sexual: %f\n"), ModerationResults.Category_Scores.Sexual)); Out.Append(FString::Printf(TEXT("sexual/minors: %f\n"), ModerationResults.Category_Scores.Sexual_Minors)); Out.Append(FString::Printf(TEXT("violence: %f\n"), ModerationResults.Category_Scores.Violence)); Out.Append(FString::Printf(TEXT("violence/graphic: %f\n\n"), ModerationResults.Category_Scores.Violence_Graphic)); Out.Append(FString::Printf(TEXT("flagged: %s"), *BoolToString(ModerationResults.Flagged))); return Out; } EOpenAIResponseError UOpenAIFuncLib::GetErrorCode(const FString& RawError) { if (RawError.Contains("ConnectionError")) return EOpenAIResponseError::NetworkError; TSharedRef> JsonReader = TJsonReaderFactory<>::Create(RawError); TSharedPtr JsonObject; if (!FJsonSerializer::Deserialize(JsonReader, JsonObject)) { return EOpenAIResponseError::Unknown; } if (JsonObject.IsValid() && JsonObject->HasField("error")) { const auto Error = JsonObject->GetObjectField("error"); if (Error->HasField("code")) { const auto Code = Error->GetStringField("code"); if (Code.Contains("invalid_api_key")) { return EOpenAIResponseError::InvalidAPIKey; } else if (Code.Contains("model_not_found")) { return EOpenAIResponseError::ModelNotFound; } } } return EOpenAIResponseError::Unknown; } FString UOpenAIFuncLib::GetErrorMessage(const FString& RawError) { TSharedRef> JsonReader = TJsonReaderFactory<>::Create(RawError); TSharedPtr JsonObject; if (!FJsonSerializer::Deserialize(JsonReader, JsonObject)) return {}; if (JsonObject.IsValid() && JsonObject->HasField("error")) { const auto Error = JsonObject->GetObjectField("error"); if (Error->HasField("message")) { return Error->GetStringField("message"); } } return {}; } FString UOpenAIFuncLib::ResponseErrorToString(EOpenAIResponseError Code) { switch (Code) { case EOpenAIResponseError::InvalidAPIKey: return "Invalid API key"; case EOpenAIResponseError::NetworkError: return "Network error"; case EOpenAIResponseError::ModelNotFound: return "Model not found"; case EOpenAIResponseError::Unknown: return "Unknown error"; } return "Unknown error code"; } // we need two markes to make clean JSON object during request serialization // basically to remove quotes const FString UOpenAIFuncLib::START_FUNCTION_OBJECT_MARKER = "START_FUNCTION_OBJECT_MARKER"; const FString UOpenAIFuncLib::END_FUNCTION_OBJECT_MARKER = "END_FUNCTION_OBJECT_MARKER"; FString UOpenAIFuncLib::MakeFunctionsString(const TSharedPtr& Json) { FString Functions; TSharedRef> Writer = TJsonWriterFactory<>::Create(&Functions); FJsonSerializer::Serialize(Json.ToSharedRef(), Writer); Functions.Append(END_FUNCTION_OBJECT_MARKER); Functions = START_FUNCTION_OBJECT_MARKER + Functions; return RemoveWhiteSpaces(Functions); } FString UOpenAIFuncLib::CleanUpFunctionsObject(const FString& Input) { FString Output{Input}; const FString StartMarker = FString::Format(TEXT("\"{0}"), {START_FUNCTION_OBJECT_MARKER}); const FString EndMarker = FString::Format(TEXT("{0}\""), {END_FUNCTION_OBJECT_MARKER}); const auto Find = [&](const FString& Str, int32 StartIndex) { return Output.Find(Str, ESearchCase::IgnoreCase, ESearchDir::FromStart, StartIndex); }; int32 StartIndex = Find(StartMarker, 0); int32 EndIndex = Find(EndMarker, StartIndex); while (StartIndex != INDEX_NONE && EndIndex != INDEX_NONE) { int32 ContentStart = StartIndex + StartMarker.Len(); int32 ContentEnd = EndIndex; // Extract the substring that needs to have backslashes removed FString ContentToClean = Output.Mid(ContentStart, ContentEnd - ContentStart); // Replace backslashes within the extracted content FString CleanedContent = ContentToClean.Replace(TEXT("\\"), TEXT("")); // Replace old content with cleaned content Output = Output.Left(ContentStart) + CleanedContent + Output.Mid(EndIndex); StartIndex = Find(StartMarker, EndIndex); EndIndex = Find(EndMarker, StartIndex); } Output = Output.Replace(*StartMarker, TEXT("")); Output = Output.Replace(*EndMarker, TEXT("")); return Output; } FString UOpenAIFuncLib::MakeURLWithQuery(const FString& URL, const OpenAI::QueryPairs& Args) { FString URLWithQuery = URL + "?"; for (const auto& [Name, Param] : Args) { URLWithQuery.Append(Name).Append("=").Append(Param).Append("&"); } URLWithQuery = URLWithQuery.LeftChop(1); return URLWithQuery; } FString UOpenAIFuncLib::WrapBase64(const FString& Base64String) { return FString::Format(TEXT("data:image/png;base64,{0}"), {Base64String}); } FString UOpenAIFuncLib::UnWrapBase64(const FString& Base64String) { const FString ToRemove = TEXT("data:image/png;base64,"); return Base64String.Replace(*ToRemove, TEXT("")); } FString UOpenAIFuncLib::FilePathToBase64(const FString& FilePath) { TArray ImageData; if (!FFileHelper::LoadFileToArray(ImageData, *FilePath)) { return {}; } const FString ImageInBase64 = FBase64::Encode(ImageData); return UOpenAIFuncLib::WrapBase64(ImageInBase64); }