2023-08-02 21:33:09 +02:00
@page "/aiart"
2023-08-02 18:33:28 +02:00
@using OpenAI_API
@using OpenAI_API.Chat
@using OpenAI_API.Models
2023-08-02 21:33:09 +02:00
@using System.Diagnostics
@inject TooltipService TooltipService
@inject DialogService DialogService
2023-08-02 18:33:28 +02:00
<PageTitle>AiArt</PageTitle>
<h1>Wunschbilder von KI nur für dich</h1>
2023-08-02 21:33:09 +02:00
<RadzenStack Orientation="Orientation.Vertical" AlignItems="AlignItems.Center">
<RadzenText TextStyle="TextStyle.H2">Nenne uns deinen Wunsch:</RadzenText>
<RadzenTextBox @bind-Value=@request Placeholder="Dein Wunsch"/>
2023-08-03 23:24:10 +02:00
2023-08-02 21:33:09 +02:00
<RadzenPanel AllowCollapse="true" Style="width: 500px;" Text="Mehr Optionen">
<ChildContent>
<RadzenCard class="rz-mt-4">
<RadzenStack Orientation="Orientation.Horizontal"
MouseEnter="@(args => ShowTemperatureTooltip(args))"
MouseLeave="TooltipService.Close"
AlignItems="AlignItems.Center" Wrap="FlexWrap.Wrap">
<RadzenText>Temperature:</RadzenText>
<RadzenSlider @bind-Value=@temperature TValue="float"
2023-08-04 15:41:26 +02:00
Step="0.1" Min="0.0m" Max="2.0m">
2023-08-02 21:33:09 +02:00
</RadzenSlider>
<RadzenText>@temperature</RadzenText>
</RadzenStack>
</RadzenCard>
</ChildContent>
</RadzenPanel>
2023-08-04 14:42:03 +02:00
<RadzenButton Visible=@_buttonVisible Click=@(async ()=> await DoStuff(true))>Generate</RadzenButton>
2023-08-03 23:24:10 +02:00
<RadzenPanel AllowCollapse="true" Style="width: 500px;" Text="Zeige Prompt">
<ChildContent>
<RadzenCard class="rz-mt-4">
<RadzenStack Orientation="Orientation.Horizontal"
MouseEnter="@(args => ShowTemperatureTooltip(args))"
MouseLeave="TooltipService.Close"
AlignItems="AlignItems.Center" Wrap="FlexWrap.Wrap">
<RadzenText>@_imagePrompt</RadzenText>
</RadzenStack>
</RadzenCard>
</ChildContent>
</RadzenPanel>
<RadzenText TextStyle="TextStyle.H4">Die Idee, die gemalt wird:</RadzenText>
<RadzenText>@_imageDescription</RadzenText>
2023-08-02 21:33:09 +02:00
<RadzenProgressBarCircular Visible=@_progressVisible ProgressBarStyle="ProgressBarStyle.Secondary" Value="100" ShowValue="false" Mode="ProgressBarMode.Indeterminate" />
<RadzenText Visible=@_progressVisible TextStyle="TextStyle.H6" Text=@BusyMessage></RadzenText>
2023-08-03 23:24:10 +02:00
<RadzenImage Path=@_imageUrl></RadzenImage>
2023-08-02 21:33:09 +02:00
2023-08-04 15:41:26 +02:00
<RadzenText Visible=@_addonsVisible TextStyle="TextStyle.H2">Verändere hier dein Bild durch Worte:</RadzenText>
2023-08-04 14:42:03 +02:00
<RadzenTextBox Visible=@_addonsVisible @bind-Value=@addons Placeholder="Mehr Farben" />
<RadzenButton Visible=@_bothVisible Click=@(async ()=> await DoStuff(false))>Generate</RadzenButton>
2023-08-05 00:43:11 +02:00
<RadzenCard>
<RadzenRow Style="width:24.5em" Gap="0.5rem" RowGap="0.5rem">
<RadzenColumn Size="6">
<FlippingImage ImageUrl="@_imageUrls[0]" HideImage="false"
Show="@(_imageStates[0] == ImageState.FadeIn)" FlipTo="FlippingImage.FlipDirection.Up"
Click="() => ShowImageDialog(_imageUrls[0])" />
</RadzenColumn>
<RadzenColumn Size="6">
<FlippingImage ImageUrl="@_imageUrls[1]" HideImage="false"
Show="@(_imageStates[1] == ImageState.FadeIn)" FlipTo="FlippingImage.FlipDirection.Right" FlipDelay="200"
Click="() => ShowImageDialog(_imageUrls[1])" />
</RadzenColumn>
<RadzenColumn Size="6">
<FlippingImage ImageUrl="@_imageUrls[2]" HideImage="false"
Show="@(_imageStates[2] == ImageState.FadeIn)" FlipTo="FlippingImage.FlipDirection.Left" FlipDelay="600"
Click="() => ShowImageDialog(_imageUrls[2])" />
</RadzenColumn>
<RadzenColumn Size="6">
<FlippingImage ImageUrl="@_imageUrls[3]" HideImage="false"
Show="@(_imageStates[3] == ImageState.FadeIn)" FlipTo="FlippingImage.FlipDirection.Down" FlipDelay="400"
Click="() => ShowImageDialog(_imageUrls[3])" />
</RadzenColumn>
</RadzenRow>
</RadzenCard>
2023-08-02 21:33:09 +02:00
</RadzenStack>
2023-08-02 18:33:28 +02:00
@code {
2023-08-04 14:42:03 +02:00
private int maxAddons = 2;
private int amountOfAddons = 0; // wird später geändert
private string _imageDescriptionPrompt = "Zusätzlich zu dem Promt erkläre kurz auf deutsch, warum du dich für diese Umsetzung des Titels entschieden hast und gib zusätzlich eine Interpretation des Bildes an. Beginne diesen Teil immer mit \"Beschreibung: \". Zuletzt Beschreibe das Bild und die verbundenen Emotionen mit drei Worten und beginne den Teil mit \"Keywords: \".";
2023-08-02 21:33:09 +02:00
private bool _progressVisible = false;
2023-08-03 23:24:10 +02:00
private bool _buttonVisible = true;
2023-08-04 14:42:03 +02:00
private bool _addonsVisible = false;
private bool _bothVisible = false;
2023-08-02 21:33:09 +02:00
public string BusyMessage { get; set; } = "Initial Message";
2023-08-05 00:43:11 +02:00
private string?[] _imageUrls = new string?[4];
private ImageState[] _imageStates = new ImageState[4];
enum ImageState
{
//Hide = 0,
FadeOut,
FadeIn,
//Show,
}
2023-08-02 21:33:09 +02:00
// Busy dialog from markup
async Task ShowBusyDialog()
{
await DialogService.OpenAsync("", ds =>
@<RadzenStack AlignItems="AlignItems.Center" Gap="2rem" Class="rz-p-12">
<RadzenProgressBarCircular ProgressBarStyle="ProgressBarStyle.Secondary" Value="100" ShowValue="false" Mode="ProgressBarMode.Indeterminate"/>
<RadzenText TextStyle="TextStyle.H6" Text=@BusyMessage></RadzenText>
</RadzenStack>, new DialogOptions() { ShowTitle = false, Style = "min-height:auto;min-width:auto;width:auto", CloseDialogOnEsc = false });
}
2023-08-05 00:43:11 +02:00
async Task ShowImageDialog(string imageUrl)
{
var result = await DialogService.OpenAsync("", ds =>
@<div class="d-flex justify-content-center align-items-center" style="height:100%;">
<RadzenImage Path="@imageUrl"/>
</div>, new DialogOptions() { CloseDialogOnOverlayClick = true });
}
2023-08-02 21:33:09 +02:00
// Busy dialog from string
async Task BusyDialog(string message)
{
await DialogService.OpenAsync("", ds =>
{
RenderFragment content = b =>
{
b.OpenElement(0, "RadzenRow");
b.OpenElement(1, "RadzenColumn");
b.AddAttribute(2, "Size", "12");
2023-08-02 18:33:28 +02:00
2023-08-02 21:33:09 +02:00
b.AddContent(3, message);
b.CloseElement();
b.CloseElement();
};
return content;
}, new DialogOptions() { ShowTitle = false, Style = "min-height:auto;min-width:auto;width:auto", CloseDialogOnEsc = false });
}
void ShowTooltip(ElementReference elementReference, string text, TooltipOptions? options = null) => TooltipService.Open(elementReference, text, options);
void ShowTemperatureTooltip(ElementReference elementReference) => TooltipService.Open(elementReference, ds =>
@<div>
Gibt an, wie <em>kreativ</em> ChatGPT sein soll.<br />
Ich glaube, eigentlich bedeutet es eher, wie <em>deterministisch</em> die Ausgabe ist.<br />
Bei 0.0 kommt immer fast die selbe Antwort. Bei zu hohen Werten kommt nur noch Schwachsinn.<br />
OpenAI empfielt einen Wert von ca. 0.9 für kreative Anwendungen.
2023-08-04 14:42:03 +02:00
</div>
,
2023-08-02 21:33:09 +02:00
new() { Position = TooltipPosition.Bottom, Duration = null});
private string _imagePrompt = "\"Painting of a soccer match with the Hamburger SV team scoring multiple goals, vibrant colors and dynamic brushstrokes.\" Watercolor + ink on paper, Pen drawing, wet-on-wet technique, dry-on-dry technique, dabbing technique.";
private string _imageDescription = "Ich habe mich für diese Interpretation des Titels entschieden, um die Dynamik und den Erfolg des Hamburger SV Fußballvereins darzustellen. Durch die Darstellung eines Spiels, in dem der HSV viele Tore schießt, wird die Leidenschaft und das Streben nach Erfolg hervorgehoben. Die lebendigen Farben und die dynamischen Pinselstriche sollen die Energie und Aufregung des Spiels wiedergeben.";
private float temperature = 0.9f;
2023-08-02 18:33:28 +02:00
private string request = "";
2023-08-04 14:42:03 +02:00
private string addons = "";
private OpenAIAPI api = new OpenAIAPI("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");
private Conversation converse;
2023-08-02 18:33:28 +02:00
2023-08-02 21:33:09 +02:00
private string _basePrompt;
2023-08-03 23:24:10 +02:00
private string _imageUrl;
2023-08-02 21:33:09 +02:00
//protected override async Task OnInitializedAsync()
//{
// _basePrompt = await File.ReadAllTextAsync($"{Directory.GetCurrentDirectory()}{@"\wwwroot\prompt.txt"}");
//}
private async Task FunnyMessageSwitcher_ImageGen(CancellationToken cancellationToken)
2023-08-02 18:33:28 +02:00
{
2023-08-02 21:33:09 +02:00
Stopwatch sw = Stopwatch.StartNew();
await Task.Delay(1000, cancellationToken);
await UpdateBusyMessage("Dauert noch eine Weile...");
await Task.Delay(1000, cancellationToken);
await UpdateBusyMessage("Gut Ding hat Weil...");
await Task.Delay(1000, cancellationToken);
await UpdateBusyMessage("Sach ma, was issn da l... achso, er ist auf Klo gegangen... Er ist bestimmt gleich wieder da...");
await Task.Delay(1000, cancellationToken);
await UpdateBusyMessage("Na, also langsam verlier ich hier die Geduld, ich dachte KI soll alles schneller machen...");
2023-08-04 14:42:03 +02:00
2023-08-02 21:33:09 +02:00
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(1000, cancellationToken);
await UpdateBusyMessage($"Keine Sorge, er arbeitet noch. Die Bilder werden gemalt... ({sw.Elapsed.Seconds}s)");
}
}
async Task UpdateBusyMessage(string newMessage)
{
BusyMessage = newMessage;
await InvokeAsync(StateHasChanged);
}
2023-08-04 15:37:07 +02:00
private readonly HttpClient _client = new();
protected override Task OnInitializedAsync()
2023-08-03 23:24:10 +02:00
{
2023-08-04 15:37:07 +02:00
var inferenceModelUrl = "https://api-inference.huggingface.co/models/Nacken/ki-kunst-kirsten-kloeckner-colab";
// TODO: nicht im repo speichern
var API_TOKEN = "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";
//_client.BaseAddress = new Uri(inferenceModelUrl);
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.Add("Authorization", $"Bearer {API_TOKEN}");
return base.OnInitializedAsync();
}
/// <summary>
/// Geneiert das Bild für den aktuellen <see cref="_imagePrompt"/>
/// </summary>
public async Task GenerateImageAsync()
{
var postData = new
{
inputs = _imagePrompt
};
JsonContent content = JsonContent.Create(postData);
async Task FailedToDrawImage()
{
bool? retry = await DialogService.Confirm("Leider konnte das Bild nicht gemalt werden. Möchtest du es noch eimal versuchen?", "Ups, ein Fehler ist aufgetreten...",
new ConfirmOptions { OkButtonText = "Ja", CancelButtonText = "Nein" });
if (retry == true)
await GenerateImageAsync();
}
try
{
var inferenceModelUrl = "https://api-inference.huggingface.co/models/Nacken/ki-kunst-kirsten-kloeckner-colab";
var response = await _client.PostAsync(inferenceModelUrl, content);
if (response?.IsSuccessStatusCode == true)
{
await using Stream imageStream = await response.Content.ReadAsStreamAsync();
using Image image = await Image.LoadAsync(imageStream);
string imgUrl = $"generated_images/{DateTime.Now:dd_MM_yyyy_hh_mm_s}.jpg";
string mapPath = $"./wwwroot/{imgUrl}";
await image.SaveAsJpegAsync(mapPath);
_imageUrl = imgUrl;
}
else
{
Console.WriteLine($"Image conversion failed: {response}");
if (Debugger.IsAttached)
Debugger.Break();
await FailedToDrawImage();
}
}
catch (Exception exception)
{
Console.WriteLine($"Image request failed: {exception}");
if (Debugger.IsAttached)
Debugger.Break();
await FailedToDrawImage();
}
2023-08-03 23:24:10 +02:00
}
2023-08-02 21:33:09 +02:00
2023-08-05 00:43:11 +02:00
/// <summary>
/// Geneiert das Bild für den aktuellen <see cref="_imagePrompt"/>
/// </summary>
public async Task<string?> GenerateImageAsync(string prompt, bool isRetry = false)
{
var postData = new
{
inputs = prompt,
options = new
{
// Cache deaktivieren, damit Huggingface für den selben Prompt unterschiedliche Ergebnisse liefert
use_cache = false,
// Erst wenn wir bereits in einem retry sind warten wir implizit auf das Model. (ignoriert quasi 503-Fehler)
wait_for_model = isRetry
}
};
JsonContent content = JsonContent.Create(postData);
async Task<string?> FailedToDrawImage()
{
bool? retry = await DialogService.Confirm("Leider konnte das Bild nicht gemalt werden. Möchtest du es noch eimal versuchen?", "Ups, ein Fehler ist aufgetreten...",
new ConfirmOptions { OkButtonText = "Ja", CancelButtonText = "Nein" });
if (retry == true)
{
return await GenerateImageAsync(prompt, true);
}
return null;
}
try
{
var inferenceModelUrl = "https://api-inference.huggingface.co/models/Nacken/ki-kunst-kirsten-kloeckner-colab";
var response = await _client.PostAsync(inferenceModelUrl, content);
if (response?.IsSuccessStatusCode == true)
{
await using Stream imageStream = await response.Content.ReadAsStreamAsync();
using Image image = await Image.LoadAsync(imageStream);
string imgUrl = $"generated_images/{DateTime.Now:dd_MM_yyyy_hh_mm_s_fffffff}.jpg";
string mapPath = $"./wwwroot/{imgUrl}";
await image.SaveAsJpegAsync(mapPath);
return imgUrl;
}
else
{
Console.WriteLine($"Image conversion failed: {response}");
if (Debugger.IsAttached)
Debugger.Break();
return await FailedToDrawImage();
}
}
catch (Exception exception)
{
Console.WriteLine($"Image request failed: {exception}");
if (Debugger.IsAttached)
Debugger.Break();
return await FailedToDrawImage();
}
return null;
}
2023-08-04 14:42:03 +02:00
private async Task DoStuff(bool newPic)
2023-08-02 21:33:09 +02:00
{
2023-08-05 00:43:11 +02:00
for (int i = 0; i < 4; i++)
_imageStates[i] = ImageState.FadeOut;
2023-08-02 21:33:09 +02:00
// Der Dialog blokiert so lange, wie der er offen ist, deshalb dürfen wir hier nicht warten, da wir sonst nie mit der Arbeit anfangen...
//Task busyDialog = ShowBusyDialog();
_progressVisible = true;
2023-08-03 23:24:10 +02:00
_buttonVisible = false;
2023-08-02 21:33:09 +02:00
2023-08-04 14:42:03 +02:00
if (converse == null || newPic)
2023-08-02 21:33:09 +02:00
{
2023-08-04 14:42:03 +02:00
amountOfAddons = maxAddons;
_addonsVisible = false;
_bothVisible = _buttonVisible && _addonsVisible;
await UpdateBusyMessage("Kirstens Assistent zerbricht sich über deine Idee den Kopf...");
2023-08-04 15:09:50 +02:00
_basePrompt = await File.ReadAllTextAsync($"{Directory.GetCurrentDirectory()}{@"/wwwroot/test_prompt.txt"}");
2023-08-04 14:42:03 +02:00
ChatRequest chatRequest = new ChatRequest
{
Temperature = temperature,
Model = Model.ChatGPTTurbo,
};
converse = api.Chat.CreateConversation(chatRequest);
converse.AppendUserInput(_basePrompt + " " + request);
}
else
2023-08-02 21:33:09 +02:00
{
2023-08-04 14:42:03 +02:00
if (amountOfAddons > 0)
{
amountOfAddons--;
_bothVisible = _buttonVisible && _addonsVisible;
await UpdateBusyMessage("Kirstens Assistent passt das Bild an deine Wünsche an...");
string addonsPrompt1 = "Erstelle einen neuen Prompt auf englisch mit den gleichen Restriktionen auf Basis des Alten mit folgender Anpassung: ";
string addonsPrompt2 = ". Denke daran nur den Prompt zu generieren und noch keine Beschreibung oder ähnliches.";
converse.AppendUserInput(addonsPrompt1 + addons + addonsPrompt2);
}
2023-08-02 21:33:09 +02:00
}
2023-08-04 14:42:03 +02:00
_imagePrompt = await converse.GetResponseFromChatbotAsync();
_imagePrompt += " Watercolor + ink on paper, Pen drawing, wet-on-wet technique, dry-on-dry technique, dabbing technique.";
converse.AppendUserInput(_imageDescriptionPrompt);
_imageDescription = await converse.GetResponseFromChatbotAsync();
2023-08-02 21:33:09 +02:00
await UpdateBusyMessage("Kirstens Assistent hat eine Idee! Er wird sie nun malen...");
2023-08-03 23:24:10 +02:00
2023-08-05 00:43:11 +02:00
// Vier Bilder generieren
for (int i = 0; i < 4; i++)
{
_imageUrls[i] = await GenerateImageAsync(_imagePrompt);
_imageStates[i] = ImageState.FadeIn;
await InvokeAsync(StateHasChanged);
}
2023-08-02 21:33:09 +02:00
_progressVisible = false;
2023-08-03 23:24:10 +02:00
_buttonVisible = true;
2023-08-04 14:42:03 +02:00
if (amountOfAddons > 0)
{
_addonsVisible = true;
_bothVisible = _buttonVisible && _addonsVisible;
await InvokeAsync(StateHasChanged);
}
else
{
_addonsVisible = false;
_bothVisible = false;
}
2023-08-02 18:33:28 +02:00
}
2023-08-04 14:42:03 +02:00
2023-08-02 18:33:28 +02:00
}
2023-08-05 00:43:11 +02:00