Skip to content

Commit

Permalink
Model rework pt 1 (#8)
Browse files Browse the repository at this point in the history
* + save generations to azure blob
  • Loading branch information
NicklausBrain authored Sep 9, 2024
1 parent 4242d4f commit 2c36e68
Show file tree
Hide file tree
Showing 11 changed files with 503 additions and 6 deletions.
49 changes: 49 additions & 0 deletions My1kWordsEe/Components/Layout/SampleV2.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
@using My1kWordsEe.Services;
@using My1kWordsEe.Models;
@using CSharpFunctionalExtensions;

@inject OpenAiService OpenAiService;
@inject TartuNlpService TartuNlpService;
@inject StabilityAiService StabilityAiService;
@inject IJSRuntime JS

@code {
[Parameter]
public SampleSentence Sample { get; set; }

protected override async Task OnInitializedAsync()
{
}

private async Task PlaySample(MouseEventArgs e)
{
await JS.InvokeVoidAsync("playAudioSample", Sample.EeAudioUrl);
}
}

<script>
window.playAudioSample = async (audioId) => {
const audio = document.getElementById(audioId);
await audio.play();
};
</script>

<div class="col-sm-6">

<ul class="list-group">
<li class="list-group-item">
@Sample.EeSentence
<button type="button" class="btn btn-primary" @onclick="PlaySample">
<i class="bi bi-volume-down-fill"></i>
<audio id="@Sample.EeAudioUrl" src="@Sample.EeAudioUrl"></audio>
</button>
</li>
<li class="list-group-item">
@Sample.EnSentence
</li>
<li class="list-group-item">
<img id="sampleImage" width="256" height="256" src="@Sample.ImageUrl" />
</li>
</ul>

</div>
100 changes: 100 additions & 0 deletions My1kWordsEe/Components/Pages/Word.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
@page "/word/{eeWord}"
@rendermode InteractiveServer

@using CSharpFunctionalExtensions
@using My1kWordsEe.Services;
@using My1kWordsEe.Models;
@using My1kWordsEe.Components.Layout;
@using My1kWordsEe.Services.Cqs;

@inject IJSRuntime JS
@inject EnsureWordCommand EnsureWordCommand
@inject CreateSampleCommand CreateSampleCommand

@code {
[Parameter]
public string EeWord { get; set; }

private Result<SampleWord>? WordMetadata;

protected override async Task OnInitializedAsync()
{
WordMetadata = await this.EnsureWordCommand.Invoke(EeWord);
}

private bool isGenerationInProgress = false;

private async Task GenerateSample(MouseEventArgs e)
{
this.isGenerationInProgress = true;
if (WordMetadata.HasValue)
{
this.WordMetadata = await this.CreateSampleCommand.Invoke(WordMetadata.Value.Value);
}
this.isGenerationInProgress = false;
}
}

<div class="text-left">
<div class="row">
<h1>@EeWord
</h1>

@if (WordMetadata.HasValue)
{
if (WordMetadata.Value.IsSuccess)
{
<h3>[@WordMetadata.Value.Value.EnWord]</h3>

@if (WordMetadata.Value.Value.EnWords.Any())
{
<h4>[@string.Join(" / ", WordMetadata.Value.Value.EnWords)]</h4>
}

<h3>@WordMetadata.Value.Value.EnExplanation</h3>
}
else
{
<h3>@WordMetadata.Value.Error</h3>
}
}
else
{
<div class="d-flex justify-content-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
}
</div>

@if (this.WordMetadata.HasValue && this.WordMetadata.Value.IsSuccess)
{
<div class="row">
@foreach (var sample in this.WordMetadata.Value.Value.Samples)
{
<SampleV2 Sample="@sample">
</SampleV2>
}

<div class="col-sm-6">
@if (this.isGenerationInProgress)
{
<div class="d-flex justify-content-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
}
else
{
<button type="button" class="btn btn-primary" onclick="@GenerateSample">
<i class="bi bi-robot"></i>
Generate sample sentence
</button>
}

</div>
</div>
}
</div>
6 changes: 4 additions & 2 deletions My1kWordsEe/Components/Pages/Words.razor
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
@using My1kWordsEe.Models;
@using My1kWordsEe.Components.Layout;

@inject NavigationManager NavigationManager
@inject OpenAiService OpenAiService

@code {
public Ee1kWords Ee1KWords { get; private set; } = new Ee1kWords();

private void SelectWord(MouseEventArgs e, string word)
{
Ee1KWords = Ee1KWords.WithSelectedWord(word);
//Ee1KWords = Ee1KWords.WithSelectedWord(word);
NavigationManager.NavigateTo($"/word/{word}");
}

private void OnSearchInput(ChangeEventArgs e)
Expand All @@ -31,7 +33,7 @@
<span class="input-group-text" id="inputGroup-sizing-default">Search</span>
</div>
<input type="text" class="form-control " aria-label="Search" aria-describedby="inputGroup-sizing-default"
@oninput="OnSearchInput" />
@oninput="OnSearchInput" />
</div>
</div>
<div class="row">
Expand Down
17 changes: 17 additions & 0 deletions My1kWordsEe/Models/SampleSentence.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Text.Json.Serialization;

namespace My1kWordsEe.Models
{
public class SampleSentence
{
public string EeWord { get; set; }

Check warning on line 7 in My1kWordsEe/Models/SampleSentence.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'EeWord' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

public string EeSentence { get; set; }

public string EnSentence { get; set; }

public Uri EeAudioUrl { get; set; }

public Uri ImageUrl { get; set; }
}
}
45 changes: 45 additions & 0 deletions My1kWordsEe/Models/SampleWord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace My1kWordsEe.Models
{
public record SampleWord
{

private string eeWord = "";
private string[] enWords = [];

private SampleSentence[] samples = [];


/// <summary>
/// Estonian word
/// </summary>
public string EeWord
{
get => this.eeWord;
init => this.eeWord = value?.ToLower() ?? "";
}

/// <summary>
/// Default translation to English
/// </summary>
public string EnWord { get; init; }

Check warning on line 24 in My1kWordsEe/Models/SampleWord.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'EnWord' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

/// <summary>
/// Alternatives to EnWord
/// </summary>
public string[] EnWords
{
get => this.enWords;
init => this.enWords = value ?? this.enWords;
}

public string EnExplanation { get; init; }

Check warning on line 35 in My1kWordsEe/Models/SampleWord.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'EnExplanation' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

public Uri EeAudioUrl { get; init; }

Check warning on line 37 in My1kWordsEe/Models/SampleWord.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'EeAudioUrl' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

public SampleSentence[] Samples
{
get => this.samples;
init => this.samples = value ?? this.samples;
}
}
}
1 change: 1 addition & 0 deletions My1kWordsEe/My1kWordsEe.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.21.2" />
<PackageReference Include="CSharpFunctionalExtensions" Version="2.42.5" />
<PackageReference Include="OpenAI" Version="2.0.0-beta.1" />
</ItemGroup>
Expand Down
14 changes: 14 additions & 0 deletions My1kWordsEe/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using My1kWordsEe.Components;
using My1kWordsEe.Services;
using My1kWordsEe.Services.Cqs;
using My1kWordsEe.Services.Db;

namespace My1kWordsEe
{
Expand Down Expand Up @@ -28,9 +30,21 @@ public static void Main(string[] args)
throw new ApplicationException("Secrets:StabilityAiKey is missing");
}

var azureBlobConnectionString =
builder.Configuration["Secrets:AzureBlobConnectionString"] ??
Environment.GetEnvironmentVariable("Secrets_AzureBlobConnectionString");

if (string.IsNullOrWhiteSpace(azureBlobConnectionString))
{
throw new ApplicationException("Secrets:AzureBlobConnectionString is missing");
}

builder.Services.AddSingleton(new StabilityAiService(stabilityAiKey));
builder.Services.AddSingleton(new OpenAiService(openApiKey));
builder.Services.AddSingleton(new AzureBlobService(azureBlobConnectionString));
builder.Services.AddSingleton(new TartuNlpService());
builder.Services.AddSingleton<EnsureWordCommand>();
builder.Services.AddSingleton<CreateSampleCommand>();

// Add services to the container.
builder.Services.AddRazorComponents()
Expand Down
66 changes: 66 additions & 0 deletions My1kWordsEe/Services/Cqs/CreateSampleCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using CSharpFunctionalExtensions;

using My1kWordsEe.Models;
using My1kWordsEe.Services.Db;

namespace My1kWordsEe.Services.Cqs
{
public class CreateSampleCommand
{
private readonly AzureBlobService azureBlobService;
private readonly OpenAiService openAiService;
private readonly TartuNlpService tartuNlpService;
private readonly StabilityAiService stabilityAiService;

public CreateSampleCommand(
AzureBlobService azureBlobService,
OpenAiService openAiService,
TartuNlpService tartuNlpService,
StabilityAiService stabilityAiService)
{
this.azureBlobService = azureBlobService;
this.openAiService = openAiService;
this.tartuNlpService = tartuNlpService;
this.stabilityAiService = stabilityAiService;
}

public async Task<Result<SampleWord>> Invoke(SampleWord word)
{
var sentence = await this.openAiService.GetSampleSentence(word.EnWord);
var imageGeneration = this.GenerateImage(sentence.Value);
var speechGeneration = this.GenerateSpeech(sentence.Value);
await Task.WhenAll(imageGeneration, speechGeneration);

var updatedWordData = word with
{
Samples = word.Samples.Append(new SampleSentence
{
EeWord = word.EeWord,
EeSentence = sentence.Value.Ee,
EnSentence = sentence.Value.En,
EeAudioUrl = speechGeneration.Result.Value,
ImageUrl = imageGeneration.Result.Value,
}).ToArray()
};

await this.azureBlobService.SaveWordData(updatedWordData);

return Result.Success(updatedWordData);
}

private async Task<Result<Uri>> GenerateImage(Sentence sentence)
{
var prompt = await this.openAiService.GetDallEPrompt(sentence.En);
var image = await this.stabilityAiService.GenerateImage(prompt.Value);
var url = await this.azureBlobService.SaveImage(image.Value);
return url;
}

private async Task<Result<Uri>> GenerateSpeech(Sentence sentence)
{
var speech = await this.tartuNlpService.GetSpeech(sentence.Ee);
var url = await this.azureBlobService.SaveAudio(speech);
return url;
}
}
}
40 changes: 40 additions & 0 deletions My1kWordsEe/Services/Cqs/EnsureWordCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using CSharpFunctionalExtensions;

using My1kWordsEe.Models;
using My1kWordsEe.Services.Db;

namespace My1kWordsEe.Services.Cqs
{
public class EnsureWordCommand
{
private readonly AzureBlobService azureBlobService;
private readonly OpenAiService openAiService;

public EnsureWordCommand(
AzureBlobService azureBlobService,
OpenAiService openAiService)
{
this.azureBlobService = azureBlobService;
this.openAiService = openAiService;
}

public async Task<Result<SampleWord>> Invoke(string eeWord)
{
var existingRecord = await azureBlobService.GetWordData(eeWord);

if (existingRecord.IsSuccess)
{
return existingRecord;
}

var sampleWord = await openAiService.GetWordMetadata(eeWord);

if (sampleWord.IsSuccess)
{
await azureBlobService.SaveWordData(sampleWord.Value);
}

return sampleWord;
}
}
}
Loading

0 comments on commit 2c36e68

Please sign in to comment.