húúúúúúúdenagycommit
|
|
@ -14,6 +14,10 @@
|
|||
<None Remove="SeemGen.Tests\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="Components\Partials\OverlayEditor.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Components\Pages\Home.razorOLD" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,13 @@
|
|||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-E9J9J414DF');
|
||||
</script>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
|
|
@ -13,7 +20,8 @@
|
|||
<link rel="stylesheet" href="app.css" />
|
||||
<link rel="stylesheet" href="main.css" />
|
||||
<link rel="stylesheet" href="admin.css" />
|
||||
<link rel="stylesheet" href="animate.css" />
|
||||
@* <link rel="stylesheet" href="animate.css" /> *@
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
|
||||
<link rel="stylesheet" href="loader.css" />
|
||||
<link rel="stylesheet" href="BLAIzor.styles.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
|
|
@ -23,21 +31,12 @@
|
|||
<script src="https://kit.fontawesome.com/12c469cb8f.js" crossorigin="anonymous"></script>
|
||||
<script src="https://assets.calendly.com/assets/external/widget.js" type="text/javascript"></script>
|
||||
<script type="text/javascript" src="scripts/background.js"> </script>
|
||||
<script type="text/javascript" src="scripts/SeemGenCss.js"> </script>
|
||||
|
||||
@* <script>
|
||||
window.applyDynamicCss = (cssContent) => {
|
||||
let styleTag = document.getElementById('seemgen-style');
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style');
|
||||
styleTag.id = 'seemgen-style';
|
||||
document.head.appendChild(styleTag);
|
||||
}
|
||||
styleTag.textContent = cssContent;
|
||||
};
|
||||
</script> *@
|
||||
@* <RadzenTheme Theme="material-dark" @rendermode="InteractiveServer" /> *@
|
||||
<RadzenTheme Theme="material" @rendermode="InteractiveServer" />
|
||||
<script type="text/javascript" src="scripts/whisperRecorder.js"></script>
|
||||
|
||||
|
||||
<RadzenTheme Theme="material-dark" @rendermode="InteractiveServer" />
|
||||
@* <RadzenTheme Theme="material" @rendermode="InteractiveServer" /> *@
|
||||
<HeadOutlet />
|
||||
<SectionOutlet SectionName="HeadContentFromPage" />
|
||||
</head>
|
||||
|
|
@ -53,5 +52,9 @@
|
|||
crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="scripts/finisher-header.es5.min.js"> </script>
|
||||
<script src="_content/Radzen.Blazor/Radzen.Blazor.js?v=@(typeof(Radzen.Colors).Assembly.GetName().Version)"></script>
|
||||
<script type="text/javascript" src="scripts/SeemGenCss.js"> </script>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-E9J9J414DF"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="page" style="z-index: 1">
|
||||
<div class="page admin-body" style="z-index: 1">
|
||||
|
||||
<main>
|
||||
@* <div class="top-row px-4" style="z-index: 2">
|
||||
|
|
|
|||
|
|
@ -27,27 +27,27 @@ else
|
|||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
<div class="row">
|
||||
<div class="form-group col-md-4">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Template Name</label>
|
||||
<InputText class="form-control" @bind-Value="currentTemplate.TemplateName" />
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<div class="form-group">
|
||||
<label>Template photo url</label>
|
||||
<InputText class="form-control" @bind-Value="currentTemplate.TemplatePhotoUrl" />
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<div class="form-group">
|
||||
<label>Tags (comma-separated)</label>
|
||||
<InputText class="form-control" @bind-Value="currentTemplate.Tags" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Description</label>
|
||||
<InputTextArea class="form-control" @bind-Value="currentTemplate.Description" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<label>CSS Content</label>
|
||||
<RadzenHtmlEditor @bind-Value=@currentCssTemplate.CssContent
|
||||
|
|
@ -57,13 +57,21 @@ else
|
|||
Paste=@OnPaste
|
||||
UploadComplete=@OnUploadComplete
|
||||
Execute=@OnExecute
|
||||
UploadUrl="upload/image">
|
||||
UploadUrl="upload/image"
|
||||
Mode=@HtmlEditorMode.Source>
|
||||
<RadzenHtmlEditorUndo />
|
||||
<RadzenHtmlEditorRedo />
|
||||
<RadzenHtmlEditorSource />
|
||||
</RadzenHtmlEditor>
|
||||
@* <InputTextArea class="form-control" @bind-Value="currentCssTemplate.CssContent" /> *@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<button class="btn btn-success" type="submit">Save Changes</button>
|
||||
</EditForm>
|
||||
|
|
|
|||
|
|
@ -5,25 +5,29 @@
|
|||
@using BLAIzor.Services
|
||||
@layout AdminLayout
|
||||
@inject ContentEditorService ContentEditorService
|
||||
@inject ScopedContentService ScopedContentService
|
||||
|
||||
<h3>Generate Website Content</h3>
|
||||
|
||||
<div class="row">
|
||||
<div class="rz-p-4 rz-text-align-center" style="width: fit-content; margin: 0 auto;">
|
||||
<p>How would you start?</p>
|
||||
|
||||
<div class="rz-p-12 rz-text-align-center">
|
||||
<RadzenRadioButtonList @bind-Value=@FromDocument TValue="bool">
|
||||
<RadzenRadioButtonList @bind-Value=@FromDocument TValue="bool" AlignItems="AlignItems.Center" Style="margin: 0 auto;">
|
||||
<Items>
|
||||
<RadzenRadioButtonListItem Text="I have a document" Value="true" />
|
||||
<RadzenRadioButtonListItem Text="Start from scratch" Value="false" />
|
||||
</Items>
|
||||
</RadzenRadioButtonList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p>@errorMessage</p>
|
||||
@{
|
||||
if(!FromDocument)
|
||||
{
|
||||
<GenerateFromScratch SiteId=@SiteId SessionId="sessionId"></GenerateFromScratch>
|
||||
// <GenerateFromScratch SiteId=@SiteId SessionId="sessionId"></GenerateFromScratch>
|
||||
<GenerateSitePages SiteId=@SiteId SessionId="sessionId"></GenerateSitePages>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -40,6 +44,7 @@
|
|||
|
||||
protected override Task OnParametersSetAsync()
|
||||
{
|
||||
sessionId = ScopedContentService.SessionId;
|
||||
//TODO get sessionId
|
||||
return base.OnParametersSetAsync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
@using System.Text.Json
|
||||
@using Sidio.Sitemap.Blazor
|
||||
@rendermode InteractiveServer
|
||||
@inject ContentService _contentService
|
||||
@inject NavigationManager _navigationManager
|
||||
@inject IHttpContextAccessor HttpContextAccessor
|
||||
@inject DesignTemplateService DesignTemplateService
|
||||
|
|
@ -21,11 +20,13 @@
|
|||
|
||||
<ErrorBoundary>
|
||||
<ChildContent>
|
||||
|
||||
<div class="page" style="z-index: 1">
|
||||
<NewNavMenu Menu="@MenuItems" BrandName="@SelectedBrandName" OnMenuClicked=@MenuClick></NewNavMenu>
|
||||
<NewNavMenu Menu="@MenuItems" BrandName="@SelectedBrandName" SiteId="SiteId" OnMenuClicked=@MenuClick></NewNavMenu>
|
||||
<main>
|
||||
|
||||
<article class="content text-center" style="position: relative; z-index: 4;">
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
@{
|
||||
if (SiteInfo != null)
|
||||
|
|
@ -49,7 +50,14 @@
|
|||
|
||||
<div class="displaysearch">
|
||||
<div class="searchBox">
|
||||
|
||||
@if (VoiceEnabled)
|
||||
{
|
||||
if (STTEnabled)
|
||||
{
|
||||
<button id="recButton" class="voicebutton bg-panel-gradient" onclick="startRecording()"><i class="fa-solid fa-microphone"></i></button>
|
||||
<button id="stopButton" class="voicebutton bg-danger" onclick="stopRecording()" hidden><i class="fa-solid fa-microphone-slash"></i></button>
|
||||
}
|
||||
}
|
||||
<input @oninput="(e) => UserInput = e.Value.ToString()"
|
||||
@onkeydown="@Enter" class="searchInput" type="text" name="" value="@UserInput" placeholder="Ask any question">
|
||||
<button data-hint="ask anything" class="searchButton border-0" @onclick="SendUserQuery" href="#">
|
||||
|
|
@ -60,24 +68,24 @@
|
|||
@{
|
||||
@if (VoiceEnabled)
|
||||
{
|
||||
if (STTEnabled)
|
||||
{
|
||||
<button id="recButton" class="btn btn-primary voicebutton" onclick="startRecording()"><i class="fa-solid fa-microphone"></i></button>
|
||||
<button id="stopButton" class="btn btn-primary voicebutton" onclick="stopRecording()" hidden><i class="fa-solid fa-microphone-slash"></i></button>
|
||||
}
|
||||
// if (STTEnabled)
|
||||
// {
|
||||
// <button id="recButton" class="btn btn-primary voicebutton" onclick="startRecording()"><i class="fa-solid fa-microphone"></i></button>
|
||||
// <button id="stopButton" class="btn btn-primary voicebutton" onclick="stopRecording()" hidden><i class="fa-solid fa-microphone-slash"></i></button>
|
||||
// }
|
||||
|
||||
if (TTSEnabled)
|
||||
{
|
||||
if (!AiVoicePermitted)
|
||||
{
|
||||
<button data-hint="listen" class="btn btn-primary voicebutton" @onclick="AllowAIVoice">
|
||||
<button data-hint="listen" class="btn btn-primary voicebutton" style="display: inline-block" @onclick="AllowAIVoice">
|
||||
<i class="fa-solid fa-volume-xmark"></i>
|
||||
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button data-hint="listen" class="btn btn-primary voicebutton" @onclick="MuteAI"><i class="fa-solid fa-volume-high"></i></button>
|
||||
<button data-hint="listen" class="btn btn-primary voicebutton" style="display: inline-block" @onclick="MuteAI"><i class="fa-solid fa-volume-high"></i></button>
|
||||
}
|
||||
<audio id="audioPlayer" hidden style="display: none;"></audio>
|
||||
}
|
||||
|
|
@ -92,8 +100,10 @@
|
|||
@* </div> *@
|
||||
</div>
|
||||
|
||||
<p id="recordingText"></p>
|
||||
|
||||
<div id="currentContent">
|
||||
<p id="recordingText"></p>
|
||||
<AnimateOnRender CssClass="animate__animated animate__backInUp">
|
||||
@{
|
||||
if (!string.IsNullOrEmpty(HtmlContent.ToString()))
|
||||
{
|
||||
|
|
@ -136,6 +146,7 @@
|
|||
|
||||
|
||||
}
|
||||
</AnimateOnRender>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" @onclick="HomeClick"><i class="fa-solid fa-rotate"></i></button>
|
||||
|
|
@ -145,6 +156,7 @@
|
|||
</main>
|
||||
<FooterComponent MenuString="@Menu" OnMenuClicked=@MenuClick></FooterComponent>
|
||||
</div>
|
||||
|
||||
</ChildContent>
|
||||
<ErrorContent Context="ex">
|
||||
<p role="alert">An error occurred: @ex.Message</p>
|
||||
|
|
@ -155,6 +167,7 @@
|
|||
}
|
||||
</ErrorContent>
|
||||
</ErrorBoundary>
|
||||
|
||||
<script>
|
||||
|
||||
var sessionId = null;
|
||||
|
|
@ -212,6 +225,7 @@
|
|||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await _logger.InfoAsync("Index component OnafterRender.", $"{SiteId}");
|
||||
await jsRuntime.InvokeVoidAsync("setSessionId", SessionId);
|
||||
await jsRuntime.InvokeVoidAsync("initHints");
|
||||
|
||||
|
|
@ -277,12 +291,14 @@
|
|||
if (!string.IsNullOrWhiteSpace(topic))
|
||||
{
|
||||
UserInput = topic;
|
||||
await ChatGptService.InitSite(SessionId, SiteInfo, TemplateCollectionName, Menu);
|
||||
await ChatGptService.ProcessContentRequest(SessionId, UserInput, SiteId, (int)SiteInfo.TemplateId!, ContentCollectionName, Menu, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ChatGptService.InitSite(SessionId, SiteInfo, TemplateCollectionName, Menu);
|
||||
await ChatGptService.GetChatGptWelcomeMessage(SessionId, SiteId, TemplateCollectionName, Menu);
|
||||
SiteModel = await ChatGptService.InitSite(SessionId, SiteId, TemplateCollectionName, Menu);
|
||||
// SiteModel = await ChatGptService.InitSite(SessionId, SiteId, TemplateCollectionName, Menu);
|
||||
//await ChatGptService.ProcessContentRequest(SessionId, MenuItems.FirstOrDefault(), SiteId, (int)SiteInfo.TemplateId!, TemplateCollectionName, Menu, true);
|
||||
}
|
||||
// HtmlContent = await ChatGptService.GetChatGptWelcomeMessage();
|
||||
|
|
@ -310,7 +326,7 @@
|
|||
public void HomeClick()
|
||||
{
|
||||
//ChatGptService.OnContentReceived -= UpdateContent;
|
||||
AIService.OnContentReceived -= UpdateContent;
|
||||
ChatGptService.OnContentReceived -= UpdateContent;
|
||||
_navigationManager.Refresh(true);
|
||||
}
|
||||
|
||||
|
|
@ -328,16 +344,16 @@
|
|||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await _logger.InfoAsync("Index component initialized.", $"{SiteId}");
|
||||
_scopedContentService.OnBrandNameChanged += HandleBrandNameChanged;
|
||||
// ChatGptService.OnContentReceived += UpdateContent;
|
||||
AIService.OnContentReceived += UpdateContent;
|
||||
AIService.OnContentReceiveFinished += UpdateFinished;
|
||||
ChatGptService.OnContentReceived += UpdateContent;
|
||||
ChatGptService.OnContentReceiveFinished += UpdateFinished;
|
||||
// ChatGptService.OnStatusChangeReceived += UpdateStatus;
|
||||
AIService.OnStatusChangeReceived += UpdateStatus;
|
||||
AIService.OnTextContentAvailable += UpdateTextContentForVoice;
|
||||
SessionId = Guid.NewGuid().ToString();
|
||||
ChatGptService.OnStatusChangeReceived += UpdateStatus;
|
||||
ChatGptService.OnTextContentAvailable += UpdateTextContentForVoice;
|
||||
SessionId = _scopedContentService.SessionId;
|
||||
_instances[SessionId] = this;
|
||||
|
||||
VoiceEnabled = configuration?.GetSection("AiSettings")?.GetValue<bool>("VoiceActivated") ?? false;
|
||||
}
|
||||
|
||||
|
|
@ -357,17 +373,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
private async void UpdateTextContentForVoice(string receivedSessionId, string content)
|
||||
{
|
||||
Console.WriteLine("UPDATETEXTCONTENT called");
|
||||
if (receivedSessionId == SessionId) // Only accept messages meant for this tab
|
||||
{
|
||||
// private async void UpdateTextContentForVoice(string receivedSessionId, string content)
|
||||
// {
|
||||
// Console.WriteLine("UPDATETEXTCONTENT called");
|
||||
// if (receivedSessionId == SessionId) // Only accept messages meant for this tab
|
||||
// {
|
||||
|
||||
TextContent = content;
|
||||
await ConvertTextToSpeech(content);
|
||||
//_scopedContentService.CurrentDOM = await jsRuntime.InvokeAsync<string>("getDivContent", "currentContent");
|
||||
}
|
||||
}
|
||||
// TextContent = content;
|
||||
// await ConvertTextToSpeech(content);
|
||||
// //_scopedContentService.CurrentDOM = await jsRuntime.InvokeAsync<string>("getDivContent", "currentContent");
|
||||
// }
|
||||
// }
|
||||
|
||||
private async void UpdateFinished(string receivedSessionId)
|
||||
{
|
||||
|
|
@ -418,15 +434,20 @@
|
|||
dynamicallyLoadedCss = "";
|
||||
HtmlContent.Clear();
|
||||
_scopedContentService.OnBrandNameChanged -= HandleBrandNameChanged;
|
||||
AIService.OnContentReceived -= UpdateContent;
|
||||
AIService.OnContentReceiveFinished -= UpdateFinished;
|
||||
AIService.OnStatusChangeReceived -= UpdateStatus;
|
||||
AIService.OnTextContentAvailable -= UpdateTextContentForVoice;
|
||||
ChatGptService.OnContentReceived -= UpdateContent;
|
||||
ChatGptService.OnContentReceiveFinished -= UpdateFinished;
|
||||
ChatGptService.OnStatusChangeReceived -= UpdateStatus;
|
||||
ChatGptService.OnTextContentAvailable -= UpdateTextContentForVoice;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await CssTemplateService.DeleteSessionCssFile(SessionId);
|
||||
_scopedContentService.OnBrandNameChanged -= HandleBrandNameChanged;
|
||||
ChatGptService.OnContentReceived -= UpdateContent;
|
||||
ChatGptService.OnContentReceiveFinished -= UpdateFinished;
|
||||
ChatGptService.OnStatusChangeReceived -= UpdateStatus;
|
||||
ChatGptService.OnTextContentAvailable -= UpdateTextContentForVoice;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
@page "/logs"
|
||||
@using BLAIzor.Models
|
||||
@inject ApplicationDbContext Db
|
||||
@attribute [Authorize]
|
||||
|
||||
<h3 class="mb-3">📋 Application Logs</h3>
|
||||
|
||||
<RadzenCard>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-2">
|
||||
<RadzenDropDown @bind-Value="selectedSeverity"
|
||||
Data="@severities"
|
||||
Placeholder="All Severities"
|
||||
AllowClear="true"
|
||||
Style="width: 100%;" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<RadzenDatePicker @bind-Value="startDate" Placeholder="From date" Style="width: 100%;" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<RadzenDatePicker @bind-Value="endDate" Placeholder="To date" Style="width: 100%;" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<RadzenButton Text="Search" Click="LoadLogs" Icon="search" Style="width: 100%;" />
|
||||
</div>
|
||||
</div>
|
||||
</RadzenCard>
|
||||
|
||||
<br />
|
||||
|
||||
<RadzenDataGrid TItem="AppLog" Data="@logs" Count="@totalCount"
|
||||
LoadData="@LoadData" AllowPaging="true" PageSize="10"
|
||||
AllowSorting="true" AllowFiltering="false"
|
||||
ColumnWidth="200px" ShowPagingSummary="true">
|
||||
<Columns>
|
||||
<RadzenDataGridColumn Width="100px" TItem="AppLog" Property="Timestamp" Title="Time" FormatString="{0:yyyy-MM-dd HH:mm:ss}" />
|
||||
<RadzenDataGridColumn Width="100px" TItem="AppLog" Property="Severity" Title="Severity" />
|
||||
<RadzenDataGridColumn TItem="AppLog" Property="Message" Title="Message" />
|
||||
<RadzenDataGridColumn TItem="AppLog" Property="Details" Title="Details" />
|
||||
</Columns>
|
||||
</RadzenDataGrid>
|
||||
|
||||
<br />
|
||||
|
||||
<h4>📊 Log Summary</h4>
|
||||
|
||||
<RadzenStack Style="width: 100%;">
|
||||
<RadzenChart SeriesClick=@OnSeriesClick>
|
||||
<RadzenPieSeries Data="@logChartData" Title="Logs" CategoryProperty="Severity" ValueProperty="Count">
|
||||
<RadzenSeriesDataLabels Visible="true" />
|
||||
</RadzenPieSeries>
|
||||
</RadzenChart>
|
||||
</RadzenStack>
|
||||
|
||||
|
||||
@code {
|
||||
private List<AppLog> logs = new();
|
||||
private int totalCount = 0;
|
||||
|
||||
private string? selectedSeverity;
|
||||
private DateTime? startDate;
|
||||
private DateTime? endDate;
|
||||
|
||||
private List<string> severities = new() { "Info", "Warning", "Error" };
|
||||
|
||||
private List<LogChartItem> logChartData = new();
|
||||
|
||||
void OnSeriesClick(SeriesClickEventArgs args)
|
||||
{
|
||||
// console.Log(args);
|
||||
}
|
||||
|
||||
private async Task LoadData(LoadDataArgs args)
|
||||
{
|
||||
var query = Db.Logs.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(selectedSeverity))
|
||||
query = query.Where(l => l.Severity == selectedSeverity);
|
||||
|
||||
if (startDate.HasValue)
|
||||
query = query.Where(l => l.Timestamp >= startDate.Value);
|
||||
|
||||
if (endDate.HasValue)
|
||||
query = query.Where(l => l.Timestamp <= endDate.Value);
|
||||
|
||||
totalCount = query.Count();
|
||||
|
||||
logs = query
|
||||
.OrderByDescending(l => l.Timestamp)
|
||||
.Skip(args.Skip ?? 0)
|
||||
.Take(args.Top ?? 10).ToList();
|
||||
|
||||
await LoadChartData();
|
||||
}
|
||||
|
||||
private async Task LoadLogs()
|
||||
{
|
||||
await LoadData(new LoadDataArgs { Skip = 0, Top = 10 });
|
||||
}
|
||||
|
||||
private async Task LoadChartData()
|
||||
{
|
||||
var query = Db.Logs.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(selectedSeverity))
|
||||
query = query.Where(l => l.Severity == selectedSeverity);
|
||||
|
||||
if (startDate.HasValue)
|
||||
query = query.Where(l => l.Timestamp >= startDate.Value);
|
||||
|
||||
if (endDate.HasValue)
|
||||
query = query.Where(l => l.Timestamp <= endDate.Value);
|
||||
|
||||
logChartData = query
|
||||
.GroupBy(l => l.Severity)
|
||||
.Select(g => new LogChartItem
|
||||
{
|
||||
Severity = g.Key,
|
||||
Count = g.Count()
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public class LogChartItem
|
||||
{
|
||||
public string Severity { get; set; } = "";
|
||||
public int Count { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,378 @@
|
|||
@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;
|
||||
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
using BLAIzor.Services;
|
||||
using Google.Cloud.Speech.V1;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.JSInterop;
|
||||
using Radzen;
|
||||
using System.Text;
|
||||
|
|
@ -18,8 +19,10 @@ namespace BLAIzor.Components.Pages
|
|||
[Inject] protected IConfiguration configuration { get; set; }
|
||||
[Inject] protected HttpClient Http { get; set; }
|
||||
[Inject] protected IJSRuntime jsRuntime { get; set; }
|
||||
[Inject] protected ISimpleLogger _logger { get; set; }
|
||||
[Inject] protected AIService ChatGptService { get; set; }
|
||||
[Inject] NotificationService NotificationService { get; set; }
|
||||
[Inject] protected NotificationService NotificationService { get; set; }
|
||||
[Inject] protected CacheService CacheService { get; set; }
|
||||
|
||||
public static readonly Dictionary<string, MainPageBase> _instances = new();
|
||||
|
||||
|
|
@ -40,7 +43,7 @@ namespace BLAIzor.Components.Pages
|
|||
public bool STTEnabled;
|
||||
public bool _initVoicePending = false;
|
||||
public bool welcomeStage = true;
|
||||
public bool AiVoicePermitted = false;
|
||||
public bool AiVoicePermitted = true;
|
||||
|
||||
public string FirstColumnClass = "";
|
||||
public bool isEmailFormVisible = false;
|
||||
|
|
@ -53,6 +56,8 @@ namespace BLAIzor.Components.Pages
|
|||
public List<MenuItem> MenuItems = new();
|
||||
public string dynamicallyLoadedCss = string.Empty;
|
||||
public MenuItem currentMenuItem = new();
|
||||
public bool IsContentSaved = false;
|
||||
|
||||
|
||||
public WebsiteContentModel SiteModel = new();
|
||||
|
||||
|
|
@ -233,6 +238,16 @@ namespace BLAIzor.Components.Pages
|
|||
|
||||
HtmlContent.Clear();
|
||||
HtmlContent.Append(menuItem.StoredHtml);
|
||||
if (currentMenuItem.ContentItemId != null) {
|
||||
var content = await _contentEditorService.GetContentItemByIdAsync((int)currentMenuItem.ContentItemId);
|
||||
if (content != null)
|
||||
{
|
||||
string removedNumbers = TextHelper.ReplaceNumbersAndSpecialCharacters(content.Content, _scopedContentService.SelectedLanguage);
|
||||
Console.WriteLine(removedNumbers);
|
||||
UpdateTextContentForVoice(SessionId, removedNumbers);
|
||||
}
|
||||
}
|
||||
IsContentSaved = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -243,6 +258,18 @@ namespace BLAIzor.Components.Pages
|
|||
}
|
||||
}
|
||||
|
||||
protected async void UpdateTextContentForVoice(string receivedSessionId, string content)
|
||||
{
|
||||
Console.WriteLine("UPDATETEXTCONTENT called");
|
||||
if (receivedSessionId == SessionId) // Only accept messages meant for this tab
|
||||
{
|
||||
|
||||
TextContent = content;
|
||||
await ConvertTextToSpeech(content);
|
||||
//_scopedContentService.CurrentDOM = await jsRuntime.InvokeAsync<string>("getDivContent", "currentContent");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisplayEmailForm(string emailAddress)
|
||||
{
|
||||
FirstColumnClass = "col-12 col-md-6";
|
||||
|
|
@ -348,7 +375,15 @@ namespace BLAIzor.Components.Pages
|
|||
NotificationService.Notify(message);
|
||||
}
|
||||
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
//await CssTemplateService.DeleteSessionCssFile(SessionId);
|
||||
_scopedContentService.OnBrandNameChanged -= HandleBrandNameChanged;
|
||||
//AIService.OnContentReceived -= UpdateContent;
|
||||
//AIService.OnContentReceiveFinished -= UpdateFinished;
|
||||
//AIService.OnStatusChangeReceived -= UpdateStatus;
|
||||
ChatGptService.OnTextContentAvailable -= UpdateTextContentForVoice;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,9 +33,6 @@ else
|
|||
<RadzenRow class="rz-text-align-center" Gap="1rem">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@foreach (var image in files.Images)
|
||||
{
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@
|
|||
@using System.Net
|
||||
@using Radzen.Blazor.Rendering
|
||||
|
||||
@rendermode InteractiveServer
|
||||
@inject ContentService _contentService
|
||||
@* @rendermode InteractiveServer *@
|
||||
@inject IEmailSender _emailService
|
||||
@inject NavigationManager _navigationManager
|
||||
@inject IHttpContextAccessor HttpContextAccessor
|
||||
|
|
@ -21,28 +20,163 @@
|
|||
@inject CssInjectorService CssService
|
||||
@inject DialogService DialogService
|
||||
|
||||
|
||||
|
||||
<div class="page" style="z-index: 1">
|
||||
<RadzenButton Click="@(args => ToggleSettings())" style="position: fixed;
|
||||
|
||||
|
||||
<div class="top-panel-outer">
|
||||
<RadzenStack class="top-panel px-2 animate__animated animate__slideInDown" JustifyContent="JustifyContent.Center" Orientation="Orientation.Horizontal" style="margin: auto; align-content:center;" Visible=@displayTopPanel>
|
||||
@{if(displayOptions) {
|
||||
<div class="rz-p-2 rz-text-align-center align-content-center">
|
||||
<strong>@currentMenuItem.Name</strong>
|
||||
</div>
|
||||
<div class="rz-p-2 rz-text-align-center align-content-center">
|
||||
<span>Happy with this look?</span>
|
||||
<RadzenButton Size="ButtonSize.ExtraSmall" class="btn m-0" Text="@(IsContentSaved ? "Overwrite" : "Save")" Click="@(args => SaveCurrentLayout(currentMenuItem))" />
|
||||
</div>
|
||||
<div class="rz-p-2 rz-text-align-center align-content-center">
|
||||
<span>Not happy?</span>
|
||||
<RadzenButton Size="ButtonSize.ExtraSmall" class="btn m-0" @ref=button Text="Regenerate" Click="@(args => MenuClick(currentMenuItem.Name))" />
|
||||
</div>
|
||||
<div class="rz-p-2 rz-text-align-center align-content-center">
|
||||
<span>Typos?</span>
|
||||
<RadzenButton Size="ButtonSize.ExtraSmall" class="btn m-0" @ref=button Text="Manual edit" Click="@(args => OpenContentEditor(currentMenuItem))" />
|
||||
</div>
|
||||
<div class="rz-p-2 rz-text-align-center align-content-center">
|
||||
<span>Cancel</span>
|
||||
<RadzenButton Size="ButtonSize.ExtraSmall" class="btn m-0" @ref=button Text="x" Click="@(args => ToggleSavedContentEdit())" />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-center">If you navigate to a menu item, you can save it's actual design for consitent looks, and faster load for the visitors</p>
|
||||
|
||||
}
|
||||
}
|
||||
</RadzenStack>
|
||||
</div>
|
||||
|
||||
<RadzenButton Click="@(args => ToggleSavedContentEdit())" Visible="@(currentMenuItem == null ? false : true)" style="position: fixed;
|
||||
z-index: 10005;
|
||||
top: 100px;
|
||||
left: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-top-left-radius: 0px;
|
||||
left: 10px;
|
||||
height: 40px;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-top-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
background: linear-gradient(307deg, rgba(12, 37, 51, 0.83) 7%, rgb(152 87 199 / 73%) 96%);
|
||||
border-top-right-radius: 20px;">
|
||||
<i class="fa-solid fa-pencil"></i>
|
||||
</RadzenButton>
|
||||
|
||||
<RadzenButton Click="@(args => ToggleSettings())" style="position: fixed;
|
||||
z-index: 10005;
|
||||
top: 150px;
|
||||
left: 10px;
|
||||
height: 40px;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-top-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
background: linear-gradient(307deg, rgba(12, 37, 51, 0.83) 7%, rgb(152 87 199 / 73%) 96%);
|
||||
border-top-right-radius: 20px;">
|
||||
<i class="fa-solid fa-wrench"></i>
|
||||
</RadzenButton>
|
||||
|
||||
<RadzenStack class="editor-window animate__animated animate__slideInLeft" Orientation="Orientation.Vertical" style="" Visible=@displaySettingsPanel>
|
||||
<RadzenStack Orientation="Orientation.Vertical" style="">
|
||||
<button class="pointer bg-transparent border-0" style="width:100%; text-align: left; " @onclick="EditSite">
|
||||
<div class="px-3 py-2 reference-button bg-panel-gradient pointer">
|
||||
<div class="text-content">
|
||||
<strong>Edit site</strong>
|
||||
<br />
|
||||
<small class="text-muted">Edit basic settings</small>
|
||||
</div>
|
||||
<div class="icon-buttons">
|
||||
|
||||
<div class="icon-circle"><i class="fa-regular fa-circle-question"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@*<button class="pointer bg-transparent border-0" style="width:100%; text-align: left; " @onclick="EditSite">
|
||||
<div class="px-3 py-2 reference-button bg-panel-gradient pointer">
|
||||
<div class="text-content">
|
||||
<strong>AI settings</strong>
|
||||
<br />
|
||||
<small class="text-muted">Edit AI settings</small>
|
||||
</div>
|
||||
<div class="icon-buttons">
|
||||
|
||||
<div class="icon-circle"><i class="fa-solid fa-hexagon-nodes-bolt"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</button>*@
|
||||
<button class="pointer bg-transparent border-0" style="width:100%; text-align: left; " @onclick="OpenManageContentGroups">
|
||||
<div class="px-3 py-2 reference-button bg-panel-gradient pointer">
|
||||
<div class="text-content">
|
||||
<strong>Edit content</strong>
|
||||
<br />
|
||||
<small class="text-muted">Manage content</small>
|
||||
</div>
|
||||
<div class="icon-buttons">
|
||||
|
||||
<div class="icon-circle"><i class="fa-solid fa-file-lines"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="pointer bg-transparent border-0" style="width:100%; text-align: left; " @onclick="EditMenu">
|
||||
<div class="px-3 py-2 reference-button bg-panel-gradient pointer">
|
||||
<div class="text-content">
|
||||
<strong>Setup menu</strong>
|
||||
<br />
|
||||
<small class="text-muted">Link content to the menu</small>
|
||||
</div>
|
||||
<div class="icon-buttons">
|
||||
|
||||
<div class="icon-circle"><i class="fa-solid fa-bars"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="pointer bg-transparent border-0" style="width:100%; text-align: left; " @onclick="EditMenu">
|
||||
<div class="px-3 py-2 reference-button bg-panel-gradient pointer">
|
||||
<div class="text-content">
|
||||
<strong>Edit looks</strong>
|
||||
<br />
|
||||
<small class="text-muted">Select design</small>
|
||||
</div>
|
||||
<div class="icon-buttons">
|
||||
|
||||
<div class="icon-circle"><i class="fa-solid fa-eye"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="pointer bg-transparent border-0" style="width:100%; text-align: left; " @onclick="OpenManageUploads">
|
||||
<div class="px-3 py-2 reference-button bg-panel-gradient pointer">
|
||||
<div class="text-content">
|
||||
<strong>Media</strong>
|
||||
<br />
|
||||
<small class="text-muted">Manage your media</small>
|
||||
</div>
|
||||
<div class="icon-buttons">
|
||||
|
||||
<div class="icon-circle"><i class="fa-solid fa-photo-film"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@* <RadzenStack Orientation="Orientation.Vertical" style="">
|
||||
<div class="rz-p-2 rz-text-align-center">
|
||||
<h3>Basic information</h3>
|
||||
<p>@SiteInfo.SiteName</p>
|
||||
<RadzenButton class="btn" Text="Edit basic info" Click="@EditSite" />
|
||||
</div>
|
||||
</RadzenStack>
|
||||
|
||||
<RadzenStack Orientation="Orientation.Vertical" style="">
|
||||
<div class="rz-p-2 rz-text-align-center">
|
||||
<h3>Site menu</h3>
|
||||
<RadzenButton class="btn" Text="Edit menu" Click="@EditMenu" />
|
||||
</div>
|
||||
</RadzenStack>
|
||||
|
||||
<RadzenStack Orientation="Orientation.Vertical" style="">
|
||||
<div class="rz-p-2 rz-text-align-center">
|
||||
<h5>Manage content</h5>
|
||||
|
|
@ -79,11 +213,11 @@
|
|||
<RadzenButton class="btn" Text="Open library" Click="@OpenManageUploads" />
|
||||
</div>
|
||||
|
||||
</RadzenStack>
|
||||
</RadzenStack>*@
|
||||
</RadzenStack>
|
||||
|
||||
|
||||
<NewNavMenu Menu="@MenuItems" BrandName="@SelectedBrandName" OnMenuClicked=@MenuClick></NewNavMenu>
|
||||
<NewNavMenu Menu="@MenuItems" BrandName="@SelectedBrandName" SiteId="siteid" OnMenuClicked=@MenuClick></NewNavMenu>
|
||||
<main>
|
||||
|
||||
<article class="content text-center" style="position: relative; z-index: 4;">
|
||||
|
|
@ -260,8 +394,9 @@
|
|||
private bool isRecording = false;
|
||||
|
||||
// private string dynamicallyLoadedCss = string.Empty;
|
||||
private bool isContentSaved = false;
|
||||
|
||||
private bool displaySettingsPanel = false;
|
||||
private bool displayTopPanel = false;
|
||||
private bool displayOptions = false;
|
||||
private bool forceRegenerate = false;
|
||||
|
||||
|
|
@ -276,14 +411,22 @@
|
|||
displaySettingsPanel = !displaySettingsPanel;
|
||||
}
|
||||
|
||||
private void ToggleSavedContentEdit()
|
||||
{
|
||||
displayTopPanel = !displayTopPanel;
|
||||
}
|
||||
|
||||
async Task SaveCurrentLayout(MenuItem menuItem)
|
||||
{
|
||||
//Save current layout called
|
||||
menuItem.StoredHtml = HtmlContent.ToString();
|
||||
Console.WriteLine($"Content length: {HtmlContent.Length}");
|
||||
Console.WriteLine(menuItem.StoredHtml);
|
||||
await _logger.InfoAsync($"Preview Component: Saving layout!", $"Content length: {HtmlContent.Length}");
|
||||
|
||||
await _logger.InfoAsync($"Preview Component: Saving layout!", $"Content length: {menuItem.StoredHtml}");
|
||||
var result = await _contentEditorService.UpdateMenuItemAsync(menuItem);
|
||||
Console.WriteLine($"menuitem updated: {result.Id}, {result.Name}");
|
||||
var message = NotificationHelper.CreateNotificationMessage("Page design saved", 2, "Success", $"Page design for {result.Name} updated successfully");
|
||||
ShowNotification(message);
|
||||
await _logger.InfoAsync($"Preview Component: Saved layout for page!", $"MenuItem updated with new design: {result.Id}, {result.Name}");
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -327,7 +470,7 @@
|
|||
public void HomeClick()
|
||||
{
|
||||
//ChatGptService.OnContentReceived -= UpdateContent;
|
||||
AIService.OnContentReceived -= UpdateContent;
|
||||
ChatGptService.OnContentReceived -= UpdateContent;
|
||||
_navigationManager.Refresh(true);
|
||||
}
|
||||
|
||||
|
|
@ -346,7 +489,7 @@
|
|||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
SessionId = Guid.NewGuid().ToString();
|
||||
SessionId = _scopedContentService.SessionId;
|
||||
SiteId = siteid;
|
||||
_instances[SessionId] = this;
|
||||
_scopedContentService.OnBrandNameChanged += HandleBrandNameChanged;
|
||||
|
|
@ -392,10 +535,10 @@
|
|||
Console.Write("------------------------");
|
||||
|
||||
// ChatGptService.OnContentReceived += UpdateContent;
|
||||
AIService.OnContentReceived += UpdateContent;
|
||||
ChatGptService.OnContentReceived += UpdateContent;
|
||||
// ChatGptService.OnStatusChangeReceived += UpdateStatus;
|
||||
AIService.OnStatusChangeReceived += UpdateStatus;
|
||||
AIService.OnTextContentAvailable += UpdateTextContentForVoice;
|
||||
ChatGptService.OnStatusChangeReceived += UpdateStatus;
|
||||
ChatGptService.OnTextContentAvailable += UpdateTextContentForVoice;
|
||||
|
||||
Menu = await GetMenuList(SiteId);
|
||||
MenuItems = await GetMenuItems(SiteId);
|
||||
|
|
@ -409,7 +552,7 @@
|
|||
else
|
||||
{
|
||||
await ChatGptService.GetChatGptWelcomeMessage(SessionId, SiteId, TemplateCollectionName, Menu);
|
||||
SiteModel = await ChatGptService.InitSite(SessionId, SiteId, TemplateCollectionName, Menu);
|
||||
// SiteModel = await ChatGptService.InitSite(SessionId, SiteId, TemplateCollectionName, Menu);
|
||||
//await ChatGptService.ProcessContentRequest(SessionId, MenuItems.FirstOrDefault(), SiteId, (int)SiteInfo.TemplateId!, TemplateCollectionName, Menu, true);
|
||||
}
|
||||
|
||||
|
|
@ -428,7 +571,10 @@
|
|||
if (menuItem != null)
|
||||
{
|
||||
currentMenuItem = menuItem;
|
||||
// IsContentSaved = string.IsNullOrEmpty(currentMenuItem.StoredHtml) ? false : true;
|
||||
// _logger.InfoAsync($"Preview - UpdateContent: {IsContentSaved}");
|
||||
displayOptions = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
//InvokeAsync(StateHasChanged); // Ensures UI updates dynamically
|
||||
await InvokeAsync(() =>
|
||||
|
|
@ -441,16 +587,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
private async void UpdateTextContentForVoice(string receivedSessionId, string content)
|
||||
{
|
||||
if (receivedSessionId == SessionId) // Only accept messages meant for this tab
|
||||
{
|
||||
// private async void UpdateTextContentForVoice(string receivedSessionId, string content)
|
||||
// {
|
||||
// if (receivedSessionId == SessionId) // Only accept messages meant for this tab
|
||||
// {
|
||||
|
||||
TextContent = content;
|
||||
await ConvertTextToSpeech(content);
|
||||
//_scopedContentService.CurrentDOM = await jsRuntime.InvokeAsync<string>("getDivContent", "currentContent");
|
||||
}
|
||||
}
|
||||
// TextContent = content;
|
||||
// await ConvertTextToSpeech(content);
|
||||
// //_scopedContentService.CurrentDOM = await jsRuntime.InvokeAsync<string>("getDivContent", "currentContent");
|
||||
// }
|
||||
// }
|
||||
|
||||
private async void UpdateFinished(string receivedSessionId)
|
||||
{
|
||||
|
|
@ -494,19 +640,30 @@
|
|||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
// public void Dispose()
|
||||
// {
|
||||
// dynamicallyLoadedCss = "";
|
||||
// HtmlContent.Clear();
|
||||
// _scopedContentService.OnBrandNameChanged -= HandleBrandNameChanged;
|
||||
// AIService.OnContentReceived -= UpdateContent;
|
||||
// AIService.OnStatusChangeReceived -= UpdateStatus;
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
dynamicallyLoadedCss = "";
|
||||
HtmlContent.Clear();
|
||||
await CssTemplateService.DeleteSessionCssFile(SessionId);
|
||||
_scopedContentService.OnBrandNameChanged -= HandleBrandNameChanged;
|
||||
AIService.OnContentReceived -= UpdateContent;
|
||||
AIService.OnStatusChangeReceived -= UpdateStatus;
|
||||
ChatGptService.OnContentReceived -= UpdateContent;
|
||||
ChatGptService.OnContentReceiveFinished -= UpdateFinished;
|
||||
ChatGptService.OnStatusChangeReceived -= UpdateStatus;
|
||||
ChatGptService.OnTextContentAvailable -= UpdateTextContentForVoice;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await CssTemplateService.DeleteSessionCssFile(SessionId);
|
||||
}
|
||||
|
||||
// public async ValueTask DisposeAsync()
|
||||
// {
|
||||
// await CssTemplateService.DeleteSessionCssFile(SessionId);
|
||||
// }
|
||||
|
||||
public async Task EditSite()
|
||||
{
|
||||
|
|
@ -525,6 +682,44 @@
|
|||
new Dictionary<string, object>() {
|
||||
{ "SiteId", siteid },
|
||||
{ "OnSiteNameChanged", new Func<string, Task>(OnSiteNameChanged) },
|
||||
{ "OnSiteInfoSaveClicked", new Func<SiteInfo, Task>(OnSiteInfoSaved) },
|
||||
},
|
||||
new DialogOptions()
|
||||
{
|
||||
Resizable = true,
|
||||
Draggable = true,
|
||||
Resize = GetResizeHandler(dialogKey),
|
||||
Drag = GetDragHandler(dialogKey),
|
||||
Width = settings.Width,
|
||||
Height = settings.Height,
|
||||
Left = settings.Left,
|
||||
Top = settings.Top,
|
||||
CssClass = "draggable-popup-dialog",
|
||||
WrapperCssClass = "draggable-popup-dialog-wrapper"
|
||||
});
|
||||
|
||||
await SaveStateAsync(dialogKey, _editSiteinfoSettings);
|
||||
}
|
||||
|
||||
public async Task EditMenu()
|
||||
{
|
||||
string dialogKey = $"EditMenuDialogSettings_{siteid}"; // can be anything unique
|
||||
|
||||
var settings = await LoadStateAsync(dialogKey)
|
||||
?? new EditSiteInfoDialogSettings
|
||||
{
|
||||
Width = "500px",
|
||||
Height = "512px",
|
||||
Left = "10%",
|
||||
Top = "10%"
|
||||
};
|
||||
|
||||
await DialogService.OpenAsync<MenuItemEditor>($"Edit menu of {SiteInfo.SiteName}",
|
||||
new Dictionary<string, object>() {
|
||||
{ "SiteId", siteid },
|
||||
{ "SessionId", SessionId },
|
||||
// { "OnSiteNameChanged", new Func<string, Task>(OnSiteNameChanged) },
|
||||
// { "OnSiteInfoSaveClicked", new Func<SiteInfo, Task>(OnSiteInfoSaved) },
|
||||
},
|
||||
new DialogOptions()
|
||||
{
|
||||
|
|
@ -549,6 +744,15 @@
|
|||
SelectedBrandName = newName;
|
||||
}
|
||||
|
||||
private async Task OnSiteInfoSaved(SiteInfo newInfo)
|
||||
{
|
||||
Console.WriteLine("Sitename updated!!!!!!");
|
||||
|
||||
var message = NotificationHelper.CreateNotificationMessage("Site information saved", 2, "Success", "Site information saved successfully");
|
||||
ShowNotification(message);
|
||||
}
|
||||
|
||||
|
||||
public async Task EditContentItem(int Id)
|
||||
{
|
||||
string dialogKey = $"EditContentItemDialogSettings_{Id}"; // can be anything unique
|
||||
|
|
@ -588,13 +792,16 @@
|
|||
|
||||
private async Task OnContentItemUpdated(ContentItem contentGroup)
|
||||
{
|
||||
Console.WriteLine("ContentGroup updated!!!!!!");
|
||||
|
||||
}
|
||||
|
||||
private async Task OnContentItemSaved(ContentItem contentGroup)
|
||||
{
|
||||
Console.WriteLine("ContentGroup edit started!!!!!!");
|
||||
|
||||
//TODO Cahce outdated
|
||||
await CacheService.UpdateContentCache(SessionId, SiteId);
|
||||
var message = NotificationHelper.CreateNotificationMessage("ContentItem saved", 2, "Success", "ContentItem saved successfully");
|
||||
ShowNotification(message);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -652,28 +859,23 @@
|
|||
private async Task OnContentGroupUpdated(ContentGroup contentGroup)
|
||||
{
|
||||
Console.WriteLine("ContentGroup updated!!!!!!");
|
||||
|
||||
}
|
||||
|
||||
private async Task OnContentGroupEditStarted(ContentGroup contentGroup)
|
||||
{
|
||||
Console.WriteLine("ContentGroup edit started!!!!!!");
|
||||
|
||||
}
|
||||
|
||||
|
||||
private async Task OnManageContentItemClicked(string methodName, int Id)
|
||||
{
|
||||
Console.WriteLine("ContentItem Edit clicked");
|
||||
|
||||
await EditContentItem(Id);
|
||||
|
||||
}
|
||||
|
||||
public async Task OpenManageUploads()
|
||||
{
|
||||
string dialogKey = $"ManageUploadsDialogSettings_{siteid}"; // can be anything unique
|
||||
|
||||
var settings = await LoadStateAsync(dialogKey)
|
||||
?? new EditSiteInfoDialogSettings
|
||||
{
|
||||
|
|
@ -698,10 +900,56 @@
|
|||
CssClass = "draggable-popup-dialog",
|
||||
WrapperCssClass = "draggable-popup-dialog-wrapper"
|
||||
});
|
||||
await SaveStateAsync(dialogKey, _editSiteinfoSettings);
|
||||
}
|
||||
|
||||
public async Task OpenContentEditor(MenuItem currentMenuItem)
|
||||
{
|
||||
string dialogKey = $"HtmlContentEditorSettings_{siteid}"; // can be anything unique
|
||||
// <HtmlContentEditor Html="@CurrentHtml" HtmlChanged="@OnHtmlUpdated" />
|
||||
var settings = await LoadStateAsync(dialogKey)
|
||||
?? new EditSiteInfoDialogSettings
|
||||
|
||||
{
|
||||
Width = "600px",
|
||||
Height = "600px",
|
||||
Left = "10%",
|
||||
Top = "10%"
|
||||
};
|
||||
|
||||
await DialogService.OpenAsync<HtmlContentEditor>($"Edit the content of this page",
|
||||
new Dictionary<string, object>() {
|
||||
{ "Html", currentMenuItem.StoredHtml },
|
||||
{ "HtmlChanged", new Func<string, Task>(OnHtmlUpdated) }
|
||||
},
|
||||
new DialogOptions()
|
||||
{
|
||||
Resizable = true,
|
||||
Draggable = true,
|
||||
Resize = GetResizeHandler(dialogKey),
|
||||
Drag = GetDragHandler(dialogKey),
|
||||
Width = settings.Width,
|
||||
Height = settings.Height,
|
||||
Left = settings.Left,
|
||||
Top = settings.Top,
|
||||
CssClass = "draggable-popup-dialog",
|
||||
WrapperCssClass = "draggable-popup-dialog-wrapper"
|
||||
});
|
||||
|
||||
await SaveStateAsync(dialogKey, _editSiteinfoSettings);
|
||||
}
|
||||
|
||||
private async Task OnHtmlUpdated(string newHtml)
|
||||
{
|
||||
HtmlContent.Clear();
|
||||
HtmlContent.Append(newHtml);
|
||||
// You can trigger embedding regeneration, saving, etc.
|
||||
currentMenuItem.StoredHtml = newHtml;
|
||||
|
||||
var result = await _contentEditorService.UpdateMenuItemAsync(currentMenuItem);
|
||||
var message = NotificationHelper.CreateNotificationMessage("Content saved", 2, "Success", "Static content saved successfully");
|
||||
ShowNotification(message);
|
||||
}
|
||||
|
||||
private Action<System.Drawing.Point> GetDragHandler(string key)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,15 +14,20 @@
|
|||
|
||||
<h1>Your Sites</h1>
|
||||
<div class="row g-0">
|
||||
<AnimateOnRender CssClass="animate__animated animate__backInUp">
|
||||
<RadzenPanel Collapsed="true" AllowCollapse="true" class="rz-my-5 rz-mx-auto" Style="width: 100%"
|
||||
Expand=@(() => Change("Panel expanded")) Collapse=@(() => Change("Panel collapsed"))>
|
||||
|
||||
<HeaderTemplate>
|
||||
<RadzenText TextStyle="TextStyle.H6" class="rz-display-flex rz-align-items-center rz-m-0">
|
||||
<RadzenIcon Icon="account_box" class="rz-me-1" /><b>New site</b>
|
||||
<RadzenIcon Icon="note_add" class="rz-me-1" /><b>New site</b>
|
||||
</RadzenText>
|
||||
</HeaderTemplate>
|
||||
<ChildContent>
|
||||
<RadzenCard class="rz-mt-4">
|
||||
|
||||
<CreateSiteWizard OnDescriptionFinalized="HandleDescriptionGenerated" UserId="@userId" />
|
||||
|
||||
@* <RadzenCard class="rz-mt-4">
|
||||
<EditForm Model="newSite" OnValidSubmit="HandleValidSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
|
@ -47,12 +52,16 @@
|
|||
<label for="siteName" class="form-label">Site description</label>
|
||||
<InputText id="siteDescription" placeholder="A description so AI will know what is this site about" class="form-control" @bind-Value="newSite.SiteDescription" />
|
||||
</div>
|
||||
<div class="col-12 col-md-12 mb-3">
|
||||
<label for="siteName" class="form-label">Site language</label>
|
||||
<InputText id="siteLanguage" placeholder="The default language of the website" class="form-control" @bind-Value="newSite.DefaultLanguage" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Create Site</button>
|
||||
</EditForm>
|
||||
|
||||
</RadzenCard>
|
||||
</RadzenCard>*@
|
||||
|
||||
|
||||
</ChildContent>
|
||||
|
|
@ -70,11 +79,16 @@
|
|||
</RadzenText>
|
||||
</HeaderTemplate>
|
||||
<ChildContent>
|
||||
@{
|
||||
if(siteInfoList.Count() > 0)
|
||||
{
|
||||
<AnimateOnRender CssClass="animate__animated animate__backInUp">
|
||||
<RadzenCard class="rz-mt-4">
|
||||
<RadzenDataList PageSize="3" WrapItems="true" AllowPaging="true"
|
||||
<RadzenDataList Density="Density.Compact" PageSize="4" WrapItems="true"
|
||||
AllowPaging="false"
|
||||
Data="@siteInfoList" TItem="SiteInfo">
|
||||
<Template Context="site">
|
||||
<RadzenCard Style="width: 250px; background-color: darkgrey">
|
||||
<RadzenCard class="admin-rz-card" Style="width: 250px;">
|
||||
<RadzenRow JustifyContent="@JustifyContent.SpaceBetween">
|
||||
<RadzenColumn Size="4" class="rz-text-truncate">
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Light" Text=@($"{site.Id}") class="rz-me-1" />
|
||||
|
|
@ -95,9 +109,10 @@
|
|||
</RadzenStack>
|
||||
</RadzenStack>
|
||||
<RadzenStack Orientation="@Orientation.Horizontal" Gap="10px" Reverse="false" JustifyContent="@JustifyContent.Center" AlignItems="@AlignItems.Center" Wrap="@FlexWrap.Wrap" Style="height: fit-content">
|
||||
<a href="/site-info/@site.Id" class="btn btn-secondary">Edit</a>
|
||||
<InputText @bind-Value="collectionName" class="form-control" style="width: 100%;" placeholder="Site name" />
|
||||
<a style="font-size: 14px;" @onclick="()=>Migrate(site.Id, collectionName)" class="btn btn-secondary">Migrate</a>
|
||||
@* <a style="font-size: 14px;" href="/generate-content/@site.Id" class="btn btn-secondary">Manage content</a> *@
|
||||
<a style="font-size: 14px;" href="/generate-content/@site.Id" class="btn btn-secondary">Manage content</a>
|
||||
<a style="font-size: 14px;" @onclick="()=>Preview(site)" class="btn btn-secondary"> Preview</a>
|
||||
|
||||
</RadzenStack>
|
||||
|
|
@ -105,6 +120,17 @@
|
|||
</Template>
|
||||
</RadzenDataList>
|
||||
</RadzenCard>
|
||||
</AnimateOnRender>
|
||||
}
|
||||
else
|
||||
{
|
||||
<RadzenCard class="rz-mt-4">
|
||||
<p>Let1s create your first website!</p>
|
||||
</RadzenCard>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</ChildContent>
|
||||
<SummaryTemplate>
|
||||
<RadzenCard class="rz-mt-4">
|
||||
|
|
@ -145,6 +171,7 @@
|
|||
{
|
||||
<p>No sites created yet.</p>
|
||||
}
|
||||
</AnimateOnRender>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -157,9 +184,28 @@
|
|||
private string? userName;
|
||||
private AuthenticationState? authState;
|
||||
int position = 1;
|
||||
public string SessionId;
|
||||
//TEMPORARY
|
||||
private string collectionName = "seemgen-collection";
|
||||
|
||||
private string FinalSiteDescription;
|
||||
|
||||
private async Task HandleDescriptionGenerated(string[] desc)
|
||||
{
|
||||
newSite.UserId = userId;
|
||||
FinalSiteDescription = desc[1];
|
||||
newSite.SiteDescription = desc[1];
|
||||
newSite.SiteName = desc[0];
|
||||
newSite.Entity = desc[2];
|
||||
newSite.Persona = desc[3];
|
||||
newSite.DefaultLanguage = desc[4];
|
||||
newSite.BrandLogoUrl = desc[5];
|
||||
newSite.FacebookUrl = desc[6];
|
||||
await HandleValidSubmit();
|
||||
// You can now store it in SiteInfo.SiteDescription
|
||||
// or prefill the full form with the rest of the properties
|
||||
}
|
||||
|
||||
void Change(string text)
|
||||
{
|
||||
Console.Write($"{text}");
|
||||
|
|
@ -201,6 +247,8 @@
|
|||
userName = CustomAuthProvider.GetUserName();
|
||||
}
|
||||
siteInfoList = await _contentEditorService.GetUserSitesAsync(userId!);
|
||||
SessionId = SiteInfoService.SessionId;
|
||||
// SiteInfoService.SessionId = SessionId;
|
||||
}
|
||||
|
||||
private async Task HandleValidSubmit()
|
||||
|
|
@ -212,6 +260,11 @@
|
|||
var result = await _contentEditorService.AddSiteInfoAsync(newSite);
|
||||
siteInfoList = await _contentEditorService.GetUserSitesAsync(userId!);
|
||||
newSite = new(); // Reset the form
|
||||
if(result != null)
|
||||
{
|
||||
|
||||
NavigationManager.NavigateTo($"/generate-content/{result.Id}");
|
||||
}
|
||||
}
|
||||
public async Task<string> GenerateSubdomainAsync(string siteName)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
@inject IJSRuntime JS
|
||||
|
||||
<div @ref="ElementRef" class="@CssClass">
|
||||
@ChildContent
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public string CssClass { get; set; } = "animate__animated animate__fadeIn";
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
private ElementReference ElementRef;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (ElementRef.Context is not null)
|
||||
{
|
||||
await Task.Delay(50); // wait for DOM to settle
|
||||
await JS.InvokeVoidAsync("seemgenAnimationHelper.restartAnimation", ElementRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
<button class="pointer bg-transparent border-0" style="width:100%; text-align: left; " @onclick="CreateNewItem">
|
||||
<div class="px-3 py-2 reference-button bg-panel-gradient-highlight pointer">
|
||||
<div class="text-content">
|
||||
<strong>Add new</strong>
|
||||
<strong>Add new content</strong>
|
||||
<br />
|
||||
<small class="text-muted">Add a new content to this group</small>
|
||||
</div>
|
||||
|
|
@ -45,12 +45,12 @@
|
|||
<li class="list-group-item">
|
||||
|
||||
<div class="p-2 reference-button">
|
||||
<div class="text-content">
|
||||
<div class="text-content text-start">
|
||||
<strong>@item.Title</strong>
|
||||
<br />
|
||||
<small class="text-muted">@item.Language - @item.Tags</small>
|
||||
</div>
|
||||
<div class="icon-buttons">
|
||||
<div class="icon-buttons text-end">
|
||||
<button class="btn btn-sm btn-outline-primary" @onclick="() => EditItem(item.Id)">Edit</button>
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteItem(item.Id)">Delete</button>
|
||||
@* <div class="icon-circle">V</div> *@
|
||||
|
|
|
|||
|
|
@ -0,0 +1,501 @@
|
|||
@using System.Net.Http.Headers
|
||||
@using System.Text.Json
|
||||
@using BLAIzor.Interfaces
|
||||
@using BLAIzor.Services
|
||||
@using SixLabors.ImageSharp
|
||||
@using SixLabors.ImageSharp.Processing
|
||||
@inject IJSRuntime JS
|
||||
@inject IHttpClientFactory HttpClientFactory
|
||||
@inject WhisperTranscriptionService WhisperService
|
||||
@inject ReplicateService ReplicateService
|
||||
|
||||
|
||||
<div class="card p-3 shadow-sm bg-panel-gradient text-white" style="max-width:100%; width: 600px; 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-y: 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>
|
||||
|
||||
@if (CurrentStep == 5)
|
||||
{
|
||||
<InputSelect class="form-select" @bind-Value="Steps[CurrentStep].Answer">
|
||||
<option value="">-- Select Language --</option>
|
||||
<option>English</option>
|
||||
<option>German</option>
|
||||
<option>Hungarian</option>
|
||||
</InputSelect>
|
||||
|
||||
}
|
||||
else if (CurrentStep == 7)
|
||||
{
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Gender</label>
|
||||
<InputSelect class="form-select" @bind-Value="targetGender">
|
||||
<option value="">Any</option>
|
||||
<option>Male</option>
|
||||
<option>Female</option>
|
||||
<option>Non-binary</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Age Group</label>
|
||||
<InputSelect class="form-select" @bind-Value="targetAge">
|
||||
<option value="">All ages</option>
|
||||
<option>Under 18</option>
|
||||
<option>18–25</option>
|
||||
<option>26–40</option>
|
||||
<option>41–60</option>
|
||||
<option>60+</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Location</label>
|
||||
<InputText class="form-control" @bind-Value="targetLocation" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">Interests</label>
|
||||
<InputText class="form-control" @bind-Value="targetInterests" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
else if (CurrentStep == 8) // Last step: Facebook
|
||||
{
|
||||
<InputText class="form-control" @bind-Value="facebookPageUrl" placeholder="https://www.facebook.com/yourpage" />
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<InputText class="form-control" @bind-Value="Steps[CurrentStep].Answer" />
|
||||
|
||||
}
|
||||
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-warning me-2" @onclick="StartRecording">🎙️ Speak</button>
|
||||
<button class="btn btn-sm btn-danger" @onclick="StopRecording">⏹️ Stop</button>
|
||||
</div>
|
||||
</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)generatedDescriptionToShow)
|
||||
<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: 500px;" />
|
||||
</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 {
|
||||
[Parameter] public EventCallback<string[]> OnDescriptionFinalized { get; set; }
|
||||
[Parameter] public string UserId { get; set; }
|
||||
|
||||
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 = 5;
|
||||
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 is the main purpose of the site?", "What is your goal with the website. Promote the brand, or acquire new customers? This is needed for SEO, and content generation."),
|
||||
new("What kind of feel or atmosphere should the site have? (e.g. friendly, professional, mysterious, playful)", "How should your website communicate?"),
|
||||
new("Where is it located (country or city)?", "This is for content generation and SEO purposes."),
|
||||
new("What is your preferred language?", "The default language of your website. As it is AI driven, AI can answer in almost any language to questions"),
|
||||
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"),
|
||||
new("Who is your target audience?", "This is needed to generate have a better overall understanding during content generation."),
|
||||
new("Do you already have a Facebook page for your business?", "Provide the URL of your Facebook page if you have one. We'll use it to generate content more accurately.")
|
||||
};
|
||||
|
||||
private string targetGender = "";
|
||||
private string targetAge = "";
|
||||
private string targetLocation = "";
|
||||
private string targetInterests = "";
|
||||
|
||||
private string generatedDescription = "";
|
||||
private string generatedDescriptionToShow = "";
|
||||
private string siteName = "";
|
||||
private string entity = "";
|
||||
private string persona = "";
|
||||
private string defaultLanguage = "";
|
||||
private string facebookPageUrl = "";
|
||||
|
||||
|
||||
|
||||
// 3. Trigger Brightdata scraping after generating site description
|
||||
private async Task GenerateSiteDescription()
|
||||
{
|
||||
var name = Steps[0].Answer;
|
||||
var siteEntity = Steps[1].Answer;
|
||||
var purpose = Steps[2].Answer;
|
||||
var feel = Steps[3].Answer;
|
||||
var location = Steps[4].Answer;
|
||||
var language = Steps[5].Answer;
|
||||
var color = Steps[6].Answer;
|
||||
var facebook = Steps[8].Answer;
|
||||
|
||||
var audience = $"people who are {(string.IsNullOrEmpty(targetGender) ? "all genders" : targetGender.ToLower())} aged {(string.IsNullOrEmpty(targetAge) ? "any age" : targetAge)}, located in {targetLocation}, interested in {targetInterests.ToLower()}";
|
||||
|
||||
siteName = name;
|
||||
entity = siteEntity;
|
||||
persona = feel;
|
||||
defaultLanguage = language;
|
||||
|
||||
generatedDescription = $@"
|
||||
{name} is a {entity.ToLower()} based in {location}, created to {purpose.ToLower()}.
|
||||
The site aims to offer a {feel.ToLower()} experience for its visitors, primarily targeting {audience}.
|
||||
The preferred language is {language}, and the design should reflect {color.ToLower()} tones for visual consistency.
|
||||
";
|
||||
|
||||
generatedDescriptionToShow = $@"
|
||||
<p><strong>{name}</strong> is a {entity.ToLower()} based in {location}, created to {purpose.ToLower()}.</p>
|
||||
<p>The site aims to offer a {feel.ToLower()} experience for its visitors, primarily targeting {audience}.</p>
|
||||
<p>The preferred language is {language}, and the design should reflect {color.ToLower()} tones for visual consistency.</p>
|
||||
";
|
||||
|
||||
// --- New: Scrape Facebook posts if provided ---
|
||||
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
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 purpose = Steps[2].Answer;
|
||||
// var feel = Steps[3].Answer;
|
||||
// var location = Steps[4].Answer;
|
||||
// var language = Steps[5].Answer;
|
||||
// var color = Steps[6].Answer;
|
||||
|
||||
// var audience = $"people who are {(string.IsNullOrEmpty(targetGender) ? "all genders" : targetGender.ToLower())} aged {(string.IsNullOrEmpty(targetAge) ? "any age" : targetAge)}, located in {targetLocation}, interested in {targetInterests.ToLower()}";
|
||||
|
||||
// siteName = name;
|
||||
// entity = siteEntity;
|
||||
// persona = feel;
|
||||
// defaultLanguage = language;
|
||||
|
||||
// generatedDescription = $@"
|
||||
// {name} is a {entity.ToLower()} based in {location}, created to {purpose.ToLower()}.
|
||||
// The site aims to offer a {feel.ToLower()} experience for its visitors, primarily targeting {audience}.
|
||||
// The preferred language is {language}, and the design should reflect {color.ToLower()} tones for visual consistency.
|
||||
// ";
|
||||
|
||||
// generatedDescriptionToShow = $@"
|
||||
// <p><strong>{name}</strong> is a {entity.ToLower()} based in {location}, created to {purpose.ToLower()}.</p>
|
||||
// <p>The site aims to offer a {feel.ToLower()} experience for its visitors, primarily targeting {audience}.</p>
|
||||
// <p>The preferred language is {language}, and the design should reflect {color.ToLower()} tones for visual consistency.</p>
|
||||
// ";
|
||||
// }
|
||||
|
||||
private void ProceedToLogoStep()
|
||||
{
|
||||
ShowLogoStep = true;
|
||||
}
|
||||
|
||||
// private async Task GenerateLogo()
|
||||
// {
|
||||
// var logoPrompt = $"Logo for a {entity} named {siteName}, with a {persona} tone, using {Steps[6].Answer} colors.";
|
||||
// GeneratedLogoUrl = await ReplicateService.GenerateImageAsync(logoPrompt);
|
||||
// logoUrl = GeneratedLogoUrl;
|
||||
// }
|
||||
|
||||
private async Task GenerateLogo()
|
||||
{
|
||||
if (logoGenerationCount >= MaxLogoGenerations)
|
||||
return;
|
||||
|
||||
IsGeneratingLogo = true;
|
||||
|
||||
var logoPrompt = $"Logo for a {entity} named {siteName}, with a {persona} tone, using {Steps[6].Answer} 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[7];
|
||||
siteInfo[0] = siteName;
|
||||
siteInfo[1] = generatedDescription;
|
||||
siteInfo[2] = entity;
|
||||
siteInfo[3] = persona;
|
||||
siteInfo[4] = defaultLanguage;
|
||||
siteInfo[5] = logoUrl;
|
||||
siteInfo[6] = facebookPageUrl;
|
||||
await OnDescriptionFinalized.InvokeAsync(siteInfo);
|
||||
}
|
||||
|
||||
private async Task StartRecording() => await JS.InvokeVoidAsync("startRecording");
|
||||
private async Task StopRecording() => await JS.InvokeVoidAsync("stopRecording");
|
||||
|
||||
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 CreateSiteWizard? _instance;
|
||||
|
||||
[JSInvokable]
|
||||
public static async Task SendAudioToServer(List<byte> audioData)
|
||||
{
|
||||
if (_instance is null) return;
|
||||
|
||||
var result = await _instance.WhisperService.TranscribeAsync(audioData.ToArray());
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
_instance.Steps[_instance.CurrentStep].Answer = result;
|
||||
_instance.StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
@* <RadzenButton Text="Edit" Click="() => ToggleGroupEdit()"></RadzenButton>
|
||||
<RadzenButton ButtonStyle=@ButtonStyle.Warning Text="Edit" Click="() => OnForceReChunkClicked(Group)"></RadzenButton> *@
|
||||
<button class="btn" type="button" @onclick="() => ToggleGroupEdit()">Edit</button>
|
||||
<button class="btn btn-warning" type="button" @onclick="() => OnForceReChunkClicked(Group)">ReChunk</button>
|
||||
@* <button class="btn btn-warning" type="button" @onclick="() => OnForceReChunkClicked(Group)">ReChunk</button> *@
|
||||
<button class="btn btn-danger" type="button" @onclick="() => DeleteGroup(Group)">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
@using BLAIzor.Models
|
||||
@using BLAIzor.Services
|
||||
@inject ContentEditorService contentEditorService
|
||||
@inject CacheService cache
|
||||
|
||||
@if (isLoading)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -215,7 +215,10 @@ else if (!string.IsNullOrEmpty(ErrorMessage))
|
|||
{
|
||||
var prompt2 = $"Analyze the following text and based on the sections suggest a list of menu items for a website. Do not attach any explanation. Text:\n\n`{document}`.";
|
||||
var response2 = await ContentEditorAIService.GetMenuSuggestionsAsync(SessionId, prompt2);
|
||||
ExtractedMenuItems = response2.Select(name => new MenuItemModel(name)).ToList();
|
||||
var valami = response2.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim('-').Trim())
|
||||
.ToList() ?? new List<string>();
|
||||
ExtractedMenuItems = valami.Select(name => new MenuItemModel(name)).ToList();
|
||||
}
|
||||
// Send the content to ChatGPT
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,7 +167,10 @@ else if (!string.IsNullOrEmpty(errorMessage))
|
|||
{
|
||||
var prompt = $"Suggest a list of menu items for a website about: {subject}. Please do not attach any explanation.";
|
||||
var response = await ContentEditorAIService.GetMenuSuggestionsAsync(SessionId, prompt);
|
||||
extractedMenuItems = response.Select(name => new MenuItemModel(name)).ToList();
|
||||
var valami = response.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim('-').Trim())
|
||||
.ToList() ?? new List<string>();
|
||||
extractedMenuItems = valami.Select(name => new MenuItemModel(name)).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,195 @@
|
|||
@* @page "/site/{SiteId:int}/generate" *@
|
||||
@using BLAIzor.Models
|
||||
@using BLAIzor.Services
|
||||
@inject HttpClient Http
|
||||
@inject ContentEditorAIService contentEditorAIService
|
||||
@inject ContentEditorService contentEditorService
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
@if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
<p class="text-red-600">Error: @errorMessage</p>
|
||||
}
|
||||
else if (isLoading || GeneratedItems.Any())
|
||||
{
|
||||
|
||||
<RadzenLayout Style="grid-template-areas: 'rz-sidebar rz-header' 'rz-sidebar rz-body'">
|
||||
<RadzenHeader>
|
||||
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0">
|
||||
<RadzenSidebarToggle Click="@(() => sidebarExpanded = !sidebarExpanded)" />
|
||||
<RadzenLabel Text=@(totalItemsToGenerate.ToString() + " page(s)") />
|
||||
</RadzenStack>
|
||||
</RadzenHeader>
|
||||
<RadzenSidebar @bind-Expanded="@sidebarExpanded">
|
||||
<RadzenPanelMenu>
|
||||
@foreach (var item in GeneratedItems)
|
||||
{
|
||||
<RadzenPanelMenuItem Text="@item.Title" Icon="account_box" Click="@(() => SelectContent(item.Title))" />
|
||||
}
|
||||
|
||||
</RadzenPanelMenu>
|
||||
<button class="btn btn-primary mt-4" @onclick="SaveContent">Save All</button>
|
||||
</RadzenSidebar>
|
||||
<RadzenBody Style="padding: 0px !important">
|
||||
@if (isLoading)
|
||||
{
|
||||
<p class="text-muted">Generating... please wait.</p>
|
||||
//progress bar
|
||||
<RadzenProgressBar Value="@generationProgress" ShowValue="true" Style="width: 100%;" />
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SelectedItem != null)
|
||||
{
|
||||
<textarea class="w-100 p-2 rounded text-white bg-panel-gradient" style="min-height:500px" @bind="SelectedItem.Content"></textarea>
|
||||
}
|
||||
}
|
||||
</RadzenBody>
|
||||
</RadzenLayout>
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-success" @onclick="GeneratePageList">Start Generation</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@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 List<ContentItem> GeneratedItems = new();
|
||||
private ContentGroup defaultGroup;
|
||||
private ContentItem SelectedItem;
|
||||
|
||||
private double generationProgress = 0.0;
|
||||
private int totalItemsToGenerate = 0;
|
||||
|
||||
|
||||
bool sidebarExpanded = true;
|
||||
|
||||
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
|
||||
};
|
||||
// Save ContentGroup to DB (if not already exists)
|
||||
var response = await contentEditorService.CreateContentGroupAsync(defaultGroup);
|
||||
defaultGroup = response;
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultGroup = existingGroup.FirstOrDefault(x => x.Name == "Pages");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void SelectContent(string title)
|
||||
{
|
||||
SelectedItem = GeneratedItems.Where(x => x.Title == title).FirstOrDefault();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task GeneratePageList()
|
||||
{
|
||||
isLoading = true;
|
||||
errorMessage = string.Empty;
|
||||
GeneratedItems.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
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}";
|
||||
|
||||
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)
|
||||
{
|
||||
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.";
|
||||
|
||||
var content = await contentEditorAIService.GetGeneratedContentAsync(SessionId, itemPrompt);
|
||||
|
||||
GeneratedItems.Add(new ContentItem
|
||||
{
|
||||
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
|
||||
});
|
||||
generationProgress += 100.0 / totalItemsToGenerate;
|
||||
// Refresh UI after each item is generated
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
SelectedItem = GeneratedItems.FirstOrDefault();
|
||||
isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task SaveContent()
|
||||
{
|
||||
isLoading = true;
|
||||
errorMessage = string.Empty;
|
||||
try
|
||||
{
|
||||
foreach (var item in GeneratedItems)
|
||||
{
|
||||
//await Http.PostAsJsonAsync("/api/contentitem", item);
|
||||
await contentEditorService.CreateContentItemAsync(item, site.VectorCollectionName);
|
||||
}
|
||||
|
||||
Navigation.NavigateTo($"/site/{SiteId}/overview");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,511 @@
|
|||
@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
|
||||
|
||||
|
||||
<div class="container-fluid flex">
|
||||
<!-- Sidebar -->
|
||||
@if (GeneratedItems.Any())
|
||||
{
|
||||
<aside class="w-64 border-r p-4 flex flex-col">
|
||||
<div class="text-lg font-semibold mb-4">Pages (@totalItemsToGenerate)</div>
|
||||
|
||||
<nav class="flex-1 overflow-auto space-y-2">
|
||||
@foreach (var item in GeneratedItems)
|
||||
{
|
||||
<button class="btn @(item == SelectedItem ? "btn-primary font-semibold" : "bg-panel-gradient")"
|
||||
@onclick="@(() => SelectContent(item.Title))">
|
||||
@item.Title
|
||||
</button>
|
||||
}
|
||||
</nav>
|
||||
@if (isLoading || isGeneratingImages)
|
||||
{
|
||||
<button disabled class="btn py-2 px-4 rounded"
|
||||
@onclick="SaveContent">
|
||||
💾 Please wait...
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn py-2 px-4 rounded"
|
||||
@onclick="SaveContent">
|
||||
💾 Save All
|
||||
</button>
|
||||
}
|
||||
</aside>
|
||||
}
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="row">
|
||||
@if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
<div class="text-red-600 font-medium mb-4">⚠️ @errorMessage</div>
|
||||
}
|
||||
|
||||
@if (isLoading)
|
||||
{
|
||||
<div class="text-gray-700 mb-4" style="width:fit-content !important; margin: 0 auto !important">
|
||||
@if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
<p>@message</p>
|
||||
}
|
||||
|
||||
@if (GeneratedItems.Any())
|
||||
{
|
||||
<RadzenProgressBarCircular Style="margin: 0 auto !important" ProgressBarStyle="ProgressBarStyle.Primary" Value="@(Math.Round(generationProgress, 0))" ShowValue="true" Size="ProgressBarCircularSize.Large" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<RadzenProgressBarCircular Style="margin: 0 auto !important" ProgressBarStyle="ProgressBarStyle.Light" Value="100" ShowValue="false" Mode="ProgressBarMode.Indeterminate" Size="ProgressBarCircularSize.Large" />
|
||||
}
|
||||
</div>
|
||||
@* <RadzenProgressBar Value="@generationProgress" ShowValue="true" Style="width: 100%;" /> *@
|
||||
}
|
||||
else if (SelectedItem != null)
|
||||
{
|
||||
|
||||
@if (isGeneratingImages)
|
||||
{
|
||||
<div class="col-12">
|
||||
<div class="text-gray-700 mb-4" style="width:fit-content !important; margin: 0 auto !important">
|
||||
<p>Generating pages... please wait.</p>
|
||||
|
||||
@* <RadzenProgressBarCircular Style="margin: 0 auto !important" ProgressBarStyle="ProgressBarStyle.Success" Value="@(Math.Round(generationProgress, 0))" ShowValue="true" Size="ProgressBarCircularSize.Large" /> *@
|
||||
<RadzenProgressBar Value="@(Math.Round(generationProgress, 0))" ShowValue="true" Style="width: 100%;" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="col-12">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Content editor -->
|
||||
<div class="col-sm-12 col-md-7">
|
||||
<h2 class="text-xl font-bold mb-2">@SelectedItem.Title</h2>
|
||||
|
||||
<textarea class="p-4 rounded text-white bg-panel-gradient" style="height:500px; width: 100%; border: 0px;"
|
||||
@bind="SelectedItem.Content"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Photo slots -->
|
||||
<div class="col-sm-12 col-md-5">
|
||||
<h2 class="text-xl font-bold mb-2">Suggested Photos</h2>
|
||||
|
||||
<div class="row" style="height: 500px; overflow: scroll">
|
||||
|
||||
|
||||
@if (SelectedItem.PhotoSlots.Any())
|
||||
{
|
||||
@foreach (var slot in SelectedItem.PhotoSlots)
|
||||
{
|
||||
<div class="col-6 rounded p-1">
|
||||
<div class="bg-panel-gradient text-white m-1">
|
||||
@* <p class="text-sm mb-2">@slot.Description</p> *@
|
||||
@if (!string.IsNullOrEmpty(slot.ImageUrl))
|
||||
{
|
||||
<div style="width: 100%; position: relative;">
|
||||
<p>Photo_slot_@(SelectedItem.PhotoSlots.IndexOf(slot) + 1)</p>
|
||||
<div style="position: absolute; height: 100px; top: 0px; right: 0px;">
|
||||
<button class="btn btn-sm btn-primary flex-1"
|
||||
@onclick="() => ReplaceUrls(slot)">
|
||||
✓ Approve
|
||||
</button>
|
||||
</div>
|
||||
<img src="@slot.ImageUrl" alt="@slot.Description" class="img-fluid" />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="width: 100%; height: 270px;">
|
||||
<RadzenProgressBarCircular Style="width:100%; margin: 0 auto;" ProgressBarStyle="ProgressBarStyle.Light" ShowValue="true" Mode="ProgressBarMode.Indeterminate" Size="ProgressBarCircularSize.Large">
|
||||
<Template>Wait</Template>
|
||||
</RadzenProgressBarCircular>
|
||||
</div>
|
||||
}
|
||||
<div class="flex gap-2">
|
||||
|
||||
<button class="btn btn-primary flex-1"
|
||||
@onclick="() => RegeneratePhoto(slot, false)">
|
||||
🎨 Regenerate
|
||||
</button>
|
||||
<button class="btn flex-1"
|
||||
@onclick="() => SelectPhoto(slot)">
|
||||
📂 Select
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-gray-400 text-sm">No photo suggestions yet.</p>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
else if (!GeneratedItems.Any())
|
||||
{
|
||||
<div class="flex flex-col items-center justify-center h-full text-center text-gray-500">
|
||||
<p class="mb-4">Click the button below to generate your site's pages based on its description.</p>
|
||||
<button class="btn text-white py-2 px-6 rounded"
|
||||
@onclick="GeneratePageList">
|
||||
🚀 Start Generation
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@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<GeneratedContentItem> 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<string?> 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";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
@using Microsoft.AspNetCore.Components.Forms
|
||||
|
||||
|
||||
|
||||
@* <InputTextArea @bind-Value="RawHtml" class="form-control" rows="10" /> *@
|
||||
<RadzenHtmlEditor @bind-Value=@RawHtml style="height: 70vh; color:#000; background-color: transparent !important;" Input=@OnInput Change=@OnChange Paste=@OnPaste UploadComplete=@OnUploadComplete Execute=@OnExecute UploadUrl="upload/image">
|
||||
<RadzenHtmlEditorUndo />
|
||||
<RadzenHtmlEditorRedo />
|
||||
<RadzenHtmlEditorSource />
|
||||
</RadzenHtmlEditor>
|
||||
|
||||
<div class="mt-3 d-flex justify-content-end gap-2">
|
||||
<button class="btn btn-primary" @onclick="OnSaveClicked">Save</button>
|
||||
<button class="btn btn-secondary" @onclick="OnCancelClicked">Cancel</button>
|
||||
</div>
|
||||
|
||||
@if (ShowPreview)
|
||||
{
|
||||
<div class="mt-4">
|
||||
<h6>Live Preview:</h6>
|
||||
<div class="border p-3 bg-light" style="min-height: 100px;" @key="RawHtml">
|
||||
@((MarkupString)RawHtml)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string Html { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public Func<string, Task> HtmlChanged { get; set; }
|
||||
|
||||
private string RawHtml = string.Empty;
|
||||
private bool ShowPreview = false;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
RawHtml = Html;
|
||||
}
|
||||
|
||||
private async Task OnSaveClicked()
|
||||
{
|
||||
// await HtmlChanged.InvokeAsync(RawHtml);
|
||||
if (HtmlChanged != null)
|
||||
await HtmlChanged.Invoke(RawHtml);
|
||||
}
|
||||
|
||||
private void OnCancelClicked()
|
||||
{
|
||||
RawHtml = Html;
|
||||
}
|
||||
|
||||
async Task OnPaste(HtmlEditorPasteEventArgs args)
|
||||
{
|
||||
// console.Log($"Paste: {args.Html}");
|
||||
// MenuItem.Content = args.Html;
|
||||
// await OnContentUpdated.InvokeAsync(MenuItem);
|
||||
}
|
||||
|
||||
async Task OnChange(string html)
|
||||
{
|
||||
// console.Log($"Change: {html}");
|
||||
// MenuItem.Content = html;
|
||||
// await OnContentUpdated.InvokeAsync(MenuItem);
|
||||
}
|
||||
|
||||
async Task OnInput(string html)
|
||||
{
|
||||
// console.Log($"Input: {html}");
|
||||
// MenuItem.Content = html;
|
||||
// await OnContentUpdated.InvokeAsync(MenuItem);
|
||||
}
|
||||
|
||||
void OnExecute(HtmlEditorExecuteEventArgs args)
|
||||
{
|
||||
//console.Log($"Execute: {args.CommandName}");
|
||||
}
|
||||
|
||||
void OnUploadComplete(UploadCompleteEventArgs args)
|
||||
{
|
||||
//console.Log($"Upload complete: {args.RawResponse}");
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ else
|
|||
<button class="pointer bg-transparent border-0 p-0" style="width:100%; text-align: left;" @onclick="AddNewGroup">
|
||||
<div class="mb-2 p-3 reference-button bg-panel-gradient-highlight pointer">
|
||||
<div class="text-content">
|
||||
<strong>Add new</strong>
|
||||
<strong>Add new group</strong>
|
||||
<br />
|
||||
<small class="text-muted">Add a new content group like "Blog", News, "Manuals" etc...</small>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ else
|
|||
[Parameter]
|
||||
public int SiteId { get; set; }
|
||||
|
||||
[Parameter] public Func<ContentGroup, Task> OnSiteInfoSaveClicked { get; set; }
|
||||
[Parameter] public Func<SiteInfo, Task> OnSiteInfoSaveClicked { get; set; }
|
||||
[Parameter] public Func<string, Task> OnSiteNameChanged { get; set; }
|
||||
[Parameter] public Func<string, int, Task> OnCancelItemClicked { get; set; }
|
||||
|
||||
|
|
@ -116,6 +116,8 @@ else
|
|||
private async Task SaveSiteInfo()
|
||||
{
|
||||
await _contentEditorService.UpdateSiteInfoAsync(siteInfo);
|
||||
if (OnSiteInfoSaveClicked != null)
|
||||
await OnSiteInfoSaveClicked.Invoke(siteInfo);
|
||||
}
|
||||
|
||||
private async void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,249 @@
|
|||
@using BLAIzor.Models
|
||||
@using BLAIzor.Services
|
||||
@using Newtonsoft.Json
|
||||
@using BLAIzor.Components.Partials
|
||||
@using System.Collections.ObjectModel
|
||||
@inject ContentEditorService ContentEditorService
|
||||
@inject ContentEditorAIService ContentEditorAIService
|
||||
@inject HtmlSnippetProcessor HtmlSnippetProcessor
|
||||
@inject QDrantService QDrantService
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
@* <div>
|
||||
<label for="subject">Enter the subject of your website:</label>
|
||||
<input id="subject" @bind="subject" class="form-control" placeholder="e.g., Web Design Company" />
|
||||
<button class="btn btn-primary mt-2" @onclick="GenerateMenuItems">Suggest New by AI</button>
|
||||
</div> *@
|
||||
|
||||
@if (isLoading)
|
||||
{
|
||||
<p>Loading suggestions...</p>
|
||||
}
|
||||
else if (extractedMenuItems.Any())
|
||||
{
|
||||
<h4>Menu Items Editor</h4>
|
||||
<div class="row">
|
||||
<div class="rz-p-sm-12">
|
||||
<RadzenStack Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.Right">
|
||||
<RadzenButton Click="ToggleReorder">
|
||||
@(allowReorder ? "Done Reordering" : "Reorder")
|
||||
</RadzenButton>
|
||||
</RadzenStack>
|
||||
|
||||
@if (allowReorder)
|
||||
{
|
||||
<RadzenDataGrid @ref="dataGrid" TItem="MenuItem" RowSelect=@OnRowSelect AllowFiltering="true" AllowColumnResize="true" AllowSorting="true" PageSize="20" AllowPaging="true"
|
||||
Data="@menuItems" ColumnWidth="300px" SelectionMode="DataGridSelectionMode.Single" RowRender="@RowRender">
|
||||
<Columns>
|
||||
<RadzenDataGridColumn Property="@nameof(MenuItem.SortOrder)" Title="Sort Order" Width="40px" />
|
||||
<RadzenDataGridColumn Property="@nameof(MenuItem.Name)" Title="Menu Name" Frozen="true" Width="200px" />
|
||||
<RadzenDataGridColumn Property="@nameof(MenuItem.ShowInMainMenu)" Title="Visible" Width="40px" />
|
||||
</Columns>
|
||||
</RadzenDataGrid>
|
||||
}
|
||||
else
|
||||
{
|
||||
<RadzenAccordion class="admin-accordion" Multiple="false">
|
||||
<Items>
|
||||
@foreach (var item in extractedMenuItems)
|
||||
{
|
||||
<RadzenAccordionItem Text="@item.MenuItem.Name" Icon="menu">
|
||||
<input @bind="item.MenuItem.Name" class="form-control mb-2" placeholder="Menu item name" />
|
||||
|
||||
<label>Parent Menu (optional):</label>
|
||||
<select class="form-control mb-2" @bind="item.MenuItem.ParentId">
|
||||
<option value="">-- No Parent --</option>
|
||||
@foreach (var parent in extractedMenuItems)
|
||||
{
|
||||
if (parent != item)
|
||||
{
|
||||
<option value="@parent.MenuItem.Id">@parent.MenuItem.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
|
||||
<label>Attach Content Item:</label>
|
||||
<select class="form-control mb-2" @bind="item.MenuItem.ContentItemId">
|
||||
<option value="">-- Select --</option>
|
||||
@foreach (var contentItem in allContentItems)
|
||||
{
|
||||
<option value="@contentItem.Id">@contentItem.Title</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
<textarea class="form-control mt-2" @bind="item.Content" placeholder="Optional inline content"></textarea>
|
||||
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-danger btn-sm mt-2" @onclick="() => RemoveMenuItem(item)">Remove</button>
|
||||
</div>
|
||||
</RadzenAccordionItem>
|
||||
}
|
||||
</Items>
|
||||
</RadzenAccordion>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-default btn-sm mt-3" @onclick="() => AddMenuItem()">Add Menu Item</button>
|
||||
<button class="btn btn-success mt-3" @onclick="() => SaveMenuItems(true)">Save All</button>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
<p class="text-danger">@errorMessage</p>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public int SiteId { get; set; }
|
||||
[Parameter] public string SessionId { get; set; }
|
||||
private string subject = string.Empty;
|
||||
ObservableCollection<MenuItem> menuItems = new();
|
||||
IList<MenuItem> selectedMenuItems;
|
||||
private List<MenuItemModel> extractedMenuItems = new();
|
||||
private List<ContentItem> allContentItems = new();
|
||||
private bool MenuItemsSaved = false;
|
||||
private bool isLoading = false;
|
||||
private string? errorMessage;
|
||||
private bool hasCollection = false;
|
||||
private bool allowReorder = false;
|
||||
private RadzenDataGrid<MenuItem> dataGrid;
|
||||
MenuItem draggedItem;
|
||||
MenuItemModel SelectedMenuItemModel = new("");
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
var site = await ContentEditorService.GetSiteInfoByIdAsync(SiteId);
|
||||
subject = site.SiteDescription ?? string.Empty;
|
||||
menuItems = new ObservableCollection<MenuItem>((await ContentEditorService.GetMenuItemsBySiteIdAsync(SiteId)).OrderBy(x => x.SortOrder));
|
||||
allContentItems = await ContentEditorService.GetAllContentItemsBySiteIdAsync(SiteId);
|
||||
var collectionResult = await QDrantService.GetCollectionBySiteIdAsync(SiteId);
|
||||
hasCollection = !string.IsNullOrEmpty(collectionResult);
|
||||
|
||||
if (menuItems.Count > 0)
|
||||
{
|
||||
foreach (var menuItem in menuItems)
|
||||
{
|
||||
var model = new MenuItemModel("") { MenuItem = menuItem };
|
||||
if (menuItem.ContentItemId != null)
|
||||
{
|
||||
var contentItem = await ContentEditorService.GetContentItemByIdAsync(menuItem.ContentItemId.Value);
|
||||
if (contentItem != null)
|
||||
{
|
||||
model.Content = contentItem.Content;
|
||||
model.ContentDescription = contentItem.Description;
|
||||
}
|
||||
}
|
||||
extractedMenuItems.Add(model);
|
||||
}
|
||||
MenuItemsSaved = true;
|
||||
}
|
||||
}
|
||||
|
||||
// private async Task GenerateMenuItems()
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(subject))
|
||||
// {
|
||||
// errorMessage = "Please enter a subject.";
|
||||
// return;
|
||||
// }
|
||||
|
||||
// isLoading = true;
|
||||
// try
|
||||
// {
|
||||
// var prompt = $"Suggest a list of menu items for a website about: {subject}. Please do not attach any explanation.";
|
||||
// var response = await ContentEditorAIService.GetMenuSuggestionsAsync(SessionId, prompt);
|
||||
// extractedMenuItems = response.Select(name => new MenuItemModel(name)).ToList();
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// errorMessage = "An error occurred while generating menu items.";
|
||||
// }
|
||||
// finally { isLoading = false; }
|
||||
// }
|
||||
|
||||
private async Task AddMenuItem()
|
||||
{
|
||||
MenuItemModel newModel = new MenuItemModel("New Menu Item");
|
||||
MenuItem newItem = new MenuItem();
|
||||
newItem.SiteInfoId = SiteId;
|
||||
newItem.Name = "New Menu Item";
|
||||
newItem.SortOrder = extractedMenuItems.Count+1;
|
||||
var result = await ContentEditorService.AddMenuItemAsync(newItem);
|
||||
newModel.MenuItem = result;
|
||||
extractedMenuItems.Add(newModel);
|
||||
if(result == null)
|
||||
{
|
||||
extractedMenuItems.Remove(newModel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task RemoveMenuItem(MenuItemModel item){
|
||||
await ContentEditorService.DeleteMenuItemAsync(item.MenuItem.Id);
|
||||
extractedMenuItems.Remove(item);
|
||||
}
|
||||
|
||||
private async Task SaveMenuItems(bool updateVectorDatabase)
|
||||
{
|
||||
//var result = await ContentEditorAIService.ProcessMenuItems(SiteId, hasCollection, extractedMenuItems, subject, MenuItemsSaved, updateVectorDatabase);
|
||||
try
|
||||
{
|
||||
foreach (var menuItemToUpdate in extractedMenuItems)
|
||||
{
|
||||
var result2 = await ContentEditorService.UpdateMenuItemAsync(menuItemToUpdate.MenuItem);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = "Some error occured: " + ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
MenuItemsSaved = true;
|
||||
hasCollection = true;
|
||||
errorMessage = "Menu saved";
|
||||
}
|
||||
}
|
||||
|
||||
void RowRender(RowRenderEventArgs<MenuItem> args)
|
||||
{
|
||||
if (!allowReorder) return;
|
||||
|
||||
args.Attributes.Add("title", "Drag row to reorder");
|
||||
args.Attributes.Add("style", "cursor:grab");
|
||||
args.Attributes.Add("draggable", "true");
|
||||
args.Attributes.Add("ondragover", "event.preventDefault();event.target.closest('.rz-data-row').classList.add('my-class')");
|
||||
args.Attributes.Add("ondragleave", "event.target.closest('.rz-data-row').classList.remove('my-class')");
|
||||
args.Attributes.Add("ondragstart", EventCallback.Factory.Create<DragEventArgs>(this, () => draggedItem = args.Data));
|
||||
args.Attributes.Add("ondrop", EventCallback.Factory.Create<DragEventArgs>(this, () =>
|
||||
{
|
||||
var draggedIndex = menuItems.IndexOf(draggedItem);
|
||||
var droppedIndex = menuItems.IndexOf(args.Data);
|
||||
var draggedModel = extractedMenuItems[draggedIndex];
|
||||
|
||||
menuItems.RemoveAt(draggedIndex);
|
||||
extractedMenuItems.RemoveAt(draggedIndex);
|
||||
|
||||
menuItems.Insert(droppedIndex, draggedItem);
|
||||
extractedMenuItems.Insert(droppedIndex, draggedModel);
|
||||
|
||||
for (int i = 0; i < menuItems.Count; i++)
|
||||
{
|
||||
menuItems[i].SortOrder = i;
|
||||
extractedMenuItems[i].MenuItem.SortOrder = i;
|
||||
}
|
||||
|
||||
JSRuntime.InvokeVoidAsync("eval", "document.querySelector('.my-class').classList.remove('my-class')");
|
||||
}));
|
||||
}
|
||||
|
||||
void ToggleReorder()
|
||||
{
|
||||
if (allowReorder) _ = SaveMenuItems(false);
|
||||
allowReorder = !allowReorder;
|
||||
}
|
||||
|
||||
void OnRowSelect(MenuItem item)
|
||||
{
|
||||
SelectedMenuItemModel = extractedMenuItems.FirstOrDefault(x => x.MenuItem.Id == item.Id) ?? new("");
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
@inject ContentEditorService _contentEditorService
|
||||
@inject IHttpContextAccessor HttpContextAccessor
|
||||
@inject IJSRuntime JS
|
||||
@inject ISimpleLogger _logger
|
||||
|
||||
<nav class="navbar fixed-top navbar-expand-lg" style="z-index: 10005">
|
||||
<div class="container-fluid">
|
||||
|
|
@ -72,7 +73,7 @@
|
|||
|
||||
</ul>
|
||||
<select @onchange="OnLanguageSelected" class="form-select" style="width:100px">
|
||||
<option value="">Lang</option>
|
||||
<option value="@SelectedLanguage">@SelectedLanguage</option>
|
||||
@foreach (var Language in Languages)
|
||||
{
|
||||
<option value="@Language">@Language</option>
|
||||
|
|
@ -84,7 +85,8 @@
|
|||
|
||||
|
||||
@code {
|
||||
public int SiteId;
|
||||
[Parameter]
|
||||
public int SiteId { get; set; }
|
||||
[Parameter]
|
||||
public List<MenuItem> Menu { get; set; }
|
||||
[Parameter] public string? BrandName { get; set; } = "BLAIzor";
|
||||
|
|
@ -114,17 +116,8 @@
|
|||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var lang = await JS.InvokeAsync<string>("getUserLanguage");
|
||||
|
||||
// Normalize and match to one of your supported languages
|
||||
if (lang.StartsWith("hu", StringComparison.OrdinalIgnoreCase))
|
||||
SelectedLanguage = "Hungarian";
|
||||
else if (lang.StartsWith("de", StringComparison.OrdinalIgnoreCase))
|
||||
SelectedLanguage = "German";
|
||||
else
|
||||
SelectedLanguage = "English";
|
||||
|
||||
_scopedContentService.SelectedLanguage = SelectedLanguage;
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
|
|
@ -133,6 +126,28 @@
|
|||
{
|
||||
//
|
||||
}
|
||||
var lang = await JS.InvokeAsync<string>("getUserLanguage");
|
||||
|
||||
SiteInfo site = await _contentEditorService.GetSiteInfoByIdAsync(SiteId);
|
||||
if (site.DefaultLanguage != null)
|
||||
{
|
||||
await _logger.InfoAsync($"NavMenu component: Setting default language to {site.DefaultLanguage} for site {site.Id}");
|
||||
_scopedContentService.WebsiteDefaultLanguage = site.DefaultLanguage;
|
||||
_scopedContentService.SelectedLanguage = site.DefaultLanguage;
|
||||
SelectedLanguage = site.DefaultLanguage;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normalize and match to one of your supported languages
|
||||
if (lang.StartsWith("hu", StringComparison.OrdinalIgnoreCase))
|
||||
SelectedLanguage = "Hungarian";
|
||||
else if (lang.StartsWith("de", StringComparison.OrdinalIgnoreCase))
|
||||
SelectedLanguage = "German";
|
||||
else
|
||||
SelectedLanguage = "English";
|
||||
await _logger.InfoAsync($"Setting default language to {SelectedLanguage} based on user language {lang}");
|
||||
_scopedContentService.SelectedLanguage = SelectedLanguage;
|
||||
}
|
||||
await base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
|
|
@ -147,4 +162,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,14 +57,14 @@
|
|||
<p>Your sample html template for a specific block, like article, card, hero or such. Here you can use sample data in the snippet, to see if your css is working well</p>
|
||||
<div class="form-group">
|
||||
<label>Sample</label><RadzenButton Click="@(() => ShowSnippetPreview(Snippet))">Preview snippet</RadzenButton>
|
||||
<RadzenHtmlEditor @bind-Value=@Snippet.SampleHtml style="height: 450px; color:#000; background-color: rgba(255,255,255,0) !important" Input=@OnInput Change=@OnChange Paste=@OnPaste UploadComplete=@OnUploadComplete Execute=@OnExecute UploadUrl="upload/image">
|
||||
@* <RadzenHtmlEditor @bind-Value=@Snippet.SampleHtml style="height: 450px; color:#000; background-color: rgba(255,255,255,0) !important" Input=@OnInput Change=@OnChange Paste=@OnPaste UploadComplete=@OnUploadComplete Execute=@OnExecute UploadUrl="upload/image">
|
||||
<RadzenHtmlEditorUndo />
|
||||
<RadzenHtmlEditorRedo />
|
||||
<RadzenHtmlEditorSource />
|
||||
</RadzenHtmlEditor>
|
||||
</RadzenHtmlEditor> *@
|
||||
</div>
|
||||
</div>
|
||||
<EventConsole @ref=@console />
|
||||
@* <EventConsole @ref=@console /> *@
|
||||
@if (IsLoading)
|
||||
{
|
||||
<p>Loading content...</p>
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
private string? userName;
|
||||
private AuthenticationState? authState;
|
||||
|
||||
EventConsole console;
|
||||
// EventConsole console;
|
||||
|
||||
public async void ShowSnippetPreview(HtmlSnippet snippet)
|
||||
{
|
||||
|
|
@ -117,30 +117,30 @@
|
|||
|
||||
async Task OnPaste(HtmlEditorPasteEventArgs args)
|
||||
{
|
||||
console.Log($"Paste: {args.Html}");
|
||||
// console.Log($"Paste: {args.Html}");
|
||||
// await OnContentUpdated.InvokeAsync(MenuItem);
|
||||
}
|
||||
|
||||
async Task OnChange(string html)
|
||||
{
|
||||
console.Log($"Change: {html}");
|
||||
// console.Log($"Change: {html}");
|
||||
|
||||
}
|
||||
|
||||
async Task OnInput(string html)
|
||||
{
|
||||
console.Log($"Input: {html}");
|
||||
// console.Log($"Input: {html}");
|
||||
|
||||
}
|
||||
|
||||
void OnExecute(HtmlEditorExecuteEventArgs args)
|
||||
{
|
||||
console.Log($"Execute: {args.CommandName}");
|
||||
// console.Log($"Execute: {args.CommandName}");
|
||||
}
|
||||
|
||||
void OnUploadComplete(UploadCompleteEventArgs args)
|
||||
{
|
||||
console.Log($"Upload complete: {args.RawResponse}");
|
||||
// console.Log($"Upload complete: {args.RawResponse}");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||
public static class TextHelper
|
||||
{
|
||||
// Special character replacement map
|
||||
private static readonly Dictionary<string, string> SpecialCharacterMap = new()
|
||||
private static readonly Dictionary<string, string> HungarianSpecialCharacterMap = new()
|
||||
{
|
||||
{ "/", " per " },
|
||||
{ "@", " kukac " },
|
||||
|
|
@ -17,27 +17,22 @@ public static class TextHelper
|
|||
//{ " - ", " mínusz " } // Example, you can add more
|
||||
};
|
||||
|
||||
public static string ReplaceNumbersAndSpecialCharacters(string text)
|
||||
private static readonly Dictionary<string, string> EnglishSpecialCharacterMap = new()
|
||||
{
|
||||
{ "/", " slash " },
|
||||
{ "@", " at " },
|
||||
{ "#", " hashtag " },
|
||||
{ "&", " and " },
|
||||
//{ ",", " vessző " },
|
||||
{ " = ", " equals " }, // Example, you can add more
|
||||
//{ " - ", " mínusz " } // Example, you can add more
|
||||
};
|
||||
|
||||
public static string ReplaceNumbersAndSpecialCharacters(string text, string language)
|
||||
{
|
||||
// Save parts that should be skipped (emails, URLs, dates)
|
||||
var protectedParts = new Dictionary<string, string>();
|
||||
|
||||
//// Protect email addresses
|
||||
//text = Regex.Replace(text, @"\b[\w\.-]+@[\w\.-]+\.\w+\b", match =>
|
||||
//{
|
||||
// string key = $"__EMAIL__{protectedParts.Count}__";
|
||||
// protectedParts[key] = match.Value;
|
||||
// return key;
|
||||
//});
|
||||
|
||||
//// Protect URLs
|
||||
//text = Regex.Replace(text, @"https?://[^\s]+", match =>
|
||||
//{
|
||||
// string key = $"__URL__{protectedParts.Count}__";
|
||||
// protectedParts[key] = match.Value;
|
||||
// return key;
|
||||
//});
|
||||
|
||||
// Protect dates like 2024.05.06
|
||||
text = Regex.Replace(text, @"\b\d{4}\.\d{2}\.\d{2}\b", match =>
|
||||
{
|
||||
|
|
@ -55,22 +50,45 @@ public static class TextHelper
|
|||
var parts = match.Value.Split('.');
|
||||
var integerPart = int.Parse(parts[0]);
|
||||
var decimalPart = int.Parse(parts[1]);
|
||||
|
||||
if(language == "Hungarian")
|
||||
{
|
||||
return $"{NumberToHungarian(integerPart)} egész {NumberToHungarian(decimalPart)} {(parts[1].Length == 1 ? "tized" : parts[1].Length == 2 ? "század" : "ezred")}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{NumberToEnglish(integerPart)} point {NumberToEnglish(decimalPart)}";
|
||||
}
|
||||
});
|
||||
|
||||
// Then replace integers
|
||||
text = Regex.Replace(text, @"\b\d+\b", match =>
|
||||
{
|
||||
int number = int.Parse(match.Value);
|
||||
if(language == "Hungarian")
|
||||
{
|
||||
return NumberToHungarian(number);
|
||||
}
|
||||
else
|
||||
{
|
||||
return NumberToEnglish(number);
|
||||
}
|
||||
});
|
||||
|
||||
// Replace special characters from dictionary
|
||||
foreach (var kvp in SpecialCharacterMap)
|
||||
if(language == "Hungarian")
|
||||
{
|
||||
foreach (var kvp in HungarianSpecialCharacterMap)
|
||||
{
|
||||
text = text.Replace(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var kvp in EnglishSpecialCharacterMap)
|
||||
{
|
||||
text = text.Replace(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace dots surrounded by spaces (optional)
|
||||
//text = Regex.Replace(text, @" (?=\.)|(?<=\.) ", " pont ");
|
||||
|
|
@ -91,6 +109,7 @@ public static class TextHelper
|
|||
|
||||
string[] units = { "", "egy", "két", "három", "négy", "öt", "hat", "hét", "nyolc", "kilenc" };
|
||||
string[] tens = { "", "tíz", "húsz", "harminc", "negyven", "ötven", "hatvan", "hetven", "nyolcvan", "kilencven" };
|
||||
string[] tensAlternate = { "", "tizen", "huszon", "harminc", "negyven", "ötven", "hatvan", "hetven", "nyolcvan", "kilencven" };
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
|
|
@ -123,7 +142,7 @@ public static class TextHelper
|
|||
if (number >= 10)
|
||||
{
|
||||
int tensPart = number / 10;
|
||||
result.Append(tens[tensPart]);
|
||||
result.Append(tensAlternate[tensPart]);
|
||||
number %= 10;
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +158,84 @@ public static class TextHelper
|
|||
return result.ToString();
|
||||
}
|
||||
|
||||
public static string NumberToEnglish(int number)
|
||||
{
|
||||
if (number == 0) return "zero";
|
||||
|
||||
string[] units = { "", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
|
||||
string[] tens = { "", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninty" };
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
if (number >= 1000)
|
||||
{
|
||||
int thousands = number / 1000;
|
||||
if (thousands == 1)
|
||||
result.Append("thousand");
|
||||
else
|
||||
{
|
||||
result.Append(NumberToHungarian(thousands));
|
||||
result.Append("thousand");
|
||||
}
|
||||
number %= 1000;
|
||||
}
|
||||
|
||||
if (number >= 100)
|
||||
{
|
||||
int hundreds = number / 100;
|
||||
if (hundreds == 1)
|
||||
result.Append("hundred");
|
||||
else
|
||||
{
|
||||
result.Append(NumberToHungarian(hundreds));
|
||||
result.Append("hundred");
|
||||
}
|
||||
number %= 100;
|
||||
}
|
||||
|
||||
if (number >= 10)
|
||||
{
|
||||
//int tensPart = number / 10;
|
||||
//result.Append(tens[tensPart]);
|
||||
//number %= 10;
|
||||
switch (number)
|
||||
{
|
||||
case 10:
|
||||
result.Append("ten");
|
||||
break;
|
||||
case 11:
|
||||
result.Append("eleven");
|
||||
break;
|
||||
case 12:
|
||||
result.Append("twelve");
|
||||
break;
|
||||
case 13:
|
||||
result.Append("thirteen");
|
||||
break;
|
||||
case 14:
|
||||
result.Append("fourteen");
|
||||
break;
|
||||
case 15:
|
||||
result.Append("fifteen");
|
||||
break;
|
||||
case 16:
|
||||
result.Append("sixteen");
|
||||
break;
|
||||
case 17:
|
||||
result.Append("seventeen");
|
||||
break;
|
||||
case 18:
|
||||
result.Append("eighteen");
|
||||
break;
|
||||
case 19:
|
||||
result.Append("nineteen");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
public static string FixJsonWithoutAI(string aiResponse)
|
||||
{
|
||||
if (aiResponse.StartsWith("```"))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BLAIzor.Interfaces
|
||||
{
|
||||
public interface IBrightDataService
|
||||
{
|
||||
Task<string?> ScrapeFacebookPostsAsync(string pageUrl, int numPosts = 10);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,884 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using BLAIzor.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BLAIzor.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250717145336_domainurl_nullable")]
|
||||
partial class domainurl_nullable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.3")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.AppLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Details")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Severity")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Logs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentChunk", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("ChunkIndex")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ContentItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("QdrantPointId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("VectorHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContentItemId");
|
||||
|
||||
b.ToTable("ContentChunks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentGroup", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("EmbeddingModel")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("LastUpdated")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SiteInfoId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("VectorSize")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SiteInfoId");
|
||||
|
||||
b.ToTable("ContentGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("ContentGroupId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("LastUpdated")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Tags")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContentGroupId");
|
||||
|
||||
b.ToTable("ContentItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.CssTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("CssContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("DesignTemplateId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("LastUpdated")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DesignTemplateId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CssTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.DesignTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeprecated")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsPrivate")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("QDrandCollectionName")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Tags")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TemplateName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TemplatePhotoUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("DesignTemplates");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
CreatedAt = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "The default template",
|
||||
IsDeprecated = false,
|
||||
IsPrivate = false,
|
||||
IsPublished = false,
|
||||
QDrandCollectionName = "html_snippets",
|
||||
Status = "Draft",
|
||||
Tags = "system",
|
||||
TemplateName = "Default Site",
|
||||
TemplatePhotoUrl = "/images/default-logo.png",
|
||||
UserId = "0988758e-e16c-4c2c-8c1e-efa3ac5f0274",
|
||||
Version = 1
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.FormDefinition", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("JsonDefinition")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SiteInfoId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SiteInfoId", "Slug")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("FormDefinitions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.MenuItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int?>("ContentGroupId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("ContentItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int?>("ParentId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("ShowInMainMenu")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("SiteInfoId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("StoredHtml")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContentGroupId");
|
||||
|
||||
b.HasIndex("ContentItemId");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.HasIndex("SiteInfoId");
|
||||
|
||||
b.ToTable("MenuItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.SiteInfo", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("BackgroundVideo")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("BrandLogoUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DefaultColor")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DefaultLanguage")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DefaultUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DomainUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("EmbeddingService")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Entity")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Persona")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("STTActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SiteDescription")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("SiteName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TTSActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int?>("TemplateId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("VectorCollectionName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("VoiceId")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("SiteInfos");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
BrandLogoUrl = "/images/default-logo.png",
|
||||
DefaultColor = "#FFFFFF",
|
||||
DefaultUrl = "https://ai.poppixel.cloud",
|
||||
DomainUrl = "poppixel.cloud",
|
||||
IsPublished = false,
|
||||
STTActive = false,
|
||||
SiteName = "Default Site",
|
||||
TTSActive = false,
|
||||
TemplateId = 1,
|
||||
UserId = "0988758e-e16c-4c2c-8c1e-efa3ac5f0274"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex")
|
||||
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex")
|
||||
.HasFilter("[NormalizedUserName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = "0988758e-e16c-4c2c-8c1e-efa3ac5f0274",
|
||||
AccessFailedCount = 0,
|
||||
ConcurrencyStamp = "a2836246-0303-4370-b283-e53a9a3f2813",
|
||||
Email = "adam.g@aycode.com",
|
||||
EmailConfirmed = true,
|
||||
LockoutEnabled = false,
|
||||
NormalizedEmail = "ADAM.G@AYCODE.COM",
|
||||
NormalizedUserName = "ADAM.G@AYCODE.COM",
|
||||
PasswordHash = "AQAAAAIAAYagAAAAEChxKCu+ReGvcZFR/6kPASbpnQdMp1MJuepeRyR4bfHTkUk8SfNAqmckGXvuw+GaGA==",
|
||||
PhoneNumberConfirmed = false,
|
||||
SecurityStamp = "7ecf121a-b0e7-4e30-a1f1-299eeaf0a9cc",
|
||||
TwoFactorEnabled = false,
|
||||
UserName = "adam.g@aycode.com"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentChunk", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.ContentItem", "ContentItem")
|
||||
.WithMany("Chunks")
|
||||
.HasForeignKey("ContentItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContentItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentGroup", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.SiteInfo", "SiteInfo")
|
||||
.WithMany("ContentGroups")
|
||||
.HasForeignKey("SiteInfoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SiteInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentItem", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.ContentGroup", "ContentGroup")
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("ContentGroupId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContentGroup");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.CssTemplate", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.DesignTemplate", "DesignTemplate")
|
||||
.WithOne("CssTemplate")
|
||||
.HasForeignKey("BLAIzor.Models.CssTemplate", "DesignTemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("DesignTemplate");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.DesignTemplate", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.FormDefinition", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.SiteInfo", "SiteInfo")
|
||||
.WithMany("FormDefinitions")
|
||||
.HasForeignKey("SiteInfoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SiteInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.MenuItem", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.ContentGroup", "ContentGroup")
|
||||
.WithMany()
|
||||
.HasForeignKey("ContentGroupId")
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
b.HasOne("BLAIzor.Models.ContentItem", "ContentItem")
|
||||
.WithMany()
|
||||
.HasForeignKey("ContentItemId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("BLAIzor.Models.MenuItem", "Parent")
|
||||
.WithMany("Children")
|
||||
.HasForeignKey("ParentId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("BLAIzor.Models.SiteInfo", "SiteInfo")
|
||||
.WithMany("MenuItems")
|
||||
.HasForeignKey("SiteInfoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContentGroup");
|
||||
|
||||
b.Navigation("ContentItem");
|
||||
|
||||
b.Navigation("Parent");
|
||||
|
||||
b.Navigation("SiteInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.SiteInfo", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.DesignTemplate", "Template")
|
||||
.WithMany("Sites")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentGroup", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentItem", b =>
|
||||
{
|
||||
b.Navigation("Chunks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.DesignTemplate", b =>
|
||||
{
|
||||
b.Navigation("CssTemplate")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Sites");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.MenuItem", b =>
|
||||
{
|
||||
b.Navigation("Children");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.SiteInfo", b =>
|
||||
{
|
||||
b.Navigation("ContentGroups");
|
||||
|
||||
b.Navigation("FormDefinitions");
|
||||
|
||||
b.Navigation("MenuItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BLAIzor.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class domainurl_nullable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "DomainUrl",
|
||||
table: "SiteInfos",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(max)");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "DomainUrl",
|
||||
table: "SiteInfos",
|
||||
type: "nvarchar(max)",
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(max)",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,893 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using BLAIzor.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BLAIzor.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250814214748_social_links")]
|
||||
partial class social_links
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.3")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.AppLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Details")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Severity")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Logs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentChunk", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("ChunkIndex")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ContentItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("QdrantPointId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("VectorHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContentItemId");
|
||||
|
||||
b.ToTable("ContentChunks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentGroup", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("EmbeddingModel")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("LastUpdated")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SiteInfoId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("VectorSize")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SiteInfoId");
|
||||
|
||||
b.ToTable("ContentGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("ContentGroupId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("LastUpdated")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Tags")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContentGroupId");
|
||||
|
||||
b.ToTable("ContentItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.CssTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("CssContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("DesignTemplateId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("LastUpdated")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DesignTemplateId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CssTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.DesignTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeprecated")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsPrivate")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("QDrandCollectionName")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Tags")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TemplateName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TemplatePhotoUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("DesignTemplates");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
CreatedAt = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "The default template",
|
||||
IsDeprecated = false,
|
||||
IsPrivate = false,
|
||||
IsPublished = false,
|
||||
QDrandCollectionName = "html_snippets",
|
||||
Status = "Draft",
|
||||
Tags = "system",
|
||||
TemplateName = "Default Site",
|
||||
TemplatePhotoUrl = "/images/default-logo.png",
|
||||
UserId = "0988758e-e16c-4c2c-8c1e-efa3ac5f0274",
|
||||
Version = 1
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.FormDefinition", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("JsonDefinition")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SiteInfoId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SiteInfoId", "Slug")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("FormDefinitions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.MenuItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int?>("ContentGroupId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("ContentItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int?>("ParentId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("ShowInMainMenu")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("SiteInfoId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("StoredHtml")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContentGroupId");
|
||||
|
||||
b.HasIndex("ContentItemId");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.HasIndex("SiteInfoId");
|
||||
|
||||
b.ToTable("MenuItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.SiteInfo", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("BackgroundVideo")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("BrandLogoUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DefaultColor")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DefaultLanguage")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DefaultUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DomainUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("EmbeddingService")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Entity")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("FacebookUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("InstagramUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Persona")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("STTActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SiteDescription")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("SiteName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TTSActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int?>("TemplateId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TwitterUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("VectorCollectionName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("VoiceId")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("SiteInfos");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
BrandLogoUrl = "/images/default-logo.png",
|
||||
DefaultColor = "#FFFFFF",
|
||||
DefaultUrl = "https://ai.poppixel.cloud",
|
||||
DomainUrl = "poppixel.cloud",
|
||||
IsPublished = false,
|
||||
STTActive = false,
|
||||
SiteName = "Default Site",
|
||||
TTSActive = false,
|
||||
TemplateId = 1,
|
||||
UserId = "0988758e-e16c-4c2c-8c1e-efa3ac5f0274"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex")
|
||||
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex")
|
||||
.HasFilter("[NormalizedUserName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = "0988758e-e16c-4c2c-8c1e-efa3ac5f0274",
|
||||
AccessFailedCount = 0,
|
||||
ConcurrencyStamp = "a2836246-0303-4370-b283-e53a9a3f2813",
|
||||
Email = "adam.g@aycode.com",
|
||||
EmailConfirmed = true,
|
||||
LockoutEnabled = false,
|
||||
NormalizedEmail = "ADAM.G@AYCODE.COM",
|
||||
NormalizedUserName = "ADAM.G@AYCODE.COM",
|
||||
PasswordHash = "AQAAAAIAAYagAAAAEChxKCu+ReGvcZFR/6kPASbpnQdMp1MJuepeRyR4bfHTkUk8SfNAqmckGXvuw+GaGA==",
|
||||
PhoneNumberConfirmed = false,
|
||||
SecurityStamp = "7ecf121a-b0e7-4e30-a1f1-299eeaf0a9cc",
|
||||
TwoFactorEnabled = false,
|
||||
UserName = "adam.g@aycode.com"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentChunk", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.ContentItem", "ContentItem")
|
||||
.WithMany("Chunks")
|
||||
.HasForeignKey("ContentItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContentItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentGroup", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.SiteInfo", "SiteInfo")
|
||||
.WithMany("ContentGroups")
|
||||
.HasForeignKey("SiteInfoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SiteInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentItem", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.ContentGroup", "ContentGroup")
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("ContentGroupId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContentGroup");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.CssTemplate", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.DesignTemplate", "DesignTemplate")
|
||||
.WithOne("CssTemplate")
|
||||
.HasForeignKey("BLAIzor.Models.CssTemplate", "DesignTemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("DesignTemplate");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.DesignTemplate", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.FormDefinition", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.SiteInfo", "SiteInfo")
|
||||
.WithMany("FormDefinitions")
|
||||
.HasForeignKey("SiteInfoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SiteInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.MenuItem", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.ContentGroup", "ContentGroup")
|
||||
.WithMany()
|
||||
.HasForeignKey("ContentGroupId")
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
b.HasOne("BLAIzor.Models.ContentItem", "ContentItem")
|
||||
.WithMany()
|
||||
.HasForeignKey("ContentItemId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("BLAIzor.Models.MenuItem", "Parent")
|
||||
.WithMany("Children")
|
||||
.HasForeignKey("ParentId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("BLAIzor.Models.SiteInfo", "SiteInfo")
|
||||
.WithMany("MenuItems")
|
||||
.HasForeignKey("SiteInfoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContentGroup");
|
||||
|
||||
b.Navigation("ContentItem");
|
||||
|
||||
b.Navigation("Parent");
|
||||
|
||||
b.Navigation("SiteInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.SiteInfo", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.DesignTemplate", "Template")
|
||||
.WithMany("Sites")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentGroup", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentItem", b =>
|
||||
{
|
||||
b.Navigation("Chunks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.DesignTemplate", b =>
|
||||
{
|
||||
b.Navigation("CssTemplate")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Sites");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.MenuItem", b =>
|
||||
{
|
||||
b.Navigation("Children");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.SiteInfo", b =>
|
||||
{
|
||||
b.Navigation("ContentGroups");
|
||||
|
||||
b.Navigation("FormDefinitions");
|
||||
|
||||
b.Navigation("MenuItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BLAIzor.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class social_links : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "FacebookUrl",
|
||||
table: "SiteInfos",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "InstagramUrl",
|
||||
table: "SiteInfos",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "TwitterUrl",
|
||||
table: "SiteInfos",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "SiteInfos",
|
||||
keyColumn: "Id",
|
||||
keyValue: 1,
|
||||
columns: new[] { "FacebookUrl", "InstagramUrl", "TwitterUrl" },
|
||||
values: new object[] { null, null, null });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FacebookUrl",
|
||||
table: "SiteInfos");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "InstagramUrl",
|
||||
table: "SiteInfos");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TwitterUrl",
|
||||
table: "SiteInfos");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,896 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using BLAIzor.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BLAIzor.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250816221528_siteinfo_designstyle")]
|
||||
partial class siteinfo_designstyle
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.3")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.AppLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Details")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Severity")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Logs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentChunk", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("ChunkIndex")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ContentItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("QdrantPointId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("VectorHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContentItemId");
|
||||
|
||||
b.ToTable("ContentChunks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentGroup", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("EmbeddingModel")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("LastUpdated")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SiteInfoId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("VectorSize")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SiteInfoId");
|
||||
|
||||
b.ToTable("ContentGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("ContentGroupId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("LastUpdated")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Tags")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContentGroupId");
|
||||
|
||||
b.ToTable("ContentItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.CssTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("CssContent")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("DesignTemplateId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("LastUpdated")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DesignTemplateId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CssTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.DesignTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeprecated")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsPrivate")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("QDrandCollectionName")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Tags")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TemplateName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TemplatePhotoUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("DesignTemplates");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
CreatedAt = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "The default template",
|
||||
IsDeprecated = false,
|
||||
IsPrivate = false,
|
||||
IsPublished = false,
|
||||
QDrandCollectionName = "html_snippets",
|
||||
Status = "Draft",
|
||||
Tags = "system",
|
||||
TemplateName = "Default Site",
|
||||
TemplatePhotoUrl = "/images/default-logo.png",
|
||||
UserId = "0988758e-e16c-4c2c-8c1e-efa3ac5f0274",
|
||||
Version = 1
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.FormDefinition", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("JsonDefinition")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SiteInfoId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SiteInfoId", "Slug")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("FormDefinitions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.MenuItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int?>("ContentGroupId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("ContentItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int?>("ParentId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("ShowInMainMenu")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("SiteInfoId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("StoredHtml")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContentGroupId");
|
||||
|
||||
b.HasIndex("ContentItemId");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.HasIndex("SiteInfoId");
|
||||
|
||||
b.ToTable("MenuItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.SiteInfo", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("BackgroundVideo")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("BrandLogoUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DefaultColor")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DefaultLanguage")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DefaultUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DesignStyle")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DomainUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("EmbeddingService")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Entity")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("FacebookUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("InstagramUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Persona")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("STTActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SiteDescription")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("SiteName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TTSActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int?>("TemplateId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TwitterUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("VectorCollectionName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("VoiceId")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("SiteInfos");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 1,
|
||||
BrandLogoUrl = "/images/default-logo.png",
|
||||
DefaultColor = "#FFFFFF",
|
||||
DefaultUrl = "https://ai.poppixel.cloud",
|
||||
DomainUrl = "poppixel.cloud",
|
||||
IsPublished = false,
|
||||
STTActive = false,
|
||||
SiteName = "Default Site",
|
||||
TTSActive = false,
|
||||
TemplateId = 1,
|
||||
UserId = "0988758e-e16c-4c2c-8c1e-efa3ac5f0274"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex")
|
||||
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex")
|
||||
.HasFilter("[NormalizedUserName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = "0988758e-e16c-4c2c-8c1e-efa3ac5f0274",
|
||||
AccessFailedCount = 0,
|
||||
ConcurrencyStamp = "a2836246-0303-4370-b283-e53a9a3f2813",
|
||||
Email = "adam.g@aycode.com",
|
||||
EmailConfirmed = true,
|
||||
LockoutEnabled = false,
|
||||
NormalizedEmail = "ADAM.G@AYCODE.COM",
|
||||
NormalizedUserName = "ADAM.G@AYCODE.COM",
|
||||
PasswordHash = "AQAAAAIAAYagAAAAEChxKCu+ReGvcZFR/6kPASbpnQdMp1MJuepeRyR4bfHTkUk8SfNAqmckGXvuw+GaGA==",
|
||||
PhoneNumberConfirmed = false,
|
||||
SecurityStamp = "7ecf121a-b0e7-4e30-a1f1-299eeaf0a9cc",
|
||||
TwoFactorEnabled = false,
|
||||
UserName = "adam.g@aycode.com"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentChunk", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.ContentItem", "ContentItem")
|
||||
.WithMany("Chunks")
|
||||
.HasForeignKey("ContentItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContentItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentGroup", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.SiteInfo", "SiteInfo")
|
||||
.WithMany("ContentGroups")
|
||||
.HasForeignKey("SiteInfoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SiteInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentItem", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.ContentGroup", "ContentGroup")
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("ContentGroupId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContentGroup");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.CssTemplate", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.DesignTemplate", "DesignTemplate")
|
||||
.WithOne("CssTemplate")
|
||||
.HasForeignKey("BLAIzor.Models.CssTemplate", "DesignTemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("DesignTemplate");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.DesignTemplate", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.FormDefinition", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.SiteInfo", "SiteInfo")
|
||||
.WithMany("FormDefinitions")
|
||||
.HasForeignKey("SiteInfoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SiteInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.MenuItem", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.ContentGroup", "ContentGroup")
|
||||
.WithMany()
|
||||
.HasForeignKey("ContentGroupId")
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
b.HasOne("BLAIzor.Models.ContentItem", "ContentItem")
|
||||
.WithMany()
|
||||
.HasForeignKey("ContentItemId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("BLAIzor.Models.MenuItem", "Parent")
|
||||
.WithMany("Children")
|
||||
.HasForeignKey("ParentId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("BLAIzor.Models.SiteInfo", "SiteInfo")
|
||||
.WithMany("MenuItems")
|
||||
.HasForeignKey("SiteInfoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContentGroup");
|
||||
|
||||
b.Navigation("ContentItem");
|
||||
|
||||
b.Navigation("Parent");
|
||||
|
||||
b.Navigation("SiteInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.SiteInfo", b =>
|
||||
{
|
||||
b.HasOne("BLAIzor.Models.DesignTemplate", "Template")
|
||||
.WithMany("Sites")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentGroup", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.ContentItem", b =>
|
||||
{
|
||||
b.Navigation("Chunks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.DesignTemplate", b =>
|
||||
{
|
||||
b.Navigation("CssTemplate")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Sites");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.MenuItem", b =>
|
||||
{
|
||||
b.Navigation("Children");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BLAIzor.Models.SiteInfo", b =>
|
||||
{
|
||||
b.Navigation("ContentGroups");
|
||||
|
||||
b.Navigation("FormDefinitions");
|
||||
|
||||
b.Navigation("MenuItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BLAIzor.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class siteinfo_designstyle : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DesignStyle",
|
||||
table: "SiteInfos",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "SiteInfos",
|
||||
keyColumn: "Id",
|
||||
keyValue: 1,
|
||||
column: "DesignStyle",
|
||||
value: null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DesignStyle",
|
||||
table: "SiteInfos");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -392,8 +392,10 @@ namespace BLAIzor.Migrations
|
|||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DesignStyle")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("DomainUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("EmbeddingService")
|
||||
|
|
@ -402,6 +404,12 @@ namespace BLAIzor.Migrations
|
|||
b.Property<string>("Entity")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("FacebookUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("InstagramUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("bit");
|
||||
|
||||
|
|
@ -423,6 +431,9 @@ namespace BLAIzor.Migrations
|
|||
b.Property<int?>("TemplateId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TwitterUrl")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
|
|
|||
|
|
@ -218,9 +218,10 @@ namespace BLAIzor.Models
|
|||
"- If there is no relevant snippet, use such layout `type` values: hero, text, features, text with image, call to action, product list, team members, testimonial, event list, blogpost, article, video player, audio player, etc\n" +
|
||||
"X Restrictions:" +
|
||||
"- DO **NOT** modify the photo urls in any way." +
|
||||
"- DO **NOT use the same text multiple times**" +
|
||||
"- Do **NOT** generate or assume new photo URLs.\n" +
|
||||
"- Do **NOT** modify photo URLs in any way.\n" +
|
||||
"- Do **NOT** skip or deny ANY part of the provided text. All of the provided text should be displayed as html \n" +
|
||||
"- Do **NOT** skip or deny ANY part of the provided text. All of the provided text should be put in blocks \n" +
|
||||
"- Do **NOT** assume ANY content, like missing prices, missing links, etc. \n" +
|
||||
"✅ Final output: JSON only, well grouped, no extra explanation or formatting, no markdown, no ``` markers.\n";
|
||||
|
||||
|
|
@ -232,74 +233,6 @@ namespace BLAIzor.Models
|
|||
{
|
||||
public static string GetHtmlRenderingSystemPromptForTextAndErrorResult(string language, string pageTitle, List<HtmlSnippet> htmlToUse, Dictionary<string, string>? photos, string[]? topics)
|
||||
{
|
||||
//var sb = new StringBuilder("You are a helpful assistant generating HTML content in " + language + " using Bootstrap.\n\n" +
|
||||
// "### Rules to Follow: \n" +
|
||||
// "- Please generate clean and structured HTML that goes between the menu and the footer of a Bootstrap5 webpage.\n" +
|
||||
// "- DO NOT include `<head>`, `<body>` tags — only content that goes inside the Bootstrap container.\n" +
|
||||
// "- Use `<h1 class='p-3'>` for the title: " + pageTitle + ".\n" +
|
||||
// "- Structure content using **separate `row` elements** for different sections.\n" +
|
||||
// "- Use Bootstrap classes to ensure proper spacing and alignment.\n" +
|
||||
// " - Use 'row justify-content-center' for layout, 'col-xx-x' classes for columns (ONLY IF multiple columns are needed), and always use 'img-fluid' class for images.\r\n" +
|
||||
// " - Do NOT use 'col' class if there is only one column to display in the row.\n" +
|
||||
// "- Do NOT use additional class for paragraphs!\n" +
|
||||
// "- Do NOT nest images inside paragraphs!\n" +
|
||||
// "- Ensure clear **separation of content into multiple sections if multiple snippets are provided**.\n");
|
||||
|
||||
//if (htmlToUse != null && htmlToUse.Any())
|
||||
//{
|
||||
// sb.AppendLine("### Using Provided Snippets:\n" +
|
||||
// "- You have been given **multiple HTML snippets**:\n");
|
||||
// foreach (var snippet in htmlToUse)
|
||||
// {
|
||||
// sb.AppendLine($"{snippet.Id}: {snippet.Name}: {snippet.Html}.\n");
|
||||
// sb.AppendLine($"Type: {snippet.Type}, Tags: {snippet.Tags}, Variant: {snippet.Variant}.\n");
|
||||
// }
|
||||
// sb.AppendLine("**DO NOT merge them into one**.\n" +
|
||||
// "- Use each snippet **as a separate section** inside its own div.\n" +
|
||||
// "- Using all the snippets is NOT mandatory, use them only if you identified content that fits in a specific snippet.\n" +
|
||||
// "- Validate if there are multiple variants of a snippet, and choose the best option, for example if the block has text and photo url, choose the variant that has image in it.\n" +
|
||||
// "- Do NOT push all text of a block in the title <h> tags, always pick a short title, followed by the rest of the content as a paragraph.\n" +
|
||||
// "- If a snippet contains a button, ensure it is placed inside a `div.text-center` for proper alignment.\n");
|
||||
//}
|
||||
|
||||
//if (photos != null && photos.Any())
|
||||
//{
|
||||
// sb.AppendLine("### Handling Photos:\n" +
|
||||
// "- ONLY use the provided photo URLs **as they are**, without modification.\n" +
|
||||
// "- Do **NOT** generate or assume image URLs.\n" +
|
||||
// "- Use these photos where appropriate:\n" +
|
||||
// " " + string.Join(", ", photos.Select(kv => $"{kv.Key}: {kv.Value}")) + "\n\n" +
|
||||
// "- Example usage:\n" +
|
||||
// " <img src='" + photos.First().Value + "' class='img-fluid' alt='" + photos.First().Key + "' />\n" +
|
||||
// "DO NOT modifiy the photo urls in any way.");
|
||||
//}
|
||||
|
||||
//if (topics != null && topics.Any())
|
||||
//{
|
||||
// sb.AppendLine("### Generating Topic Buttons:\n" +
|
||||
// "Start this section with a title `Related`\n" +
|
||||
// "- Create a **separate button** for each topic.\n" +
|
||||
// $"- Make sure the topics are in {language}, if not, translate the name of them." +
|
||||
// "- Each button should use `btn btn-primary` and call `callAI('{original_non_translated_topicName}')` on click.\n" +
|
||||
// "- List of topics:\n" +
|
||||
// " " + string.Join(", ", topics) + "\n\n" +
|
||||
// "- Example:\n" +
|
||||
// " <button class='btn btn-primary' onclick='callAI(\"" + topics.FirstOrDefault() + "\")'>" + topics.FirstOrDefault() + "</button>\n" +
|
||||
// "Put this section always as last, on the bottom of the page.\n");
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// sb.AppendLine("- **No topics provided** → **Do NOT generate topic buttons.**");
|
||||
//}
|
||||
|
||||
//sb.AppendLine("- DO **NOT** merge different content sections.\n" +
|
||||
// "- DO **NOT** wrap the entire content in a single `div`— use separate section tags.\n" +
|
||||
// "- DO **NOT** modify the photo urls in any way.\n" +
|
||||
// "- Do **NOT** generate or assume new photo URLs.\n" +
|
||||
// "- Do **NOT** skip or deny ANY part of the provided text. All of the provided text should be displayed as html\n" +
|
||||
// "- Do **NOT** add ANY extra content, like assumed prices, or assumed links.\n" +
|
||||
// "- If the snippet contains an image, but there is no photo url available, SKIP adding the image tag.\n" +
|
||||
// "- **Never** add explanations or start with ```html syntax markers.\n");
|
||||
|
||||
var sb = new StringBuilder($"You are a helpful assistant generating HTML in {language} using Bootstrap 5.\n\n" +
|
||||
"### General Instructions:\n" +
|
||||
|
|
@ -358,13 +291,14 @@ namespace BLAIzor.Models
|
|||
}
|
||||
|
||||
sb.AppendLine("\n### DO NOT:\n" +
|
||||
"- Merge different content blocks.\n" +
|
||||
"- Remove javascript or <script> tags"+
|
||||
"- Wrap the full output in a single `div`.\n" +
|
||||
"- Skip any provided content.\n" +
|
||||
"- Add assumed text (e.g. prices, links).\n" +
|
||||
"- Add `<img>` if no image URL exists.\n" +
|
||||
"- Include explanation, markdown, or ` ```html` markers.\n");
|
||||
"- DO **NOT** merge different content blocks.\n" +
|
||||
"- DO **NOT** remove javascript or <script> tags" +
|
||||
"- DO **NOT** wrap the full output in a single `div`.\n" +
|
||||
"- DO **NOT** skip any provided content.\n" +
|
||||
"- DO **NOT** add assumed text (e.g. prices, links).\n" +
|
||||
"- DO **NOT** add `<img>` if no image URL exists.\n" +
|
||||
"- DO **NOT** modify image URLs. not even adding 'https://example.com or such before the relaitve path'\n" +
|
||||
"- DO **NOT** include explanation, markdown, or ` ```html` markers.\n");
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ namespace BLAIzor.Models
|
|||
public Message[] Messages { get; set; } = Array.Empty<Message>();
|
||||
[JsonProperty("stream")]
|
||||
public bool Stream { get; set; } = true;
|
||||
//[JsonProperty("reasoning_effort")]
|
||||
//public string ReasoningEffort { get; set; } = "minimal";
|
||||
//[JsonProperty("verbosity")]
|
||||
//public string Verbosity { get; set; } = "high";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BLAIzor.Models
|
||||
{
|
||||
public class GeneratedContentItem : ContentItem
|
||||
{
|
||||
public List<PhotoSlot> PhotoSlots { get; set; } = new();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BLAIzor.Models
|
||||
{
|
||||
public class PhotoSlot
|
||||
{
|
||||
public string Description { get; set; } // AI-generated suggestion
|
||||
public string ImageUrl { get; set; } // AI-generated or user-selected image
|
||||
public int SortOrder { get; set; } // order in which it appears in the UI
|
||||
}
|
||||
}
|
||||
|
|
@ -19,21 +19,26 @@ namespace BLAIzor.Models
|
|||
public string UserId { get; set; }
|
||||
|
||||
[Url(ErrorMessage = "The DomainUrl field must be a valid URL.")]
|
||||
public string DomainUrl { get; set; } // For custom domains
|
||||
public string? DomainUrl { get; set; } // For custom domains
|
||||
|
||||
[Url(ErrorMessage = "The DefaultUrl field must be a valid URL.")]
|
||||
public string DefaultUrl { get; set; } // For generated subdomains
|
||||
public bool IsPublished { get; set; } // For generated subdomains
|
||||
public bool IsPublished { get; set; }
|
||||
public int? TemplateId { get; set; }
|
||||
public bool TTSActive { get; set; } = false;
|
||||
public bool STTActive { get; set; } = false;
|
||||
public string? VoiceId { get; set; }
|
||||
public string? Persona { get; set; }
|
||||
public string? Entity { get; set; }
|
||||
public string? DesignStyle { get; set; } // e.g. "Modern", "Classic", "Photorealistic"
|
||||
public string? DefaultLanguage { get; set; }
|
||||
public string? BackgroundVideo { get; set; }
|
||||
public string? VectorCollectionName { get; set; }
|
||||
public string? EmbeddingService { get; set; }
|
||||
public string? FacebookUrl { get; set; }
|
||||
public string? TwitterUrl { get; set; }
|
||||
public string? InstagramUrl { get; set; }
|
||||
|
||||
public List<ContentGroup> ContentGroups { get; set; } = new();
|
||||
|
||||
// Navigation property for IdentityUser
|
||||
|
|
|
|||
40
Program.cs
|
|
@ -1,6 +1,8 @@
|
|||
using BLAIzor.Components;
|
||||
using BLAIzor.Components.Partials;
|
||||
using BLAIzor.Data;
|
||||
using BLAIzor.Helpers;
|
||||
using BLAIzor.Interfaces;
|
||||
using BLAIzor.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
|
@ -12,6 +14,8 @@ using Radzen;
|
|||
using Sidio.Sitemap.AspNetCore;
|
||||
using Sidio.Sitemap.Blazor;
|
||||
using Sidio.Sitemap.Core.Services;
|
||||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var configuration = builder.Configuration;
|
||||
|
|
@ -35,6 +39,12 @@ builder.Services.Configure<KestrelServerOptions>(options =>
|
|||
options.Limits.MaxRequestBodySize = 50 * 1024 * 1024;
|
||||
});
|
||||
|
||||
var env = builder.Environment; // This is IWebHostEnvironment
|
||||
|
||||
if (env.IsProduction())
|
||||
{
|
||||
// do production-specific setup
|
||||
}
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents()
|
||||
|
|
@ -42,9 +52,20 @@ builder.Services.AddRazorComponents()
|
|||
|
||||
builder.Services.AddRadzenComponents();
|
||||
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||
|
||||
// Required for scoped injection (used in pages/services)
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseSqlServer(connectionString));
|
||||
|
||||
// Required for factory-based logging or background usage
|
||||
builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
|
||||
options.UseSqlServer(connectionString), ServiceLifetime.Scoped);
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddScoped<ISimpleLogger, SimpleLogger>();
|
||||
builder.Services.AddScoped<AIService>();
|
||||
builder.Services.AddSingleton<ContentService>();
|
||||
//builder.Services.AddSingleton<ContentService>();
|
||||
builder.Services.AddScoped<ScopedContentService>();
|
||||
builder.Services.AddScoped<QDrantService>();
|
||||
builder.Services.AddScoped<OpenAIEmbeddingService>();
|
||||
|
|
@ -58,17 +79,32 @@ builder.Services.AddScoped<CustomAuthenticationStateProvider>();
|
|||
builder.Services.Configure<SmtpSettings>(builder.Configuration.GetSection("Smtp"));
|
||||
builder.Services.AddTransient<IEmailSender, EmailService>();
|
||||
builder.Services.AddScoped<ContentEditorService>();
|
||||
builder.Services.AddScoped<ContentEditorAIService>();
|
||||
builder.Services.AddScoped<CssTemplateService>();
|
||||
builder.Services.AddScoped<DesignTemplateService>();
|
||||
builder.Services.AddScoped<OpenAIApiService>();
|
||||
builder.Services.AddScoped<DeepSeekApiService>();
|
||||
builder.Services.AddScoped<OpenAiRealtimeService>();
|
||||
builder.Services.AddScoped<CerebrasAPIService>();
|
||||
builder.Services.AddScoped<KimiApiService>();
|
||||
builder.Services.AddScoped<CssInjectorService>();
|
||||
builder.Services.AddScoped<LocalVectorSearchService>();
|
||||
builder.Services.AddScoped<WebsiteContentLoaderService>();
|
||||
builder.Services.AddScoped<CacheService>();
|
||||
builder.Services.AddSingleton<CreateSiteWizard>();
|
||||
builder.Services.AddScoped<WhisperTranscriptionService>();
|
||||
builder.Services.AddScoped<IBrightDataService, BrightDataService>();
|
||||
|
||||
builder.Services.AddHttpClient<ReplicateService>(client =>
|
||||
{
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Bearer", "r8_MUApXYIE5mRjxqy20tsGLehWBJkCzNj0Cwvrh");
|
||||
});
|
||||
|
||||
builder.Services.AddHostedService<TempFileCleanupService>();
|
||||
builder.Services.AddScoped<ISimpleLogger, SimpleLogger>();
|
||||
|
||||
|
||||
|
||||
builder.Services.AddServerSideBlazor().AddCircuitOptions(options => options.DetailedErrors = true).AddHubOptions(options =>
|
||||
{
|
||||
options.MaximumReceiveMessageSize = 1024000; // e.g. 100 KB
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ using BLAIzor.Helpers;
|
|||
using System.Collections;
|
||||
using Qdrant.Client.Grpc;
|
||||
using Microsoft.CodeAnalysis.Elfie.Model.Tree;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
namespace BLAIzor.Services
|
||||
{
|
||||
public class AIService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ContentService _contentService;
|
||||
private readonly ContentEditorService _contentEditorService;
|
||||
private readonly ScopedContentService _scopedContentService;
|
||||
private readonly OpenAIEmbeddingService _openAIEmbeddingService;
|
||||
|
|
@ -41,17 +41,19 @@ namespace BLAIzor.Services
|
|||
private readonly OpenAiRealtimeService _openAIRealtimeService;
|
||||
private readonly DeepSeekApiService _deepSeekApiService;
|
||||
private readonly CerebrasAPIService _cerebrasAPIService;
|
||||
private readonly KimiApiService _kimiApiService;
|
||||
private readonly QDrantService _qDrantService;
|
||||
private readonly NavigationManager _navigationManager;
|
||||
private readonly LocalVectorSearchService _localVectorSearchService;
|
||||
private readonly WebsiteContentLoaderService _websiteContentLoaderService;
|
||||
private readonly ISimpleLogger _logger;
|
||||
private readonly CacheService _cacheService;
|
||||
|
||||
|
||||
public static IConfiguration? _configuration;
|
||||
|
||||
|
||||
public AIService(HttpClient httpClient,
|
||||
ContentService contentService,
|
||||
ContentEditorService contentEditorService,
|
||||
ScopedContentService scopedContentService,
|
||||
QDrantService qDrantService,
|
||||
|
|
@ -61,13 +63,15 @@ namespace BLAIzor.Services
|
|||
DeepSeekApiService deepSeekApiService,
|
||||
OpenAiRealtimeService openAIRealtimeService,
|
||||
CerebrasAPIService cerebrasAPIService,
|
||||
KimiApiService kimiApiService,
|
||||
NavigationManager navigationManager,
|
||||
IConfiguration? configuration,
|
||||
LocalVectorSearchService localVectorSearchService,
|
||||
WebsiteContentLoaderService websiteContentLoaderService)
|
||||
WebsiteContentLoaderService websiteContentLoaderService,
|
||||
ISimpleLogger logger,
|
||||
CacheService cacheService)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_contentService = contentService;
|
||||
_contentEditorService = contentEditorService;
|
||||
_scopedContentService = scopedContentService;
|
||||
_qDrantService = qDrantService;
|
||||
|
|
@ -77,6 +81,7 @@ namespace BLAIzor.Services
|
|||
_deepSeekApiService = deepSeekApiService;
|
||||
_openAIRealtimeService = openAIRealtimeService;
|
||||
_cerebrasAPIService = cerebrasAPIService;
|
||||
_kimiApiService = kimiApiService;
|
||||
_navigationManager = navigationManager;
|
||||
_localVectorSearchService = localVectorSearchService;
|
||||
_websiteContentLoaderService = websiteContentLoaderService;
|
||||
|
|
@ -85,15 +90,17 @@ namespace BLAIzor.Services
|
|||
_openAIApiService.RegisterCallback(HandleActionInvoked, HandleFinishedInvoked, HandleErrorInvoked);
|
||||
_cerebrasAPIService.RegisterCallback(HandleActionInvoked, HandleFinishedInvoked, HandleErrorInvoked);
|
||||
_openAIRealtimeService.RegisterCallback(HandleActionInvoked);
|
||||
_logger = logger;
|
||||
_cacheService = cacheService;
|
||||
}
|
||||
|
||||
private const string OpenAiEndpoint = "https://api.openai.com/v1/chat/completions";
|
||||
public string _apiKey;
|
||||
public static event Action<string, string, MenuItem?>? OnContentReceived;
|
||||
public static event Action<string>? OnContentReceiveFinished;
|
||||
public static event Action<string, string>? OnContentReceivedError;
|
||||
public static event Action<string, string>? OnStatusChangeReceived;
|
||||
public static event Action<string, string>? OnTextContentAvailable;
|
||||
public event Action<string, string, MenuItem?>? OnContentReceived;
|
||||
public event Action<string>? OnContentReceiveFinished;
|
||||
public event Action<string, string>? OnContentReceivedError;
|
||||
public event Action<string, string>? OnStatusChangeReceived;
|
||||
public event Action<string, string>? OnTextContentAvailable;
|
||||
public string Mood = "cool, and professional";
|
||||
private string _workingContent = null;
|
||||
public bool UseWebsocket = false;
|
||||
|
|
@ -135,18 +142,19 @@ namespace BLAIzor.Services
|
|||
|
||||
}
|
||||
|
||||
public async Task<WebsiteContentModel> InitSite(string sessionId, int SiteId, string templateCollectionName, string menuList = "")
|
||||
{
|
||||
string currentUri = _navigationManager.Uri;
|
||||
SiteInfo site = await _contentEditorService.GetSiteInfoByIdAsync(SiteId);
|
||||
WebsiteContentModel siteModel = null;
|
||||
siteModel = await _websiteContentLoaderService.LoadAllAsync(
|
||||
site,
|
||||
_qDrantService.GetPointsFromQdrantAsyncByPointIds
|
||||
);
|
||||
|
||||
_scopedContentService.WebsiteContentModel = siteModel;
|
||||
_scopedContentService.SessionId = sessionId;
|
||||
|
||||
public async Task<WebsiteContentModel> InitSite(string sessionId, SiteInfo site, string templateCollectionName, string menuList = "")
|
||||
{
|
||||
await _logger.InfoAsync($"InitSite: method called", templateCollectionName);
|
||||
|
||||
string currentUri = _navigationManager.Uri;
|
||||
if (site.DefaultLanguage != null)
|
||||
{
|
||||
_scopedContentService.WebsiteDefaultLanguage = site.DefaultLanguage;
|
||||
}
|
||||
WebsiteContentModel siteModel = await _cacheService.UpdateContentCache(sessionId, site.Id);
|
||||
//_scopedContentService.SessionId = sessionId;
|
||||
|
||||
_scopedContentService.AvailableTemplateSnippets = await GetSnippetsForDisplay(sessionId, templateCollectionName);
|
||||
return siteModel;
|
||||
|
|
@ -154,14 +162,16 @@ namespace BLAIzor.Services
|
|||
|
||||
public async Task GetChatGptWelcomeMessage(string sessionId, int SiteId, string templateCollectionName, string menuList = "")
|
||||
{
|
||||
var siteModel = await InitSite(sessionId, SiteId, templateCollectionName, menuList);
|
||||
await _logger.InfoAsync($"GetChatGptWelcomeMessage: method called", templateCollectionName);
|
||||
SiteInfo site = await _contentEditorService.GetSiteInfoByIdAsync(SiteId);
|
||||
var siteModel = await InitSite(sessionId, site, templateCollectionName, menuList);
|
||||
//_apiKey = GetApiKey();
|
||||
//List<string> qdrantPoint = await _qDrantService.GetContentAsync(SiteId, _scopedContentService.WebsiteContent.Items.FirstOrDefault());
|
||||
string extractedText = "";
|
||||
//nullcheck
|
||||
if (siteModel != null)
|
||||
{
|
||||
await _logger.InfoAsync($"GetChatGptWelcomeMessage: sitemodel not null", siteModel.SiteInfoId.ToString());
|
||||
if (siteModel.ContentItems != null && siteModel.ContentItems.Count > 0)
|
||||
{
|
||||
if (siteModel.ContentItems.OrderBy(sm => sm.ContentItem.Id).FirstOrDefault().VectorPoints != null)
|
||||
|
|
@ -205,17 +215,30 @@ namespace BLAIzor.Services
|
|||
AiProvider = GetAiSettings();
|
||||
if (AiProvider == "cerebras")
|
||||
{
|
||||
await _cerebrasAPIService.GetCerebrasStreamedResponse(sessionId, systemMessage, userMessage);
|
||||
var result = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, userMessage);
|
||||
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
||||
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
||||
|
||||
}
|
||||
else if (AiProvider == "chatgpt")
|
||||
{
|
||||
await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage);
|
||||
//var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userMessage);
|
||||
//var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
||||
//OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
||||
|
||||
}
|
||||
else if (AiProvider == "deepseek")
|
||||
{
|
||||
//await _deepSeekApiService.GetChatGPTStreamedResponse(systemMessage, userMessage);
|
||||
}
|
||||
if (AiProvider == "kimi")
|
||||
{
|
||||
var result = await _kimiApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userMessage);
|
||||
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
||||
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -234,6 +257,8 @@ namespace BLAIzor.Services
|
|||
|
||||
public async Task ProcessUserIntent(string sessionId, string userPrompt, int siteId, int templateId, string contentCollectionName, string menuList = "")
|
||||
{
|
||||
await _logger.InfoAsync($"ProcessUserIntent: method called", $"sessionId: {sessionId}, userprompt: {userPrompt}, siteId: {siteId}, templateId: {templateId}, contentCollectionName: {contentCollectionName}");
|
||||
|
||||
//Console.WriteLine($"SITE ID: {siteId}");
|
||||
OnStatusChangeReceived?.Invoke(sessionId, "Understanding your request...");
|
||||
|
||||
|
|
@ -244,7 +269,6 @@ namespace BLAIzor.Services
|
|||
|
||||
var baseResult = await ValidateAndFixJson<ChatGPTResultBase>(resultJson, FixJsonWithAI);
|
||||
//var baseResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTResultBase>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
var fixedResult = System.Text.Json.JsonSerializer.Serialize(baseResult);
|
||||
|
||||
if (baseResult == null)
|
||||
{
|
||||
|
|
@ -252,6 +276,7 @@ namespace BLAIzor.Services
|
|||
return;
|
||||
}
|
||||
|
||||
var fixedResult = System.Text.Json.JsonSerializer.Serialize(baseResult);
|
||||
OnStatusChangeReceived?.Invoke(sessionId, "Making a decision");
|
||||
|
||||
// Process result based on type
|
||||
|
|
@ -362,7 +387,7 @@ namespace BLAIzor.Services
|
|||
|
||||
public async Task ProcessContentRequest(string sessionId, string requestedMenu, int siteId, int templateId, string contentCollectionName, string menuList = "", bool forceUnmodified = false)
|
||||
{
|
||||
|
||||
await _logger.InfoAsync($"ProcessContentRequest: method called", $"sessionId: {sessionId}, requestedMenu: {requestedMenu}, siteId: {siteId}, templateId: {templateId}, contentCollectionName: {contentCollectionName}");
|
||||
//Console.Write($"\n\n SessionId: {sessionId}\n\n");
|
||||
//string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _contentService.SelectedDocument);
|
||||
|
||||
|
|
@ -438,22 +463,24 @@ namespace BLAIzor.Services
|
|||
|
||||
private async Task ProcessMethodResult(string sessionId, string resultJson)
|
||||
{
|
||||
var fixedResult = await ValidateAndFixJson<ChatGPTMethodResult>(resultJson, FixJsonWithAI);
|
||||
//var methodResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTMethodResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
if (fixedResult != null)
|
||||
await _logger.InfoAsync($"ProcessMethodResult: method called", $"sessionId: {sessionId}, json: {resultJson}");
|
||||
//var fixedResult = await ValidateAndFixJson<ChatGPTMethodResult>(resultJson, FixJsonWithAI); //ANOTHER fixing is useless, json is from fixed format
|
||||
var methodResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTMethodResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
if (methodResult != null)
|
||||
{
|
||||
OnStatusChangeReceived?.Invoke(sessionId, "Initiating the task you requested");
|
||||
await DisplayHtml(sessionId, fixedResult.Text, fixedResult.MethodToCall, fixedResult.Parameter);
|
||||
await DisplayHtml(sessionId, methodResult.Text, methodResult.MethodToCall, methodResult.Parameter);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessTextResult(string sessionId, string pageTitle, string resultJson, int templateId, string collectionName)
|
||||
{
|
||||
var fixedResult = await ValidateAndFixJson<ChatGPTTextResult>(resultJson, FixJsonWithAI);
|
||||
//var textResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTTextResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
if (fixedResult != null)
|
||||
await _logger.InfoAsync($"ProcessTextResult: method called", $"sessionId: {sessionId}, json: {resultJson}, pageTitle: {pageTitle}, templateId: {templateId}, collectionName: {collectionName}");
|
||||
//var fixedResult = await ValidateAndFixJson<ChatGPTTextResult>(resultJson, FixJsonWithAI); //ANOTHER fixing is useless, json is from fixed format
|
||||
var textResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTTextResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
if (textResult != null)
|
||||
{
|
||||
string contentJson = await GetContentFromQuery(sessionId, fixedResult.Text, _workingContent);
|
||||
string contentJson = await GetContentFromQuery(sessionId, textResult.Text, _workingContent);
|
||||
//Console.Write("\r \n ProcessTextResult: Content: " + contentJson + "\r \n");
|
||||
await ProcessContent(sessionId, pageTitle, contentJson, templateId, collectionName);
|
||||
}
|
||||
|
|
@ -461,6 +488,7 @@ namespace BLAIzor.Services
|
|||
|
||||
public async Task<T?> ValidateAndFixJson<T>(string json, Func<string, Task<string>> aiFixer)
|
||||
{
|
||||
await _logger.InfoAsync($"ValidateAndFixJson: method called", $"json: {json}");
|
||||
try
|
||||
{
|
||||
return System.Text.Json.JsonSerializer.Deserialize<T>(json, new JsonSerializerOptions
|
||||
|
|
@ -470,8 +498,8 @@ namespace BLAIzor.Services
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//Console.WriteLine($"❌ JSON parse failed: {ex.Message}");
|
||||
|
||||
await _logger.ErrorAsync($"ValidateAndFixJson: failed first!", $"message: {ex.Message}");
|
||||
var prompt = BuildJsonFixPrompt(json, ex.Message, typeof(T).Name);
|
||||
var fixedJson = await aiFixer(prompt);
|
||||
|
||||
|
|
@ -484,7 +512,8 @@ namespace BLAIzor.Services
|
|||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
//Console.WriteLine($"❌ AI-fix parse failed: {ex2.Message}");
|
||||
await _logger.ErrorAsync($"ValidateAndFixJson: failed again!", $"❌ AI-fix parse failed: {ex2.Message}");
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
|
@ -509,7 +538,7 @@ namespace BLAIzor.Services
|
|||
}
|
||||
else { return ""; }
|
||||
|
||||
//return await _openAIApiService.GetSimpleChatGPTResponseNoSession("You are a JSON-fixing assistant.", prompt);
|
||||
|
||||
}
|
||||
|
||||
private string BuildJsonFixPrompt(string json, string errorMessage, string targetTypeName)
|
||||
|
|
@ -532,17 +561,19 @@ namespace BLAIzor.Services
|
|||
|
||||
private async Task ProcessExaminationResult(string sessionId, string resultJson, int templateId, string collectionName)
|
||||
{
|
||||
var fixedResult = await ValidateAndFixJson<ChatGPTExaminationResult>(resultJson, FixJsonWithAI);
|
||||
//var explanationResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTExaminationResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
if (fixedResult != null)
|
||||
await _logger.InfoAsync($"ProcessExaminationResult: method called", $"sessionId: {sessionId}, json: {resultJson}, templateId: {templateId}, collectionName: {collectionName}");
|
||||
//var fixedResult = await ValidateAndFixJson<ChatGPTExaminationResult>(resultJson, FixJsonWithAI);
|
||||
var explanationResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTExaminationResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
if (explanationResult != null)
|
||||
{
|
||||
string contentJson = await GetExplanationFromQuery(sessionId, fixedResult.Text, _scopedContentService.CurrentDOM);
|
||||
string contentJson = await GetExplanationFromQuery(sessionId, explanationResult.Text, _scopedContentService.CurrentDOM);
|
||||
await ProcessContent(sessionId, "Examination", contentJson, templateId, collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessContent(string sessionId, string pageTitle, string contentJson, int templateId, string contentCollectionName)
|
||||
{
|
||||
await _logger.InfoAsync($"ProcessContent: method called", $"sessionId: {sessionId}, pageTitle: {pageTitle}, json: {contentJson}, templateId: {templateId}, contentCollectionName: {contentCollectionName}");
|
||||
try
|
||||
{
|
||||
var fixedResult = await ValidateAndFixJson<ChatGPTContentResult>(contentJson, FixJsonWithAI);
|
||||
|
|
@ -559,7 +590,7 @@ namespace BLAIzor.Services
|
|||
|
||||
//We have the text all available now, let's pass it to the voice generator
|
||||
//TODO modify photos handling, move audio generation to the layout area
|
||||
string removedNumbers = TextHelper.ReplaceNumbersAndSpecialCharacters(fixedResult.Text);
|
||||
string removedNumbers = TextHelper.ReplaceNumbersAndSpecialCharacters(fixedResult.Text, _scopedContentService.SelectedLanguage);
|
||||
Console.WriteLine(removedNumbers);
|
||||
OnTextContentAvailable?.Invoke(sessionId, removedNumbers);
|
||||
//List<HtmlSnippet> snippets = await GetSnippetsForDisplay(sessionId, collectionName);
|
||||
|
|
@ -581,6 +612,7 @@ namespace BLAIzor.Services
|
|||
//passing menuitem further
|
||||
private async Task ProcessContent(string sessionId, MenuItem requestedMenu, string contentJson, int templateId, string collectionName)
|
||||
{
|
||||
await _logger.InfoAsync($"ProcessContent: method called", $"sessionId: {sessionId}, menuItem: {requestedMenu.Name}, json: {contentJson}, templateId: {templateId}, contentCollectionName: {collectionName}");
|
||||
try
|
||||
{
|
||||
var fixedResult = await ValidateAndFixJson<ChatGPTContentResult>(contentJson, FixJsonWithAI);
|
||||
|
|
@ -597,7 +629,7 @@ namespace BLAIzor.Services
|
|||
|
||||
//We have the text all available now, let's pass it to the voice generator
|
||||
//TODO modify photos handling, move audio generation to the layout area
|
||||
string removedNumbers = TextHelper.ReplaceNumbersAndSpecialCharacters(fixedResult.Text);
|
||||
string removedNumbers = TextHelper.ReplaceNumbersAndSpecialCharacters(fixedResult.Text, _scopedContentService.SelectedLanguage);
|
||||
Console.WriteLine(removedNumbers);
|
||||
OnTextContentAvailable?.Invoke(sessionId, removedNumbers);
|
||||
//List<HtmlSnippet> snippets = await GetSnippetsForDisplay(sessionId, collectionName);
|
||||
|
|
@ -618,6 +650,7 @@ namespace BLAIzor.Services
|
|||
|
||||
private async Task ProcessErrorResult(string sessionId, string resultJson)
|
||||
{
|
||||
await _logger.InfoAsync($"ProcessErrorResult: method called", $"sessionId: {sessionId}, json: {resultJson}");
|
||||
var errorResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTErrorResult>(resultJson);
|
||||
if (errorResult != null)
|
||||
{
|
||||
|
|
@ -634,6 +667,7 @@ namespace BLAIzor.Services
|
|||
/// <returns></returns>
|
||||
public async Task<string> GetContentFromQuery(string sessionId, string userPrompt, string content = null, bool forceUnmodified = false)
|
||||
{
|
||||
await _logger.InfoAsync($"GetContentFromQuery: method called", $"sessionId: {sessionId}, userPrompt: {userPrompt}, content: {content}, forceUnmodified: {forceUnmodified}");
|
||||
string extractedText;
|
||||
if (content == null)
|
||||
{
|
||||
|
|
@ -803,6 +837,7 @@ namespace BLAIzor.Services
|
|||
|
||||
public async Task<string> GetJsonResultFromQuery(string sessionId, int siteId, string userPrompt)
|
||||
{
|
||||
await _logger.InfoAsync($"GetJsonResultFromQuery: method called", $"SessionId: {sessionId}, userPrompt: {userPrompt}, siteId: {siteId}");
|
||||
//string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _contentService.SelectedDocument);
|
||||
//_apiKey = GetApiKey();
|
||||
//start with embeddings
|
||||
|
|
@ -900,6 +935,7 @@ namespace BLAIzor.Services
|
|||
|
||||
public async Task<List<HtmlSnippet>> GetSnippetsForDisplay(string sessionId, string collectionName)
|
||||
{
|
||||
await _logger.InfoAsync($"GetSnippetsForDisplay: method called", $"SessionId: {sessionId}, collectionName: {collectionName}");
|
||||
_apiKey = GetApiKey();
|
||||
OnStatusChangeReceived?.Invoke(sessionId, "Looking up the UI template elements for you");
|
||||
//string availableSnippetList = "";
|
||||
|
|
@ -937,6 +973,7 @@ namespace BLAIzor.Services
|
|||
//for textresult and errorresult
|
||||
public async Task DisplayHtml(string sessionId, string pageTitle, LayoutPlan layoutPlan, List<HtmlSnippet> htmlToUse, string[]? topics = null, Dictionary<string, string>? photos = null)
|
||||
{
|
||||
await _logger.InfoAsync($"DisplayHtml: method called", $"SessionId: {sessionId}, pageTitle: {pageTitle}, layoutPlan: {layoutPlan.Blocks.Count}, htmlToUse: {htmlToUse.Count}, topics: {topics?.Length}, photos: {photos?.Count}");
|
||||
//Console.Write($"\n SessionId: {sessionId} \n");
|
||||
OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
|
||||
|
||||
|
|
@ -944,8 +981,9 @@ namespace BLAIzor.Services
|
|||
//Console.WriteLine($"DISPLAYHTML Topics: {topics} \n\n");
|
||||
|
||||
string systemMessage = AiPrompts.HtmlRendering.GetHtmlRenderingSystemPromptForTextAndErrorResult(_scopedContentService.SelectedLanguage, pageTitle, htmlToUse, photos, topics);
|
||||
string userMessage = AiPrompts.HtmlRendering.HtmlRenderingUserPromptForTextAndErrorResult;
|
||||
string assistantMessage = AiPrompts.HtmlRendering.GetHtmlRenderingAssistantMessageForTextAndErrorResult(layoutPlan);
|
||||
//string userMessage = AiPrompts.HtmlRendering.HtmlRenderingUserPromptForTextAndErrorResult;
|
||||
//string assistantMessage = AiPrompts.HtmlRendering.GetHtmlRenderingAssistantMessageForTextAndErrorResult(layoutPlan);
|
||||
string userMessage = AiPrompts.HtmlRendering.HtmlRenderingUserPromptForTextAndErrorResult + AiPrompts.HtmlRendering.GetHtmlRenderingAssistantMessageForTextAndErrorResult(layoutPlan);
|
||||
|
||||
//string assistantMessage = "`Provided layout plan, that contains the text to be displayed as HTML`:";
|
||||
|
||||
|
|
@ -961,14 +999,14 @@ namespace BLAIzor.Services
|
|||
if (AiProvider == "cerebras")
|
||||
{
|
||||
//await _cerebrasAPIService.GetCerebrasStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
||||
var result = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
||||
var result = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, userMessage, "", -1);
|
||||
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
||||
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
||||
}
|
||||
else if (AiProvider == "chatgpt")
|
||||
{
|
||||
//await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
||||
var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
||||
var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userMessage, "", -1);
|
||||
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
||||
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
||||
}
|
||||
|
|
@ -983,12 +1021,13 @@ namespace BLAIzor.Services
|
|||
}
|
||||
else
|
||||
{
|
||||
await _openAIRealtimeService.GetChatGPTResponseAsync(sessionId, systemMessage, userMessage + assistantMessage);
|
||||
await _openAIRealtimeService.GetChatGPTResponseAsync(sessionId, systemMessage, userMessage, "");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisplayHtmlForMenu(string sessionId, MenuItem requestedMenu, LayoutPlan layoutPlan, List<HtmlSnippet> htmlToUse, string[]? topics = null, Dictionary<string, string>? photos = null)
|
||||
{
|
||||
await _logger.InfoAsync($"DisplayHtmlForMenu: method called", $"SessionId: {sessionId}, requestedMenu: {requestedMenu.Name}, layoutPlan: {layoutPlan.Blocks.Count}, htmlToUse: {htmlToUse.Count}, topics: {topics?.Length}, photos: {photos?.Count}");
|
||||
//Console.Write($"\n SessionId: {sessionId} \n");
|
||||
OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
|
||||
|
||||
|
|
@ -1042,13 +1081,15 @@ namespace BLAIzor.Services
|
|||
|
||||
public async Task<LayoutPlan?> DisplayLayoutPlanFromContent(string sessionId, string pageTitle, string interMediateResult, List<HtmlSnippet> htmlToUse, string[]? topics = null, Dictionary<string, string>? photos = null)
|
||||
{
|
||||
//Console.Write($"\n SessionId: {sessionId} \n");
|
||||
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: method called for methodResult", sessionId.ToString());
|
||||
OnStatusChangeReceived?.Invoke(sessionId, "Planning layout based on the provided content");
|
||||
|
||||
//Console.WriteLine($"LAYOUTBUILDER Text: {interMediateResult}\n\n");
|
||||
//Console.WriteLine($"LAYOUTBUILDER Snippets: {htmlToUse.Count}\n\n");
|
||||
//Console.WriteLine($"LAYOUTBUILDER Photos: {photos} \n\n");
|
||||
//Console.WriteLine($"LAYOUTBUILDER Topics: {topics} \n\n");
|
||||
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: starting text", $"{interMediateResult}");
|
||||
|
||||
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: starting snippets count", htmlToUse.Count.ToString());
|
||||
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: starting photos count", photos.Count.ToString());
|
||||
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: starting topics count", topics.Length.ToString());
|
||||
|
||||
|
||||
string systemMessage = AiPrompts.LayoutPlanning.GetLayoutPlanningSystemPrompt(htmlToUse, photos, topics);
|
||||
string userMessage = AiPrompts.LayoutPlanning.GetLayoutPlanningUserPrompt(interMediateResult, pageTitle, photos);
|
||||
|
|
@ -1093,7 +1134,7 @@ namespace BLAIzor.Services
|
|||
{
|
||||
//Console.WriteLine("AI Response:");
|
||||
//Console.WriteLine(aiResponse);
|
||||
|
||||
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: ai Response", $"{aiResponse}");
|
||||
aiResponse = TextHelper.FixJsonWithoutAI(aiResponse);
|
||||
aiResponse = TextHelper.RemoveTabs(aiResponse);
|
||||
layoutPlan = System.Text.Json.JsonSerializer.Deserialize<LayoutPlan>(aiResponse, new JsonSerializerOptions
|
||||
|
|
@ -1101,39 +1142,44 @@ namespace BLAIzor.Services
|
|||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
|
||||
if (layoutPlan?.Blocks == null || layoutPlan.Blocks.Any(b => string.IsNullOrEmpty(b.Type) || string.IsNullOrEmpty(b.RawContent)))
|
||||
//if (layoutPlan?.Blocks == null || layoutPlan.Blocks.Any(b => string.IsNullOrEmpty(b.Type) || string.IsNullOrEmpty(b.RawContent)))
|
||||
if (layoutPlan?.Blocks == null || layoutPlan.Blocks.Any(b => string.IsNullOrEmpty(b.Type)))
|
||||
{
|
||||
//try to fix with AI)
|
||||
//Console.WriteLine("Trying to fix with AI.");
|
||||
await _logger.WarnAsync($"DisplayLayoutPlanFromContent: trying to fix with AI", $"{aiResponse}");
|
||||
layoutPlan = await ValidateAndFixJson<LayoutPlan>(aiResponse, FixJsonWithAI);
|
||||
if (layoutPlan?.Blocks == null || layoutPlan.Blocks.Any(b => string.IsNullOrEmpty(b.Type) || string.IsNullOrEmpty(b.RawContent)))
|
||||
{
|
||||
Console.WriteLine("Invalid block structure in response.");
|
||||
|
||||
await _logger.ErrorAsync($"DisplayLayoutPlanFromContent: Invalid block structure in response.", $"{layoutPlan.Blocks.Count}");
|
||||
layoutPlan = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Deserialization failed: " + ex.Message);
|
||||
Console.WriteLine("Deserialization failed on json: " + aiResponse);
|
||||
await _logger.ErrorAsync($"DisplayLayoutPlanFromContent: ai Response", ex.Message);
|
||||
layoutPlan = null;
|
||||
}
|
||||
|
||||
if (layoutPlan == null)
|
||||
{
|
||||
retry++;
|
||||
Console.WriteLine("Retrying due to invalid format...");
|
||||
await _logger.WarnAsync($"DisplayLayoutPlanFromContent: Retrying due to invalid format...", $"{aiResponse}");
|
||||
|
||||
}
|
||||
}
|
||||
foreach (var block in layoutPlan.Blocks)
|
||||
{
|
||||
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: reading blocks", $"{block.Order}, {block.Type}, {block.PreferredSnippetId}");
|
||||
Console.WriteLine($"{block.Order}, {block.Type}, {block.PreferredSnippetId}");
|
||||
|
||||
if (block.ContentMap != null && block.ContentMap.Count > 0)
|
||||
{
|
||||
foreach (var key in block.ContentMap.Keys)
|
||||
{
|
||||
Console.WriteLine($"key {key} : value: {block.ContentMap[key]}");
|
||||
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: reading ContentMap", $"key {key} : value: {block.ContentMap[key]}");
|
||||
//Console.WriteLine($"key {key} : value: {block.ContentMap[key]}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1146,6 +1192,7 @@ namespace BLAIzor.Services
|
|||
//for methodResult
|
||||
public async Task DisplayHtml(string sessionId, string interMediateResult, string methodToCall = "", string methodParameter = "")//, string[]? topics = null)
|
||||
{
|
||||
await _logger.InfoAsync($"DisplayHtml: method called for methodResult", sessionId.ToString());
|
||||
//Console.Write($"\n SessionId: {sessionId} \n");
|
||||
OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
|
||||
|
||||
|
|
@ -1162,6 +1209,7 @@ namespace BLAIzor.Services
|
|||
AiProvider = GetAiSettings();
|
||||
if (AiProvider == "cerebras")
|
||||
{
|
||||
await _logger.InfoAsync($"DisplayHtml: streamed response", sessionId.ToString());
|
||||
await _cerebrasAPIService.GetCerebrasStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
||||
}
|
||||
else if (AiProvider == "chatgpt")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
using BLAIzor.Interfaces;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BLAIzor.Services
|
||||
{
|
||||
public class BrightDataService : IBrightDataService
|
||||
{
|
||||
|
||||
private readonly ISimpleLogger _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private string _apiToken;
|
||||
public static IConfiguration? _configuration;
|
||||
public BrightDataService(ISimpleLogger logger, IHttpClientFactory httpClientFactory, IConfiguration configuration)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
private string GetScraperSettings() =>
|
||||
_configuration?.GetSection("ScraperSettings")?.GetValue<string>("Provider") ?? string.Empty;
|
||||
|
||||
public string GetApiKey()
|
||||
{
|
||||
if (_configuration == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
if (_configuration.GetSection("ScraperSettings") == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return _configuration.GetSection("ScraperSettings").GetValue<string>("ApiKey")!;
|
||||
|
||||
}
|
||||
|
||||
public async Task<string?> ScrapeFacebookPostsAsync(string pageUrl, int numPosts = 10)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pageUrl))
|
||||
return null;
|
||||
|
||||
_apiToken = GetApiKey();
|
||||
|
||||
try
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiToken}");
|
||||
|
||||
var url = "https://api.brightdata.com/datasets/v3/trigger?dataset_id=gd_lkaxegm826bjpoo9m5&include_errors=true&limit_multiple_results=20";
|
||||
|
||||
var payload = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
url = pageUrl,
|
||||
num_of_posts = numPosts,
|
||||
posts_to_not_include = Array.Empty<string>(),
|
||||
start_date = "",
|
||||
end_date = ""
|
||||
}
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
var response = await client.PostAsync(url, new StringContent(json, Encoding.UTF8, "application/json"));
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var scrapeId = await response.Content.ReadAsStringAsync();
|
||||
//"snapshot_id
|
||||
|
||||
if (string.IsNullOrWhiteSpace(scrapeId))
|
||||
{
|
||||
await _logger.ErrorAsync("Failed to initiate scraping for Facebook posts.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
//have to keep checking:
|
||||
//result: "{\"snapshot_id\":\"s_mec12qv422avgbv9jl\"}"
|
||||
//let's extract the scrapeId from the response
|
||||
scrapeId = scrapeId.Trim('"').Split(':')[1].Trim('"');
|
||||
//remove all other characters
|
||||
scrapeId = scrapeId.Replace("{", "").Replace("}", "").Replace("\"", "");
|
||||
|
||||
var checkUrl = $"https://api.brightdata.com/datasets/v3/progress/{scrapeId}";
|
||||
var statusResponse = await client.GetAsync(checkUrl);
|
||||
var responseString = await statusResponse.Content.ReadAsStringAsync();
|
||||
|
||||
int attempt = 0;
|
||||
//make a cycle
|
||||
|
||||
while (responseString.Contains("status") && !responseString.Contains("ready"))
|
||||
{
|
||||
if (attempt >= 60)
|
||||
{
|
||||
await _logger.ErrorAsync($"Failed to get scraping status for Facebook page: {pageUrl} after multiple attempts.");
|
||||
return null;
|
||||
}
|
||||
// Wait for a while before retrying
|
||||
await Task.Delay(5000); // Wait for 5 seconds
|
||||
statusResponse = await client.GetAsync(checkUrl);
|
||||
responseString = await statusResponse.Content.ReadAsStringAsync();
|
||||
attempt++;
|
||||
|
||||
}
|
||||
|
||||
// Now fetch the snapshot
|
||||
|
||||
var snapshotUrl = $"https://api.brightdata.com/datasets/v3/snapshot/{scrapeId}?format=json";
|
||||
|
||||
var snapshotResponse = await client.GetAsync(snapshotUrl);
|
||||
var snapshotString = await snapshotResponse.Content.ReadAsStringAsync();
|
||||
|
||||
return snapshotString;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error scraping Facebook: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using BLAIzor.Models;
|
||||
|
||||
namespace BLAIzor.Services
|
||||
{
|
||||
public class CacheService
|
||||
{
|
||||
private readonly WebsiteContentLoaderService _websiteContentLoaderService;
|
||||
private readonly ISimpleLogger _logger;
|
||||
private readonly ScopedContentService _scopedContentService;
|
||||
private readonly ContentEditorService _contentEditorService;
|
||||
private readonly QDrantService _qDrantService;
|
||||
|
||||
public CacheService(
|
||||
WebsiteContentLoaderService websiteContentLoaderService,
|
||||
ISimpleLogger logger,
|
||||
ScopedContentService scopedContentService,
|
||||
ContentEditorService contentEditorService,
|
||||
QDrantService qDrantService
|
||||
)
|
||||
{
|
||||
_websiteContentLoaderService = websiteContentLoaderService;
|
||||
_logger = logger;
|
||||
_scopedContentService = scopedContentService;
|
||||
_contentEditorService = contentEditorService;
|
||||
_qDrantService = qDrantService;
|
||||
}
|
||||
|
||||
public async Task<WebsiteContentModel> UpdateContentCache(string sessionId, int siteId)
|
||||
{
|
||||
await _logger.InfoAsync($"UpdateCache: method called", $"sessionId: {sessionId}, siteId: {siteId}");
|
||||
SiteInfo site = await _contentEditorService.GetSiteInfoByIdAsync(siteId);
|
||||
WebsiteContentModel siteModel = null;
|
||||
siteModel = await _websiteContentLoaderService.LoadAllAsync(
|
||||
site,
|
||||
_qDrantService.GetPointsFromQdrantAsyncByPointIds
|
||||
);
|
||||
_scopedContentService.WebsiteContentModel = siteModel;
|
||||
return siteModel;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using BLAIzor.Data;
|
||||
using BLAIzor.Models;
|
||||
using Microsoft.DotNet.Scaffolding.Shared;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Identity.Client;
|
||||
|
||||
|
|
@ -9,24 +10,50 @@ namespace BLAIzor.Services
|
|||
{
|
||||
//private readonly AIService _aiService;
|
||||
private readonly OpenAIApiService _openAIApiService;
|
||||
private readonly OpenAiRealtimeService _openAIRealtimeService;
|
||||
private readonly DeepSeekApiService _deepSeekApiService;
|
||||
private readonly CerebrasAPIService _cerebrasAPIService;
|
||||
private readonly KimiApiService _kimiApiService;
|
||||
//private readonly ApplicationDbContext _context;
|
||||
private readonly QDrantService _qDrantService;
|
||||
private readonly HtmlSnippetProcessor _htmlSnippetProcessor;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly ScopedContentService _scopedContentService;
|
||||
private readonly ContentEditorService _contentEditorService;
|
||||
private readonly ISimpleLogger _logger;
|
||||
|
||||
public static IConfiguration? _configuration;
|
||||
|
||||
public ContentEditorAIService(/*AIService aiService,*/ OpenAIApiService openAIApiService, /*ApplicationDbContext context,*/ QDrantService qDrantService, HtmlSnippetProcessor htmlSnippetProcessor, IServiceScopeFactory serviceScopeFactory, ScopedContentService scopedContentService, IConfiguration? configuration, ContentEditorService contentEditorService)
|
||||
public bool UseWebsocket = false;
|
||||
private string AiProvider = "";
|
||||
|
||||
public ContentEditorAIService(
|
||||
OpenAIApiService openAIApiService,
|
||||
DeepSeekApiService deepSeekApiService,
|
||||
OpenAiRealtimeService openAIRealtimeService,
|
||||
CerebrasAPIService cerebrasAPIService,
|
||||
KimiApiService kimiApiService,
|
||||
/*ApplicationDbContext context,*/
|
||||
QDrantService qDrantService,
|
||||
HtmlSnippetProcessor htmlSnippetProcessor,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
ScopedContentService scopedContentService,
|
||||
ISimpleLogger logger,
|
||||
IConfiguration? configuration,
|
||||
ContentEditorService contentEditorService)
|
||||
{
|
||||
//_aiService = aiService;
|
||||
_openAIApiService = openAIApiService;
|
||||
_deepSeekApiService = deepSeekApiService;
|
||||
_openAIRealtimeService = openAIRealtimeService;
|
||||
_cerebrasAPIService = cerebrasAPIService;
|
||||
_kimiApiService = kimiApiService;
|
||||
//_context = context;
|
||||
_qDrantService = qDrantService;
|
||||
_htmlSnippetProcessor = htmlSnippetProcessor;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_scopedContentService = scopedContentService;
|
||||
_logger = logger;
|
||||
_configuration = configuration;
|
||||
_contentEditorService = contentEditorService;
|
||||
}
|
||||
|
|
@ -34,22 +61,88 @@ namespace BLAIzor.Services
|
|||
private string GetAiEmbeddingSettings() =>
|
||||
_configuration?.GetSection("AiSettings")?.GetValue<string>("EmbeddingService") ?? string.Empty;
|
||||
|
||||
private string GetAiSettings() =>
|
||||
_configuration?.GetSection("AiSettings")?.GetValue<string>("Provider") ?? string.Empty;
|
||||
|
||||
// Existing methods
|
||||
public async Task<List<string>> GetMenuSuggestionsAsync(string sessionId, string prompt)
|
||||
public async Task<string> GetMenuSuggestionsAsync(string sessionId, string prompt)
|
||||
{
|
||||
string systemMessage = "You are a helpful assistant that helps the user in creating a website.";
|
||||
var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||
|
||||
return result.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim('-').Trim())
|
||||
.ToList() ?? new List<string>();
|
||||
//return result.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||
// .Select(line => line.Trim('-').Trim())
|
||||
// .ToList() ?? new List<string>();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<string> GetFacebookContentAsync(string sessionId, string prompt)
|
||||
{
|
||||
string systemMessage = "You are a helpful assistant that helps the user in creating a website content from facebook posts.";
|
||||
var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||
await _logger.InfoAsync("Facebook content: " + result);
|
||||
//return result.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||
// .Select(line => line.Trim('-').Trim())
|
||||
// .ToList() ?? new List<string>();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<string> GetGeneratedContentAsync(string sessionId, string prompt)
|
||||
{
|
||||
string systemMessage = "You are a helpful assistant that helps the user in creating a website. Do not generate html, just plain text.";
|
||||
var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||
string systemMessage = "You are a helpful assistant that helps the user plan the content of a website. Do not generate html, just plain text.";
|
||||
//var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||
string result = "";
|
||||
|
||||
AiProvider = GetAiSettings();
|
||||
if (AiProvider == "cerebras")
|
||||
{
|
||||
result = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, prompt);
|
||||
|
||||
}
|
||||
else if (AiProvider == "chatgpt")
|
||||
{
|
||||
result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||
|
||||
}
|
||||
else if (AiProvider == "deepseek")
|
||||
{
|
||||
//await _deepSeekApiService.GetChatGPTStreamedResponse(systemMessage, userMessage);
|
||||
result = await _deepSeekApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||
}
|
||||
if (AiProvider == "kimi")
|
||||
{
|
||||
result = await _kimiApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<string> GetPhotoPromptAsync(string sessionId, string prompt)
|
||||
{
|
||||
string systemMessage = "You are a helpful assistant that writes image prompts. Respond only with the image prompt, no explanation, or information added.";
|
||||
//var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||
string result = "";
|
||||
|
||||
AiProvider = GetAiSettings();
|
||||
if (AiProvider == "cerebras")
|
||||
{
|
||||
result = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, prompt);
|
||||
|
||||
}
|
||||
else if (AiProvider == "chatgpt")
|
||||
{
|
||||
result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||
|
||||
}
|
||||
else if (AiProvider == "deepseek")
|
||||
{
|
||||
//await _deepSeekApiService.GetChatGPTStreamedResponse(systemMessage, userMessage);
|
||||
result = await _deepSeekApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||
}
|
||||
if (AiProvider == "kimi")
|
||||
{
|
||||
result = await _kimiApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,32 +3,55 @@ using BLAIzor.Models;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Identity.Client;
|
||||
using BLAIzor.Helpers;
|
||||
|
||||
|
||||
using Qdrant.Client.Grpc;
|
||||
using System.Collections;
|
||||
|
||||
namespace BLAIzor.Services
|
||||
{
|
||||
|
||||
|
||||
public class ContentEditorService
|
||||
{
|
||||
//private readonly AIService _aiService;
|
||||
#region Private Fields
|
||||
|
||||
private readonly OpenAIApiService _openAIApiService;
|
||||
//private readonly ApplicationDbContext _context;
|
||||
private readonly QDrantService _qDrantService;
|
||||
private readonly HtmlSnippetProcessor _htmlSnippetProcessor;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly ScopedContentService _scopedContentService;
|
||||
private readonly OpenAIEmbeddingService _openAIEmbeddingService;
|
||||
private readonly LocalEmbeddingService _localEmbeddingService;
|
||||
private readonly ISimpleLogger _logger;
|
||||
|
||||
|
||||
public static IConfiguration? _configuration;
|
||||
|
||||
public ContentEditorService(/*AIService aiService,*/ OpenAIApiService openAIApiService, /*ApplicationDbContext context,*/ QDrantService qDrantService, OpenAIEmbeddingService openAIEmbeddingService, LocalEmbeddingService localEmbeddingService, HtmlSnippetProcessor htmlSnippetProcessor, IServiceScopeFactory serviceScopeFactory, ScopedContentService scopedContentService, IConfiguration? configuration)
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentEditorService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="openAIApiService">The OpenAI API service.</param>
|
||||
/// <param name="qDrantService">The Qdrant service.</param>
|
||||
/// <param name="openAIEmbeddingService">The OpenAI embedding service.</param>
|
||||
/// <param name="localEmbeddingService">The local embedding service.</param>
|
||||
/// <param name="htmlSnippetProcessor">The HTML snippet processor.</param>
|
||||
/// <param name="serviceScopeFactory">The service scope factory for creating database contexts.</param>
|
||||
/// <param name="scopedContentService">The scoped content service.</param>
|
||||
/// <param name="configuration">The application configuration.</param>
|
||||
public ContentEditorService(OpenAIApiService openAIApiService,
|
||||
QDrantService qDrantService,
|
||||
OpenAIEmbeddingService openAIEmbeddingService,
|
||||
LocalEmbeddingService localEmbeddingService,
|
||||
HtmlSnippetProcessor htmlSnippetProcessor,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
ScopedContentService scopedContentService,
|
||||
IConfiguration? configuration,
|
||||
ISimpleLogger logger)
|
||||
{
|
||||
//_aiService = aiService;
|
||||
_openAIApiService = openAIApiService;
|
||||
//_context = context;
|
||||
_qDrantService = qDrantService;
|
||||
_openAIEmbeddingService = openAIEmbeddingService;
|
||||
_localEmbeddingService = localEmbeddingService;
|
||||
|
|
@ -36,84 +59,132 @@ namespace BLAIzor.Services
|
|||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_scopedContentService = scopedContentService;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the AI embedding service setting from the configuration.
|
||||
/// </summary>
|
||||
/// <returns>The name of the embedding service (e.g., "local", "openai").</returns>
|
||||
private string GetAiEmbeddingSettings() =>
|
||||
_configuration?.GetSection("AiSettings")?.GetValue<string>("EmbeddingService") ?? string.Empty;
|
||||
|
||||
// CRUD methods for MenuItems
|
||||
/// <summary>
|
||||
/// Removes content chunks and their corresponding Qdrant entries by their IDs.
|
||||
/// </summary>
|
||||
/// <param name="chunkIds">A list of content chunk IDs to remove.</param>
|
||||
/// <param name="collectionName">The name of the Qdrant collection.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
private async Task RemoveChunksAndQdrantEntriesByIdsAsync(List<int> chunkIds, string collectionName)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Create a new MenuItem
|
||||
var chunks = await db.ContentChunks
|
||||
.Where(c => chunkIds.Contains(c.Id))
|
||||
.ToListAsync();
|
||||
|
||||
var pointIds = chunks.Select(c => Guid.Parse(c.QdrantPointId)).ToArray();
|
||||
await _qDrantService.DeletePointsAsync(pointIds, collectionName);
|
||||
|
||||
db.ContentChunks.RemoveRange(chunks);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a unique vector collection name for a given site.
|
||||
/// </summary>
|
||||
/// <param name="siteInfo">The site information.</param>
|
||||
/// <returns>A generated vector collection name.</returns>
|
||||
public string GetGeneratedVectorCollectionName(SiteInfo siteInfo)
|
||||
{
|
||||
var safeName = siteInfo.SiteName?.ToLower().Replace(" ", "_").Replace("-", "_");
|
||||
return $"site_{safeName}_{Guid.NewGuid().ToString().Substring(0, 8)}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MenuItem CRUD Operations
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new menu item to the database.
|
||||
/// </summary>
|
||||
/// <param name="menuItem">The menu item to add.</param>
|
||||
/// <returns>The added menu item with its generated ID.</returns>
|
||||
public async Task<MenuItem> AddMenuItemAsync(MenuItem menuItem)
|
||||
{
|
||||
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
|
||||
var result = await _context.MenuItems.AddAsync(menuItem);
|
||||
await _context.SaveChangesAsync();
|
||||
return result.Entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds items as list
|
||||
/// Adds multiple menu items to the database.
|
||||
/// </summary>
|
||||
/// <param name="menuItems"></param>
|
||||
/// <returns>the number of modified rows</returns>
|
||||
/// <param name="menuItems">A list of menu items to add.</param>
|
||||
/// <returns>The number of state entries written to the database.</returns>
|
||||
public async Task<int> AddMenuItemsAsync(List<MenuItem> menuItems)
|
||||
{
|
||||
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
_context.MenuItems.AddRange(menuItems);
|
||||
var result = await _context.SaveChangesAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Get all MenuItems for a specific SiteInfo
|
||||
/// <summary>
|
||||
/// Retrieves all menu items for a specific site.
|
||||
/// </summary>
|
||||
/// <param name="siteInfoId">The ID of the site.</param>
|
||||
/// <returns>A list of menu items belonging to the specified site.</returns>
|
||||
public async Task<List<MenuItem>> GetMenuItemsBySiteIdAsync(int siteInfoId)
|
||||
{
|
||||
await _logger.InfoAsync($"GetMenuItemsBySiteIdAsync: method called", siteInfoId.ToString());
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
var result = await _context.MenuItems
|
||||
.Where(m => m.SiteInfoId == siteInfoId)
|
||||
.ToListAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//GET MENUS WITH CHILDREN
|
||||
/// <summary>
|
||||
/// Retrieves all top-level menu items for a specific site, including their children.
|
||||
/// </summary>
|
||||
/// <param name="siteInfoId">The ID of the site.</param>
|
||||
/// <returns>A list of top-level menu items with their associated children.</returns>
|
||||
public async Task<List<MenuItem>> GetMenuItemsBySiteIdWithChildrenAsync(int siteInfoId)
|
||||
{
|
||||
await _logger.InfoAsync($"GetMenuItemsBySiteIdWithChildrenAsync: method called", siteInfoId.ToString());
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
var result = await _context.MenuItems
|
||||
.Where(m => m.SiteInfoId == siteInfoId && m.ParentId == null)
|
||||
.Include(m => m.Children)
|
||||
.ToListAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update an existing MenuItem
|
||||
/// <summary>
|
||||
/// Updates an existing menu item in the database.
|
||||
/// </summary>
|
||||
/// <param name="menuItem">The menu item with updated information.</param>
|
||||
/// <returns>The updated menu item.</returns>
|
||||
/// <exception cref="Exception">Thrown if the menu item is not found.</exception>
|
||||
public async Task<MenuItem> UpdateMenuItemAsync(MenuItem menuItem)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
|
@ -132,21 +203,21 @@ namespace BLAIzor.Services
|
|||
existingMenuItem.SortOrder = menuItem.SortOrder;
|
||||
existingMenuItem.ShowInMainMenu = menuItem.ShowInMainMenu;
|
||||
|
||||
// No need for _context.MenuItems.Update(existingMenuItem);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return existingMenuItem;
|
||||
}
|
||||
|
||||
// Delete a MenuItem
|
||||
/// <summary>
|
||||
/// Deletes a menu item from the database.
|
||||
/// </summary>
|
||||
/// <param name="menuItemId">The ID of the menu item to delete.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
/// <exception cref="Exception">Thrown if the menu item is not found.</exception>
|
||||
public async Task DeleteMenuItemAsync(int menuItemId)
|
||||
{
|
||||
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
var menuItem = await _context.MenuItems.FindAsync(menuItemId);
|
||||
if (menuItem == null)
|
||||
{
|
||||
|
|
@ -155,32 +226,39 @@ namespace BLAIzor.Services
|
|||
|
||||
_context.MenuItems.Remove(menuItem);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ContentGroup Operations
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the first ContentGroup associated with the given SiteInfoId.
|
||||
/// Returns null if not found.
|
||||
/// </summary>
|
||||
/// <param name="siteInfoId">The ID of the site information.</param>
|
||||
/// <returns>The first ContentGroup found, or null if none exists.</returns>
|
||||
public async Task<ContentGroup?> GetContentGroupBySiteInfoIdAsync(int siteInfoId)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
var result = await _context.ContentGroups
|
||||
.Where(cg => cg.SiteInfoId == siteInfoId)
|
||||
.OrderBy(cg => cg.Id) // or by LastUpdated, Name, etc.
|
||||
.OrderBy(cg => cg.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first ContentGroup for a site (can be filtered by type or slug).
|
||||
/// Returns a list of ContentGroups for a site, with optional filtering by type or slug.
|
||||
/// </summary>
|
||||
/// <param name="siteInfoId">The ID of the site information.</param>
|
||||
/// <param name="type">Optional. The type of the content group to filter by.</param>
|
||||
/// <param name="slug">Optional. The slug of the content group to filter by.</param>
|
||||
/// <returns>A list of matching ContentGroups.</returns>
|
||||
public async Task<List<ContentGroup>> GetContentGroupsBySiteInfoIdAsync(int siteInfoId, string? type = null, string? slug = null)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
|
|
@ -205,6 +283,12 @@ namespace BLAIzor.Services
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing content group in the database.
|
||||
/// </summary>
|
||||
/// <param name="updatedGroup">The content group with updated information.</param>
|
||||
/// <returns>The updated content group.</returns>
|
||||
/// <exception cref="Exception">Thrown if the content group is not found.</exception>
|
||||
public async Task<ContentGroup> UpdateContentGroupByIdAsync(ContentGroup updatedGroup)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
|
@ -228,6 +312,11 @@ namespace BLAIzor.Services
|
|||
return existingGroup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a content group by its ID.
|
||||
/// </summary>
|
||||
/// <param name="contentGroupId">The ID of the content group to delete.</param>
|
||||
/// <returns><c>true</c> if the content group was deleted, <c>false</c> otherwise.</returns>
|
||||
public async Task<bool> DeleteContentGroupByIdAsync(int contentGroupId)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
|
@ -243,6 +332,11 @@ namespace BLAIzor.Services
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new content group in the database.
|
||||
/// </summary>
|
||||
/// <param name="newGroup">The new content group to create.</param>
|
||||
/// <returns>The newly created content group with its generated ID and timestamps.</returns>
|
||||
public async Task<ContentGroup> CreateContentGroupAsync(ContentGroup newGroup)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
|
@ -257,10 +351,12 @@ namespace BLAIzor.Services
|
|||
return newGroup;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns all ContentGroups for a site (can be filtered by type).
|
||||
/// Returns all ContentGroups for a site, with optional filtering by type.
|
||||
/// </summary>
|
||||
/// <param name="siteInfoId">The ID of the site information.</param>
|
||||
/// <param name="type">Optional. The type of the content group to filter by.</param>
|
||||
/// <returns>A list of matching ContentGroups.</returns>
|
||||
public async Task<List<ContentGroup>> GetAllContentGroupsBySiteInfoIdAsync(int siteInfoId, string? type = null)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
|
|
@ -278,7 +374,17 @@ namespace BLAIzor.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<ContentItem> GetContentItemByIdAsync(int contentItemId, string? type = null)
|
||||
#endregion
|
||||
|
||||
#region ContentItem Operations
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a content item by its ID, optionally filtered by type.
|
||||
/// </summary>
|
||||
/// <param name="contentItemId">The ID of the content item.</param>
|
||||
/// <param name="type">Optional. The type of the content item to filter by.</param>
|
||||
/// <returns>The matching content item with its chunks, or null if not found.</returns>
|
||||
public async Task<ContentItem?> GetContentItemByIdAsync(int contentItemId, string? type = null)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
|
|
@ -290,6 +396,11 @@ namespace BLAIzor.Services
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a content item by its ID, including related ContentGroup, Chunks, and SiteInfo.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the content item.</param>
|
||||
/// <returns>The matching content item, or null if not found.</returns>
|
||||
public async Task<ContentItem?> GetContentItemByIdAsync(int id)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
|
@ -298,41 +409,25 @@ namespace BLAIzor.Services
|
|||
return query.FirstOrDefault();
|
||||
}
|
||||
|
||||
//public async Task<ContentItem> UpdateContentItemByIdAsync(ContentItem item)
|
||||
//{
|
||||
// using var scope = _serviceScopeFactory.CreateScope();
|
||||
// var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// var existing = await db.ContentItems.FindAsync(item.Id);
|
||||
// if (existing == null) throw new Exception("ContentItem not found.");
|
||||
|
||||
// // Update properties manually (or use AutoMapper if you prefer)
|
||||
// existing.Title = item.Title;
|
||||
// existing.Description = item.Description;
|
||||
// existing.Content = item.Content;
|
||||
// existing.Language = item.Language;
|
||||
// existing.Tags = item.Tags;
|
||||
// existing.IsPublished = item.IsPublished;
|
||||
// existing.Version = item.Version;
|
||||
// existing.LastUpdated = item.LastUpdated;
|
||||
|
||||
// await db.SaveChangesAsync();
|
||||
// return existing;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new content item, chunks its content, generates embeddings, and inserts them into Qdrant.
|
||||
/// </summary>
|
||||
/// <param name="item">The content item to create.</param>
|
||||
/// <param name="collectionName">The name of the Qdrant collection to insert into.</param>
|
||||
/// <returns>The created content item.</returns>
|
||||
/// <exception cref="Exception">Thrown if the saved ContentItem cannot be retrieved.</exception>
|
||||
public async Task<ContentItem> CreateContentItemAsync(ContentItem item, string collectionName)
|
||||
{
|
||||
await _logger.InfoAsync($"CreateContentItemAsync: method called", item.Id.ToString());
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
item.CreatedAt = DateTime.UtcNow;
|
||||
item.LastUpdated = DateTime.UtcNow;
|
||||
|
||||
// Save to DB first to get ContentItem.Id
|
||||
db.ContentItems.Add(item);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// Get the full ContentItem with ContentGroup included (needed for SiteId)
|
||||
var fullItem = await db.ContentItems
|
||||
.Include(ci => ci.ContentGroup)
|
||||
.FirstOrDefaultAsync(ci => ci.Id == item.Id);
|
||||
|
|
@ -340,17 +435,15 @@ namespace BLAIzor.Services
|
|||
if (fullItem == null)
|
||||
throw new Exception("Failed to retrieve saved ContentItem.");
|
||||
|
||||
// 🧠 Chunking
|
||||
if (!string.IsNullOrEmpty(item.Content))
|
||||
{
|
||||
var chunks = ChunkingHelper.SplitStructuredText(item.Content, 3000); // customize if needed
|
||||
var chunks = ChunkingHelper.SplitStructuredText(item.Content, 3000);
|
||||
var vectorizedChunks = await VectorizeChunksWithGuidsAsync(chunks, fullItem, collectionName);
|
||||
|
||||
// 🔗 Save chunk references
|
||||
var chunkEntities = vectorizedChunks.Select((chunk, index) => new ContentChunk
|
||||
{
|
||||
ContentItemId = item.Id,
|
||||
QdrantPointId = chunk.UId, // now using GUIDs
|
||||
QdrantPointId = chunk.UId,
|
||||
ChunkIndex = index,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
}).ToList();
|
||||
|
|
@ -362,9 +455,13 @@ namespace BLAIzor.Services
|
|||
return item;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Vectorizes a list of content chunks, generates unique GUIDs for each, and inserts them into Qdrant.
|
||||
/// </summary>
|
||||
/// <param name="chunks">The list of text chunks to vectorize.</param>
|
||||
/// <param name="item">The content item associated with these chunks.</param>
|
||||
/// <param name="collectionName">The name of the Qdrant collection.</param>
|
||||
/// <returns>A list of <see cref="WebPageContent"/> representing the vectorized chunks.</returns>
|
||||
public async Task<List<WebPageContent>> VectorizeChunksWithGuidsAsync(List<string> chunks, ContentItem item, string collectionName)
|
||||
{
|
||||
var result = new List<WebPageContent>();
|
||||
|
|
@ -388,6 +485,7 @@ namespace BLAIzor.Services
|
|||
|
||||
result.Add(new WebPageContent
|
||||
{
|
||||
Id = Guid.Parse(uid),
|
||||
UId = uid,
|
||||
SiteId = item.ContentGroup.SiteInfoId,
|
||||
Type = "content-item",
|
||||
|
|
@ -403,14 +501,19 @@ namespace BLAIzor.Services
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Vectorizes a list of content chunks and inserts them into Qdrant.
|
||||
/// </summary>
|
||||
/// <param name="chunks">The list of text chunks to vectorize.</param>
|
||||
/// <param name="item">The content item associated with these chunks.</param>
|
||||
/// <param name="collectionName">The name of the Qdrant collection.</param>
|
||||
/// <returns>A list of <see cref="WebPageContent"/> representing the vectorized chunks.</returns>
|
||||
public async Task<List<WebPageContent>> VectorizeChunksAsync(List<string> chunks, ContentItem item, string collectionName)
|
||||
{
|
||||
var result = new List<WebPageContent>();
|
||||
|
||||
foreach (var (chunk, index) in chunks.Select((c, i) => (c, i)))
|
||||
{
|
||||
//var combinedText = $"{pageContent.Name}: {pageContent.Description} - {chunk}";
|
||||
var combinedText = $"{chunk}";
|
||||
float[] embedding = [];
|
||||
|
||||
|
|
@ -424,10 +527,6 @@ namespace BLAIzor.Services
|
|||
embedding = await _openAIEmbeddingService.GenerateEmbeddingAsync(combinedText);
|
||||
}
|
||||
|
||||
// Add data for batch insertion
|
||||
|
||||
var vector = embedding;
|
||||
|
||||
var uid = Guid.NewGuid();
|
||||
|
||||
var webChunk = new WebPageContent
|
||||
|
|
@ -439,34 +538,22 @@ namespace BLAIzor.Services
|
|||
Name = item.Title,
|
||||
Description = item.Description,
|
||||
Content = chunk,
|
||||
Vectors = vector,
|
||||
Vectors = embedding,
|
||||
LastUpdated = DateTime.UtcNow
|
||||
};
|
||||
|
||||
|
||||
result.Add(webChunk);
|
||||
}
|
||||
await _qDrantService.QDrantInsertManyAsync(result, collectionName);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private async Task RemoveChunksAndQdrantEntriesByIdsAsync(List<int> chunkIds, string collectionName)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
var chunks = await db.ContentChunks
|
||||
.Where(c => chunkIds.Contains(c.Id))
|
||||
.ToListAsync();
|
||||
|
||||
var pointIds = chunks.Select(c => Guid.Parse(c.QdrantPointId)).ToArray();
|
||||
await _qDrantService.DeletePointsAsync(pointIds, collectionName);
|
||||
|
||||
db.ContentChunks.RemoveRange(chunks);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces a re-chunking and re-embedding of all content items within a specified content group.
|
||||
/// </summary>
|
||||
/// <param name="contentGroupId">The ID of the content group to re-chunk.</param>
|
||||
/// <param name="collectionName">The name of the Qdrant collection. If null, the existing collection name will be used.</param>
|
||||
/// <returns><c>true</c> if the operation was successful, <c>false</c> otherwise.</returns>
|
||||
public async Task<bool> ForceRechunkContentGroupAsync(int contentGroupId, string collectionName = null)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
|
@ -488,12 +575,17 @@ namespace BLAIzor.Services
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the recreation of a Qdrant collection for a given site, including re-chunking and re-embedding all content.
|
||||
/// </summary>
|
||||
/// <param name="siteInfoId">The ID of the site for which to recreate the collection.</param>
|
||||
/// <returns><c>true</c> if the collection was successfully recreated and content re-synced, <c>false</c> otherwise.</returns>
|
||||
/// <exception cref="Exception">Thrown if the new Qdrant collection cannot be created.</exception>
|
||||
public async Task<bool> ForceRecreateQdrantCollectionAsync(int siteInfoId)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Load site + all content + chunks
|
||||
var site = await db.SiteInfos
|
||||
.Include(s => s.ContentGroups)
|
||||
.ThenInclude(g => g.Items)
|
||||
|
|
@ -505,21 +597,17 @@ namespace BLAIzor.Services
|
|||
|
||||
string oldCollectionName = site.VectorCollectionName;
|
||||
|
||||
// ❌ Delete old Qdrant collection
|
||||
await _qDrantService.DeleteCollectionAsync(oldCollectionName);
|
||||
|
||||
// ✅ Generate and create new collection
|
||||
string newCollectionName = GetGeneratedVectorCollectionName(site);
|
||||
|
||||
bool created = await _qDrantService.CreateQdrantCollectionAsync(newCollectionName);
|
||||
if (!created)
|
||||
throw new Exception($"Failed to create Qdrant collection '{newCollectionName}'.");
|
||||
|
||||
// Update and persist new collection name
|
||||
site.VectorCollectionName = newCollectionName;
|
||||
await UpdateSiteInfoAsync(site); // Assumes this method updates it properly
|
||||
await UpdateSiteInfoAsync(site);
|
||||
|
||||
// ♻️ Rechunk and reinsert all content items using fresh DTOs
|
||||
foreach (var group in site.ContentGroups)
|
||||
{
|
||||
foreach (var item in group.Items.ToList())
|
||||
|
|
@ -545,8 +633,11 @@ namespace BLAIzor.Services
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all content items belonging to a specific content group.
|
||||
/// </summary>
|
||||
/// <param name="contentGroupId">The ID of the content group.</param>
|
||||
/// <returns>A list of content items in the specified group, ordered by last updated date descending.</returns>
|
||||
public async Task<List<ContentItem>> GetContentItemsByGroupIdAsync(int contentGroupId)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
|
|
@ -559,6 +650,38 @@ namespace BLAIzor.Services
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all content items belonging to a specific content group.
|
||||
/// </summary>
|
||||
/// <param name="contentGroupId">The ID of the content group.</param>
|
||||
/// <returns>A list of content items in the specified group, ordered by last updated date descending.</returns>
|
||||
public async Task<List<ContentItem>> GetAllContentItemsBySiteIdAsync(int siteId)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
//var site = await GetSiteInfoByIdAsync(siteId);
|
||||
//List<ContentItem> contentList = new List<ContentItem>();
|
||||
//foreach (var item in site.ContentGroups)
|
||||
//{
|
||||
// var contentItems = await GetContentItemsByGroupIdAsync(item.Id);
|
||||
// contentList.AddRange(contentItems);
|
||||
//}
|
||||
//return contentList;
|
||||
return await context.ContentItems
|
||||
.Where(ci => ci.ContentGroup.SiteInfoId == siteId)
|
||||
.Include(ci => ci.Chunks)
|
||||
.ToListAsync();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new content item in the database.
|
||||
/// </summary>
|
||||
/// <param name="newItem">The new content item to create.</param>
|
||||
/// <returns>The newly created content item with its generated ID and timestamps.</returns>
|
||||
public async Task<ContentItem?> CreateContentItemAsync(ContentItem newItem)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
|
@ -573,6 +696,15 @@ namespace BLAIzor.Services
|
|||
return newItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves changes to a content item and synchronizes its chunks and embeddings with Qdrant.
|
||||
/// If content has changed or re-chunking is forced, old chunks are removed and new ones are created and embedded.
|
||||
/// </summary>
|
||||
/// <param name="dto">The content item data transfer object with updated information.</param>
|
||||
/// <param name="collectionName">The name of the Qdrant collection to sync with.</param>
|
||||
/// <param name="forceRechunk">If set to <c>true</c>, forces re-chunking and re-embedding even if content hasn't changed.</param>
|
||||
/// <returns>The updated and synchronized content item.</returns>
|
||||
/// <exception cref="Exception">Thrown if the content item is not found.</exception>
|
||||
public async Task<ContentItem> SaveAndSyncContentItemAsync(ContentItem dto, string collectionName, bool forceRechunk = false)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
|
@ -586,10 +718,8 @@ namespace BLAIzor.Services
|
|||
if (existing == null)
|
||||
throw new Exception("ContentItem not found.");
|
||||
|
||||
// Determine if content has changed
|
||||
bool contentChanged = existing.Content?.Trim() != dto.Content?.Trim();
|
||||
|
||||
// Update scalar fields only (don't touch navigation collections directly)
|
||||
existing.Title = dto.Title;
|
||||
existing.Description = dto.Description;
|
||||
existing.Content = dto.Content;
|
||||
|
|
@ -602,26 +732,22 @@ namespace BLAIzor.Services
|
|||
{
|
||||
existing.Version++;
|
||||
|
||||
// 🧹 Remove old chunks (track-safe)
|
||||
var chunkIds = existing.Chunks.Select(c => c.Id).ToList();
|
||||
if (chunkIds.Any())
|
||||
{
|
||||
await RemoveChunksAndQdrantEntriesByIdsAsync(chunkIds, collectionName);
|
||||
|
||||
// Detach removed chunks from current EF context to avoid SaveChanges conflict
|
||||
foreach (var chunk in existing.Chunks.ToList())
|
||||
{
|
||||
db.Entry(chunk).State = EntityState.Detached;
|
||||
}
|
||||
|
||||
existing.Chunks.Clear(); // avoid tracking conflicts
|
||||
existing.Chunks.Clear();
|
||||
}
|
||||
|
||||
// 🔪 Chunk and embed again
|
||||
var newChunks = ChunkingHelper.SplitStructuredText(existing.Content, 3000);
|
||||
var webChunks = await VectorizeChunksAsync(newChunks, existing, collectionName);
|
||||
|
||||
// 💾 Save chunk metadata in SQL
|
||||
var chunkEntities = webChunks.Select((w, i) => new ContentChunk
|
||||
{
|
||||
ContentItemId = existing.Id,
|
||||
|
|
@ -637,7 +763,11 @@ namespace BLAIzor.Services
|
|||
return existing;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Groups content items by their content group type.
|
||||
/// </summary>
|
||||
/// <param name="model">The website content model containing content items.</param>
|
||||
/// <returns>A dictionary where the key is the content group type and the value is a list of content item models.</returns>
|
||||
public static Dictionary<string, List<ContentItemModel>> GroupContentItemsByType(WebsiteContentModel model)
|
||||
{
|
||||
return model.ContentItems
|
||||
|
|
@ -645,8 +775,11 @@ namespace BLAIzor.Services
|
|||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a content item by its ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the content item to delete.</param>
|
||||
/// <returns><c>true</c> if the content item was deleted, <c>false</c> otherwise.</returns>
|
||||
public async Task<bool> DeleteContentItemByIdAsync(int id)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
|
|
@ -663,11 +796,13 @@ namespace BLAIzor.Services
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the Qdrant point IDs associated with content chunks belonging to a specific content group.
|
||||
/// </summary>
|
||||
/// <param name="contentGroupId">The ID of the content group.</param>
|
||||
/// <returns>An array of Qdrant point IDs (represented as chunk indices).</returns>
|
||||
public async Task<int[]> GetPointIdsByContentGroupIdAsync(int contentGroupId)
|
||||
{
|
||||
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
|
@ -678,21 +813,20 @@ namespace BLAIzor.Services
|
|||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public string GetGeneratedVectorCollectionName(SiteInfo siteInfo)
|
||||
{
|
||||
var safeName = siteInfo.SiteName?.ToLower().Replace(" ", "_").Replace("-", "_");
|
||||
return $"site_{safeName}_{Guid.NewGuid().ToString().Substring(0, 8)}";
|
||||
}
|
||||
|
||||
#region SiteInfo Operations
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves site information by its ID.
|
||||
/// </summary>
|
||||
/// <param name="SiteInfoId">The ID of the site information.</param>
|
||||
/// <returns>The matching site information, or the first site information if not found.</returns>
|
||||
public async Task<SiteInfo> GetSiteInfoByIdAsync(int SiteInfoId)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
var result = await _context.SiteInfos.Where(x => x.Id == SiteInfoId).FirstOrDefaultAsync();
|
||||
if (result == null)
|
||||
{
|
||||
|
|
@ -703,17 +837,18 @@ namespace BLAIzor.Services
|
|||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves site information by its ID, including associated form definitions.
|
||||
/// </summary>
|
||||
/// <param name="siteId">The ID of the site.</param>
|
||||
/// <returns>The matching site information with form definitions, or a new <see cref="SiteInfo"/> if not found.</returns>
|
||||
public async Task<SiteInfo?> GetSiteInfoWithFormsByIdAsync(int siteId)
|
||||
{
|
||||
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
var result = await _context.SiteInfos.Include(s => s.FormDefinitions).FirstOrDefaultAsync(s => s.Id == siteId);
|
||||
if (result == null)
|
||||
{
|
||||
|
|
@ -724,16 +859,18 @@ namespace BLAIzor.Services
|
|||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves site information by its name.
|
||||
/// </summary>
|
||||
/// <param name="siteName">The name of the site.</param>
|
||||
/// <returns>The matching site information, or the first site information if not found.</returns>
|
||||
public async Task<SiteInfo> GetSiteInfoByNameAsync(string siteName)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
var result = await _context.SiteInfos.Where(x => x.SiteName == siteName).FirstOrDefaultAsync();
|
||||
if (result == null)
|
||||
{
|
||||
|
|
@ -743,18 +880,19 @@ namespace BLAIzor.Services
|
|||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves site information by its default URL.
|
||||
/// </summary>
|
||||
/// <param name="url">The default URL of the site.</param>
|
||||
/// <returns>The matching site information, or the first site information if not found.</returns>
|
||||
public async Task<SiteInfo> GetSiteInfoByUrlAsync(string url)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
var result = await _context.SiteInfos.Where(x => x.DefaultUrl == url).FirstOrDefaultAsync();
|
||||
if (result == null)
|
||||
{
|
||||
|
|
@ -764,111 +902,71 @@ namespace BLAIzor.Services
|
|||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates existing site information in the database.
|
||||
/// </summary>
|
||||
/// <param name="siteInfo">The site information with updated details.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task UpdateSiteInfoAsync(SiteInfo siteInfo)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
_context.SiteInfos.Update(siteInfo);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//collectionName change detection... do we really need to check? it should not be manually set.. if set, whole rechunk could be intitated...
|
||||
//public async Task UpdateSiteInfoAndReChunkIfNeededAsync(SiteInfo updatedSiteInfo)
|
||||
//{
|
||||
// using var scope = _serviceScopeFactory.CreateScope();
|
||||
// var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// var existing = await _context.SiteInfos
|
||||
// .AsNoTracking()
|
||||
// .FirstOrDefaultAsync(s => s.Id == updatedSiteInfo.Id);
|
||||
|
||||
// if (existing == null)
|
||||
// throw new Exception("SiteInfo not found.");
|
||||
|
||||
// bool collectionChanged = existing.VectorCollectionName != updatedSiteInfo.VectorCollectionName;
|
||||
|
||||
// _context.SiteInfos.Update(updatedSiteInfo);
|
||||
// await _context.SaveChangesAsync();
|
||||
|
||||
// if (collectionChanged)
|
||||
// {
|
||||
// // Create new collection if needed
|
||||
// bool exists = await _qDrantService.CollectionExistsAsync(updatedSiteInfo.VectorCollectionName);
|
||||
// if (!exists)
|
||||
// {
|
||||
// bool created = await _qDrantService.CreateQdrantCollectionAsync(updatedSiteInfo.VectorCollectionName);
|
||||
// if (!created)
|
||||
// throw new Exception("Failed to create new Qdrant collection.");
|
||||
// }
|
||||
|
||||
// // Load all ContentItems for this site
|
||||
// var contentItems = await _context.ContentItems
|
||||
// .Include(ci => ci.ContentGroup)
|
||||
// .Where(ci => ci.ContentGroup.SiteInfoId == updatedSiteInfo.Id)
|
||||
// .ToListAsync();
|
||||
|
||||
// foreach (var item in contentItems)
|
||||
// {
|
||||
// // Rechunk into new collection
|
||||
// await SaveAndSyncContentItemAsync(item, updatedSiteInfo.VectorCollectionName, true);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all sites associated with a specific user.
|
||||
/// </summary>
|
||||
/// <param name="userId">The ID of the user.</param>
|
||||
/// <returns>A list of sites belonging to the specified user.</returns>
|
||||
public async Task<List<SiteInfo>> GetUserSitesAsync(string userId)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
return await _context.SiteInfos
|
||||
.Where(s => s.UserId == userId)
|
||||
.ToListAsync();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//public async Task<List<SiteInfo>> GetSitesAsync()
|
||||
//{
|
||||
// using (var scope = _serviceScopeFactory.CreateScope())
|
||||
// {
|
||||
// var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// // Now use dbContext safely without violating DI rules
|
||||
// return await _context.SiteInfos.ToListAsync();
|
||||
|
||||
// }
|
||||
|
||||
//}
|
||||
/// <summary>
|
||||
/// Retrieves all site information records from the database.
|
||||
/// </summary>
|
||||
/// <returns>A list of all site information records.</returns>
|
||||
public async Task<List<SiteInfo>> GetAllSitesAsync()
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
return await _context.SiteInfos.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds new site information to the database and creates a corresponding Qdrant collection.
|
||||
/// </summary>
|
||||
/// <param name="siteInfo">The site information to add.</param>
|
||||
/// <returns>The added site information.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="siteInfo"/> is null.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown if an error occurs during the site creation or Qdrant collection creation.</exception>
|
||||
public async Task<SiteInfo> AddSiteInfoAsync(SiteInfo siteInfo)
|
||||
{
|
||||
using (var scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
// Now use dbContext safely without violating DI rules
|
||||
if (siteInfo == null)
|
||||
throw new ArgumentNullException(nameof(siteInfo), "SiteInfo cannot be null.");
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(siteInfo.VectorCollectionName))
|
||||
{
|
||||
siteInfo.VectorCollectionName = GetGeneratedVectorCollectionName(siteInfo);
|
||||
|
|
@ -878,14 +976,10 @@ namespace BLAIzor.Services
|
|||
var result = await _context.SaveChangesAsync();
|
||||
if (result > 0)
|
||||
{
|
||||
//check if collection exists already
|
||||
bool checkResult = await _qDrantService.CollectionExistsAsync(siteInfo.VectorCollectionName);
|
||||
if (checkResult)
|
||||
{
|
||||
//collection already exists (shouldn't exists, so it is occupied...), we need to create a new one.
|
||||
//collection does not exist, create it
|
||||
siteInfo.VectorCollectionName = GetGeneratedVectorCollectionName(siteInfo);
|
||||
//update the site info with the new collection name
|
||||
_context.SiteInfos.Update(siteInfo);
|
||||
await _context.SaveChangesAsync();
|
||||
bool qresult = await _qDrantService.CreateQdrantCollectionAsync(siteInfo.VectorCollectionName);
|
||||
|
|
@ -896,7 +990,6 @@ namespace BLAIzor.Services
|
|||
}
|
||||
else
|
||||
{
|
||||
//collection does not exist, create it
|
||||
bool qresult = await _qDrantService.CreateQdrantCollectionAsync(siteInfo.VectorCollectionName);
|
||||
if (!qresult)
|
||||
{
|
||||
|
|
@ -908,13 +1001,13 @@ namespace BLAIzor.Services
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the exception (using a logging framework like Serilog, NLog, etc.)
|
||||
throw new InvalidOperationException("An error occurred while adding the site info.", ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
///TEMPORARY
|
||||
///
|
||||
|
|
|
|||
|
|
@ -0,0 +1,213 @@
|
|||
using BLAIzor.Models;
|
||||
using Newtonsoft.Json;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
|
||||
namespace BLAIzor.Services
|
||||
{
|
||||
public class KimiApiService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public KimiApiService(IConfiguration configuration, HttpClient httpClient)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
private const string DeepSeekEndpoint = "https://api.moonshot.ai/v1/chat/completions";
|
||||
public string _apiKey;
|
||||
private Action<string> _callback;
|
||||
public string GetApiKey()
|
||||
{
|
||||
if (_configuration == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
if (_configuration.GetSection("Kimi") == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return _configuration.GetSection("Kimi").GetValue<string>("ApiKey")!;
|
||||
|
||||
}
|
||||
|
||||
public void RegisterCallback(Action<string> callback)
|
||||
{
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
public async Task<string> GetSimpleChatGPTResponse(string systemMessage, string userMessage, string? assistantMessage = null)
|
||||
{
|
||||
_apiKey = GetApiKey();
|
||||
|
||||
var requestBody = new ChatGPTRequest();
|
||||
|
||||
if (assistantMessage == null)
|
||||
{
|
||||
requestBody = new ChatGPTRequest
|
||||
{
|
||||
Model = "moonshot-v1-8k",
|
||||
Temperature = 0.2,
|
||||
Messages = new[]
|
||||
{
|
||||
new Message { Role = "system", Content = systemMessage },
|
||||
new Message { Role = "user", Content = userMessage }
|
||||
},
|
||||
Stream = false
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
requestBody = new ChatGPTRequest
|
||||
{
|
||||
Model = "moonshot-v1-8k",
|
||||
Temperature = 0.2,
|
||||
Messages = new[]
|
||||
{
|
||||
new Message { Role = "system", Content = systemMessage },
|
||||
new Message {Role = "assistant", Content = assistantMessage },
|
||||
new Message { Role = "user", Content = userMessage }
|
||||
},
|
||||
Stream = false
|
||||
};
|
||||
}
|
||||
string requestJson = System.Text.Json.JsonSerializer.Serialize(requestBody, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false
|
||||
}
|
||||
);
|
||||
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, "application/json");
|
||||
|
||||
_httpClient.DefaultRequestHeaders.Clear();
|
||||
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}");
|
||||
|
||||
var response = await _httpClient.PostAsync(DeepSeekEndpoint, requestContent);
|
||||
response.EnsureSuccessStatusCode();
|
||||
Console.Write(response.Content.ReadAsStringAsync());
|
||||
var responseBody = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Console.Write(responseBody.GetRawText());
|
||||
|
||||
var result = responseBody.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString() ?? "No response";
|
||||
Console.Write("Answer: " + result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<string> GetChatGPTStreamedResponse(string sessionId, string systemMessage, string userMessage, string? assistanMessage = null)
|
||||
{
|
||||
_apiKey = GetApiKey();
|
||||
|
||||
ChatGPTRequest finalRequestBody;
|
||||
|
||||
if (assistanMessage == null)
|
||||
{
|
||||
finalRequestBody = new ChatGPTRequest
|
||||
{
|
||||
Model = "kimi-k2-0711-preview",
|
||||
Temperature = 0.2,
|
||||
Messages = new[]
|
||||
{
|
||||
new Message { Role = "system", Content = systemMessage },
|
||||
new Message { Role = "user", Content = userMessage }
|
||||
},
|
||||
Stream = true
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
finalRequestBody = new ChatGPTRequest
|
||||
{
|
||||
Model = "kimi-k2-0711-preview",
|
||||
Temperature = 0.2,
|
||||
Messages = new[]
|
||||
{
|
||||
new Message { Role = "system", Content = systemMessage },
|
||||
new Message {Role = "assistant", Content= assistanMessage},
|
||||
new Message { Role = "user", Content = userMessage }
|
||||
},
|
||||
Stream = true
|
||||
};
|
||||
|
||||
}
|
||||
string requestJson = System.Text.Json.JsonSerializer.Serialize(finalRequestBody, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true
|
||||
}
|
||||
);
|
||||
var finalRequestContent = new StringContent(requestJson, Encoding.UTF8, "application/json");
|
||||
|
||||
Console.Write(finalRequestContent);
|
||||
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post, DeepSeekEndpoint)
|
||||
{
|
||||
Content = finalRequestContent
|
||||
};
|
||||
|
||||
_httpClient.DefaultRequestHeaders.Clear();
|
||||
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}");
|
||||
|
||||
// Use SendAsync with streamed response
|
||||
var sResponse = await _httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead);
|
||||
sResponse.EnsureSuccessStatusCode();
|
||||
|
||||
using var responseStream = await sResponse.Content.ReadAsStreamAsync();
|
||||
using var reader = new StreamReader(responseStream);
|
||||
|
||||
//Console.Write("Streamed response:");
|
||||
string streamedHtmlContent = string.Empty;
|
||||
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
var line = await reader.ReadLineAsync();
|
||||
//Console.WriteLine($"Raw Stream Line: {line}"); // Log each line
|
||||
if (!string.IsNullOrWhiteSpace(line) && line.StartsWith("data: "))
|
||||
{
|
||||
var jsonResponse = line.Substring(6); // Remove "data: " prefix
|
||||
if (jsonResponse == "[DONE]")
|
||||
{
|
||||
Console.WriteLine("Stream ended.");
|
||||
break; // End of stream
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//Console.WriteLine($"JSON Response: {jsonResponse}"); // Debug JSON response
|
||||
//TODO: do we really have to += the content, or should we just invoke with the delta?
|
||||
var chunk = JsonConvert.DeserializeObject<StreamedResponse>(jsonResponse);
|
||||
if (chunk?.Choices != null && chunk.Choices.Count > 0 && chunk.Choices[0].Delta?.Content != null)
|
||||
{
|
||||
streamedHtmlContent += chunk.Choices[0].Delta.Content; // Append the streamed content
|
||||
if (!string.IsNullOrEmpty(streamedHtmlContent) && _callback != null)
|
||||
{
|
||||
_callback?.Invoke(streamedHtmlContent);
|
||||
}
|
||||
//Console.WriteLine($"Appended Text: {chunk.Choices[0].Delta.Content}"); // Debug appended text
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("No content in this chunk.");
|
||||
}
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
Console.WriteLine($"Deserialization error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Final streamed content:");
|
||||
Console.WriteLine(streamedHtmlContent);
|
||||
|
||||
|
||||
|
||||
return streamedHtmlContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +57,8 @@ namespace BLAIzor.Services
|
|||
var requestBody = new ChatGPTRequest
|
||||
{
|
||||
Model = modelName,
|
||||
Temperature = 0.2,
|
||||
//Temperature = 0.2,
|
||||
Temperature = 1,
|
||||
Messages = assistantMessage == null || assistantMessage == string.Empty
|
||||
? new[]
|
||||
{
|
||||
|
|
@ -115,7 +116,7 @@ namespace BLAIzor.Services
|
|||
var requestBody = new ChatGPTRequest
|
||||
{
|
||||
Model = modelName,
|
||||
Temperature = 0.2,
|
||||
Temperature = 1,
|
||||
Messages = assistantMessage == null || assistantMessage == string.Empty
|
||||
? new[]
|
||||
{
|
||||
|
|
@ -170,7 +171,7 @@ namespace BLAIzor.Services
|
|||
var requestBody = new ChatGPTRequest
|
||||
{
|
||||
Model = modelName,
|
||||
Temperature = 0.2,
|
||||
Temperature = 1,
|
||||
Messages = assistantMessage == null
|
||||
? new[]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
using System.Text.Json;
|
||||
|
||||
namespace BLAIzor.Services
|
||||
{
|
||||
public class ReplicateService
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
|
||||
public ReplicateService(HttpClient http)
|
||||
{
|
||||
_http = http;
|
||||
}
|
||||
public async Task<string> GenerateImageAsync(string prompt, bool removeBackground)
|
||||
{
|
||||
return await GenerateImageAsync("https://api.replicate.com/v1/models/black-forest-labs/flux-schnell/predictions", prompt, removeBackground);
|
||||
}
|
||||
public async Task<string> GenerateLogoAsync(string prompt, bool removeBackground)
|
||||
{
|
||||
return await GenerateImageAsync("https://api.replicate.com/v1/models/google/imagen-4-fast/predictions", prompt, removeBackground);
|
||||
}
|
||||
|
||||
public async Task<string> GenerateImageAsync(string apiUrl, string prompt, bool removeBackground)
|
||||
{
|
||||
var request = new
|
||||
{
|
||||
input = new { prompt = prompt, aspect_ratio = "1:1", output_format = "jpg" }
|
||||
|
||||
};
|
||||
|
||||
var createResponse = await _http.PostAsJsonAsync(apiUrl, request);
|
||||
createResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var createJson = await createResponse.Content.ReadFromJsonAsync<JsonElement>();
|
||||
if (!createJson.TryGetProperty("id", out var idProp))
|
||||
throw new Exception("Replicate response missing prediction ID.");
|
||||
|
||||
string predictionId = idProp.GetString();
|
||||
string status = "";
|
||||
JsonElement finalJson;
|
||||
|
||||
for (int attempt = 0; attempt < 30; attempt++)
|
||||
{
|
||||
var getResponse = await _http.GetAsync($"https://api.replicate.com/v1/predictions/{predictionId}");
|
||||
getResponse.EnsureSuccessStatusCode();
|
||||
|
||||
finalJson = await getResponse.Content.ReadFromJsonAsync<JsonElement>();
|
||||
status = finalJson.GetProperty("status").GetString();
|
||||
|
||||
if (status == "succeeded")
|
||||
{
|
||||
var output = finalJson.GetProperty("output");
|
||||
|
||||
if (output.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var imageUrl = output.GetString();
|
||||
if (removeBackground)
|
||||
{
|
||||
return await RemoveBackgroundAsync(imageUrl);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return imageUrl;
|
||||
}
|
||||
}
|
||||
else if (output.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
var length = output.GetArrayLength();
|
||||
var imageUrl = output[0].ToString();
|
||||
if (removeBackground)
|
||||
{
|
||||
return await RemoveBackgroundAsync(imageUrl);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return "Replicate response succeeded but no output image URL found.";
|
||||
}
|
||||
else if (status == "failed")
|
||||
{
|
||||
return "Replicate prediction failed.";
|
||||
}
|
||||
|
||||
await Task.Delay(2500);
|
||||
}
|
||||
|
||||
return "Timeout waiting for Replicate prediction to complete.";
|
||||
}
|
||||
|
||||
public async Task<string> RemoveBackgroundAsync(string imageUrl)
|
||||
{
|
||||
var request = new
|
||||
{
|
||||
version = "a029dff38972b5fda4ec5d75d7d1cd25aeff621d2cf4946a41055d7db66b80bc",
|
||||
input = new { image = imageUrl }
|
||||
};
|
||||
|
||||
var createResponse = await _http.PostAsJsonAsync("https://api.replicate.com/v1/predictions", request);
|
||||
createResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var createJson = await createResponse.Content.ReadFromJsonAsync<JsonElement>();
|
||||
if (!createJson.TryGetProperty("id", out var idProp))
|
||||
throw new Exception("Replicate response missing prediction ID.");
|
||||
|
||||
string predictionId = idProp.GetString();
|
||||
string status = "";
|
||||
JsonElement finalJson;
|
||||
|
||||
for (int attempt = 0; attempt < 20; attempt++)
|
||||
{
|
||||
var getResponse = await _http.GetAsync($"https://api.replicate.com/v1/predictions/{predictionId}");
|
||||
getResponse.EnsureSuccessStatusCode();
|
||||
|
||||
finalJson = await getResponse.Content.ReadFromJsonAsync<JsonElement>();
|
||||
status = finalJson.GetProperty("status").GetString();
|
||||
|
||||
if (status == "succeeded")
|
||||
{
|
||||
var output = finalJson.GetProperty("output");
|
||||
|
||||
if (output.ValueKind == JsonValueKind.String)
|
||||
return output.GetString();
|
||||
|
||||
return "Replicate response succeeded but no output image URL found.";
|
||||
}
|
||||
else if (status == "failed")
|
||||
{
|
||||
return "Replicate prediction failed.";
|
||||
}
|
||||
|
||||
await Task.Delay(1500);
|
||||
}
|
||||
|
||||
return "Timeout waiting for Replicate prediction to complete.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ namespace BLAIzor.Services
|
|||
public ScopedContentService(ApplicationDbContext context, IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
//_context = context;
|
||||
SessionId = Guid.NewGuid().ToString();
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
|
||||
|
|
@ -45,10 +46,10 @@ namespace BLAIzor.Services
|
|||
|
||||
// set { }
|
||||
//}
|
||||
public string? WebsiteDefaultLanguage { get; set; }
|
||||
public string SelectedLanguage { get; set; } = "English";
|
||||
|
||||
public string SessionId { get; set; }
|
||||
|
||||
public string SessionId { get; }
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
using BLAIzor.Data;
|
||||
using BLAIzor.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
|
||||
namespace BLAIzor.Services
|
||||
{
|
||||
public class SimpleLogger : ISimpleLogger
|
||||
{
|
||||
private readonly ApplicationDbContext _dbContext;
|
||||
private readonly IDbContextFactory<ApplicationDbContext> _dbFactory;
|
||||
private readonly IWebHostEnvironment _env;
|
||||
private LogLevel _currentLevel = LogLevel.Info;
|
||||
private bool _consoleEnabled = true;
|
||||
|
||||
public SimpleLogger(ApplicationDbContext dbContext, IWebHostEnvironment env)
|
||||
public SimpleLogger(IDbContextFactory<ApplicationDbContext> dbFactory, IWebHostEnvironment env)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_dbFactory = dbFactory;
|
||||
_env = env;
|
||||
}
|
||||
|
||||
|
|
@ -42,10 +43,20 @@ namespace BLAIzor.Services
|
|||
Timestamp = DateTime.UtcNow
|
||||
};
|
||||
|
||||
if (_env.IsProduction())
|
||||
if (_env.IsDevelopment())
|
||||
{
|
||||
_dbContext.Logs.Add(log);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||
|
||||
log = new AppLog
|
||||
{
|
||||
Severity = level.ToString(),
|
||||
Message = message,
|
||||
Details = details,
|
||||
Timestamp = DateTime.UtcNow
|
||||
};
|
||||
|
||||
db.Logs.Add(log);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (_consoleEnabled)
|
||||
|
|
@ -53,7 +64,7 @@ namespace BLAIzor.Services
|
|||
var color = Console.ForegroundColor;
|
||||
Console.ForegroundColor = level switch
|
||||
{
|
||||
LogLevel.Info => ConsoleColor.Gray,
|
||||
LogLevel.Info => ConsoleColor.Green,
|
||||
LogLevel.Warning => ConsoleColor.Yellow,
|
||||
LogLevel.Error => ConsoleColor.Red,
|
||||
_ => ConsoleColor.White
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
namespace BLAIzor.Services
|
||||
{
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
|
||||
public class WhisperTranscriptionService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private string _apiKey = ""; // Store this securely!
|
||||
|
||||
public WhisperTranscriptionService(IHttpClientFactory httpClientFactory, IConfiguration configuration)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
private string GetApiKey() =>
|
||||
_configuration?.GetSection("OpenAI")?.GetValue<string>("ApiKey") ?? string.Empty;
|
||||
|
||||
public async Task<string?> TranscribeAsync(byte[] audioData)
|
||||
{
|
||||
|
||||
_apiKey = GetApiKey();
|
||||
|
||||
try
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);
|
||||
|
||||
var content = new MultipartFormDataContent
|
||||
{
|
||||
{ new ByteArrayContent(audioData), "file", "audio.webm" },
|
||||
{ new StringContent("whisper-1"), "model" }
|
||||
};
|
||||
|
||||
var response = await client.PostAsync("https://api.openai.com/v1/audio/transcriptions", content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var resultJson = await response.Content.ReadAsStringAsync();
|
||||
var json = JsonDocument.Parse(resultJson);
|
||||
return json.RootElement.GetProperty("text").GetString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("STT error: " + ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
Menuitem saving procedure fix
|
||||
MenuItem reordering fix
|
||||
Caching vector hash and version checking
|
||||
Vector hashes?
|
||||
AI LayoutBuilder sophistication
|
||||
Language detection fix
|
||||
Language detection for AI voice fix
|
||||
TTS special characters localization (done)
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Data Source=194.164.235.47;Initial Catalog=BLAIzor;Integrated Security=False;Persist Security Info=False;User ID=sa;Password=v6f_?xNfg9N1;Trust Server Certificate=True"
|
||||
"DefaultConnection": "Data Source=195.26.231.218;Initial Catalog=BLAIzor;Integrated Security=False;Persist Security Info=False;User ID=sa;Password=v6f_?xNfg9N1;Trust Server Certificate=True"
|
||||
//"DefaultConnection": "Data Source=185.51.190.197;Initial Catalog=BLAIzor;Integrated Security=False;Persist Security Info=False;User ID=sa;Password=v6f_?xNfg9N1;Trust Server Certificate=True"
|
||||
//"DefaultConnection": "Server=tcp:poppixel.database.windows.net,1433;Initial Catalog=Poppixel;Integrated Security=False;Persist Security Info=False;User ID=Adam;Password=v6f_?xNfg9N1;TrustServerCertificate=True;Connection Timeout=30"
|
||||
},
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
},
|
||||
"AiSettings": {
|
||||
"Provider": "cerebras",
|
||||
//"Provider": "kimi",
|
||||
//"Provider": "chatgpt",
|
||||
"VoiceActivated": true,
|
||||
"EmbeddingService": "openai"
|
||||
|
|
@ -32,19 +33,24 @@
|
|||
},
|
||||
"Cerebras": {
|
||||
"ApiKey": "csk-3pwm3pjjrpcmmt6rm6k8f43n6rhh3h5pjn6jn9m9j4pyevrp",
|
||||
"Model": "llama-3.3-70b"
|
||||
"Model": "gpt-oss-120b"
|
||||
//"Model": "llama-3.3-70b"
|
||||
//"Model": "llama-4-scout-17b-16e-instruct"
|
||||
//"Model": "qwen-3-32b"
|
||||
//"Model": "deepseek-r1-distill-llama-70b"
|
||||
//"Model": "llama3.1-8b"
|
||||
},
|
||||
"Kimi": {
|
||||
"ApiKey": "sk-GSHABNe1qCpfNmMTQfjtb57j1OOMyJcyMYJqVRV5EXZmcaBM"
|
||||
},
|
||||
"OpenAI": {
|
||||
//"CredentialsPath": "D:\\GOOGLECREDENTIALS\\client_secret_359861037120-m3mjvr3kg51i2c2qb38dav62uuqoqs5k.apps.googleusercontent.com.json"
|
||||
"ApiKey": "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",
|
||||
//"ApiKey": "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" //VG3Law
|
||||
//"Model": "gpt-4.1-mini"
|
||||
"Model": "gpt-4o-mini"
|
||||
//"Model": "gpt-4o-mini"
|
||||
//"Model": "gpt-4.1-nano"
|
||||
"Model": "gpt-5-nano"
|
||||
},
|
||||
"QDrant": {
|
||||
//"CredentialsPath": "D:\\GOOGLECREDENTIALS\\client_secret_359861037120-m3mjvr3kg51i2c2qb38dav62uuqoqs5k.apps.googleusercontent.com.json"
|
||||
|
|
@ -57,5 +63,9 @@
|
|||
"ElevenLabsAPI": {
|
||||
"ApiKey": "sk_adaa84dce6ed60504c71aff230f2b8bdbd0effa347f715b6"
|
||||
},
|
||||
"ScraperSettings": {
|
||||
"Provider": "BrightData",
|
||||
"ApiKey": "2137725d-f768-49fd-9c85-f9caf90518e7"
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,63 @@
|
|||
/*@import url('https://fonts.googleapis.com/css2?family=Quicksand&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css?family=Comfortaa:400,700,300');*/
|
||||
|
||||
article {
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.admin-body {
|
||||
background-color: #060816;
|
||||
min-height: 100vh;
|
||||
color: #fff;
|
||||
--rz-base-800: #36244c !important;
|
||||
--rz-base-background-color: #36244c !important;
|
||||
}
|
||||
|
||||
.admin-body p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.admin-body .btn {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.admin-body .btn:hover {
|
||||
color: #000 !important;
|
||||
background-color: aliceblue;
|
||||
border-color: var(--bs-btn-hover-border-color) transparent;
|
||||
}
|
||||
|
||||
.rz-panel {
|
||||
background-color: transparent !important;
|
||||
color: #fff !important;
|
||||
--rz-text-h6-color: #fff !important;
|
||||
--rz-text-body1-color: #fff !important;
|
||||
}
|
||||
|
||||
.rz-card {
|
||||
background: transparent !important;
|
||||
border: unset !important;
|
||||
color: #fff !important;
|
||||
--rz-text-h6-color: #fff !important;
|
||||
--rz-text-body1-color: #fff !important;
|
||||
}
|
||||
|
||||
.admin-rz-card {
|
||||
background: linear-gradient(to bottom, #63358d, #7d3d7b) !important;
|
||||
border: unset !important;
|
||||
border-radius: 20px !important;
|
||||
}
|
||||
|
||||
.rz-text-h6 {
|
||||
}
|
||||
|
||||
.rz-text-body {
|
||||
}
|
||||
|
||||
.rz-sidebar {
|
||||
background-color: #36244c !important;
|
||||
}
|
||||
|
||||
label {
|
||||
display: unset !important;
|
||||
color: #fff !important;
|
||||
|
|
@ -85,6 +142,10 @@ label {
|
|||
background: linear-gradient(to bottom, #533e7e, #3c3666) !important;
|
||||
}
|
||||
|
||||
.bg.bg-panel-gradient:hover {
|
||||
background: linear-gradient(to bottom, #63358d, #7d3d7b) !important;
|
||||
}
|
||||
|
||||
.bg-panel-gradient-highlight {
|
||||
background: linear-gradient(to bottom, #63358d, #7d3d7b) !important;
|
||||
}
|
||||
|
|
@ -118,7 +179,6 @@ label {
|
|||
box-shadow: 8px 6px 8px 2px rgba(0, 0, 0, 0.4);
|
||||
--rz-dialog-title-background-color: transparent;
|
||||
--rz-primary: #87b1d6;
|
||||
|
||||
}
|
||||
|
||||
.rz-dialog .rz-button {
|
||||
|
|
@ -204,19 +264,68 @@ Don't show
|
|||
backdrop-filter: blur(6px);
|
||||
color: #b0daff;
|
||||
z-index: 10008 !important;
|
||||
top: 150px;
|
||||
top: 200px;
|
||||
border-radius: 20px;
|
||||
min-width: 200px;
|
||||
max-width: 200px;
|
||||
height: 80vh;
|
||||
height: fit-content;
|
||||
margin-left: 10px;
|
||||
padding: 10px;
|
||||
box-shadow: 8px 6px 8px 2px rgba(0, 0, 0, 0.4);
|
||||
--rz-primary: #87b1d6;
|
||||
overflow-y: scroll;
|
||||
/*overflow-y: scroll;*/
|
||||
}
|
||||
|
||||
.editor-window .btn {
|
||||
margin: 5px;
|
||||
border-radius: 20px;
|
||||
background-color: var(--rz-primary);
|
||||
color: var(--rz-on-primary);
|
||||
}
|
||||
|
||||
.editor-window strong {
|
||||
color: #63f0f9;
|
||||
}
|
||||
|
||||
.editor-window span {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.editor-window small {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.editor-button .text-muted {
|
||||
color: #b2b2b2 !important;
|
||||
}
|
||||
|
||||
.top-panel-outer {
|
||||
position: fixed;
|
||||
z-index: 10010 !important;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.top-panel {
|
||||
position: relative;
|
||||
background-color: #0c2533 !important;
|
||||
/*background: linear-gradient(153deg,rgba(12, 37, 51, 0.83) 0%, rgba(87, 188, 199, 0.81) 50%, rgba(237, 83, 196, 0.84) 100%);*/
|
||||
background: linear-gradient(307deg, rgba(12, 37, 51, 0.83) 7%, rgb(152 87 199 / 73%) 96%);
|
||||
backdrop-filter: blur(6px);
|
||||
color: #b0daff;
|
||||
z-index: 10010 !important;
|
||||
top: 0px;
|
||||
border-bottom-left-radius: 15px;
|
||||
border-bottom-right-radius: 15px;
|
||||
height: 70px;
|
||||
min-width: 500px;
|
||||
width: 800px;
|
||||
margin-left: 10px;
|
||||
box-shadow: 8px 6px 8px 2px rgba(0, 0, 0, 0.4);
|
||||
--rz-primary: #87b1d6;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.radzen-popup {
|
||||
|
|
@ -249,3 +358,123 @@ Don't show
|
|||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.admin-accordion {
|
||||
--rz-accordion-item-background-color: transparent;
|
||||
--rz-accordion-item-color: #fff;
|
||||
}
|
||||
|
||||
.rz-html-editor {
|
||||
background-color: transparent !important;
|
||||
border: unset !important;
|
||||
}
|
||||
|
||||
.rz-html-editor:focus-within {
|
||||
border: unset !important;
|
||||
outline: unset !important;
|
||||
}
|
||||
|
||||
.rz-html-editor-toolbar {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.rz-html-editor-content {
|
||||
background-color: transparent !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.rz-textarea {
|
||||
background-color: transparent !important;
|
||||
color: #fff !important;
|
||||
background-image: linear-gradient(#62448e 50%, #5a4083 50%);
|
||||
background-size: 100% 3rem;
|
||||
font-size: 0.8rem;
|
||||
border: 1px solid #CCC;
|
||||
width: 100%;
|
||||
/* height: 400px; */
|
||||
line-height: 1.5rem !important;
|
||||
margin: 0 auto;
|
||||
padding: 4px 8px !important;
|
||||
background-attachment: local;
|
||||
}
|
||||
|
||||
.rz-textarea:focus {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.rz-html-editor-content {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.rz-accordion > .rz-expander {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.rz-accordion-content {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.rz-accordion-header {
|
||||
background-color: #36244c !important;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 20px !important;
|
||||
}
|
||||
|
||||
.step-circle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background-color: #5c4f83;
|
||||
color: white;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
margin: 0 auto;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.step-circle.current {
|
||||
background-color: #87b1d6;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.step-circle.completed {
|
||||
background: linear-gradient(to bottom, #63358d, #7d3d7b) !important;
|
||||
}
|
||||
|
||||
.progress-line {
|
||||
height: 3px;
|
||||
background-color: #555;
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #87b1d6;
|
||||
border: 0 !important;
|
||||
color: #000000;
|
||||
width: fit-content !important;
|
||||
font-weight: bold !important;
|
||||
transition: all 0.2s ease !important;
|
||||
margin: 15px !important;
|
||||
border-radius: 20px !important;
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
border-width: 0 !important;
|
||||
font-weight: bold !important;
|
||||
transition: all 0.2s ease !important;
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
border-width: 0 !important;
|
||||
font-weight: bold !important;
|
||||
transition: all 0.2s ease !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ a, .btn-link {
|
|||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
/*.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
}*/
|
||||
|
||||
.content {
|
||||
padding-top: 1.1rem;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,16 @@
|
|||
@import url('https://fonts.googleapis.com/css?family=Comfortaa:400,700,300');*/
|
||||
|
||||
|
||||
.voicebutton {
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
padding: 0px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 0 auto !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.radzen-popup {
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
|
@ -125,7 +135,7 @@
|
|||
}
|
||||
|
||||
.searchBox:hover > .searchInput {
|
||||
width: calc(100% - 60px);
|
||||
width: calc(100% - 120px);
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +144,10 @@
|
|||
color: #2f3640;
|
||||
}
|
||||
|
||||
.searchBox:hover > .voicebutton {
|
||||
display: unset;
|
||||
}
|
||||
|
||||
.searchButton {
|
||||
color: white;
|
||||
float: right;
|
||||
|
|
@ -309,9 +323,7 @@ ul {
|
|||
list-style-type: none;
|
||||
}
|
||||
|
||||
.rz-html-editor {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
h1 {
|
||||
|
|
|
|||
|
|
@ -106,3 +106,13 @@ window.initVoiceRecorder = function (dotnetMethodName) {
|
|||
}
|
||||
};
|
||||
};
|
||||
|
||||
window.seemgenAnimationHelper = {
|
||||
restartAnimation: function (el) {
|
||||
if (!el) return;
|
||||
el.classList.remove("animate__animated");
|
||||
void el.offsetWidth; // Reflow trick
|
||||
el.classList.add("animate__animated");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
let mediaRecorder;
|
||||
let recordedChunks = [];
|
||||
|
||||
window.startRecording = async () => {
|
||||
recordedChunks = [];
|
||||
console.log("startRecording called");
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
mediaRecorder = new MediaRecorder(stream);
|
||||
|
||||
mediaRecorder.ondataavailable = (event) => {
|
||||
if (event.data.size > 0) {
|
||||
recordedChunks.push(event.data);
|
||||
}
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = async () => {
|
||||
const blob = new Blob(recordedChunks, { type: 'audio/webm' });
|
||||
const arrayBuffer = await blob.arrayBuffer();
|
||||
const byteArray = new Uint8Array(arrayBuffer);
|
||||
|
||||
// Send to Blazor server
|
||||
DotNet.invokeMethodAsync('BLAIzor', 'SendAudioToServer', Array.from(byteArray));
|
||||
};
|
||||
|
||||
mediaRecorder.start();
|
||||
};
|
||||
|
||||
window.stopRecording = () => {
|
||||
mediaRecorder.stop();
|
||||
};
|
||||
|
|
@ -1,576 +0,0 @@
|
|||
/*@import url('https://fonts.googleapis.com/css2?family=Quicksand&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css?family=Comfortaa:400,700,300');*/
|
||||
|
||||
/*search*/
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
label {
|
||||
color: #000;
|
||||
/* display: none; */
|
||||
}
|
||||
|
||||
.img-fluid {
|
||||
max-height: 50vh;
|
||||
width: auto;
|
||||
|
||||
}
|
||||
|
||||
.pop-img {
|
||||
border-radius: 20px !important;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
#maincontrol {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
margin-bottom: 10px;
|
||||
z-index:10000;
|
||||
}
|
||||
|
||||
#currentContent {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.displaysearch {
|
||||
padding-left: 5vw;
|
||||
padding-right: 5vw;
|
||||
}
|
||||
|
||||
.card-img-top {
|
||||
max-height: 50vh;
|
||||
width: auto;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #87b1d6;
|
||||
border: 0;
|
||||
color: #000000;
|
||||
width: fit-content;
|
||||
font-weight: bold;
|
||||
transition: all 0.2s ease;
|
||||
margin: 15px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.voicebutton {
|
||||
border-radius: 50% !important;
|
||||
padding: 10px !important;
|
||||
width: 40px;
|
||||
height:40px;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 20px !important;
|
||||
}
|
||||
|
||||
.menubtn {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border: 0;
|
||||
color: #000000;
|
||||
/* width: 98%; */
|
||||
font-weight: bold;
|
||||
border-radius: 20px;
|
||||
height: 40px;
|
||||
transition: all 0.2s ease;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 20px !important;
|
||||
}
|
||||
|
||||
.bg-dark-secondary {
|
||||
background-color: #111422 !important;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #111422;
|
||||
}
|
||||
|
||||
input.search_bar{
|
||||
border: none;
|
||||
outline: none;
|
||||
width: 75px;
|
||||
border-radius: 55px;
|
||||
margin: 0 auto;
|
||||
font-size: 1.3em;
|
||||
color: #0d2840;
|
||||
padding: 15px 30px 15px 45px;
|
||||
transition: all .3s cubic-bezier(0,0,.5,1.5);
|
||||
box-shadow: 0 3px 10px -2px rgba(0,0,0,.1);
|
||||
background: rgba(255, 255, 255, 0.3) url(https://i.imgur.com/seveWIw.png) no-repeat center center;
|
||||
}
|
||||
|
||||
input.search_bar:focus{
|
||||
width: 100%;
|
||||
background-position: calc(100% - 35px) center
|
||||
}
|
||||
|
||||
/*Removes default x in search fields (webkit only i guess)*/
|
||||
input[type=search]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
/*Changes the color of the placeholder*/
|
||||
::-webkit-input-placeholder {
|
||||
color: #0d2840;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
:-moz-placeholder {
|
||||
color: #0d2840;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
::-moz-placeholder {
|
||||
color: #0d2840;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
:-ms-input-placeholder {
|
||||
color: #0d2840;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
/*search*/
|
||||
|
||||
/*Search2*/
|
||||
.searchBox {
|
||||
width: 60px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
height: 60px;
|
||||
border-radius: 40px;
|
||||
padding: 10px;
|
||||
margin: 0 auto;
|
||||
transition: 0.8s;
|
||||
}
|
||||
|
||||
.searchInput:active > .searchBox{
|
||||
width:100%
|
||||
}
|
||||
.searchInput:focus > .searchBox {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.searchInput::placeholder {
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.searchBox:hover {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.searchBox:hover > .searchInput {
|
||||
width: calc(100% - 60px);
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.searchBox:hover > .searchButton {
|
||||
background: white;
|
||||
color: #2f3640;
|
||||
}
|
||||
|
||||
.searchButton {
|
||||
color: white;
|
||||
float: right;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50px;
|
||||
background-color: #e493d0;
|
||||
background-image: radial-gradient(closest-side, rgba(235, 105, 78, 1), rgba(235, 105, 78, 0)), radial-gradient(closest-side, rgba(243, 11, 164, 1), rgba(243, 11, 164, 0)), radial-gradient(closest-side, rgba(254, 234, 131, 1), rgba(254, 234, 131, 0)), radial-gradient(closest-side, rgba(170, 142, 245, 1), rgba(170, 142, 245, 0)), radial-gradient(closest-side, rgba(248, 192, 147, 1), rgba(248, 192, 147, 0));
|
||||
background-size: 130vmax 130vmax, 80vmax 80vmax, 90vmax 90vmax, 110vmax 110vmax, 90vmax 90vmax;
|
||||
background-position: -80vmax -80vmax, 60vmax -30vmax, 10vmax 10vmax, -30vmax -10vmax, 50vmax 50vmax;
|
||||
background-repeat: no-repeat;
|
||||
animation: 10s movement linear infinite;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
font-size: 1.3em !important;
|
||||
color: #0d2840 !important;
|
||||
float: left;
|
||||
padding: 0;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
transition: 0.4s;
|
||||
line-height: 40px;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
/*Search2*/
|
||||
|
||||
|
||||
.event {
|
||||
border-radius: 20px !important;
|
||||
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||
backdrop-filter: blur(20px);
|
||||
border: 0;
|
||||
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.06), 0 2px 4px rgba(0, 0, 0, 0.07);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
/*card design*/
|
||||
.card {
|
||||
border-radius: 20px !important;
|
||||
overflow: hidden;
|
||||
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||
backdrop-filter: blur(20px);
|
||||
border: 0;
|
||||
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.06), 0 2px 4px rgba(0, 0, 0, 0.07);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 6px 30px rgba(0, 0, 0, 0.1), 0 10px 8px rgba(0, 0, 0, 0.015);
|
||||
}
|
||||
|
||||
.card-body .card-title {
|
||||
font-family: 'Lato', sans-serif;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.3px;
|
||||
font-size: 24px;
|
||||
color: #121212;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
font-family: 'Lato', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
letter-spacing: 0.3px;
|
||||
color: #4E4E4E;
|
||||
}
|
||||
|
||||
.card .container {
|
||||
width: 88%;
|
||||
/*background: #F0EEF8;*/
|
||||
border-radius: 30px;
|
||||
/*height: 140px;*/
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container:hover > img {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.container img {
|
||||
/*padding: 75px;*/
|
||||
/*margin-top: -40px;
|
||||
margin-bottom: -40px;*/
|
||||
transition: 0.4s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #e493d0;
|
||||
background-image: radial-gradient(closest-side, rgba(235, 105, 78, 1), rgba(235, 105, 78, 0)), radial-gradient(closest-side, rgba(243, 11, 164, 1), rgba(243, 11, 164, 0)), radial-gradient(closest-side, rgba(254, 234, 131, 1), rgba(254, 234, 131, 0)), radial-gradient(closest-side, rgba(170, 142, 245, 1), rgba(170, 142, 245, 0)), radial-gradient(closest-side, rgba(248, 192, 147, 1), rgba(248, 192, 147, 0));
|
||||
background-size: 130vmax 130vmax, 80vmax 80vmax, 90vmax 90vmax, 110vmax 110vmax, 90vmax 90vmax;
|
||||
background-position: -80vmax -80vmax, 60vmax -30vmax, 10vmax 10vmax, -30vmax -10vmax, 50vmax 50vmax;
|
||||
background-repeat: no-repeat;
|
||||
animation: 10s movement linear infinite;
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
background-color: #e493d0;
|
||||
background-image: radial-gradient(closest-side, rgba(235, 105, 78, 1), rgba(235, 105, 78, 0)), radial-gradient(closest-side, rgba(243, 11, 164, 1), rgba(243, 11, 164, 0)), radial-gradient(closest-side, rgba(254, 234, 131, 1), rgba(254, 234, 131, 0)), radial-gradient(closest-side, rgba(170, 142, 245, 1), rgba(170, 142, 245, 0)), radial-gradient(closest-side, rgba(248, 192, 147, 1), rgba(248, 192, 147, 0));
|
||||
background-size: 130vmax 130vmax, 80vmax 80vmax, 90vmax 90vmax, 110vmax 110vmax, 90vmax 90vmax;
|
||||
background-position: -80vmax -80vmax, 60vmax -30vmax, 10vmax 10vmax, -30vmax -10vmax, 50vmax 50vmax;
|
||||
background-repeat: no-repeat;
|
||||
animation: 10s movement linear infinite;
|
||||
}
|
||||
/*card design*/
|
||||
|
||||
/*bg*/
|
||||
|
||||
|
||||
:root {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
body {
|
||||
/*font-family: 'Comfortaa', 'Arial Narrow', Arial, sans-serif;*/
|
||||
/*font-family: 'Quicksand', sans-serif;*/
|
||||
color: #fff !important;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background-color: #060816;
|
||||
/*background: linear-gradient(295deg,#060816,#090f59,#440959,#000888);
|
||||
background-size: 240% 240%;
|
||||
animation: gradient-animation 24s ease infinite;*/
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
body::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
@keyframes gradient-animation {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.myspan {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 5rem;
|
||||
color: transparent;
|
||||
text-shadow: 0px 0px 1px rgba(255, 255, 255, .6), 0px 4px 4px rgba(0, 0, 0, .05);
|
||||
letter-spacing: .2rem;
|
||||
}
|
||||
|
||||
@keyframes movement {
|
||||
0%, 100% {
|
||||
background-size: 130vmax 130vmax, 80vmax 80vmax, 90vmax 90vmax, 110vmax 110vmax, 90vmax 90vmax;
|
||||
background-position: -80vmax -80vmax, 60vmax -30vmax, 10vmax 10vmax, -30vmax -10vmax, 50vmax 50vmax;
|
||||
}
|
||||
|
||||
25% {
|
||||
background-size: 100vmax 100vmax, 90vmax 90vmax, 100vmax 100vmax, 90vmax 90vmax, 60vmax 60vmax;
|
||||
background-position: -60vmax -90vmax, 50vmax -40vmax, 0vmax -20vmax, -40vmax -20vmax, 40vmax 60vmax;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-size: 80vmax 80vmax, 110vmax 110vmax, 80vmax 80vmax, 60vmax 60vmax, 80vmax 80vmax;
|
||||
background-position: -50vmax -70vmax, 40vmax -30vmax, 10vmax 0vmax, 20vmax 10vmax, 30vmax 70vmax;
|
||||
}
|
||||
|
||||
75% {
|
||||
background-size: 90vmax 90vmax, 90vmax 90vmax, 100vmax 100vmax, 90vmax 90vmax, 70vmax 70vmax;
|
||||
background-position: -50vmax -40vmax, 50vmax -30vmax, 20vmax 0vmax, -10vmax 10vmax, 40vmax 60vmax;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*bg*/
|
||||
|
||||
.mytextarea {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(20px);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
border-width: 0px;
|
||||
height: unset !important;
|
||||
}
|
||||
|
||||
.mytextarea:active {
|
||||
border-width: 0px;
|
||||
}
|
||||
|
||||
.mytextarea:focus-visible {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-width: 0px !important;
|
||||
outline: -webkit-focus-ring-color auto 0px;
|
||||
outline-color: transparent;
|
||||
}
|
||||
|
||||
.navbar-toggler {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.7rem;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 5px;
|
||||
display: unset !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-select > option {
|
||||
background-color: rgba(255, 255, 255, 0.2)
|
||||
}
|
||||
|
||||
.contactform-overlay {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
padding: 100px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
/* padding-top: 10vh; */
|
||||
backdrop-filter: blur(20px);
|
||||
/* background-color: rgba(1, 1, 1, .4); */
|
||||
}
|
||||
|
||||
.form-control {
|
||||
background-color: rgba(255,255,255,0.4);
|
||||
border-radius: 5px;
|
||||
height: 50px;
|
||||
}
|
||||
.form-control::placeholder {
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.contactform-close-overlay {
|
||||
position: relative;
|
||||
height: 10vh;
|
||||
}
|
||||
|
||||
.contactform-popup-content {
|
||||
height: 80vh;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.contactform-popup-close {
|
||||
position: relative;
|
||||
height: 10vh;
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
.calendly-overlay {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
top: 0px;
|
||||
/* padding-top: 10vh; */
|
||||
backdrop-filter: blur(20px);
|
||||
/* background-color: rgba(1, 1, 1, .4); */
|
||||
}
|
||||
|
||||
.calendly-close-overlay {
|
||||
position: relative;
|
||||
height: 10vh;
|
||||
}
|
||||
|
||||
.calendly-popup-content {
|
||||
height: 80vh;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.calendly-popup-close {
|
||||
position: relative;
|
||||
height: 10vh;
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
#myVideo {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.table {
|
||||
color: #fff !important;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.show {
|
||||
font-size: 1.6rem;
|
||||
letter-spacing: 2px;
|
||||
/*height: 100vh;*/
|
||||
}
|
||||
|
||||
.navbar-collapse {
|
||||
/*height: 100vh;*/
|
||||
/*display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;*/
|
||||
text-align: center !important;
|
||||
align-content: center;
|
||||
/*overflow-y: scroll;*/
|
||||
}
|
||||
|
||||
.navbar-collapse .nav-link {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.navbar-collapse .nav-item:not(:last-child) {
|
||||
border-bottom: 0px solid white;
|
||||
/*padding: 0.2em 4em;*/
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: #111422;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
top: 60px;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #EEEEEE;
|
||||
font-size: 1,1rem;
|
||||
}
|
||||
.container-fluid {
|
||||
margin-bottom:20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 🔽 Mobile Responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
h1 { font-size: 2rem; }
|
||||
h2 { font-size: 1.6rem; }
|
||||
h3 { font-size: 1.4rem; }
|
||||
p, li { font-size: 1rem; }
|
||||
.navbar-collapse .nav-link { font-size: 1.1rem; }
|
||||
p {text-align: justify;}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
h1 { font-size: 1.6rem; }
|
||||
h2 { font-size: 1.3rem; }
|
||||
h3 { font-size: 1.1rem; }
|
||||
p, li { font-size: 0.95rem; }
|
||||
.navbar-collapse .nav-link { font-size: 1rem; }
|
||||
p {text-align: justify;}
|
||||
}
|
||||
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 3.8 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 116 KiB |