diff --git a/ConsoleApp/ConsoleApp.csproj b/ConsoleApp/ConsoleApp.csproj new file mode 100644 index 00000000..fceb1a53 --- /dev/null +++ b/ConsoleApp/ConsoleApp.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + enable + enable + 716f7a77-339c-46aa-89a6-2dc47eab1d23 + + + + + + + + + + + + + + diff --git a/ConsoleApp/Program.cs b/ConsoleApp/Program.cs new file mode 100644 index 00000000..d6c6b391 --- /dev/null +++ b/ConsoleApp/Program.cs @@ -0,0 +1,79 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +using My1kWordsEe.Services; +using My1kWordsEe.Services.Cqs; +using My1kWordsEe.Services.Db; + +namespace ConsoleApp +{ + internal class Program + { + + private static readonly string[] Words = new string[] + { + }; + + static async Task Main(string[] args) + { + using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole()); + IConfigurationRoot config = new ConfigurationBuilder() + .AddUserSecrets() + .Build(); + + var openAiKey = config["Secrets:OpenAiKey"]; + + if (string.IsNullOrWhiteSpace(openAiKey)) + { + throw new ApplicationException("Secrets:OpenAiKey is missing"); + } + + var azureBlobConnectionString = config["Secrets:AzureBlobConnectionString"]; + + if (string.IsNullOrWhiteSpace(azureBlobConnectionString)) + { + throw new ApplicationException("Secrets:AzureBlobConnectionString is missing"); + } + + var blob = new AzureBlobService(azureBlobConnectionString); + var openAi = new OpenAiService(factory.CreateLogger(), openAiKey); + var ensure = new EnsureWordCommand(blob, openAi); + + var errors = new List(); + + Console.WriteLine(Words.Length); + foreach (var word in Words) + { + Console.WriteLine($"Processing {word}"); + + var ensureCmd = await ensure.Invoke(word); + if (ensureCmd.IsFailure) + { + Console.WriteLine($"Failed to ensure {word}: {ensureCmd.Error}"); + errors.Add($"Failed to ensure {word}: {ensureCmd.Error}"); + } + + var data = await blob.GetWordData(word); + + if (data.IsSuccess) + { + var wordData = data.Value; + var update = wordData with + { + EeAudioUrl = new Uri( + $"https://my1kee.blob.core.windows.net/audio/{wordData.EeWord}.wav") + }; + + await blob.SaveWordData(update); + } + else + { + errors.Add($"Failed to get data for {word}"); + Console.WriteLine($"Failed to get data for {word}"); + } + } + + File.WriteAllLines("cmd-errors-5.txt", errors); + } + } +} \ No newline at end of file diff --git a/My1kWordsEe/Components/Pages/Word.razor b/My1kWordsEe/Components/Pages/Word.razor index 302fa73c..e7a888cf 100644 --- a/My1kWordsEe/Components/Pages/Word.razor +++ b/My1kWordsEe/Components/Pages/Word.razor @@ -33,11 +33,33 @@ } this.isGenerationInProgress = false; } + + private async Task SpeakWord(MouseEventArgs e) + { + await JS.InvokeVoidAsync("speakWord", EeWord); + } + + private bool IsDataLoadedOk => WordMetadata.HasValue && WordMetadata.Value.IsSuccess; } + +
-

@EeWord +

+ @EeWord + + @if (IsDataLoadedOk) + { + + + + }

