diff --git a/MusicTypeChat.ServiceInterface/AppConfig.cs b/MusicTypeChat.ServiceInterface/AppConfig.cs index 4c1ed8a..57b1c01 100644 --- a/MusicTypeChat.ServiceInterface/AppConfig.cs +++ b/MusicTypeChat.ServiceInterface/AppConfig.cs @@ -2,21 +2,12 @@ public class AppConfig { - public GcpConfig? GcpConfig { get; set; } - public S3Config? AwsConfig { get; set; } - public S3Config? R2Config { get; set; } - public AzureConfig? AzureConfig { get; set; } public SiteConfig Music { get; set; } public string? NodePath { get; set; } public string? FfmpegPath { get; set; } public string? WhisperPath { get; set; } public int NodeProcessTimeoutMs { get; set; } = 120 * 1000; - public GcpConfig AssertGcpConfig() => GcpConfig ?? throw new Exception($"{nameof(GcpConfig)} is not configured"); - public S3Config AssertAwsConfig() => AwsConfig ?? throw new Exception($"{nameof(AwsConfig)} is not configured"); - public S3Config AssertR2Config() => R2Config ?? throw new Exception($"{nameof(R2Config)} is not configured"); - public AzureConfig AssertAzureConfig() => AzureConfig ?? throw new Exception($"{nameof(AzureConfig)} is not configured"); - public SiteConfig GetSiteConfig(string name) { return name.ToLower() switch @@ -33,29 +24,4 @@ public class SiteConfig public string? RecognizerId { get; set; } public string? PhraseSetId { get; set; } public string? VocabularyName { get; set; } -} - - -public class GcpConfig -{ - public string Project { get; set; } - public string Location { get; set; } - public string Bucket { get; set; } -} - -public class S3Config -{ - public string? AccountId { get; set; } - public string? AccessKey { get; set; } - public string? SecretKey { get; set; } - public string? Region { get; set; } - public string Bucket { get; set; } -} - -public class AzureConfig -{ - public string? SpeechKey { get; set; } - public string? SpeechRegion { get; set; } - public string? ConnectionString { get; set; } - public string ContainerName { get; set; } } \ No newline at end of file diff --git a/MusicTypeChat/Configure.AppHost.cs b/MusicTypeChat/Configure.AppHost.cs index 922cdf7..de49ea1 100644 --- a/MusicTypeChat/Configure.AppHost.cs +++ b/MusicTypeChat/Configure.AppHost.cs @@ -1,4 +1,6 @@ using Funq; +using ServiceStack.Aws; +using ServiceStack.Azure; using ServiceStack.Configuration; using ServiceStack.Host; using ServiceStack.IO; @@ -14,16 +16,41 @@ public class AppHost : AppHostBase, IHostingStartup public void Configure(IWebHostBuilder builder) => builder .ConfigureServices((context,services) => { // Configure ASP.NET Core IOC Dependencies - var appConfig = new AppConfig(); - context.Configuration.Bind(nameof(AppConfig), appConfig); + var appConfig = context.Configuration.GetSection(nameof(AppConfig)).Get(); services.AddSingleton(appConfig); + var aws = context.Configuration.GetSection(nameof(AwsConfig))?.Get(); + if (aws != null) + { + aws.AccountId ??= Environment.GetEnvironmentVariable("AWS_ACCOUNT_ID"); + aws.AccessKey ??= Environment.GetEnvironmentVariable("AWS_ACCESS_KEY_ID"); + aws.SecretKey ??= Environment.GetEnvironmentVariable("AWS_SECRET_ACCESS_KEY"); + aws.Region ??= Environment.GetEnvironmentVariable("AWS_REGION"); + services.AddSingleton(aws); + } + + var r2 = context.Configuration.GetSection(nameof(R2Config))?.Get(); + if (r2 != null) + { + r2.AccountId ??= Environment.GetEnvironmentVariable("R2_ACCOUNT_ID"); + r2.AccessKey ??= Environment.GetEnvironmentVariable("R2_ACCESS_KEY_ID"); + r2.SecretKey ??= Environment.GetEnvironmentVariable("R2_SECRET_ACCESS_KEY"); + services.AddSingleton(r2); + } + + var azure = context.Configuration.GetSection(nameof(AzureConfig))?.Get(); + if (azure != null) + { + azure.SpeechKey ??= Environment.GetEnvironmentVariable("SPEECH_KEY"); + azure.SpeechRegion ??= Environment.GetEnvironmentVariable("SPEECH_REGION"); + azure.ConnectionString ??= Environment.GetEnvironmentVariable("AZURE_BLOB_CONNECTION_STRING"); + services.AddSingleton(azure); + } + if (!AppTasks.IsRunAsAppTask()) { - appConfig.NodePath = ProcessUtils.FindExePath("node") - ?? throw new Exception("Could not resolve path to node"); - appConfig.FfmpegPath = ProcessUtils.FindExePath("ffmpeg"); - appConfig.WhisperPath = ProcessUtils.FindExePath("whisper"); + appConfig.NodePath ??= ProcessUtils.FindExePath("node") ?? throw new Exception("Could not resolve path to node"); + appConfig.FfmpegPath ??= ProcessUtils.FindExePath("ffmpeg"); } }); diff --git a/MusicTypeChat/Configure.Speech.cs b/MusicTypeChat/Configure.Speech.cs index df47766..c2ae072 100644 --- a/MusicTypeChat/Configure.Speech.cs +++ b/MusicTypeChat/Configure.Speech.cs @@ -1,10 +1,10 @@ -using Amazon; -using Amazon.TranscribeService; -using ServiceStack.AI; +using ServiceStack.AI; using ServiceStack.IO; using ServiceStack.GoogleCloud; +using ServiceStack.Aws; +using ServiceStack.Azure; +using Amazon.TranscribeService; using Google.Cloud.Speech.V2; -using Microsoft.CognitiveServices.Speech; using MusicTypeChat.ServiceInterface; [assembly: HostingStartup(typeof(MusicTypeChat.ConfigureSpeech))] @@ -22,41 +22,49 @@ public void Configure(IWebHostBuilder builder) => builder if (speechProvider == nameof(GoogleCloudSpeechToText)) { GoogleCloudConfig.AssertValidCredentials(); - services.AddSingleton(c => { - var config = c.Resolve(); - var google = config.AssertGcpConfig(); - return new GoogleCloudSpeechToText(SpeechClient.Create(), - new GoogleCloudSpeechConfig { - Project = google.Project, - Location = google.Location, - Bucket = google.Bucket, - RecognizerId = config.Music.RecognizerId, - PhraseSetId = config.Music.PhraseSetId, - } - ); + services.AddSingleton(c => new SpeechToTextFactory + { + Resolve = feature => + { + var config = c.Resolve(); + var gcp = c.Resolve(); + var siteConfig = config.GetSiteConfig(feature); + + return new GoogleCloudSpeechToText( + SpeechClient.Create(), + gcp.ToSpeechToTextConfig(x => { + x.RecognizerId = siteConfig.RecognizerId; + x.PhraseSetId = siteConfig.PhraseSetId; + })) + { + VirtualFiles = HostContext.VirtualFiles + }; + } }); } else if (speechProvider == nameof(AwsSpeechToText)) { - services.AddSingleton(c => { - var config = c.Resolve(); - var a = config.AssertAwsConfig(); - return new AwsSpeechToText(new AmazonTranscribeServiceClient( - a.AccessKey, a.SecretKey, RegionEndpoint.GetBySystemName(a.Region)), - new AwsSpeechToTextConfig { - Bucket = a.Bucket, - VocabularyName = config.Music.VocabularyName, - }); + services.AddSingleton(c => new SpeechToTextFactory + { + Resolve = feature => + { + var config = c.Resolve(); + var aws = c.Resolve(); + var siteConfig = config.GetSiteConfig(feature); + + return new AwsSpeechToText( + new AmazonTranscribeServiceClient(aws.AccessKey, aws.SecretKey, aws.ToRegionEndpoint()), + aws.ToSpeechToTextConfig(x => x.VocabularyName = siteConfig.VocabularyName)) + { + VirtualFiles = HostContext.VirtualFiles + }; + } }); } else if (speechProvider == nameof(AzureSpeechToText)) { - services.AddSingleton(c => { - var az = c.Resolve().AssertAzureConfig(); - var config = SpeechConfig.FromSubscription(az.SpeechKey, az.SpeechRegion); - config.SpeechRecognitionLanguage = "en-US"; - return new AzureSpeechToText(config); - }); + services.AddSingleton(c => + new AzureSpeechToText(c.Resolve().ToSpeechConfig())); } else if (speechProvider == nameof(WhisperApiSpeechToText)) { @@ -64,9 +72,12 @@ public void Configure(IWebHostBuilder builder) => builder } else if (speechProvider == nameof(WhisperLocalSpeechToText)) { - services.AddSingleton(c => new WhisperLocalSpeechToText { - WhisperPath = c.Resolve().WhisperPath ?? ProcessUtils.FindExePath("whisper"), - TimeoutMs = c.Resolve().NodeProcessTimeoutMs, + services.AddSingleton(c => { + var config = c.Resolve(); + return new WhisperLocalSpeechToText { + WhisperPath = config.WhisperPath ?? ProcessUtils.FindExePath("whisper"), + TimeoutMs = config.NodeProcessTimeoutMs, + }; }); } else throw new NotSupportedException($"Unknown SpeechProvider '{speechProvider}'"); @@ -74,9 +85,9 @@ public void Configure(IWebHostBuilder builder) => builder .ConfigureAppHost(afterConfigure:appHost => { if (AppTasks.IsRunAsAppTask()) return; - if (appHost.Resolve() is IRequireVirtualFiles requireVirtualFiles) + if (appHost.TryResolve() is IRequireVirtualFiles requireVirtualFiles) { requireVirtualFiles.VirtualFiles = appHost.VirtualFiles; } }); -} \ No newline at end of file +} diff --git a/MusicTypeChat/Configure.Vfs.cs b/MusicTypeChat/Configure.Vfs.cs index 57e800d..9cd67ce 100644 --- a/MusicTypeChat/Configure.Vfs.cs +++ b/MusicTypeChat/Configure.Vfs.cs @@ -1,10 +1,10 @@ -using Amazon; -using Amazon.S3; -using Google.Cloud.Storage.V1; +using ServiceStack.IO; +using ServiceStack.Aws; +using ServiceStack.Azure; using ServiceStack.Azure.Storage; using ServiceStack.GoogleCloud; -using ServiceStack.IO; -using MusicTypeChat.ServiceInterface; +using Amazon.S3; +using Google.Cloud.Storage.V1; [assembly: HostingStartup(typeof(MusicTypeChat.ConfigureVfs))] @@ -22,31 +22,31 @@ public void Configure(IWebHostBuilder builder) => builder { GoogleCloudConfig.AssertValidCredentials(); appHost.VirtualFiles = new GoogleCloudVirtualFiles( - StorageClient.Create(), appHost.Resolve().AssertGcpConfig().Bucket); + StorageClient.Create(), appHost.Resolve().Bucket!); } else if (vfsProvider == nameof(S3VirtualFiles)) { - var awsConfig = appHost.Resolve().AssertAwsConfig(); + var aws = appHost.Resolve(); appHost.VirtualFiles = new S3VirtualFiles(new AmazonS3Client( - awsConfig.AccessKey, - awsConfig.SecretKey, - RegionEndpoint.GetBySystemName(awsConfig.Region)), awsConfig.Bucket); + aws.AccessKey, + aws.SecretKey, + aws.ToRegionEndpoint()), aws.Bucket); } else if (vfsProvider == nameof(R2VirtualFiles)) { - var r2Config = appHost.Resolve().AssertR2Config(); + var r2 = appHost.Resolve(); appHost.VirtualFiles = new R2VirtualFiles(new AmazonS3Client( - r2Config.AccessKey, - r2Config.SecretKey, + r2.AccessKey, + r2.SecretKey, new AmazonS3Config { - ServiceURL = $"https://{r2Config.AccountId}.r2.cloudflarestorage.com", - }), r2Config.Bucket); + ServiceURL = r2.ToServiceUrl(), + }), r2.Bucket); } else if (vfsProvider == nameof(AzureBlobVirtualFiles)) { - var azureConfig = appHost.Resolve().AssertAzureConfig(); - appHost.VirtualFiles = new AzureBlobVirtualFiles(azureConfig.ConnectionString, azureConfig.ContainerName); + var azure = appHost.Resolve(); + appHost.VirtualFiles = new AzureBlobVirtualFiles(azure.ConnectionString, azure.ContainerName); } //else uses default FileSystemVirtualFiles }); -} +} \ No newline at end of file diff --git a/MusicTypeChat/appsettings.json b/MusicTypeChat/appsettings.json index c025024..8707f1c 100644 --- a/MusicTypeChat/appsettings.json +++ b/MusicTypeChat/appsettings.json @@ -20,21 +20,21 @@ "VfsProvider": "FileSystemVirtualFiles", "SpeechProvider": "WhisperApiSpeechToText", "TypeChatProvider": "NodeTypeChat", + "GoogleCloudConfig": { + "Project": "servicestackdemo", + "Location": "global", + "Bucket": "servicestack-typechat" + }, + "AwsConfig": { + "Bucket": "servicestack-typechat" + }, + "R2Config": { + "Bucket": "servicestack-typechat" + }, + "AzureConfig": { + "ContainerName": "servicestack-typechat" + }, "AppConfig": { - "GcpConfig": { - "Project": "servicestackdemo", - "Location": "global", - "Bucket": "servicestack-typechat" - }, - "AwsConfig": { - "Bucket": "servicestack-typechat" - }, - "R2Config": { - "Bucket": "servicestack-typechat" - }, - "AzureConfig": { - "ContainerName": "servicestack-typechat" - }, "Music": { "GptPath": "gpt/music" }