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");
}
}