@if (WordMetadata.HasValue) diff --git a/My1kWordsEe/My1kWordsEe.sln b/My1kWordsEe/My1kWordsEe.sln index c3817c04..1b9968fe 100644 --- a/My1kWordsEe/My1kWordsEe.sln +++ b/My1kWordsEe/My1kWordsEe.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.10.35122.118 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "My1kWordsEe", "My1kWordsEe.csproj", "{497B021C-F03E-4CB6-BC42-3A9D8BC68F68}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "My1kWordsEe", "My1kWordsEe.csproj", "{497B021C-F03E-4CB6-BC42-3A9D8BC68F68}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp", "..\ConsoleApp\ConsoleApp.csproj", "{ECE5CED5-98E9-4FAB-A3CB-0A5002C88D9C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {497B021C-F03E-4CB6-BC42-3A9D8BC68F68}.Debug|Any CPU.Build.0 = Debug|Any CPU {497B021C-F03E-4CB6-BC42-3A9D8BC68F68}.Release|Any CPU.ActiveCfg = Release|Any CPU {497B021C-F03E-4CB6-BC42-3A9D8BC68F68}.Release|Any CPU.Build.0 = Release|Any CPU + {ECE5CED5-98E9-4FAB-A3CB-0A5002C88D9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECE5CED5-98E9-4FAB-A3CB-0A5002C88D9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECE5CED5-98E9-4FAB-A3CB-0A5002C88D9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECE5CED5-98E9-4FAB-A3CB-0A5002C88D9C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/My1kWordsEe/Program.cs b/My1kWordsEe/Program.cs index d93ae6e3..dc51ae2b 100644 --- a/My1kWordsEe/Program.cs +++ b/My1kWordsEe/Program.cs @@ -9,14 +9,15 @@ public class Program { public static void Main(string[] args) { + // default log: Console, Debug, EventSource var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddEnvironmentVariables(); - var openApiKey = + var openAiKey = builder.Configuration["Secrets:OpenAiKey"] ?? Environment.GetEnvironmentVariable("Secrets_OpenAiKey"); - if (string.IsNullOrWhiteSpace(openApiKey)) + if (string.IsNullOrWhiteSpace(openAiKey)) { throw new ApplicationException("Secrets:OpenAiKey is missing"); } @@ -40,7 +41,7 @@ public static void Main(string[] args) } builder.Services.AddSingleton(new StabilityAiService(stabilityAiKey)); - builder.Services.AddSingleton(new OpenAiService(openApiKey)); + builder.Services.AddSingleton((p) => new OpenAiService(p.GetRequiredService>(), openAiKey)); builder.Services.AddSingleton(new AzureBlobService(azureBlobConnectionString)); builder.Services.AddSingleton(new TartuNlpService()); builder.Services.AddSingleton(); diff --git a/My1kWordsEe/Services/Cqs/EnsureWordCommand.cs b/My1kWordsEe/Services/Cqs/EnsureWordCommand.cs index 909550e7..7a91d787 100644 --- a/My1kWordsEe/Services/Cqs/EnsureWordCommand.cs +++ b/My1kWordsEe/Services/Cqs/EnsureWordCommand.cs @@ -31,6 +31,7 @@ public async Task> Invoke(string eeWord) if (sampleWord.IsSuccess) { + // generate audio await azureBlobService.SaveWordData(sampleWord.Value); } diff --git a/My1kWordsEe/Services/Db/AzureBlobService.cs b/My1kWordsEe/Services/Db/AzureBlobService.cs index 1bd69852..392079b0 100644 --- a/My1kWordsEe/Services/Db/AzureBlobService.cs +++ b/My1kWordsEe/Services/Db/AzureBlobService.cs @@ -52,14 +52,16 @@ await blob.UploadAsync( overwrite: true); } - public async Task SaveAudio(Stream audioStream) + public async Task SaveAudio(Stream audioStream, string blobName) { BlobContainerClient container = await GetAudioContainer(); - BlobClient blob = container.GetBlobClient(WavBlobName()); - await blob.UploadAsync(audioStream); + BlobClient blob = container.GetBlobClient(blobName); + await blob.UploadAsync(audioStream, overwrite: true); return blob.Uri; } + public Task SaveAudio(Stream audioStream) => SaveAudio(audioStream, WavBlobName()); + public async Task SaveImage(Stream imageStream) { BlobContainerClient container = await GetImageContainer(); diff --git a/My1kWordsEe/Services/OpenAiService.cs b/My1kWordsEe/Services/OpenAiService.cs index 1913be8d..789c6e8a 100644 --- a/My1kWordsEe/Services/OpenAiService.cs +++ b/My1kWordsEe/Services/OpenAiService.cs @@ -12,9 +12,12 @@ namespace My1kWordsEe.Services { public class OpenAiService { - public OpenAiService(string apiKey) + private readonly ILogger logger; + + public OpenAiService(ILogger logger, string apiKey) { - ApiKey = apiKey; + this.logger = logger; + this.ApiKey = apiKey; } private string ApiKey { get; } @@ -93,21 +96,20 @@ public async Task> GetSampleSentence(string eeWord) ChatCompletion chatCompletion = await client.CompleteChatAsync( [ new SystemChatMessage( - "Sa oled keeleõppe süsteemi abiline, mis aitab õppida enim levinud eesti keele sõnu. " + - "Sinu sisend on üks sõna eesti keeles. " + - "Sinu ülesanne on kirjutada selle kasutamise kohta lihtne lühike näitelause, kasutades seda sõna. " + - "Lauses kasuta kõige levinuimaid ja lihtsamaid sõnu eesti keeles et toetada keeleõpet. " + - "Sinu väljund on JSON objekt, milles on näitelaus eesti keeles ja selle vastav tõlge inglise keelde:\n" + - "```\n" + - "{\"ee_sentence\": \"\", \"en_sentence\": \"\" }" + - "\n```" + - "\n Tagastab ainult json-objekti!"), + "Sa oled keeleõppe süsteemi abiline, mis aitab õppida enim levinud eesti keele sõnu.\n" + + "Sinu sisend on üks sõna eesti keeles.\n" + + "Sinu ülesanne on kirjutada selle kasutamise kohta lihtne lühike näitelause, kasutades seda sõna.\n" + + "Lauses kasuta kõige levinuimaid ja lihtsamaid sõnu eesti keeles et toetada keeleõpet.\n" + + "Teie väljundiks on JSON-objekt koos eestikeelse näidislausega ja sellele vastav tõlge inglise keelde vastavalt lepingule:\n" + + "```\n{\n" + + "\"ee_sentence\": \"\", \"en_sentence\": \"\"" + + "\n}\n```"), new UserChatMessage(eeWord), ]); foreach (var c in chatCompletion.Content) { - var jsonStr = c.Text.Trim('`', ' ', '\'', '"'); + var jsonStr = c.Text.Replace("json", "", StringComparison.OrdinalIgnoreCase).Trim('`', ' ', '\'', '"'); var sentence = JsonSerializer.Deserialize(jsonStr); if (sentence == null) { @@ -124,26 +126,26 @@ public async Task> GetSampleSentence(string eeWord) public async Task> GetWordMetadata(string word) { - ChatClient client = new(model: "gpt-4o-mini", ApiKey); + ChatClient client = new(model: "gpt-4o", ApiKey); ChatCompletion chatCompletion = await client.CompleteChatAsync( [ new SystemChatMessage( - "Your input is an Estonian word. " + - "Your output is word metadata in JSON:\n" + + "Sinu sisend on eestikeelne sõna.\n" + + "Kui antud sõna ei ole eestikeelne, tagasta 404\n"+ + "Teie väljund on sõna metaandmed JSON-is vastavalt antud lepingule:\n" + "```\n{\n" + - "ee_word: \"\",\n" + - "en_word: \"\"\n" + - "en_words: []\n" + - "en_explanation: \"\"\n" + - "}\n```\n" + - "If the given word is not Estonian return 404"), + "\"ee_word\": \"\",\n" + + "\"en_word\": \"\"\n" + + "\"en_words\": []\n" + + "\"en_explanation\": \"\"\n" + + "}\n```\n"), new UserChatMessage(word), ]); foreach (var c in chatCompletion.Content) { - var jsonStr = c.Text.Trim('`', ' ', '\'', '"'); + var jsonStr = c.Text.Replace("json", "", StringComparison.OrdinalIgnoreCase).Trim('`', ' ', '\'', '"'); if (jsonStr.Contains("404")) { @@ -168,8 +170,9 @@ public async Task> GetWordMetadata(string word) }); } } - catch (JsonException) + catch (JsonException jsonException) { + this.logger.LogError(jsonException, "Failed to deserialize JSON: {jsonStr}", jsonStr); return Result.Failure("Unexpected data returned by AI"); } }