Add Games File

This commit is contained in:
Ivan Anikin
2026-05-19 13:42:41 +07:00
parent 7908479e0c
commit 12e7e22c1a
3672 changed files with 125922 additions and 0 deletions
@@ -0,0 +1,30 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "QwenTTSDataAssetFactories.h"
#include "QwenTTSPresetDataAsset.h"
#include "QwenTTSStreamingSettingsDataAsset.h"
UQwenTTSStreamingSettingsDataAssetFactory::UQwenTTSStreamingSettingsDataAssetFactory()
{
SupportedClass = UQwenTTSStreamingSettingsDataAsset::StaticClass();
bCreateNew = true;
bEditAfterNew = true;
}
UObject* UQwenTTSStreamingSettingsDataAssetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
return NewObject<UQwenTTSStreamingSettingsDataAsset>(InParent, Class, Name, Flags | RF_Transactional);
}
UQwenTTSPresetDataAssetFactory::UQwenTTSPresetDataAssetFactory()
{
SupportedClass = UQwenTTSPresetDataAsset::StaticClass();
bCreateNew = true;
bEditAfterNew = true;
}
UObject* UQwenTTSPresetDataAssetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
return NewObject<UQwenTTSPresetDataAsset>(InParent, Class, Name, Flags | RF_Transactional);
}
@@ -0,0 +1,286 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "QwenTTSPresetDetailsCustomization.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "HttpModule.h"
#include "Interfaces/IHttpResponse.h"
#include "Json.h"
#include "PropertyHandle.h"
#include "QwenTTSPresetDataAsset.h"
#include "QwenTTSStreamingLog.h"
#include "QwenTTSStreamingSettingsDataAsset.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SComboBox.h"
#include "Widgets/Text/STextBlock.h"
#define LOCTEXT_NAMESPACE "FQwenTTSPresetDetailsCustomization"
FText FQwenTTSPresetChoice::GetDisplayText() const
{
if (!Label.IsEmpty())
{
return FText::FromString(FString::Printf(TEXT("%s (%s)"), *Label, *Id));
}
return FText::FromString(Id);
}
TSharedRef<IDetailCustomization> FQwenTTSPresetDetailsCustomization::MakeInstance()
{
return MakeShared<FQwenTTSPresetDetailsCustomization>();
}
void FQwenTTSPresetDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
TArray<TWeakObjectPtr<UObject>> Objects;
DetailBuilder.GetObjectsBeingCustomized(Objects);
if (Objects.Num() > 0)
{
PresetAsset = Cast<UQwenTTSPresetDataAsset>(Objects[0].Get());
}
SettingsHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UQwenTTSPresetDataAsset, Settings));
PresetIdHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UQwenTTSPresetDataAsset, PresetId));
DetailBuilder.HideProperty(SettingsHandle);
DetailBuilder.HideProperty(PresetIdHandle);
IDetailCategoryBuilder& PresetCategory = DetailBuilder.EditCategory(TEXT("Qwen TTS|Preset"));
PresetCategory.AddProperty(SettingsHandle.ToSharedRef());
PresetCategory.AddCustomRow(LOCTEXT("PresetPickerFilter", "Preset Picker"))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("PresetPickerLabel", "Preset Picker"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MinDesiredWidth(520.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SAssignNew(PresetComboBox, SComboBox<TSharedPtr<FQwenTTSPresetChoice>>)
.OptionsSource(&PresetChoices)
.OnGenerateWidget(this, &FQwenTTSPresetDetailsCustomization::MakePresetComboWidget)
.OnSelectionChanged(this, &FQwenTTSPresetDetailsCustomization::HandleSelectionChanged)
[
SNew(STextBlock)
.Text(this, &FQwenTTSPresetDetailsCustomization::GetSelectedPresetText)
.Font(IDetailLayoutBuilder::GetDetailFont())
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(6.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SButton)
.Text(LOCTEXT("RefreshPresets", "Refresh"))
.OnClicked(this, &FQwenTTSPresetDetailsCustomization::RefreshPresets)
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.0f, 4.0f, 0.0f, 0.0f)
[
SNew(STextBlock)
.Text(this, &FQwenTTSPresetDetailsCustomization::GetStatusText)
.Font(IDetailLayoutBuilder::GetDetailFont())
.AutoWrapText(true)
]
];
PresetCategory.AddProperty(PresetIdHandle.ToSharedRef());
RefreshPresets();
}
FReply FQwenTTSPresetDetailsCustomization::RefreshPresets()
{
const FString StatusUrl = GetStatusUrl();
if (StatusUrl.IsEmpty())
{
StatusText = LOCTEXT("MissingStatusUrl", "Set Settings or Gateway Status Url before refreshing presets.");
return FReply::Handled();
}
StatusText = FText::Format(LOCTEXT("LoadingPresets", "Loading presets from {0}"), FText::FromString(StatusUrl));
UE_LOG(LogQwenTTSStreaming, Log, TEXT("Refreshing Qwen TTS presets from %s"), *StatusUrl);
const TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
Request->SetURL(StatusUrl);
Request->SetVerb(TEXT("GET"));
Request->SetHeader(TEXT("Accept"), TEXT("application/json"));
Request->OnProcessRequestComplete().BindSP(StaticCastSharedRef<FQwenTTSPresetDetailsCustomization>(AsShared()), &FQwenTTSPresetDetailsCustomization::HandlePresetResponse);
Request->ProcessRequest();
return FReply::Handled();
}
void FQwenTTSPresetDetailsCustomization::HandlePresetResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
if (!bWasSuccessful || !Response.IsValid())
{
StatusText = LOCTEXT("PresetRequestFailed", "Preset request failed.");
UE_LOG(LogQwenTTSStreaming, Warning, TEXT("Qwen TTS preset request failed."));
return;
}
if (Response->GetResponseCode() < 200 || Response->GetResponseCode() >= 300)
{
StatusText = FText::Format(LOCTEXT("PresetHttpFailed", "Preset request failed with HTTP {0}."), Response->GetResponseCode());
UE_LOG(LogQwenTTSStreaming, Warning, TEXT("Qwen TTS preset request failed. HTTP=%d Body=%s"), Response->GetResponseCode(), *Response->GetContentAsString());
return;
}
SetPresetChoicesFromStatusJson(Response->GetContentAsString());
}
void FQwenTTSPresetDetailsCustomization::SetPresetChoicesFromStatusJson(const FString& ResponseText)
{
TSharedPtr<FJsonObject> JsonObject;
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ResponseText);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
StatusText = LOCTEXT("BadPresetJson", "Preset response is not valid JSON.");
UE_LOG(LogQwenTTSStreaming, Warning, TEXT("Qwen TTS preset response is not valid JSON."));
return;
}
const TArray<TSharedPtr<FJsonValue>>* PresetRefs = nullptr;
if (!JsonObject->TryGetArrayField(TEXT("preset_refs"), PresetRefs))
{
StatusText = LOCTEXT("NoPresetRefs", "Preset response has no preset_refs array.");
UE_LOG(LogQwenTTSStreaming, Warning, TEXT("Qwen TTS preset response has no preset_refs array."));
return;
}
PresetChoices.Reset();
for (const TSharedPtr<FJsonValue>& Value : *PresetRefs)
{
const TSharedPtr<FJsonObject> PresetObject = Value.IsValid() ? Value->AsObject() : nullptr;
if (!PresetObject.IsValid())
{
continue;
}
FString Id;
if (!PresetObject->TryGetStringField(TEXT("id"), Id) || Id.IsEmpty())
{
continue;
}
TSharedRef<FQwenTTSPresetChoice> Choice = MakeShared<FQwenTTSPresetChoice>();
Choice->Id = Id;
PresetObject->TryGetStringField(TEXT("label"), Choice->Label);
PresetObject->TryGetStringField(TEXT("ref_text"), Choice->RefText);
PresetObject->TryGetStringField(TEXT("source"), Choice->Source);
PresetChoices.Add(Choice);
}
if (PresetComboBox.IsValid())
{
PresetComboBox->RefreshOptions();
}
StatusText = FText::Format(LOCTEXT("PresetLoadedCount", "Loaded {0} preset(s)."), PresetChoices.Num());
UE_LOG(LogQwenTTSStreaming, Log, TEXT("Loaded %d Qwen TTS preset(s)."), PresetChoices.Num());
}
FString FQwenTTSPresetDetailsCustomization::GetStatusUrl() const
{
UObject* SettingsObject = nullptr;
if (SettingsHandle.IsValid() && SettingsHandle->GetValue(SettingsObject) == FPropertyAccess::Success)
{
if (const UQwenTTSStreamingSettingsDataAsset* Settings = Cast<UQwenTTSStreamingSettingsDataAsset>(SettingsObject))
{
return Settings->GetResolvedGatewayStatusUrl();
}
}
if (const UQwenTTSPresetDataAsset* Preset = PresetAsset.Get())
{
if (Preset->Settings)
{
return Preset->Settings->GetResolvedGatewayStatusUrl();
}
}
return GetDefault<UQwenTTSStreamingSettingsDataAsset>()->GetResolvedGatewayStatusUrl();
}
TSharedPtr<FQwenTTSPresetChoice> FQwenTTSPresetDetailsCustomization::GetSelectedChoice() const
{
FString CurrentPresetId;
if (PresetIdHandle.IsValid())
{
PresetIdHandle->GetValue(CurrentPresetId);
}
for (const TSharedPtr<FQwenTTSPresetChoice>& Choice : PresetChoices)
{
if (Choice.IsValid() && Choice->Id == CurrentPresetId)
{
return Choice;
}
}
return nullptr;
}
FText FQwenTTSPresetDetailsCustomization::GetSelectedPresetText() const
{
if (const TSharedPtr<FQwenTTSPresetChoice> Choice = GetSelectedChoice())
{
return Choice->GetDisplayText();
}
FString CurrentPresetId;
if (PresetIdHandle.IsValid() && PresetIdHandle->GetValue(CurrentPresetId) == FPropertyAccess::Success && !CurrentPresetId.IsEmpty())
{
return FText::FromString(CurrentPresetId);
}
return LOCTEXT("NoPresetSelected", "Select preset");
}
FText FQwenTTSPresetDetailsCustomization::GetStatusText() const
{
if (const TSharedPtr<FQwenTTSPresetChoice> Choice = GetSelectedChoice())
{
if (!Choice->RefText.IsEmpty())
{
return FText::FromString(FString::Printf(TEXT("%s: %s"), *Choice->Source, *Choice->RefText));
}
}
return StatusText;
}
void FQwenTTSPresetDetailsCustomization::HandleSelectionChanged(TSharedPtr<FQwenTTSPresetChoice> Choice, ESelectInfo::Type SelectInfo)
{
if (!Choice.IsValid() || SelectInfo == ESelectInfo::Direct)
{
return;
}
if (PresetIdHandle.IsValid())
{
PresetIdHandle->SetValue(Choice->Id);
StatusText = FText::Format(LOCTEXT("PresetSelected", "Selected {0}."), Choice->GetDisplayText());
UE_LOG(LogQwenTTSStreaming, Log, TEXT("Selected Qwen TTS preset. Label=%s Id=%s"), *Choice->Label, *Choice->Id);
}
}
TSharedRef<SWidget> FQwenTTSPresetDetailsCustomization::MakePresetComboWidget(TSharedPtr<FQwenTTSPresetChoice> Choice) const
{
return SNew(STextBlock)
.Text(Choice.IsValid() ? Choice->GetDisplayText() : LOCTEXT("InvalidPresetChoice", "Invalid preset"))
.ToolTipText(Choice.IsValid() ? FText::FromString(Choice->RefText) : FText::GetEmpty());
}
#undef LOCTEXT_NAMESPACE
@@ -0,0 +1,47 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "IDetailCustomization.h"
#include "Interfaces/IHttpRequest.h"
#include "Widgets/Input/SComboBox.h"
class IPropertyHandle;
class UQwenTTSPresetDataAsset;
struct FQwenTTSPresetChoice
{
FString Id;
FString Label;
FString RefText;
FString Source;
FText GetDisplayText() const;
};
class FQwenTTSPresetDetailsCustomization : public IDetailCustomization
{
public:
static TSharedRef<IDetailCustomization> MakeInstance();
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
private:
TWeakObjectPtr<UQwenTTSPresetDataAsset> PresetAsset;
TSharedPtr<IPropertyHandle> PresetIdHandle;
TSharedPtr<IPropertyHandle> SettingsHandle;
TArray<TSharedPtr<FQwenTTSPresetChoice>> PresetChoices;
TSharedPtr<SComboBox<TSharedPtr<FQwenTTSPresetChoice>>> PresetComboBox;
FText StatusText = FText::FromString(TEXT("Not loaded"));
FReply RefreshPresets();
void HandlePresetResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
void SetPresetChoicesFromStatusJson(const FString& ResponseText);
FString GetStatusUrl() const;
TSharedPtr<FQwenTTSPresetChoice> GetSelectedChoice() const;
FText GetSelectedPresetText() const;
FText GetStatusText() const;
void HandleSelectionChanged(TSharedPtr<FQwenTTSPresetChoice> Choice, ESelectInfo::Type SelectInfo);
TSharedRef<SWidget> MakePresetComboWidget(TSharedPtr<FQwenTTSPresetChoice> Choice) const;
};
@@ -0,0 +1,114 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AssetTypeActions_Base.h"
#include "AssetToolsModule.h"
#include "IAssetTools.h"
#include "Modules/ModuleManager.h"
#include "PropertyEditorModule.h"
#include "QwenTTSPresetDataAsset.h"
#include "QwenTTSPresetDetailsCustomization.h"
#include "QwenTTSStreamingSettingsDataAsset.h"
#define LOCTEXT_NAMESPACE "FQwenTTSStreamingEditorModule"
namespace
{
class FQwenTTSSettingsAssetTypeActions : public FAssetTypeActions_Base
{
public:
explicit FQwenTTSSettingsAssetTypeActions(EAssetTypeCategories::Type InCategory)
: Category(InCategory)
{
}
virtual FText GetName() const override { return LOCTEXT("QwenTTSSettingsName", "Qwen TTS Streaming Settings"); }
virtual FColor GetTypeColor() const override { return FColor(86, 182, 194); }
virtual UClass* GetSupportedClass() const override { return UQwenTTSStreamingSettingsDataAsset::StaticClass(); }
virtual uint32 GetCategories() override { return Category; }
virtual FText GetAssetDescription(const FAssetData& AssetData) const override
{
return LOCTEXT("QwenTTSSettingsDescription", "Gateway and playback settings for Qwen TTS streaming.");
}
private:
EAssetTypeCategories::Type Category;
};
class FQwenTTSPresetAssetTypeActions : public FAssetTypeActions_Base
{
public:
explicit FQwenTTSPresetAssetTypeActions(EAssetTypeCategories::Type InCategory)
: Category(InCategory)
{
}
virtual FText GetName() const override { return LOCTEXT("QwenTTSPresetName", "Qwen TTS Preset"); }
virtual FColor GetTypeColor() const override { return FColor(120, 176, 96); }
virtual UClass* GetSupportedClass() const override { return UQwenTTSPresetDataAsset::StaticClass(); }
virtual uint32 GetCategories() override { return Category; }
virtual FText GetAssetDescription(const FAssetData& AssetData) const override
{
return LOCTEXT("QwenTTSPresetDescription", "Voice preset and generation parameters for Qwen TTS streaming.");
}
private:
EAssetTypeCategories::Type Category;
};
}
class FQwenTTSStreamingEditorModule : public IModuleInterface
{
public:
virtual void StartupModule() override
{
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
AudioCategory = AssetTools.FindAdvancedAssetCategory(FName("Audio"));
if (AudioCategory == EAssetTypeCategories::None)
{
AudioCategory = AssetTools.RegisterAdvancedAssetCategory(FName("Audio"), LOCTEXT("AudioCategory", "Audio"));
}
RegisterAssetTypeAction(AssetTools, MakeShared<FQwenTTSSettingsAssetTypeActions>(AudioCategory));
RegisterAssetTypeAction(AssetTools, MakeShared<FQwenTTSPresetAssetTypeActions>(AudioCategory));
FPropertyEditorModule& PropertyEditor = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyEditor.RegisterCustomClassLayout(
UQwenTTSPresetDataAsset::StaticClass()->GetFName(),
FOnGetDetailCustomizationInstance::CreateStatic(&FQwenTTSPresetDetailsCustomization::MakeInstance));
PropertyEditor.NotifyCustomizationModuleChanged();
}
virtual void ShutdownModule() override
{
if (FModuleManager::Get().IsModuleLoaded("AssetTools"))
{
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
for (const TSharedRef<IAssetTypeActions>& Action : RegisteredAssetTypeActions)
{
AssetTools.UnregisterAssetTypeActions(Action);
}
}
RegisteredAssetTypeActions.Empty();
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
{
FPropertyEditorModule& PropertyEditor = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyEditor.UnregisterCustomClassLayout(UQwenTTSPresetDataAsset::StaticClass()->GetFName());
PropertyEditor.NotifyCustomizationModuleChanged();
}
}
private:
void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef<IAssetTypeActions> Action)
{
AssetTools.RegisterAssetTypeActions(Action);
RegisteredAssetTypeActions.Add(Action);
}
EAssetTypeCategories::Type AudioCategory = EAssetTypeCategories::Sounds;
TArray<TSharedRef<IAssetTypeActions>> RegisteredAssetTypeActions;
};
IMPLEMENT_MODULE(FQwenTTSStreamingEditorModule, QwenTTSStreamingEditor)
#undef LOCTEXT_NAMESPACE