diff --git a/Unreal/PS_AI_Agent/Config/DefaultEngine.ini b/Unreal/PS_AI_Agent/Config/DefaultEngine.ini index 3a95522..4e40f5b 100644 --- a/Unreal/PS_AI_Agent/Config/DefaultEngine.ini +++ b/Unreal/PS_AI_Agent/Config/DefaultEngine.ini @@ -181,6 +181,9 @@ bUseManualIPAddress=False ManualIPAddress= [/Script/PS_AI_ConvAgent.PS_AI_ConvAgent_Settings_ElevenLabs] -API_Key=7b73c4244ccbec394cc010aaab01b0ec59ce0b11fc636ce4828354f675ca14a5 +; NOTE: there is intentionally no API_Key here. The ElevenLabs key is provided +; per-install by the client at runtime (BlueprintLibrary "Set ElevenLabs API Key") +; and stored in a SaveGame slot (Saved/SaveGames/PS_AI_ConvAgent_ElevenLabs.sav), +; so it is never committed or baked into a shipped build. ServerRegion=Global diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent.cpp index d06cc19..2597bf1 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent.cpp @@ -19,6 +19,10 @@ void FPS_AI_ConvAgentModule::StartupModule() Settings = NewObject(GetTransientPackage(), "PS_AI_ConvAgent_Settings_ElevenLabs", RF_Standalone); Settings->AddToRoot(); + // NOTE: the client-provided API key (if any) is loaded lazily on the first + // GetSettings() call rather than here — see GetSettings(). Loading at module + // startup would be too early: the SaveGame system may not be ready yet. + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) { SettingsModule->RegisterSettings( diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_BlueprintLibrary.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_BlueprintLibrary.cpp index 5ca4d05..868c296 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_BlueprintLibrary.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_BlueprintLibrary.cpp @@ -1,7 +1,34 @@ // Copyright ASTERION. All Rights Reserved. #include "PS_AI_ConvAgent_BlueprintLibrary.h" +#include "PS_AI_ConvAgent.h" +#include "PS_AI_ConvAgent_SaveGame_ElevenLabs.h" #include "Components/SkeletalMeshComponent.h" +#include "Kismet/GameplayStatics.h" + +namespace +{ + // In-memory cache of the client API key. This BlueprintLibrary is the single + // owner of the key — every consumer (WebSocket auth, region probe, editor sync + // tools) reads it via GetElevenLabsAPIKey(). The SaveGame on disk is the + // persistent backing store; this cache avoids a disk read on every access. + // All access is on the game thread, so no synchronization is needed. + bool GElevenLabsKeyLoaded = false; + FString GElevenLabsKeyCache; + + /** Load the API-key SaveGame from its slot, or null if none exists yet. */ + UPS_AI_ConvAgent_SaveGame_ElevenLabs* LoadElevenLabsKeySaveGame() + { + const TCHAR* Slot = UPS_AI_ConvAgent_SaveGame_ElevenLabs::GetSlotName(); + const int32 UserIndex = UPS_AI_ConvAgent_SaveGame_ElevenLabs::GetUserIndex(); + if (UGameplayStatics::DoesSaveGameExist(Slot, UserIndex)) + { + return Cast( + UGameplayStatics::LoadGameFromSlot(Slot, UserIndex)); + } + return nullptr; + } +} void UPS_AI_ConvAgent_BlueprintLibrary::SetPostProcessAnimBlueprint( USkeletalMeshComponent* SkelMeshComp, @@ -15,3 +42,71 @@ void UPS_AI_ConvAgent_BlueprintLibrary::SetPostProcessAnimBlueprint( SkelMeshComp->SetOverridePostProcessAnimBP(AnimBPClass); } + +// ── ElevenLabs API key (per-user, client-provided) ────────────────────────── + +void UPS_AI_ConvAgent_BlueprintLibrary::SetElevenLabsAPIKey(const FString& Key) +{ + // 1) Persist to the SaveGame slot (writes to disk immediately). + UPS_AI_ConvAgent_SaveGame_ElevenLabs* Save = LoadElevenLabsKeySaveGame(); + if (!Save) + { + Save = Cast( + UGameplayStatics::CreateSaveGameObject(UPS_AI_ConvAgent_SaveGame_ElevenLabs::StaticClass())); + } + if (!Save) + { + UE_LOG(LogTemp, Warning, TEXT("[PS_AI_ConvAgent] SetElevenLabsAPIKey: failed to create SaveGame object.")); + return; + } + + Save->API_Key = Key; + const bool bSaved = UGameplayStatics::SaveGameToSlot( + Save, + UPS_AI_ConvAgent_SaveGame_ElevenLabs::GetSlotName(), + UPS_AI_ConvAgent_SaveGame_ElevenLabs::GetUserIndex()); + + if (!bSaved) + { + UE_LOG(LogTemp, Warning, TEXT("[PS_AI_ConvAgent] SetElevenLabsAPIKey: SaveGameToSlot failed " + "(check write permissions at the install location).")); + } + + // 2) Update the in-memory cache so the new key takes effect immediately + // (next conversation / region probe / editor sync) without a restart. + GElevenLabsKeyCache = Key; + GElevenLabsKeyLoaded = true; +} + +FString UPS_AI_ConvAgent_BlueprintLibrary::GetElevenLabsAPIKey() +{ + // Lazy-load once from the SaveGame, then serve from the in-memory cache. + if (!GElevenLabsKeyLoaded) + { + GElevenLabsKeyLoaded = true; + if (const UPS_AI_ConvAgent_SaveGame_ElevenLabs* Save = LoadElevenLabsKeySaveGame()) + { + GElevenLabsKeyCache = Save->API_Key; + } + } + return GElevenLabsKeyCache; +} + +bool UPS_AI_ConvAgent_BlueprintLibrary::HasElevenLabsAPIKey() +{ + return !GetElevenLabsAPIKey().IsEmpty(); +} + +void UPS_AI_ConvAgent_BlueprintLibrary::ClearElevenLabsAPIKey() +{ + const TCHAR* Slot = UPS_AI_ConvAgent_SaveGame_ElevenLabs::GetSlotName(); + const int32 UserIndex = UPS_AI_ConvAgent_SaveGame_ElevenLabs::GetUserIndex(); + if (UGameplayStatics::DoesSaveGameExist(Slot, UserIndex)) + { + UGameplayStatics::DeleteGameInSlot(Slot, UserIndex); + } + + // Clear the cache too so the running session stops using the old key. + GElevenLabsKeyCache.Empty(); + GElevenLabsKeyLoaded = true; +} diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp index 3a7e0bc..ab73c85 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp @@ -10,6 +10,7 @@ #include "PS_AI_ConvAgent_InteractionSubsystem.h" #include "PS_AI_ConvAgent_InteractionComponent.h" #include "PS_AI_ConvAgent.h" +#include "PS_AI_ConvAgent_BlueprintLibrary.h" #include "Components/AudioComponent.h" #include "Sound/SoundAttenuation.h" @@ -2822,12 +2823,13 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::DrawDebugHUD() const void UPS_AI_ConvAgent_ElevenLabsComponent::FetchServerRegion() { const UPS_AI_ConvAgent_Settings_ElevenLabs* Settings = FPS_AI_ConvAgentModule::Get().GetSettings(); - if (!Settings || Settings->API_Key.IsEmpty()) return; + const FString APIKey = UPS_AI_ConvAgent_BlueprintLibrary::GetElevenLabsAPIKey(); + if (!Settings || APIKey.IsEmpty()) return; auto Request = FHttpModule::Get().CreateRequest(); Request->SetURL(Settings->GetAPIBaseURL() + TEXT("/v1/models")); Request->SetVerb(TEXT("GET")); - Request->SetHeader(TEXT("xi-api-key"), Settings->API_Key); + Request->SetHeader(TEXT("xi-api-key"), APIKey); TWeakObjectPtr WeakThis(this); Request->OnProcessRequestComplete().BindLambda( diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_TestAPIKey_AsyncAction.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_TestAPIKey_AsyncAction.cpp new file mode 100644 index 0000000..fd3139c --- /dev/null +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_TestAPIKey_AsyncAction.cpp @@ -0,0 +1,78 @@ +// Copyright ASTERION. All Rights Reserved. + +#include "PS_AI_ConvAgent_TestAPIKey_AsyncAction.h" +#include "PS_AI_ConvAgent.h" +#include "HttpModule.h" +#include "Interfaces/IHttpRequest.h" +#include "Interfaces/IHttpResponse.h" + +UPS_AI_ConvAgent_TestAPIKey_AsyncAction* UPS_AI_ConvAgent_TestAPIKey_AsyncAction::TestElevenLabsAPIKey(const FString& Key) +{ + UPS_AI_ConvAgent_TestAPIKey_AsyncAction* Action = NewObject(); + Action->KeyToTest = Key; + return Action; +} + +void UPS_AI_ConvAgent_TestAPIKey_AsyncAction::Activate() +{ + // Keep this action alive across the async HTTP round-trip. Removed right + // before SetReadyToDestroy() once a result has been broadcast. + AddToRoot(); + + if (KeyToTest.IsEmpty()) + { + OnInvalid.Broadcast(TEXT("API key is empty.")); + RemoveFromRoot(); + SetReadyToDestroy(); + return; + } + + // Respect the configured server region for the probe URL; fall back to global. + FString BaseURL = TEXT("https://api.elevenlabs.io"); + if (FPS_AI_ConvAgentModule::IsAvailable()) + { + if (const UPS_AI_ConvAgent_Settings_ElevenLabs* Settings = FPS_AI_ConvAgentModule::Get().GetSettings()) + { + BaseURL = Settings->GetAPIBaseURL(); + } + } + + const TSharedRef Request = FHttpModule::Get().CreateRequest(); + Request->SetURL(BaseURL + TEXT("/v1/models")); + Request->SetVerb(TEXT("GET")); + Request->SetHeader(TEXT("xi-api-key"), KeyToTest); + + TWeakObjectPtr WeakThis(this); + Request->OnProcessRequestComplete().BindLambda( + [WeakThis](FHttpRequestPtr /*Req*/, FHttpResponsePtr Resp, bool bSuccess) + { + if (!WeakThis.IsValid()) + { + return; + } + UPS_AI_ConvAgent_TestAPIKey_AsyncAction* Self = WeakThis.Get(); + + const int32 Code = Resp.IsValid() ? Resp->GetResponseCode() : 0; + if (bSuccess && Code == 200) + { + Self->OnValid.Broadcast(TEXT("API key is valid.")); + } + else if (Code == 401 || Code == 403) + { + Self->OnInvalid.Broadcast(FString::Printf(TEXT("Invalid API key (HTTP %d)."), Code)); + } + else if (Code == 0) + { + Self->OnInvalid.Broadcast(TEXT("Could not reach ElevenLabs (network error).")); + } + else + { + Self->OnInvalid.Broadcast(FString::Printf(TEXT("Key test failed (HTTP %d)."), Code)); + } + + Self->RemoveFromRoot(); + Self->SetReadyToDestroy(); + }); + + Request->ProcessRequest(); +} diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_WebSocket_ElevenLabsProxy.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_WebSocket_ElevenLabsProxy.cpp index 8a6e38a..e4b109e 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_WebSocket_ElevenLabsProxy.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_WebSocket_ElevenLabsProxy.cpp @@ -2,6 +2,7 @@ #include "PS_AI_ConvAgent_WebSocket_ElevenLabsProxy.h" #include "PS_AI_ConvAgent.h" +#include "PS_AI_ConvAgent_BlueprintLibrary.h" #include "WebSocketsModule.h" #include "IWebSocket.h" @@ -43,9 +44,12 @@ void UPS_AI_ConvAgent_WebSocket_ElevenLabsProxy::Connect(const FString& AgentIDO // Headers: the ElevenLabs Conversational AI WS endpoint accepts the // xi-api-key header on the initial HTTP upgrade request. + // The key comes from the client-provided SaveGame (via the BlueprintLibrary); + // an explicit override passed to Connect() still takes precedence. TMap UpgradeHeaders; - const UPS_AI_ConvAgent_Settings_ElevenLabs* Settings = FPS_AI_ConvAgentModule::Get().GetSettings(); - const FString ResolvedKey = APIKeyOverride.IsEmpty() ? Settings->API_Key : APIKeyOverride; + const FString ResolvedKey = APIKeyOverride.IsEmpty() + ? UPS_AI_ConvAgent_BlueprintLibrary::GetElevenLabsAPIKey() + : APIKeyOverride; if (!ResolvedKey.IsEmpty()) { UpgradeHeaders.Add(TEXT("xi-api-key"), ResolvedKey); diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent.h index 877e6c1..1e0264f 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent.h @@ -31,13 +31,12 @@ class PS_AI_CONVAGENT_API UPS_AI_ConvAgent_Settings_ElevenLabs : public UObject GENERATED_BODY() public: - /** - * ElevenLabs API key. - * Obtain from https://elevenlabs.io – used to authenticate WebSocket connections. - * Keep this secret; do not ship with the key hard-coded in a shipping build. - */ - UPROPERTY(Config, EditAnywhere, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs API") - FString API_Key; + // NOTE: The ElevenLabs API key is NOT stored here on purpose. + // It is provided per-install by the client at runtime and persisted in a + // SaveGame slot (see UPS_AI_ConvAgent_SaveGame_ElevenLabs). Read/write it via + // UPS_AI_ConvAgent_BlueprintLibrary::Get/SetElevenLabsAPIKey(). This keeps the + // key out of Project Settings / DefaultEngine.ini so it can never be committed + // or baked into a shipped build. /** * Server region for ElevenLabs API. @@ -119,7 +118,8 @@ public: return FModuleManager::Get().IsModuleLoaded("PS_AI_ConvAgent"); } - /** Access the settings object at runtime */ + /** Access the settings object at runtime (region, custom URL, logging, global + * prompt). The API key is NOT here — see UPS_AI_ConvAgent_BlueprintLibrary. */ UPS_AI_ConvAgent_Settings_ElevenLabs* GetSettings() const; private: diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BlueprintLibrary.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BlueprintLibrary.h index 0982128..3aac3b8 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BlueprintLibrary.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_BlueprintLibrary.h @@ -28,4 +28,30 @@ public: meta = (DisplayName = "Set Post Process Anim Blueprint")) static void SetPostProcessAnimBlueprint(USkeletalMeshComponent* SkelMeshComp, TSubclassOf AnimBPClass); + + // ── ElevenLabs API key (per-user, client-provided) ──────────────────────── + + /** Store the client's ElevenLabs API key. Persists it per-user in + * Saved/Config/.../Game.ini AND applies it immediately to the running plugin + * (no restart needed). Call this from your settings UI once the user has + * entered — and ideally validated via "Test ElevenLabs API Key" — their key. */ + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs API Key", + meta = (DisplayName = "Set ElevenLabs API Key")) + static void SetElevenLabsAPIKey(const FString& Key); + + /** Return the currently stored client ElevenLabs API key (empty if none). */ + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs API Key", + meta = (DisplayName = "Get ElevenLabs API Key")) + static FString GetElevenLabsAPIKey(); + + /** True if a non-empty client ElevenLabs API key has been stored. + * Use this on startup to decide whether to force the user to the key entry screen. */ + UFUNCTION(BlueprintPure, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs API Key", + meta = (DisplayName = "Has ElevenLabs API Key")) + static bool HasElevenLabsAPIKey(); + + /** Clear the stored client ElevenLabs API key (both on disk and in memory). */ + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs API Key", + meta = (DisplayName = "Clear ElevenLabs API Key")) + static void ClearElevenLabsAPIKey(); }; diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_SaveGame_ElevenLabs.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_SaveGame_ElevenLabs.h new file mode 100644 index 0000000..2d48544 --- /dev/null +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_SaveGame_ElevenLabs.h @@ -0,0 +1,36 @@ +// Copyright ASTERION. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/SaveGame.h" +#include "PS_AI_ConvAgent_SaveGame_ElevenLabs.generated.h" + +// ───────────────────────────────────────────────────────────────────────────── +// UPS_AI_ConvAgent_SaveGame_ElevenLabs +// +// Per-install persistence for the CLIENT's own ElevenLabs API key. Written via +// UGameplayStatics::SaveGameToSlot to: +// - Editor/PIE : /Saved/SaveGames/PS_AI_ConvAgent_ElevenLabs.sav +// - Packaged : //Saved/SaveGames/PS_AI_ConvAgent_ElevenLabs.sav +// +// Binary file → the key is never stored in plaintext, never committed, never +// baked into the shipped config. The key is entered once at runtime via the +// settings UI (BlueprintLibrary "Set ElevenLabs API Key"). +// ───────────────────────────────────────────────────────────────────────────── +UCLASS() +class PS_AI_CONVAGENT_API UPS_AI_ConvAgent_SaveGame_ElevenLabs : public USaveGame +{ + GENERATED_BODY() + +public: + /** Client-provided ElevenLabs API key. Empty until the user enters one. */ + UPROPERTY() + FString API_Key; + + /** Fixed save slot name used for the key. */ + static const TCHAR* GetSlotName() { return TEXT("PS_AI_ConvAgent_ElevenLabs"); } + + /** Fixed user index used for the key slot. */ + static constexpr int32 GetUserIndex() { return 0; } +}; diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_TestAPIKey_AsyncAction.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_TestAPIKey_AsyncAction.h new file mode 100644 index 0000000..03192c3 --- /dev/null +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_TestAPIKey_AsyncAction.h @@ -0,0 +1,47 @@ +// Copyright ASTERION. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "PS_AI_ConvAgent_TestAPIKey_AsyncAction.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPS_AI_ConvAgent_TestAPIKeyResult, const FString&, Message); + +// ───────────────────────────────────────────────────────────────────────────── +// UPS_AI_ConvAgent_TestAPIKey_AsyncAction +// +// Latent Blueprint node that validates an ElevenLabs API key by issuing a +// lightweight GET /v1/models request with the key as the xi-api-key header. +// - HTTP 200 → OnValid (key works) +// - HTTP 401 / other → OnInvalid (bad key, or network/server error) +// +// Designed for a settings UI: let the user type their key, run this node, and +// only call "Set ElevenLabs API Key" on the OnValid branch. +// ───────────────────────────────────────────────────────────────────────────── +UCLASS() +class PS_AI_CONVAGENT_API UPS_AI_ConvAgent_TestAPIKey_AsyncAction : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + /** Test an ElevenLabs API key. Pass the key the user just entered in your settings UI. */ + UFUNCTION(BlueprintCallable, Category = "ASTERION|PS_AI_ConvAgent|ElevenLabs API Key", + meta = (BlueprintInternalUseOnly = "true", DisplayName = "Test ElevenLabs API Key")) + static UPS_AI_ConvAgent_TestAPIKey_AsyncAction* TestElevenLabsAPIKey(const FString& Key); + + /** Fired when the key is valid (HTTP 200). The Message contains a human-readable status. */ + UPROPERTY(BlueprintAssignable) + FPS_AI_ConvAgent_TestAPIKeyResult OnValid; + + /** Fired when the key is invalid or the request failed. The Message explains why. */ + UPROPERTY(BlueprintAssignable) + FPS_AI_ConvAgent_TestAPIKeyResult OnInvalid; + + //~ Begin UBlueprintAsyncActionBase interface + virtual void Activate() override; + //~ End UBlueprintAsyncActionBase interface + +private: + FString KeyToTest; +}; diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_ActionSetCustomization_ElevenLabs.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_ActionSetCustomization_ElevenLabs.cpp index 6a03d03..0fab1ac 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_ActionSetCustomization_ElevenLabs.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_ActionSetCustomization_ElevenLabs.cpp @@ -3,6 +3,7 @@ #include "PS_AI_ConvAgent_ActionSetCustomization_ElevenLabs.h" #include "PS_AI_ConvAgent_ActionSet_ElevenLabs.h" #include "PS_AI_ConvAgent.h" +#include "PS_AI_ConvAgent_BlueprintLibrary.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" @@ -65,14 +66,8 @@ void FPS_AI_ConvAgent_ActionSetCustomization_ElevenLabs::OnUpdateAllAgentsClicke // ───────────────────────────────────────────────────────────────────────────── FString FPS_AI_ConvAgent_ActionSetCustomization_ElevenLabs::GetAPIKey() const { - if (FPS_AI_ConvAgentModule::IsAvailable()) - { - if (const UPS_AI_ConvAgent_Settings_ElevenLabs* Settings = FPS_AI_ConvAgentModule::Get().GetSettings()) - { - return Settings->API_Key; - } - } - return FString(); + // Key is client-provided and stored in a SaveGame slot (see BlueprintLibrary). + return UPS_AI_ConvAgent_BlueprintLibrary::GetElevenLabsAPIKey(); } UPS_AI_ConvAgent_ActionSet_ElevenLabs* FPS_AI_ConvAgent_ActionSetCustomization_ElevenLabs::GetEditedAsset() const diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_AgentConfigCustomization_ElevenLabs.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_AgentConfigCustomization_ElevenLabs.cpp index 08568de..432899d 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_AgentConfigCustomization_ElevenLabs.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_AgentConfigCustomization_ElevenLabs.cpp @@ -4,6 +4,7 @@ #include "PS_AI_ConvAgent_AgentConfig_ElevenLabs.h" #include "PS_AI_ConvAgent_Tool_ElevenLabs.h" #include "PS_AI_ConvAgent.h" +#include "PS_AI_ConvAgent_BlueprintLibrary.h" #include "AssetRegistry/AssetRegistryModule.h" @@ -1508,14 +1509,8 @@ void FPS_AI_ConvAgent_AgentConfigCustomization_ElevenLabs::OnFetchAgentClicked() // ───────────────────────────────────────────────────────────────────────────── FString FPS_AI_ConvAgent_AgentConfigCustomization_ElevenLabs::GetAPIKey() const { - if (FPS_AI_ConvAgentModule::IsAvailable()) - { - if (const UPS_AI_ConvAgent_Settings_ElevenLabs* Settings = FPS_AI_ConvAgentModule::Get().GetSettings()) - { - return Settings->API_Key; - } - } - return FString(); + // Key is client-provided and stored in a SaveGame slot (see BlueprintLibrary). + return UPS_AI_ConvAgent_BlueprintLibrary::GetElevenLabsAPIKey(); } UPS_AI_ConvAgent_AgentConfig_ElevenLabs* FPS_AI_ConvAgent_AgentConfigCustomization_ElevenLabs::GetEditedAsset() const diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_ToolCustomization_ElevenLabs.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_ToolCustomization_ElevenLabs.cpp index 00e3e1a..17defa4 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_ToolCustomization_ElevenLabs.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgentEditor/Private/PS_AI_ConvAgent_ToolCustomization_ElevenLabs.cpp @@ -5,6 +5,7 @@ #include "PS_AI_ConvAgent_AgentConfig_ElevenLabs.h" #include "PS_AI_ConvAgent_AgentConfigCustomization_ElevenLabs.h" #include "PS_AI_ConvAgent.h" +#include "PS_AI_ConvAgent_BlueprintLibrary.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" @@ -820,15 +821,8 @@ void FPS_AI_ConvAgent_ToolCustomization_ElevenLabs::OnUpdateAllAgentsClicked() // ───────────────────────────────────────────────────────────────────────────── FString FPS_AI_ConvAgent_ToolCustomization_ElevenLabs::GetAPIKey() const { - if (FPS_AI_ConvAgentModule::IsAvailable()) - { - if (const UPS_AI_ConvAgent_Settings_ElevenLabs* Settings = - FPS_AI_ConvAgentModule::Get().GetSettings()) - { - return Settings->API_Key; - } - } - return FString(); + // Key is client-provided and stored in a SaveGame slot (see BlueprintLibrary). + return UPS_AI_ConvAgent_BlueprintLibrary::GetElevenLabsAPIKey(); } UPS_AI_ConvAgent_Tool_ElevenLabs* FPS_AI_ConvAgent_ToolCustomization_ElevenLabs::GetEditedAsset() const