@page "/site/{SiteId:int}/generate" @using BLAIzor.Interfaces @using BLAIzor.Models @using BLAIzor.Services @using System.Text.RegularExpressions @using SixLabors.ImageSharp @using SixLabors.ImageSharp.Processing @inject HttpClient Http @inject ContentEditorAIService contentEditorAIService @inject ContentEditorService contentEditorService @inject NavigationManager Navigation @inject ReplicateService ReplicateService @inject IHttpClientFactory HttpClientFactory @inject IBrightDataService BrightDataService
@if (GeneratedItems.Any()) { }
@if (!string.IsNullOrEmpty(errorMessage)) {
⚠️ @errorMessage
} @if (isLoading) {
@if (!string.IsNullOrEmpty(message)) {

@message

} @if (GeneratedItems.Any()) { } else { }
@* *@ } else if (SelectedItem != null) { @if (isGeneratingImages) {

Generating pages... please wait.

@* *@
}

Please review the generated content and the images. You are free to modify the text, but please don't remove photo tags, they are handled automatically. Please approve the images you like. You can also regenerate, and select photos from your device.

@SelectedItem.Title

Suggested Photos

@if (SelectedItem.PhotoSlots.Any()) { @foreach (var slot in SelectedItem.PhotoSlots) {
@*

@slot.Description

*@ @if (!string.IsNullOrEmpty(slot.ImageUrl)) {

Photo_slot_@(SelectedItem.PhotoSlots.IndexOf(slot) + 1)

@slot.Description
} else {
}
} } else {

No photo suggestions yet.

}
} else if (!GeneratedItems.Any()) {

Click the button below to generate your site's pages based on its description.

}
@code { [Parameter] public int SiteId { get; set; } [Parameter] public string SessionId { get; set; } = string.Empty; private SiteInfo site; private bool isLoading = false; private string errorMessage = string.Empty; private string message = "Generating pages... please wait."; private List GeneratedItems = new(); private ContentGroup defaultGroup; private GeneratedContentItem SelectedItem; private double generationProgress = 0.0; private int totalItemsToGenerate = 0; private bool isGeneratingImages = false; protected override async Task OnInitializedAsync() { site = await contentEditorService.GetSiteInfoByIdAsync(SiteId); var existingGroup = await contentEditorService.GetContentGroupsBySiteInfoIdAsync(SiteId); if (!existingGroup.Any(x => x.Name == "Pages")) { defaultGroup = new ContentGroup { SiteInfoId = SiteId, Name = "Pages", Slug = "pages", Type = "Page", VectorSize = 1536, EmbeddingModel = "openai", Version = 0 }; defaultGroup = await contentEditorService.CreateContentGroupAsync(defaultGroup); } else { defaultGroup = existingGroup.First(x => x.Name == "Pages"); var content = await contentEditorService.GetContentItemsByGroupIdAsync(defaultGroup.Id); GeneratedItems = content.Select(c => new GeneratedContentItem { Id = c.Id, Title = c.Title, Description = c.Description, Language = c.Language, Content = c.Content, ContentGroupId = c.ContentGroupId, Tags = c.Tags, IsPublished = c.IsPublished, Version = c.Version }).ToList(); } } private void SelectContent(string title) { SelectedItem = GeneratedItems.FirstOrDefault(x => x.Title == title); StateHasChanged(); } private async Task GeneratePageList() { isLoading = true; errorMessage = string.Empty; GeneratedItems.Clear(); string facebookContent = string.Empty; if (!string.IsNullOrWhiteSpace(site.FacebookUrl)) { message = "Reading Facebook posts... please wait."; var valami = await BrightDataService.ScrapeFacebookPostsAsync(site.FacebookUrl, 20); if (valami == null || !valami.Any()) { errorMessage = "No Facebook posts found. Please check the URL."; isLoading = false; } else { var grabFacebookPrompt = $"Based on the following Facebook posts, extract the main topics and themes that could be used to generate website pages. " + $"Focus on the content relevant to the site description: {site.SiteDescription}. " + $"Content found on Facebook in JSON format: {valami}"; facebookContent = await contentEditorAIService.GetFacebookContentAsync(SessionId, grabFacebookPrompt); } } try { message = "Generating pages... please wait."; // 1️⃣ Ask AI for the page titles first var pageListPrompt = $"Based on the following site description, list the main website pages in {site.DefaultLanguage} as a comma-separated list. " + "Only return the page titles." + $"Site Description: {site.SiteDescription}." + $"Content found on facebook: {facebookContent}"; var pageListText = await contentEditorAIService.GetMenuSuggestionsAsync(SessionId, pageListPrompt); var pageTitles = pageListText .Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(t => t.Trim()) .Where(t => !string.IsNullOrWhiteSpace(t)) .Distinct() .ToList(); totalItemsToGenerate = pageTitles.Count; foreach (var title in pageTitles) { message = $"Generating content for page: {title}... please wait."; // 2️⃣ Generate content WITH inline photo placeholders var itemPrompt = $"Write a full, well-structured website page text content in {site.DefaultLanguage} for a page titled '{title}'. " + $"The tone should be {site.Persona}, and match the following site description: '{site.SiteDescription}'. " + "Include headers, clear sections, and persuasive copy. " + "Insert image placeholders in the text like [PHOTO_SLOT_1: short image description] at natural points where an illustration would be helpful. " + "Number them sequentially starting from 1."; var content = await contentEditorAIService.GetGeneratedContentAsync(SessionId, itemPrompt); // 3️⃣ Extract PhotoSlots from placeholders var matches = Regex.Matches(content, @"\[PHOTO_SLOT_(\d+):\s*(.+?)\]"); var photoSlots = matches .Select(m => new PhotoSlot { Description = m.Groups[2].Value.Trim() }) .ToList(); // 4️⃣ Build GeneratedContentItem var generatedItem = new GeneratedContentItem { Title = title, Description = $"A {defaultGroup.Type} called {title} of a website called {site.SiteName}.", Language = site.DefaultLanguage, Content = content, ContentGroupId = defaultGroup.Id, Tags = title, IsPublished = false, Version = 0, PhotoSlots = photoSlots }; GeneratedItems.Add(generatedItem); generationProgress += 100.0 / totalItemsToGenerate; StateHasChanged(); } } catch (Exception ex) { errorMessage = ex.Message; } finally { message = "Generating images... please wait."; SelectedItem = GeneratedItems.FirstOrDefault(); isLoading = false; generationProgress = 0; isGeneratingImages = true; StateHasChanged(); foreach (var page in GeneratedItems) { totalItemsToGenerate += page.PhotoSlots.Count; } foreach (var page in GeneratedItems) { foreach (var image in page.PhotoSlots) { await GeneratePhoto(image, false); generationProgress += 100.0 / totalItemsToGenerate; StateHasChanged(); } } isGeneratingImages = false; StateHasChanged(); } } private async Task SaveContent() { isLoading = true; errorMessage = string.Empty; try { int i = 0; foreach (var item in GeneratedItems) { //get all photo urls from content and replace them with uploaded version // if (!string.IsNullOrWhiteSpace(GeneratedLogoUrl) && GeneratedLogoUrl.StartsWith("http")) // { // var savedPath = await DownloadAndSaveImage(GeneratedLogoUrl); // if (!string.IsNullOrEmpty(savedPath)) // { // logoUrl = savedPath; // } // } item.Content = Regex.Replace(SelectedItem.Content, @"\[PHOTO_SLOT_\d+:[^\]]*\]", ""); var contentItem = await contentEditorService.CreateContentItemAsync(item, site.VectorCollectionName); var newMenuItem = new MenuItem { SiteInfoId = SiteId, ContentItemId = contentItem.Id, ContentGroupId = defaultGroup.Id, ShowInMainMenu = true, Slug = contentItem.Title.ToLower(), SortOrder = i, Name = contentItem.Title }; await contentEditorService.AddMenuItemAsync(newMenuItem); i++; } Navigation.NavigateTo($"/preview/{SiteId}"); } catch (Exception ex) { errorMessage = ex.Message; } finally { isLoading = false; } } private async Task RegeneratePhoto(PhotoSlot slot, bool removeBackground) { slot.ImageUrl = ""; StateHasChanged(); await GeneratePhoto(slot, removeBackground); } private async Task GeneratePhoto(PhotoSlot slot, bool removeBackground) { if (SelectedItem == null) { errorMessage = "No content item selected."; return; } var userPrompt = $"Generate an image prompt for me for AN ILLUSTRATION. Use the colors: {site.DefaultColor}. " + $"The photo should have a {site.Persona} feel, and must be in {site.DesignStyle} style " + $"My general photo idea: {slot.Description}. " + "DO NOT generate a UI design or UX design prompt, the image will be part of the website content."; var imagePrompt = await contentEditorAIService.GetPhotoPromptAsync(SessionId, userPrompt); try { var imageUrl = await ReplicateService.GenerateImageAsync(imagePrompt, removeBackground); slot.ImageUrl = imageUrl; } catch (Exception ex) { errorMessage = $"Image generation failed: {ex.Message}"; } } private async Task ReplaceUrls(PhotoSlot slot) { // Find the index of this slot in the SelectedItem's PhotoSlots list var slotIndex = SelectedItem.PhotoSlots.IndexOf(slot); if (slotIndex >= 0) { var imageUrl = await DownloadAndSaveImage(slot.ImageUrl); // Replace placeholder with actual image URL SelectedItem.Content = Regex.Replace( SelectedItem.Content, $@"\[PHOTO_SLOT_{slotIndex + 1}:[^\]]*\]", $"[Photo URL: {imageUrl}]" ); slot.ImageUrl = imageUrl; } } private async Task DownloadAndSaveImage(string imageUrl) { try { var uploadPath = Path.Combine("wwwroot", "uploads", site.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/{site.UserId}/images/{fileName}"; } catch (Exception ex) { Console.WriteLine($"Error saving logo: {ex.Message}"); return null; } } private void SelectPhoto(PhotoSlot slot) { // TODO: implement image picker dialog // For now, just simulate selecting an existing image slot.ImageUrl = "/images/placeholder.png"; } }