379 lines
13 KiB
Plaintext
379 lines
13 KiB
Plaintext
@page "/create-logo"
|
|
@using BLAIzor.Components.Layout
|
|
@attribute [Authorize]
|
|
@layout AdminLayout
|
|
@using System.Net.Http.Headers
|
|
@using System.Text.Json
|
|
@using BLAIzor.Services
|
|
@using Microsoft.AspNetCore.Components.Authorization
|
|
@using SixLabors.ImageSharp
|
|
@using SixLabors.ImageSharp.Processing
|
|
@inject IJSRuntime JS
|
|
@inject IHttpClientFactory HttpClientFactory
|
|
@inject WhisperTranscriptionService WhisperService
|
|
@inject ReplicateService ReplicateService
|
|
@inject AuthenticationStateProvider AuthenticationStateProvider
|
|
@inject CustomAuthenticationStateProvider CustomAuthProvider
|
|
|
|
<div class="card p-3 shadow-sm bg-panel-gradient text-white" style="max-width:100%; width: 700px; height: 70vh; margin: 0 auto; border-radius: 20px;">
|
|
<div class="card-header text-center text-white">
|
|
|
|
<h3 class="mb-3">Let's build your website step by step</h3>
|
|
</div>
|
|
|
|
<div class="card-body" style="overflow: scroll">
|
|
|
|
<p class="text-white-50 small mb-3">
|
|
Step @CurrentStep of @Steps.Count (@(CurrentStep * 100 / Steps.Count)% complete)
|
|
</p>
|
|
|
|
@if (CurrentStep < Steps.Count)
|
|
{
|
|
<AnimateOnRender CssClass="animate__animated animate__backInUp" @key="CurrentStep">
|
|
<div class="my-3 text-center">
|
|
<label class="form-label">@Steps[CurrentStep].Question</label>
|
|
<p class="text-muted">@Steps[CurrentStep].Description</p>
|
|
|
|
<InputText class="form-control" @bind-Value="Steps[CurrentStep].Answer" />
|
|
|
|
|
|
</div>
|
|
</AnimateOnRender>
|
|
<div class="d-flex justify-content-between mt-3">
|
|
<button class="btn btn-secondary" @onclick="PreviousStep" disabled="@IsFirstStep">Back</button>
|
|
<button class="btn btn-primary" @onclick="NextStep">Next</button>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<h5 class="mt-4">Site Description Preview</h5>
|
|
<div class="p-3 bg-panel-gradient-highlight text-dark mb-3 rounded">
|
|
@((MarkupString)generatedDescription)
|
|
<button class="btn btn-success" @onclick="ProceedToLogoStep">Use this description</button>
|
|
</div>
|
|
|
|
}
|
|
|
|
@if (ShowLogoStep)
|
|
{
|
|
<div class="text-center my-4">
|
|
<h5>Would you like to upload a logo or generate one?</h5>
|
|
|
|
<div class="my-3">
|
|
@* <input type="file" @onchange="UploadLogo" class="form-control" /> *@
|
|
<InputFile class="btn btn-default" type="file" multiple OnChange=HandleFileUpload accept=".jpg,.png" />
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
<button class="btn btn-outline-primary" @onclick="GenerateLogo" disabled="@IsGeneratingLogo">
|
|
@(logoGenerationCount == 0 ? "Generate Logo with AI" : "Regenerate Logo")
|
|
</button>
|
|
@if (logoGenerationCount > 0 && logoGenerationCount < MaxLogoGenerations)
|
|
{
|
|
<p class="text-muted small mt-2">
|
|
You can regenerate the logo @(MaxLogoGenerations - logoGenerationCount) more time(s).
|
|
</p>
|
|
}
|
|
</div>
|
|
|
|
@if (!string.IsNullOrWhiteSpace(GeneratedLogoUrl))
|
|
{
|
|
<div class="my-3">
|
|
<img src="@GeneratedLogoUrl" class="img-fluid rounded shadow" style="max-height: 512px;" />
|
|
</div>
|
|
}
|
|
</div>
|
|
<div class="p-3 bg-panel-gradient-highlight text-dark mb-3 rounded">
|
|
<button class="btn btn-success" @onclick="SaveDescription">Save</button>
|
|
</div>
|
|
}
|
|
|
|
</div>
|
|
<div class="card-footer my-4 d-flex justify-content-between align-items-center text-white small">
|
|
@for (int i = 0; i < Steps.Count; i++)
|
|
{
|
|
<div class="text-center flex-fill">
|
|
<div class="mb-1">
|
|
<div class="@GetStepCircleClass(i)">
|
|
@(i + 1)
|
|
</div>
|
|
</div>
|
|
@* <div style="min-height: 36px;">@Steps[i].Question.Split('?')[0]</div> *@
|
|
@* @if (i < Steps.Count - 1)
|
|
{ *@
|
|
<div class="progress-line mx-auto"></div>
|
|
@* } *@
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
|
|
public string UserId { get; set; } = string.Empty;
|
|
private string userName = string.Empty;
|
|
private AuthenticationState authState;
|
|
|
|
private int CurrentStep = 0;
|
|
private bool IsFirstStep => CurrentStep == 0;
|
|
private bool ShowLogoStep = false;
|
|
private string? GeneratedLogoUrl;
|
|
private string? logoUrl;
|
|
|
|
private int logoGenerationCount = 0;
|
|
private const int MaxLogoGenerations = 10;
|
|
private bool IsGeneratingLogo = false;
|
|
|
|
private List<QuestionStep> Steps = new()
|
|
{
|
|
new("What is the name of your site?", "The domain name or the brand name"),
|
|
new("Is it for a brand, a person, a cause, a blog, a service, a store, a beauty salon or something else?", "The entity that the site will introduce to the users. "),
|
|
new("What kind of feel or atmosphere should the site have? (e.g. friendly, professional, mysterious, playful)", "How should your website communicate?"),
|
|
new("What color(s) do you associate with your brand or prefer for the visuals?", "The colors you have in mind. This is needed for design suggestions and photo generation"),
|
|
};
|
|
|
|
|
|
|
|
private string generatedDescription = "";
|
|
private string siteName = "";
|
|
private string entity = "";
|
|
private string persona = "";
|
|
private string colors = "";
|
|
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
_instance = this;
|
|
authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
|
if (authState.User.Identity?.IsAuthenticated == true)
|
|
{
|
|
UserId = CustomAuthProvider.GetUserId();
|
|
userName = CustomAuthProvider.GetUserName();
|
|
}
|
|
}
|
|
|
|
private void NextStep()
|
|
{
|
|
if (CurrentStep < Steps.Count)
|
|
CurrentStep++;
|
|
if (CurrentStep == Steps.Count)
|
|
GenerateSiteDescription();
|
|
}
|
|
|
|
private void PreviousStep()
|
|
{
|
|
if (CurrentStep > 0)
|
|
CurrentStep--;
|
|
}
|
|
|
|
private void GenerateSiteDescription()
|
|
{
|
|
var name = Steps[0].Answer;
|
|
var siteEntity = Steps[1].Answer;
|
|
var feel = Steps[2].Answer;
|
|
colors = Steps[3].Answer;
|
|
|
|
|
|
siteName = name;
|
|
entity = siteEntity;
|
|
persona = feel;
|
|
|
|
generatedDescription = $@"
|
|
<p><strong>{name}</strong> is a {entity.ToLower()}.</p>
|
|
<p>The logo should offer {feel.ToLower()} experience for its viewers.</p>
|
|
<p>The design should reflect {colors.ToLower()} tones for visual consistency.</p>
|
|
";
|
|
}
|
|
|
|
private void ProceedToLogoStep()
|
|
{
|
|
ShowLogoStep = true;
|
|
}
|
|
|
|
|
|
private async Task GenerateLogo()
|
|
{
|
|
if (logoGenerationCount >= MaxLogoGenerations)
|
|
return;
|
|
|
|
IsGeneratingLogo = true;
|
|
|
|
var logoPrompt = $"Logo for a {entity} named {siteName}, with a {persona} tone, using {colors} colors. DO NOT ADD taglines, or any text other than the brand name: {siteName}.";
|
|
GeneratedLogoUrl = await ReplicateService.GenerateLogoAsync(logoPrompt, true);
|
|
logoUrl = GeneratedLogoUrl;
|
|
|
|
logoGenerationCount++;
|
|
|
|
IsGeneratingLogo = false;
|
|
}
|
|
|
|
private async Task SaveDescription()
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(GeneratedLogoUrl) && GeneratedLogoUrl.StartsWith("http"))
|
|
{
|
|
var savedPath = await DownloadAndSaveImage(GeneratedLogoUrl);
|
|
if (!string.IsNullOrEmpty(savedPath))
|
|
{
|
|
logoUrl = savedPath;
|
|
}
|
|
}
|
|
|
|
string[] siteInfo = new string[6];
|
|
siteInfo[0] = siteName;
|
|
siteInfo[1] = generatedDescription;
|
|
siteInfo[2] = entity;
|
|
siteInfo[3] = persona;
|
|
siteInfo[5] = logoUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
private class QuestionStep
|
|
{
|
|
public string Question { get; }
|
|
public string Description { get; }
|
|
public string Answer { get; set; } = "";
|
|
|
|
public QuestionStep(string question, string description)
|
|
{
|
|
Question = question;
|
|
Description = description;
|
|
}
|
|
}
|
|
|
|
private string GetStepCircleClass(int index)
|
|
{
|
|
if (index < CurrentStep)
|
|
return "step-circle completed";
|
|
else if (index == CurrentStep)
|
|
return "step-circle current";
|
|
else
|
|
return "step-circle";
|
|
}
|
|
|
|
private async Task HandleFileUpload(InputFileChangeEventArgs e)
|
|
{
|
|
if (e.FileCount == 0) return;
|
|
|
|
try
|
|
{
|
|
var uploadPath = Path.Combine("wwwroot", "uploads", UserId);
|
|
|
|
foreach (var file in e.GetMultipleFiles())
|
|
{
|
|
var folder = GetFolderForFile(file.ContentType);
|
|
var folderPath = Path.Combine(uploadPath, folder);
|
|
|
|
// Create target directory
|
|
Directory.CreateDirectory(folderPath);
|
|
|
|
var filePath = Path.Combine(folderPath, file.Name);
|
|
await using (var stream = new FileStream(filePath, FileMode.Create))
|
|
{
|
|
await file.OpenReadStream(50 * 1024 * 1024).CopyToAsync(stream);
|
|
}
|
|
|
|
var relativePath = $"/uploads/{UserId}/{folder}/{file.Name}";
|
|
AppendFilePathToContent(file.ContentType, relativePath);
|
|
|
|
// Generate thumbnail if it's an image
|
|
string? thumbnailRelativePath = null;
|
|
|
|
if (file.ContentType.StartsWith("image/"))
|
|
{
|
|
var thumbnailFolder = Path.Combine(folderPath, "thumbnails");
|
|
Directory.CreateDirectory(thumbnailFolder);
|
|
|
|
var thumbnailPath = Path.Combine(thumbnailFolder, file.Name);
|
|
using var image = await Image.LoadAsync(file.OpenReadStream());
|
|
image.Mutate(x => x.Resize(new ResizeOptions
|
|
{
|
|
Size = new Size(300, 0),
|
|
Mode = ResizeMode.Max
|
|
}));
|
|
await image.SaveAsync(thumbnailPath);
|
|
|
|
thumbnailRelativePath = $"/uploads/{UserId}/{folder}/thumbnails/{file.Name}";
|
|
}
|
|
|
|
AppendFilePathToContent(file.ContentType, relativePath, thumbnailRelativePath);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error uploading files: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
//IsLoading = false;
|
|
}
|
|
}
|
|
|
|
private string GetFolderForFile(string contentType)
|
|
{
|
|
return contentType switch
|
|
{
|
|
var type when type.StartsWith("image/") => "images",
|
|
var type when type.StartsWith("video/") => "videos",
|
|
var type when type.StartsWith("audio/") => "audio",
|
|
_ => "others"
|
|
};
|
|
}
|
|
|
|
private void AppendFilePathToContent(string contentType, string relativePath, string? thumbnailPath = null)
|
|
{
|
|
if (contentType.StartsWith("image/"))
|
|
{
|
|
logoUrl = relativePath;
|
|
GeneratedLogoUrl = relativePath;
|
|
var ThumbnailUrl = thumbnailPath ?? string.Empty;
|
|
|
|
}
|
|
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task<string?> DownloadAndSaveImage(string imageUrl)
|
|
{
|
|
try
|
|
{
|
|
var uploadPath = Path.Combine("wwwroot", "uploads", UserId, "images");
|
|
Directory.CreateDirectory(uploadPath);
|
|
|
|
var fileName = $"logo_{Guid.NewGuid().ToString().Substring(0, 8)}.jpg";
|
|
var filePath = Path.Combine(uploadPath, fileName);
|
|
|
|
var httpClient = HttpClientFactory.CreateClient();
|
|
var imageBytes = await httpClient.GetByteArrayAsync(imageUrl);
|
|
|
|
await File.WriteAllBytesAsync(filePath, imageBytes);
|
|
|
|
// Generate thumbnail
|
|
var thumbnailFolder = Path.Combine(uploadPath, "thumbnails");
|
|
Directory.CreateDirectory(thumbnailFolder);
|
|
|
|
var thumbnailPath = Path.Combine(thumbnailFolder, fileName);
|
|
using var image = Image.Load(imageBytes);
|
|
image.Mutate(x => x.Resize(new ResizeOptions
|
|
{
|
|
Size = new Size(300, 0),
|
|
Mode = ResizeMode.Max
|
|
}));
|
|
await image.SaveAsync(thumbnailPath);
|
|
|
|
return $"/uploads/{UserId}/images/{fileName}";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error saving logo: {ex.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
// STT Hook
|
|
private static LogoGenerator? _instance;
|
|
|
|
}
|