húúúúúúúdenagycommit
|
|
@ -14,6 +14,10 @@
|
||||||
<None Remove="SeemGen.Tests\**" />
|
<None Remove="SeemGen.Tests\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Remove="Components\Partials\OverlayEditor.razor" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Components\Pages\Home.razorOLD" />
|
<None Remove="Components\Pages\Home.razorOLD" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,13 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<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 charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
@ -13,7 +20,8 @@
|
||||||
<link rel="stylesheet" href="app.css" />
|
<link rel="stylesheet" href="app.css" />
|
||||||
<link rel="stylesheet" href="main.css" />
|
<link rel="stylesheet" href="main.css" />
|
||||||
<link rel="stylesheet" href="admin.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="loader.css" />
|
||||||
<link rel="stylesheet" href="BLAIzor.styles.css" />
|
<link rel="stylesheet" href="BLAIzor.styles.css" />
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<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://kit.fontawesome.com/12c469cb8f.js" crossorigin="anonymous"></script>
|
||||||
<script src="https://assets.calendly.com/assets/external/widget.js" type="text/javascript"></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/background.js"> </script>
|
||||||
<script type="text/javascript" src="scripts/SeemGenCss.js"> </script>
|
|
||||||
|
|
||||||
@* <script>
|
<script type="text/javascript" src="scripts/whisperRecorder.js"></script>
|
||||||
window.applyDynamicCss = (cssContent) => {
|
|
||||||
let styleTag = document.getElementById('seemgen-style');
|
|
||||||
if (!styleTag) {
|
<RadzenTheme Theme="material-dark" @rendermode="InteractiveServer" />
|
||||||
styleTag = document.createElement('style');
|
@* <RadzenTheme Theme="material" @rendermode="InteractiveServer" /> *@
|
||||||
styleTag.id = 'seemgen-style';
|
|
||||||
document.head.appendChild(styleTag);
|
|
||||||
}
|
|
||||||
styleTag.textContent = cssContent;
|
|
||||||
};
|
|
||||||
</script> *@
|
|
||||||
@* <RadzenTheme Theme="material-dark" @rendermode="InteractiveServer" /> *@
|
|
||||||
<RadzenTheme Theme="material" @rendermode="InteractiveServer" />
|
|
||||||
<HeadOutlet />
|
<HeadOutlet />
|
||||||
<SectionOutlet SectionName="HeadContentFromPage" />
|
<SectionOutlet SectionName="HeadContentFromPage" />
|
||||||
</head>
|
</head>
|
||||||
|
|
@ -53,5 +52,9 @@
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
<script type="text/javascript" src="scripts/finisher-header.es5.min.js"> </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 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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="page" style="z-index: 1">
|
<div class="page admin-body" style="z-index: 1">
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
@* <div class="top-row px-4" style="z-index: 2">
|
@* <div class="top-row px-4" style="z-index: 2">
|
||||||
|
|
|
||||||
|
|
@ -27,43 +27,51 @@ else
|
||||||
<DataAnnotationsValidator />
|
<DataAnnotationsValidator />
|
||||||
<ValidationSummary />
|
<ValidationSummary />
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-md-4">
|
<div class="col-12 col-md-6">
|
||||||
<label>Template Name</label>
|
<div class="form-group">
|
||||||
<InputText class="form-control" @bind-Value="currentTemplate.TemplateName" />
|
<label>Template Name</label>
|
||||||
|
<InputText class="form-control" @bind-Value="currentTemplate.TemplateName" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Template photo url</label>
|
||||||
|
<InputText class="form-control" @bind-Value="currentTemplate.TemplatePhotoUrl" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Tags (comma-separated)</label>
|
||||||
|
<InputText class="form-control" @bind-Value="currentTemplate.Tags" />
|
||||||
|
</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
|
||||||
|
style="height: 450px; color:#000; background-color: rgba(255,255,255,0.4)"
|
||||||
|
Input=@OnInput
|
||||||
|
Change=@OnChange
|
||||||
|
Paste=@OnPaste
|
||||||
|
UploadComplete=@OnUploadComplete
|
||||||
|
Execute=@OnExecute
|
||||||
|
UploadUrl="upload/image"
|
||||||
|
Mode=@HtmlEditorMode.Source>
|
||||||
|
<RadzenHtmlEditorUndo />
|
||||||
|
<RadzenHtmlEditorRedo />
|
||||||
|
<RadzenHtmlEditorSource />
|
||||||
|
</RadzenHtmlEditor>
|
||||||
|
@* <InputTextArea class="form-control" @bind-Value="currentCssTemplate.CssContent" /> *@
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label>Template photo url</label>
|
|
||||||
<InputText class="form-control" @bind-Value="currentTemplate.TemplatePhotoUrl" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group col-md-4">
|
|
||||||
<label>Tags (comma-separated)</label>
|
|
||||||
<InputText class="form-control" @bind-Value="currentTemplate.Tags" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Description</label>
|
|
||||||
<InputTextArea class="form-control" @bind-Value="currentTemplate.Description" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>CSS Content</label>
|
|
||||||
<RadzenHtmlEditor @bind-Value=@currentCssTemplate.CssContent
|
|
||||||
style="height: 450px; color:#000; background-color: rgba(255,255,255,0.4)"
|
|
||||||
Input=@OnInput
|
|
||||||
Change=@OnChange
|
|
||||||
Paste=@OnPaste
|
|
||||||
UploadComplete=@OnUploadComplete
|
|
||||||
Execute=@OnExecute
|
|
||||||
UploadUrl="upload/image">
|
|
||||||
<RadzenHtmlEditorUndo />
|
|
||||||
<RadzenHtmlEditorRedo />
|
|
||||||
<RadzenHtmlEditorSource />
|
|
||||||
</RadzenHtmlEditor>
|
|
||||||
@* <InputTextArea class="form-control" @bind-Value="currentCssTemplate.CssContent" /> *@
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-success" type="submit">Save Changes</button>
|
<button class="btn btn-success" type="submit">Save Changes</button>
|
||||||
</EditForm>
|
</EditForm>
|
||||||
|
|
|
||||||
|
|
@ -5,25 +5,29 @@
|
||||||
@using BLAIzor.Services
|
@using BLAIzor.Services
|
||||||
@layout AdminLayout
|
@layout AdminLayout
|
||||||
@inject ContentEditorService ContentEditorService
|
@inject ContentEditorService ContentEditorService
|
||||||
|
@inject ScopedContentService ScopedContentService
|
||||||
|
|
||||||
<h3>Generate Website Content</h3>
|
<h3>Generate Website Content</h3>
|
||||||
|
|
||||||
<p>How would you start?</p>
|
<div class="row">
|
||||||
|
<div class="rz-p-4 rz-text-align-center" style="width: fit-content; margin: 0 auto;">
|
||||||
<div class="rz-p-12 rz-text-align-center">
|
<p>How would you start?</p>
|
||||||
<RadzenRadioButtonList @bind-Value=@FromDocument TValue="bool">
|
<RadzenRadioButtonList @bind-Value=@FromDocument TValue="bool" AlignItems="AlignItems.Center" Style="margin: 0 auto;">
|
||||||
<Items>
|
<Items>
|
||||||
<RadzenRadioButtonListItem Text="I have a document" Value="true" />
|
<RadzenRadioButtonListItem Text="I have a document" Value="true" />
|
||||||
<RadzenRadioButtonListItem Text="Start from scratch" Value="false" />
|
<RadzenRadioButtonListItem Text="Start from scratch" Value="false" />
|
||||||
</Items>
|
</Items>
|
||||||
</RadzenRadioButtonList>
|
</RadzenRadioButtonList>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<p>@errorMessage</p>
|
<p>@errorMessage</p>
|
||||||
@{
|
@{
|
||||||
if(!FromDocument)
|
if(!FromDocument)
|
||||||
{
|
{
|
||||||
<GenerateFromScratch SiteId=@SiteId SessionId="sessionId"></GenerateFromScratch>
|
// <GenerateFromScratch SiteId=@SiteId SessionId="sessionId"></GenerateFromScratch>
|
||||||
|
<GenerateSitePages SiteId=@SiteId SessionId="sessionId"></GenerateSitePages>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -40,6 +44,7 @@
|
||||||
|
|
||||||
protected override Task OnParametersSetAsync()
|
protected override Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
|
sessionId = ScopedContentService.SessionId;
|
||||||
//TODO get sessionId
|
//TODO get sessionId
|
||||||
return base.OnParametersSetAsync();
|
return base.OnParametersSetAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
@using System.Text.Json
|
@using System.Text.Json
|
||||||
@using Sidio.Sitemap.Blazor
|
@using Sidio.Sitemap.Blazor
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
@inject ContentService _contentService
|
|
||||||
@inject NavigationManager _navigationManager
|
@inject NavigationManager _navigationManager
|
||||||
@inject IHttpContextAccessor HttpContextAccessor
|
@inject IHttpContextAccessor HttpContextAccessor
|
||||||
@inject DesignTemplateService DesignTemplateService
|
@inject DesignTemplateService DesignTemplateService
|
||||||
|
|
@ -21,14 +20,16 @@
|
||||||
|
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
|
|
||||||
<div class="page" style="z-index: 1">
|
<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>
|
<main>
|
||||||
|
|
||||||
<article class="content text-center" style="position: relative; z-index: 4;">
|
<article class="content text-center" style="position: relative; z-index: 4;">
|
||||||
|
|
||||||
<PageTitle>Home</PageTitle>
|
<PageTitle>Home</PageTitle>
|
||||||
@{
|
@{
|
||||||
if(SiteInfo!= null)
|
if (SiteInfo != null)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(SiteInfo.BackgroundVideo))
|
if (!string.IsNullOrEmpty(SiteInfo.BackgroundVideo))
|
||||||
{
|
{
|
||||||
|
|
@ -49,7 +50,14 @@
|
||||||
|
|
||||||
<div class="displaysearch">
|
<div class="displaysearch">
|
||||||
<div class="searchBox">
|
<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()"
|
<input @oninput="(e) => UserInput = e.Value.ToString()"
|
||||||
@onkeydown="@Enter" class="searchInput" type="text" name="" value="@UserInput" placeholder="Ask any question">
|
@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="#">
|
<button data-hint="ask anything" class="searchButton border-0" @onclick="SendUserQuery" href="#">
|
||||||
|
|
@ -60,24 +68,24 @@
|
||||||
@{
|
@{
|
||||||
@if (VoiceEnabled)
|
@if (VoiceEnabled)
|
||||||
{
|
{
|
||||||
if (STTEnabled)
|
// if (STTEnabled)
|
||||||
{
|
// {
|
||||||
<button id="recButton" class="btn btn-primary voicebutton" onclick="startRecording()"><i class="fa-solid fa-microphone"></i></button>
|
// <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>
|
// <button id="stopButton" class="btn btn-primary voicebutton" onclick="stopRecording()" hidden><i class="fa-solid fa-microphone-slash"></i></button>
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (TTSEnabled)
|
if (TTSEnabled)
|
||||||
{
|
{
|
||||||
if (!AiVoicePermitted)
|
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>
|
<i class="fa-solid fa-volume-xmark"></i>
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
else
|
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>
|
<audio id="audioPlayer" hidden style="display: none;"></audio>
|
||||||
}
|
}
|
||||||
|
|
@ -92,50 +100,53 @@
|
||||||
@* </div> *@
|
@* </div> *@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p id="recordingText"></p>
|
|
||||||
<div id="currentContent">
|
|
||||||
@{
|
|
||||||
if (!string.IsNullOrEmpty(HtmlContent.ToString()))
|
|
||||||
{
|
|
||||||
|
|
||||||
if (isEmailFormVisible)
|
<div id="currentContent">
|
||||||
|
<p id="recordingText"></p>
|
||||||
|
<AnimateOnRender CssClass="animate__animated animate__backInUp">
|
||||||
|
@{
|
||||||
|
if (!string.IsNullOrEmpty(HtmlContent.ToString()))
|
||||||
{
|
{
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
if (isEmailFormVisible)
|
||||||
<div class="pt-5 @FirstColumnClass">
|
{
|
||||||
@((MarkupString)HtmlContent.ToString())
|
<div class="container-fluid">
|
||||||
</div>
|
<div class="row">
|
||||||
<div class="pt-5 col-12 col-md-6">
|
<div class="pt-5 @FirstColumnClass">
|
||||||
<ContactFormComponent ContactFormModel="@ContactFormModel" DocumentEmailAddress="@DocumentEmailAddress" OnDataChanged="@ContentChangedInForm"></ContactFormComponent>
|
@((MarkupString)HtmlContent.ToString())
|
||||||
<button @onclick="CancelEmail" class="btn btn-primary">Cancel</button>
|
</div>
|
||||||
|
<div class="pt-5 col-12 col-md-6">
|
||||||
|
<ContactFormComponent ContactFormModel="@ContactFormModel" DocumentEmailAddress="@DocumentEmailAddress" OnDataChanged="@ContentChangedInForm"></ContactFormComponent>
|
||||||
|
<button @onclick="CancelEmail" class="btn btn-primary">Cancel</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="pt-5 @FirstColumnClass">
|
||||||
|
@((MarkupString)HtmlContent.ToString())
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="pt-5 @FirstColumnClass">
|
<div class="text-center row" style="height: 70vh;">
|
||||||
@((MarkupString)HtmlContent.ToString())
|
<p>@StatusContent</p>
|
||||||
|
<div class="mydiv"></div>
|
||||||
|
<div class="mydiv"></div>
|
||||||
|
<div class="mydiv"></div>
|
||||||
|
<div class="mydiv"></div>
|
||||||
|
<div class="mydiv"></div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
</AnimateOnRender>
|
||||||
{
|
|
||||||
<div class="text-center row" style="height: 70vh;">
|
|
||||||
<p>@StatusContent</p>
|
|
||||||
<div class="mydiv"></div>
|
|
||||||
<div class="mydiv"></div>
|
|
||||||
<div class="mydiv"></div>
|
|
||||||
<div class="mydiv"></div>
|
|
||||||
<div class="mydiv"></div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-primary" @onclick="HomeClick"><i class="fa-solid fa-rotate"></i></button>
|
<button class="btn btn-primary" @onclick="HomeClick"><i class="fa-solid fa-rotate"></i></button>
|
||||||
|
|
@ -145,6 +156,7 @@
|
||||||
</main>
|
</main>
|
||||||
<FooterComponent MenuString="@Menu" OnMenuClicked=@MenuClick></FooterComponent>
|
<FooterComponent MenuString="@Menu" OnMenuClicked=@MenuClick></FooterComponent>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
<ErrorContent Context="ex">
|
<ErrorContent Context="ex">
|
||||||
<p role="alert">An error occurred: @ex.Message</p>
|
<p role="alert">An error occurred: @ex.Message</p>
|
||||||
|
|
@ -155,6 +167,7 @@
|
||||||
}
|
}
|
||||||
</ErrorContent>
|
</ErrorContent>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var sessionId = null;
|
var sessionId = null;
|
||||||
|
|
@ -212,6 +225,7 @@
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
|
await _logger.InfoAsync("Index component OnafterRender.", $"{SiteId}");
|
||||||
await jsRuntime.InvokeVoidAsync("setSessionId", SessionId);
|
await jsRuntime.InvokeVoidAsync("setSessionId", SessionId);
|
||||||
await jsRuntime.InvokeVoidAsync("initHints");
|
await jsRuntime.InvokeVoidAsync("initHints");
|
||||||
|
|
||||||
|
|
@ -277,12 +291,14 @@
|
||||||
if (!string.IsNullOrWhiteSpace(topic))
|
if (!string.IsNullOrWhiteSpace(topic))
|
||||||
{
|
{
|
||||||
UserInput = topic;
|
UserInput = topic;
|
||||||
|
await ChatGptService.InitSite(SessionId, SiteInfo, TemplateCollectionName, Menu);
|
||||||
await ChatGptService.ProcessContentRequest(SessionId, UserInput, SiteId, (int)SiteInfo.TemplateId!, ContentCollectionName, Menu, true);
|
await ChatGptService.ProcessContentRequest(SessionId, UserInput, SiteId, (int)SiteInfo.TemplateId!, ContentCollectionName, Menu, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
await ChatGptService.InitSite(SessionId, SiteInfo, TemplateCollectionName, Menu);
|
||||||
await ChatGptService.GetChatGptWelcomeMessage(SessionId, SiteId, 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);
|
//await ChatGptService.ProcessContentRequest(SessionId, MenuItems.FirstOrDefault(), SiteId, (int)SiteInfo.TemplateId!, TemplateCollectionName, Menu, true);
|
||||||
}
|
}
|
||||||
// HtmlContent = await ChatGptService.GetChatGptWelcomeMessage();
|
// HtmlContent = await ChatGptService.GetChatGptWelcomeMessage();
|
||||||
|
|
@ -310,7 +326,7 @@
|
||||||
public void HomeClick()
|
public void HomeClick()
|
||||||
{
|
{
|
||||||
//ChatGptService.OnContentReceived -= UpdateContent;
|
//ChatGptService.OnContentReceived -= UpdateContent;
|
||||||
AIService.OnContentReceived -= UpdateContent;
|
ChatGptService.OnContentReceived -= UpdateContent;
|
||||||
_navigationManager.Refresh(true);
|
_navigationManager.Refresh(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -328,16 +344,16 @@
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
await _logger.InfoAsync("Index component initialized.", $"{SiteId}");
|
||||||
_scopedContentService.OnBrandNameChanged += HandleBrandNameChanged;
|
_scopedContentService.OnBrandNameChanged += HandleBrandNameChanged;
|
||||||
// ChatGptService.OnContentReceived += UpdateContent;
|
// ChatGptService.OnContentReceived += UpdateContent;
|
||||||
AIService.OnContentReceived += UpdateContent;
|
ChatGptService.OnContentReceived += UpdateContent;
|
||||||
AIService.OnContentReceiveFinished += UpdateFinished;
|
ChatGptService.OnContentReceiveFinished += UpdateFinished;
|
||||||
// ChatGptService.OnStatusChangeReceived += UpdateStatus;
|
// ChatGptService.OnStatusChangeReceived += UpdateStatus;
|
||||||
AIService.OnStatusChangeReceived += UpdateStatus;
|
ChatGptService.OnStatusChangeReceived += UpdateStatus;
|
||||||
AIService.OnTextContentAvailable += UpdateTextContentForVoice;
|
ChatGptService.OnTextContentAvailable += UpdateTextContentForVoice;
|
||||||
SessionId = Guid.NewGuid().ToString();
|
SessionId = _scopedContentService.SessionId;
|
||||||
_instances[SessionId] = this;
|
_instances[SessionId] = this;
|
||||||
|
|
||||||
VoiceEnabled = configuration?.GetSection("AiSettings")?.GetValue<bool>("VoiceActivated") ?? false;
|
VoiceEnabled = configuration?.GetSection("AiSettings")?.GetValue<bool>("VoiceActivated") ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -357,17 +373,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void UpdateTextContentForVoice(string receivedSessionId, string content)
|
// private async void UpdateTextContentForVoice(string receivedSessionId, string content)
|
||||||
{
|
// {
|
||||||
Console.WriteLine("UPDATETEXTCONTENT called");
|
// Console.WriteLine("UPDATETEXTCONTENT called");
|
||||||
if (receivedSessionId == SessionId) // Only accept messages meant for this tab
|
// if (receivedSessionId == SessionId) // Only accept messages meant for this tab
|
||||||
{
|
// {
|
||||||
|
|
||||||
TextContent = content;
|
// TextContent = content;
|
||||||
await ConvertTextToSpeech(content);
|
// await ConvertTextToSpeech(content);
|
||||||
//_scopedContentService.CurrentDOM = await jsRuntime.InvokeAsync<string>("getDivContent", "currentContent");
|
// //_scopedContentService.CurrentDOM = await jsRuntime.InvokeAsync<string>("getDivContent", "currentContent");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
private async void UpdateFinished(string receivedSessionId)
|
private async void UpdateFinished(string receivedSessionId)
|
||||||
{
|
{
|
||||||
|
|
@ -418,15 +434,20 @@
|
||||||
dynamicallyLoadedCss = "";
|
dynamicallyLoadedCss = "";
|
||||||
HtmlContent.Clear();
|
HtmlContent.Clear();
|
||||||
_scopedContentService.OnBrandNameChanged -= HandleBrandNameChanged;
|
_scopedContentService.OnBrandNameChanged -= HandleBrandNameChanged;
|
||||||
AIService.OnContentReceived -= UpdateContent;
|
ChatGptService.OnContentReceived -= UpdateContent;
|
||||||
AIService.OnContentReceiveFinished -= UpdateFinished;
|
ChatGptService.OnContentReceiveFinished -= UpdateFinished;
|
||||||
AIService.OnStatusChangeReceived -= UpdateStatus;
|
ChatGptService.OnStatusChangeReceived -= UpdateStatus;
|
||||||
AIService.OnTextContentAvailable -= UpdateTextContentForVoice;
|
ChatGptService.OnTextContentAvailable -= UpdateTextContentForVoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
await CssTemplateService.DeleteSessionCssFile(SessionId);
|
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 BLAIzor.Services;
|
||||||
using Google.Cloud.Speech.V1;
|
using Google.Cloud.Speech.V1;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using Radzen;
|
using Radzen;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -18,8 +19,10 @@ namespace BLAIzor.Components.Pages
|
||||||
[Inject] protected IConfiguration configuration { get; set; }
|
[Inject] protected IConfiguration configuration { get; set; }
|
||||||
[Inject] protected HttpClient Http { get; set; }
|
[Inject] protected HttpClient Http { get; set; }
|
||||||
[Inject] protected IJSRuntime jsRuntime { get; set; }
|
[Inject] protected IJSRuntime jsRuntime { get; set; }
|
||||||
|
[Inject] protected ISimpleLogger _logger { get; set; }
|
||||||
[Inject] protected AIService ChatGptService { 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();
|
public static readonly Dictionary<string, MainPageBase> _instances = new();
|
||||||
|
|
||||||
|
|
@ -40,7 +43,7 @@ namespace BLAIzor.Components.Pages
|
||||||
public bool STTEnabled;
|
public bool STTEnabled;
|
||||||
public bool _initVoicePending = false;
|
public bool _initVoicePending = false;
|
||||||
public bool welcomeStage = true;
|
public bool welcomeStage = true;
|
||||||
public bool AiVoicePermitted = false;
|
public bool AiVoicePermitted = true;
|
||||||
|
|
||||||
public string FirstColumnClass = "";
|
public string FirstColumnClass = "";
|
||||||
public bool isEmailFormVisible = false;
|
public bool isEmailFormVisible = false;
|
||||||
|
|
@ -53,6 +56,8 @@ namespace BLAIzor.Components.Pages
|
||||||
public List<MenuItem> MenuItems = new();
|
public List<MenuItem> MenuItems = new();
|
||||||
public string dynamicallyLoadedCss = string.Empty;
|
public string dynamicallyLoadedCss = string.Empty;
|
||||||
public MenuItem currentMenuItem = new();
|
public MenuItem currentMenuItem = new();
|
||||||
|
public bool IsContentSaved = false;
|
||||||
|
|
||||||
|
|
||||||
public WebsiteContentModel SiteModel = new();
|
public WebsiteContentModel SiteModel = new();
|
||||||
|
|
||||||
|
|
@ -233,6 +238,16 @@ namespace BLAIzor.Components.Pages
|
||||||
|
|
||||||
HtmlContent.Clear();
|
HtmlContent.Clear();
|
||||||
HtmlContent.Append(menuItem.StoredHtml);
|
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
|
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)
|
public async Task DisplayEmailForm(string emailAddress)
|
||||||
{
|
{
|
||||||
FirstColumnClass = "col-12 col-md-6";
|
FirstColumnClass = "col-12 col-md-6";
|
||||||
|
|
@ -348,7 +375,15 @@ namespace BLAIzor.Components.Pages
|
||||||
NotificationService.Notify(message);
|
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">
|
<RadzenRow class="rz-text-align-center" Gap="1rem">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@foreach (var image in files.Images)
|
@foreach (var image in files.Images)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@
|
||||||
@using System.Net
|
@using System.Net
|
||||||
@using Radzen.Blazor.Rendering
|
@using Radzen.Blazor.Rendering
|
||||||
|
|
||||||
@rendermode InteractiveServer
|
@* @rendermode InteractiveServer *@
|
||||||
@inject ContentService _contentService
|
|
||||||
@inject IEmailSender _emailService
|
@inject IEmailSender _emailService
|
||||||
@inject NavigationManager _navigationManager
|
@inject NavigationManager _navigationManager
|
||||||
@inject IHttpContextAccessor HttpContextAccessor
|
@inject IHttpContextAccessor HttpContextAccessor
|
||||||
|
|
@ -21,28 +20,163 @@
|
||||||
@inject CssInjectorService CssService
|
@inject CssInjectorService CssService
|
||||||
@inject DialogService DialogService
|
@inject DialogService DialogService
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="page" style="z-index: 1">
|
<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;
|
z-index: 10005;
|
||||||
top: 100px;
|
top: 100px;
|
||||||
left: 0px;
|
left: 10px;
|
||||||
border-bottom-left-radius: 0px;
|
height: 40px;
|
||||||
border-top-left-radius: 0px;
|
border-bottom-left-radius: 20px;
|
||||||
|
border-top-left-radius: 20px;
|
||||||
border-bottom-right-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;">
|
border-top-right-radius: 20px;">
|
||||||
<i class="fa-solid fa-wrench"></i>
|
<i class="fa-solid fa-wrench"></i>
|
||||||
</RadzenButton>
|
</RadzenButton>
|
||||||
|
|
||||||
<RadzenStack class="editor-window animate__animated animate__slideInLeft" Orientation="Orientation.Vertical" style="" Visible=@displaySettingsPanel>
|
<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">
|
<div class="rz-p-2 rz-text-align-center">
|
||||||
<h3>Basic information</h3>
|
<h3>Basic information</h3>
|
||||||
<p>@SiteInfo.SiteName</p>
|
<p>@SiteInfo.SiteName</p>
|
||||||
<RadzenButton class="btn" Text="Edit basic info" Click="@EditSite" />
|
<RadzenButton class="btn" Text="Edit basic info" Click="@EditSite" />
|
||||||
</div>
|
</div>
|
||||||
</RadzenStack>
|
</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="">
|
<RadzenStack Orientation="Orientation.Vertical" style="">
|
||||||
<div class="rz-p-2 rz-text-align-center">
|
<div class="rz-p-2 rz-text-align-center">
|
||||||
<h5>Manage content</h5>
|
<h5>Manage content</h5>
|
||||||
|
|
@ -79,11 +213,11 @@
|
||||||
<RadzenButton class="btn" Text="Open library" Click="@OpenManageUploads" />
|
<RadzenButton class="btn" Text="Open library" Click="@OpenManageUploads" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</RadzenStack>
|
</RadzenStack>*@
|
||||||
</RadzenStack>
|
</RadzenStack>
|
||||||
|
|
||||||
|
|
||||||
<NewNavMenu Menu="@MenuItems" BrandName="@SelectedBrandName" OnMenuClicked=@MenuClick></NewNavMenu>
|
<NewNavMenu Menu="@MenuItems" BrandName="@SelectedBrandName" SiteId="siteid" OnMenuClicked=@MenuClick></NewNavMenu>
|
||||||
<main>
|
<main>
|
||||||
|
|
||||||
<article class="content text-center" style="position: relative; z-index: 4;">
|
<article class="content text-center" style="position: relative; z-index: 4;">
|
||||||
|
|
@ -260,8 +394,9 @@
|
||||||
private bool isRecording = false;
|
private bool isRecording = false;
|
||||||
|
|
||||||
// private string dynamicallyLoadedCss = string.Empty;
|
// private string dynamicallyLoadedCss = string.Empty;
|
||||||
private bool isContentSaved = false;
|
|
||||||
private bool displaySettingsPanel = false;
|
private bool displaySettingsPanel = false;
|
||||||
|
private bool displayTopPanel = false;
|
||||||
private bool displayOptions = false;
|
private bool displayOptions = false;
|
||||||
private bool forceRegenerate = false;
|
private bool forceRegenerate = false;
|
||||||
|
|
||||||
|
|
@ -276,14 +411,22 @@
|
||||||
displaySettingsPanel = !displaySettingsPanel;
|
displaySettingsPanel = !displaySettingsPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ToggleSavedContentEdit()
|
||||||
|
{
|
||||||
|
displayTopPanel = !displayTopPanel;
|
||||||
|
}
|
||||||
|
|
||||||
async Task SaveCurrentLayout(MenuItem menuItem)
|
async Task SaveCurrentLayout(MenuItem menuItem)
|
||||||
{
|
{
|
||||||
//Save current layout called
|
//Save current layout called
|
||||||
menuItem.StoredHtml = HtmlContent.ToString();
|
menuItem.StoredHtml = HtmlContent.ToString();
|
||||||
Console.WriteLine($"Content length: {HtmlContent.Length}");
|
await _logger.InfoAsync($"Preview Component: Saving layout!", $"Content length: {HtmlContent.Length}");
|
||||||
Console.WriteLine(menuItem.StoredHtml);
|
|
||||||
|
await _logger.InfoAsync($"Preview Component: Saving layout!", $"Content length: {menuItem.StoredHtml}");
|
||||||
var result = await _contentEditorService.UpdateMenuItemAsync(menuItem);
|
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()
|
public void HomeClick()
|
||||||
{
|
{
|
||||||
//ChatGptService.OnContentReceived -= UpdateContent;
|
//ChatGptService.OnContentReceived -= UpdateContent;
|
||||||
AIService.OnContentReceived -= UpdateContent;
|
ChatGptService.OnContentReceived -= UpdateContent;
|
||||||
_navigationManager.Refresh(true);
|
_navigationManager.Refresh(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,7 +489,7 @@
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
SessionId = Guid.NewGuid().ToString();
|
SessionId = _scopedContentService.SessionId;
|
||||||
SiteId = siteid;
|
SiteId = siteid;
|
||||||
_instances[SessionId] = this;
|
_instances[SessionId] = this;
|
||||||
_scopedContentService.OnBrandNameChanged += HandleBrandNameChanged;
|
_scopedContentService.OnBrandNameChanged += HandleBrandNameChanged;
|
||||||
|
|
@ -392,10 +535,10 @@
|
||||||
Console.Write("------------------------");
|
Console.Write("------------------------");
|
||||||
|
|
||||||
// ChatGptService.OnContentReceived += UpdateContent;
|
// ChatGptService.OnContentReceived += UpdateContent;
|
||||||
AIService.OnContentReceived += UpdateContent;
|
ChatGptService.OnContentReceived += UpdateContent;
|
||||||
// ChatGptService.OnStatusChangeReceived += UpdateStatus;
|
// ChatGptService.OnStatusChangeReceived += UpdateStatus;
|
||||||
AIService.OnStatusChangeReceived += UpdateStatus;
|
ChatGptService.OnStatusChangeReceived += UpdateStatus;
|
||||||
AIService.OnTextContentAvailable += UpdateTextContentForVoice;
|
ChatGptService.OnTextContentAvailable += UpdateTextContentForVoice;
|
||||||
|
|
||||||
Menu = await GetMenuList(SiteId);
|
Menu = await GetMenuList(SiteId);
|
||||||
MenuItems = await GetMenuItems(SiteId);
|
MenuItems = await GetMenuItems(SiteId);
|
||||||
|
|
@ -409,7 +552,7 @@
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await ChatGptService.GetChatGptWelcomeMessage(SessionId, SiteId, 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);
|
//await ChatGptService.ProcessContentRequest(SessionId, MenuItems.FirstOrDefault(), SiteId, (int)SiteInfo.TemplateId!, TemplateCollectionName, Menu, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -428,7 +571,10 @@
|
||||||
if (menuItem != null)
|
if (menuItem != null)
|
||||||
{
|
{
|
||||||
currentMenuItem = menuItem;
|
currentMenuItem = menuItem;
|
||||||
|
// IsContentSaved = string.IsNullOrEmpty(currentMenuItem.StoredHtml) ? false : true;
|
||||||
|
// _logger.InfoAsync($"Preview - UpdateContent: {IsContentSaved}");
|
||||||
displayOptions = true;
|
displayOptions = true;
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
//InvokeAsync(StateHasChanged); // Ensures UI updates dynamically
|
//InvokeAsync(StateHasChanged); // Ensures UI updates dynamically
|
||||||
await InvokeAsync(() =>
|
await InvokeAsync(() =>
|
||||||
|
|
@ -441,16 +587,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void UpdateTextContentForVoice(string receivedSessionId, string content)
|
// private async void UpdateTextContentForVoice(string receivedSessionId, string content)
|
||||||
{
|
// {
|
||||||
if (receivedSessionId == SessionId) // Only accept messages meant for this tab
|
// if (receivedSessionId == SessionId) // Only accept messages meant for this tab
|
||||||
{
|
// {
|
||||||
|
|
||||||
TextContent = content;
|
// TextContent = content;
|
||||||
await ConvertTextToSpeech(content);
|
// await ConvertTextToSpeech(content);
|
||||||
//_scopedContentService.CurrentDOM = await jsRuntime.InvokeAsync<string>("getDivContent", "currentContent");
|
// //_scopedContentService.CurrentDOM = await jsRuntime.InvokeAsync<string>("getDivContent", "currentContent");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
private async void UpdateFinished(string receivedSessionId)
|
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 = "";
|
dynamicallyLoadedCss = "";
|
||||||
HtmlContent.Clear();
|
HtmlContent.Clear();
|
||||||
|
await CssTemplateService.DeleteSessionCssFile(SessionId);
|
||||||
_scopedContentService.OnBrandNameChanged -= HandleBrandNameChanged;
|
_scopedContentService.OnBrandNameChanged -= HandleBrandNameChanged;
|
||||||
AIService.OnContentReceived -= UpdateContent;
|
ChatGptService.OnContentReceived -= UpdateContent;
|
||||||
AIService.OnStatusChangeReceived -= UpdateStatus;
|
ChatGptService.OnContentReceiveFinished -= UpdateFinished;
|
||||||
|
ChatGptService.OnStatusChangeReceived -= UpdateStatus;
|
||||||
|
ChatGptService.OnTextContentAvailable -= UpdateTextContentForVoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
// public async ValueTask DisposeAsync()
|
||||||
await CssTemplateService.DeleteSessionCssFile(SessionId);
|
// {
|
||||||
}
|
// await CssTemplateService.DeleteSessionCssFile(SessionId);
|
||||||
|
// }
|
||||||
|
|
||||||
public async Task EditSite()
|
public async Task EditSite()
|
||||||
{
|
{
|
||||||
|
|
@ -525,7 +682,45 @@
|
||||||
new Dictionary<string, object>() {
|
new Dictionary<string, object>() {
|
||||||
{ "SiteId", siteid },
|
{ "SiteId", siteid },
|
||||||
{ "OnSiteNameChanged", new Func<string, Task>(OnSiteNameChanged) },
|
{ "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()
|
new DialogOptions()
|
||||||
{
|
{
|
||||||
Resizable = true,
|
Resizable = true,
|
||||||
|
|
@ -549,6 +744,15 @@
|
||||||
SelectedBrandName = newName;
|
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)
|
public async Task EditContentItem(int Id)
|
||||||
{
|
{
|
||||||
string dialogKey = $"EditContentItemDialogSettings_{Id}"; // can be anything unique
|
string dialogKey = $"EditContentItemDialogSettings_{Id}"; // can be anything unique
|
||||||
|
|
@ -568,7 +772,7 @@
|
||||||
{ "OnContentUpdated", new Func<ContentItem, Task>(OnContentItemUpdated) },
|
{ "OnContentUpdated", new Func<ContentItem, Task>(OnContentItemUpdated) },
|
||||||
{ "OnSaved", new Func<ContentItem, Task>(OnContentItemSaved) },
|
{ "OnSaved", new Func<ContentItem, Task>(OnContentItemSaved) },
|
||||||
{ "OnCancelled", new Func<Task>(OnEditContentItemCancelClicked) },
|
{ "OnCancelled", new Func<Task>(OnEditContentItemCancelClicked) },
|
||||||
},
|
},
|
||||||
new DialogOptions()
|
new DialogOptions()
|
||||||
{
|
{
|
||||||
Resizable = true,
|
Resizable = true,
|
||||||
|
|
@ -588,13 +792,16 @@
|
||||||
|
|
||||||
private async Task OnContentItemUpdated(ContentItem contentGroup)
|
private async Task OnContentItemUpdated(ContentItem contentGroup)
|
||||||
{
|
{
|
||||||
Console.WriteLine("ContentGroup updated!!!!!!");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnContentItemSaved(ContentItem contentGroup)
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -630,7 +837,7 @@
|
||||||
new Dictionary<string, object>() {
|
new Dictionary<string, object>() {
|
||||||
{ "SiteInfoId", siteid },
|
{ "SiteInfoId", siteid },
|
||||||
{ "OnManageContentItemClicked", new Func<string, int, Task>(OnManageContentItemClicked) }
|
{ "OnManageContentItemClicked", new Func<string, int, Task>(OnManageContentItemClicked) }
|
||||||
},
|
},
|
||||||
|
|
||||||
new DialogOptions()
|
new DialogOptions()
|
||||||
{
|
{
|
||||||
|
|
@ -652,28 +859,23 @@
|
||||||
private async Task OnContentGroupUpdated(ContentGroup contentGroup)
|
private async Task OnContentGroupUpdated(ContentGroup contentGroup)
|
||||||
{
|
{
|
||||||
Console.WriteLine("ContentGroup updated!!!!!!");
|
Console.WriteLine("ContentGroup updated!!!!!!");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnContentGroupEditStarted(ContentGroup contentGroup)
|
private async Task OnContentGroupEditStarted(ContentGroup contentGroup)
|
||||||
{
|
{
|
||||||
Console.WriteLine("ContentGroup edit started!!!!!!");
|
Console.WriteLine("ContentGroup edit started!!!!!!");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task OnManageContentItemClicked(string methodName, int Id)
|
private async Task OnManageContentItemClicked(string methodName, int Id)
|
||||||
{
|
{
|
||||||
Console.WriteLine("ContentItem Edit clicked");
|
Console.WriteLine("ContentItem Edit clicked");
|
||||||
|
|
||||||
await EditContentItem(Id);
|
await EditContentItem(Id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OpenManageUploads()
|
public async Task OpenManageUploads()
|
||||||
{
|
{
|
||||||
string dialogKey = $"ManageUploadsDialogSettings_{siteid}"; // can be anything unique
|
string dialogKey = $"ManageUploadsDialogSettings_{siteid}"; // can be anything unique
|
||||||
|
|
||||||
var settings = await LoadStateAsync(dialogKey)
|
var settings = await LoadStateAsync(dialogKey)
|
||||||
?? new EditSiteInfoDialogSettings
|
?? new EditSiteInfoDialogSettings
|
||||||
{
|
{
|
||||||
|
|
@ -698,10 +900,56 @@
|
||||||
CssClass = "draggable-popup-dialog",
|
CssClass = "draggable-popup-dialog",
|
||||||
WrapperCssClass = "draggable-popup-dialog-wrapper"
|
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);
|
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)
|
private Action<System.Drawing.Point> GetDragHandler(string key)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,20 @@
|
||||||
|
|
||||||
<h1>Your Sites</h1>
|
<h1>Your Sites</h1>
|
||||||
<div class="row g-0">
|
<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%"
|
<RadzenPanel Collapsed="true" AllowCollapse="true" class="rz-my-5 rz-mx-auto" Style="width: 100%"
|
||||||
Expand=@(() => Change("Panel expanded")) Collapse=@(() => Change("Panel collapsed"))>
|
Expand=@(() => Change("Panel expanded")) Collapse=@(() => Change("Panel collapsed"))>
|
||||||
|
|
||||||
<HeaderTemplate>
|
<HeaderTemplate>
|
||||||
<RadzenText TextStyle="TextStyle.H6" class="rz-display-flex rz-align-items-center rz-m-0">
|
<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>
|
</RadzenText>
|
||||||
</HeaderTemplate>
|
</HeaderTemplate>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<RadzenCard class="rz-mt-4">
|
|
||||||
|
<CreateSiteWizard OnDescriptionFinalized="HandleDescriptionGenerated" UserId="@userId" />
|
||||||
|
|
||||||
|
@* <RadzenCard class="rz-mt-4">
|
||||||
<EditForm Model="newSite" OnValidSubmit="HandleValidSubmit">
|
<EditForm Model="newSite" OnValidSubmit="HandleValidSubmit">
|
||||||
<DataAnnotationsValidator />
|
<DataAnnotationsValidator />
|
||||||
<ValidationSummary />
|
<ValidationSummary />
|
||||||
|
|
@ -47,12 +52,16 @@
|
||||||
<label for="siteName" class="form-label">Site description</label>
|
<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" />
|
<InputText id="siteDescription" placeholder="A description so AI will know what is this site about" class="form-control" @bind-Value="newSite.SiteDescription" />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Create Site</button>
|
<button type="submit" class="btn btn-primary">Create Site</button>
|
||||||
</EditForm>
|
</EditForm>
|
||||||
|
|
||||||
</RadzenCard>
|
</RadzenCard>*@
|
||||||
|
|
||||||
|
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
|
|
@ -60,61 +69,78 @@
|
||||||
</RadzenPanel>
|
</RadzenPanel>
|
||||||
|
|
||||||
@if (siteInfoList.Any())
|
@if (siteInfoList.Any())
|
||||||
{
|
{
|
||||||
|
|
||||||
<RadzenPanel AllowCollapse="true" class="rz-my-5 rz-mx-auto" Style="width: 100%"
|
<RadzenPanel AllowCollapse="true" class="rz-my-5 rz-mx-auto" Style="width: 100%"
|
||||||
Expand=@(() => Change("Panel expanded")) Collapse=@(() => Change("Panel collapsed"))>
|
Expand=@(() => Change("Panel expanded")) Collapse=@(() => Change("Panel collapsed"))>
|
||||||
<HeaderTemplate>
|
<HeaderTemplate>
|
||||||
<RadzenText TextStyle="TextStyle.H6" class="rz-display-flex rz-align-items-center rz-m-0">
|
<RadzenText TextStyle="TextStyle.H6" class="rz-display-flex rz-align-items-center rz-m-0">
|
||||||
<RadzenIcon Icon="account_box" class="rz-me-1" /><b>Sites created</b>
|
<RadzenIcon Icon="account_box" class="rz-me-1" /><b>Sites created</b>
|
||||||
</RadzenText>
|
</RadzenText>
|
||||||
</HeaderTemplate>
|
</HeaderTemplate>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<RadzenCard class="rz-mt-4">
|
@{
|
||||||
<RadzenDataList PageSize="3" WrapItems="true" AllowPaging="true"
|
if(siteInfoList.Count() > 0)
|
||||||
Data="@siteInfoList" TItem="SiteInfo">
|
{
|
||||||
<Template Context="site">
|
<AnimateOnRender CssClass="animate__animated animate__backInUp">
|
||||||
<RadzenCard Style="width: 250px; background-color: darkgrey">
|
<RadzenCard class="rz-mt-4">
|
||||||
<RadzenRow JustifyContent="@JustifyContent.SpaceBetween">
|
<RadzenDataList Density="Density.Compact" PageSize="4" WrapItems="true"
|
||||||
<RadzenColumn Size="4" class="rz-text-truncate">
|
AllowPaging="false"
|
||||||
<RadzenBadge BadgeStyle="BadgeStyle.Light" Text=@($"{site.Id}") class="rz-me-1" />
|
Data="@siteInfoList" TItem="SiteInfo">
|
||||||
<b>@(site.SiteName)</b>
|
<Template Context="site">
|
||||||
</RadzenColumn>
|
<RadzenCard class="admin-rz-card" Style="width: 250px;">
|
||||||
<RadzenColumn Size="8" class="rz-text-align-end">
|
<RadzenRow JustifyContent="@JustifyContent.SpaceBetween">
|
||||||
<RadzenBadge BadgeStyle="BadgeStyle.Success" Text=@($"new") />
|
<RadzenColumn Size="4" class="rz-text-truncate">
|
||||||
</RadzenColumn>
|
<RadzenBadge BadgeStyle="BadgeStyle.Light" Text=@($"{site.Id}") class="rz-me-1" />
|
||||||
</RadzenRow>
|
<b>@(site.SiteName)</b>
|
||||||
<hr style="border: none; background-color: var(--rz-text-disabled-color); height: 1px; margin: 1rem 0;" />
|
</RadzenColumn>
|
||||||
<RadzenStack Orientation="Orientation.Vertical" AlignItems="AlignItems.Center" Gap="1rem">
|
<RadzenColumn Size="8" class="rz-text-align-end">
|
||||||
<RadzenImage Path="@site.BrandLogoUrl" class="img-fluid" Style="max-height: 100px;" AlternateText="@(site.SiteName)" />
|
<RadzenBadge BadgeStyle="BadgeStyle.Success" Text=@($"new") />
|
||||||
<RadzenStack Gap="0">
|
</RadzenColumn>
|
||||||
<RadzenText TextStyle="TextStyle.H6" class="rz-mb-0">@(site.SiteName)</RadzenText>
|
</RadzenRow>
|
||||||
<RadzenText TextStyle="TextStyle.Body1">@site.DefaultUrl</RadzenText>
|
<hr style="border: none; background-color: var(--rz-text-disabled-color); height: 1px; margin: 1rem 0;" />
|
||||||
<RadzenText TextStyle="TextStyle.Body2">Fefault color: @(site.DefaultColor)</RadzenText>
|
<RadzenStack Orientation="Orientation.Vertical" AlignItems="AlignItems.Center" Gap="1rem">
|
||||||
<RadzenText TextStyle="TextStyle.Body2">Theme: @(site.TemplateId)</RadzenText>
|
<RadzenImage Path="@site.BrandLogoUrl" class="img-fluid" Style="max-height: 100px;" AlternateText="@(site.SiteName)" />
|
||||||
</RadzenStack>
|
<RadzenStack Gap="0">
|
||||||
</RadzenStack>
|
<RadzenText TextStyle="TextStyle.H6" class="rz-mb-0">@(site.SiteName)</RadzenText>
|
||||||
<RadzenStack Orientation="@Orientation.Horizontal" Gap="10px" Reverse="false" JustifyContent="@JustifyContent.Center" AlignItems="@AlignItems.Center" Wrap="@FlexWrap.Wrap" Style="height: fit-content">
|
<RadzenText TextStyle="TextStyle.Body1">@site.DefaultUrl</RadzenText>
|
||||||
<InputText @bind-Value="collectionName" class="form-control" style="width: 100%;" placeholder="Site name" />
|
<RadzenText TextStyle="TextStyle.Body2">Fefault color: @(site.DefaultColor)</RadzenText>
|
||||||
<a style="font-size: 14px;" @onclick="()=>Migrate(site.Id, collectionName)" class="btn btn-secondary">Migrate</a>
|
<RadzenText TextStyle="TextStyle.Body2">Theme: @(site.TemplateId)</RadzenText>
|
||||||
@* <a style="font-size: 14px;" href="/generate-content/@site.Id" class="btn btn-secondary">Manage content</a> *@
|
</RadzenStack>
|
||||||
<a style="font-size: 14px;" @onclick="()=>Preview(site)" class="btn btn-secondary"> Preview</a>
|
</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;" @onclick="()=>Preview(site)" class="btn btn-secondary"> Preview</a>
|
||||||
|
|
||||||
</RadzenStack>
|
</RadzenStack>
|
||||||
|
</RadzenCard>
|
||||||
|
</Template>
|
||||||
|
</RadzenDataList>
|
||||||
|
</RadzenCard>
|
||||||
|
</AnimateOnRender>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<RadzenCard class="rz-mt-4">
|
||||||
|
<p>Let1s create your first website!</p>
|
||||||
</RadzenCard>
|
</RadzenCard>
|
||||||
</Template>
|
}
|
||||||
</RadzenDataList>
|
|
||||||
</RadzenCard>
|
}
|
||||||
</ChildContent>
|
|
||||||
<SummaryTemplate>
|
</ChildContent>
|
||||||
<RadzenCard class="rz-mt-4">
|
<SummaryTemplate>
|
||||||
<b>@siteInfoList.Count() Sites</b>
|
<RadzenCard class="rz-mt-4">
|
||||||
</RadzenCard>
|
<b>@siteInfoList.Count() Sites</b>
|
||||||
</SummaryTemplate>
|
</RadzenCard>
|
||||||
</RadzenPanel>
|
</SummaryTemplate>
|
||||||
|
</RadzenPanel>
|
||||||
|
|
||||||
|
|
||||||
@* foreach (var item in siteInfoList)
|
@* foreach (var item in siteInfoList)
|
||||||
{
|
{
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<div class="card m-2">
|
<div class="card m-2">
|
||||||
|
|
@ -140,11 +166,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
} *@
|
} *@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<p>No sites created yet.</p>
|
<p>No sites created yet.</p>
|
||||||
}
|
}
|
||||||
|
</AnimateOnRender>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -157,9 +184,28 @@
|
||||||
private string? userName;
|
private string? userName;
|
||||||
private AuthenticationState? authState;
|
private AuthenticationState? authState;
|
||||||
int position = 1;
|
int position = 1;
|
||||||
|
public string SessionId;
|
||||||
//TEMPORARY
|
//TEMPORARY
|
||||||
private string collectionName = "seemgen-collection";
|
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)
|
void Change(string text)
|
||||||
{
|
{
|
||||||
Console.Write($"{text}");
|
Console.Write($"{text}");
|
||||||
|
|
@ -201,6 +247,8 @@
|
||||||
userName = CustomAuthProvider.GetUserName();
|
userName = CustomAuthProvider.GetUserName();
|
||||||
}
|
}
|
||||||
siteInfoList = await _contentEditorService.GetUserSitesAsync(userId!);
|
siteInfoList = await _contentEditorService.GetUserSitesAsync(userId!);
|
||||||
|
SessionId = SiteInfoService.SessionId;
|
||||||
|
// SiteInfoService.SessionId = SessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleValidSubmit()
|
private async Task HandleValidSubmit()
|
||||||
|
|
@ -212,6 +260,11 @@
|
||||||
var result = await _contentEditorService.AddSiteInfoAsync(newSite);
|
var result = await _contentEditorService.AddSiteInfoAsync(newSite);
|
||||||
siteInfoList = await _contentEditorService.GetUserSitesAsync(userId!);
|
siteInfoList = await _contentEditorService.GetUserSitesAsync(userId!);
|
||||||
newSite = new(); // Reset the form
|
newSite = new(); // Reset the form
|
||||||
|
if(result != null)
|
||||||
|
{
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo($"/generate-content/{result.Id}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public async Task<string> GenerateSubdomainAsync(string siteName)
|
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">
|
<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="px-3 py-2 reference-button bg-panel-gradient-highlight pointer">
|
||||||
<div class="text-content">
|
<div class="text-content">
|
||||||
<strong>Add new</strong>
|
<strong>Add new content</strong>
|
||||||
<br />
|
<br />
|
||||||
<small class="text-muted">Add a new content to this group</small>
|
<small class="text-muted">Add a new content to this group</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -45,12 +45,12 @@
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
|
|
||||||
<div class="p-2 reference-button">
|
<div class="p-2 reference-button">
|
||||||
<div class="text-content">
|
<div class="text-content text-start">
|
||||||
<strong>@item.Title</strong>
|
<strong>@item.Title</strong>
|
||||||
<br />
|
<br />
|
||||||
<small class="text-muted">@item.Language - @item.Tags</small>
|
<small class="text-muted">@item.Language - @item.Tags</small>
|
||||||
</div>
|
</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-primary" @onclick="() => EditItem(item.Id)">Edit</button>
|
||||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteItem(item.Id)">Delete</button>
|
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteItem(item.Id)">Delete</button>
|
||||||
@* <div class="icon-circle">V</div> *@
|
@* <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 Text="Edit" Click="() => ToggleGroupEdit()"></RadzenButton>
|
||||||
<RadzenButton ButtonStyle=@ButtonStyle.Warning Text="Edit" Click="() => OnForceReChunkClicked(Group)"></RadzenButton> *@
|
<RadzenButton ButtonStyle=@ButtonStyle.Warning Text="Edit" Click="() => OnForceReChunkClicked(Group)"></RadzenButton> *@
|
||||||
<button class="btn" type="button" @onclick="() => ToggleGroupEdit()">Edit</button>
|
<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>
|
<button class="btn btn-danger" type="button" @onclick="() => DeleteGroup(Group)">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
@using BLAIzor.Models
|
@using BLAIzor.Models
|
||||||
@using BLAIzor.Services
|
@using BLAIzor.Services
|
||||||
@inject ContentEditorService contentEditorService
|
@inject ContentEditorService contentEditorService
|
||||||
|
@inject CacheService cache
|
||||||
|
|
||||||
@if (isLoading)
|
@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 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);
|
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
|
// 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 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);
|
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)
|
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">
|
<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="mb-2 p-3 reference-button bg-panel-gradient-highlight pointer">
|
||||||
<div class="text-content">
|
<div class="text-content">
|
||||||
<strong>Add new</strong>
|
<strong>Add new group</strong>
|
||||||
<br />
|
<br />
|
||||||
<small class="text-muted">Add a new content group like "Blog", News, "Manuals" etc...</small>
|
<small class="text-muted">Add a new content group like "Blog", News, "Manuals" etc...</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ else
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int SiteId { get; set; }
|
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, Task> OnSiteNameChanged { get; set; }
|
||||||
[Parameter] public Func<string, int, Task> OnCancelItemClicked { get; set; }
|
[Parameter] public Func<string, int, Task> OnCancelItemClicked { get; set; }
|
||||||
|
|
||||||
|
|
@ -116,6 +116,8 @@ else
|
||||||
private async Task SaveSiteInfo()
|
private async Task SaveSiteInfo()
|
||||||
{
|
{
|
||||||
await _contentEditorService.UpdateSiteInfoAsync(siteInfo);
|
await _contentEditorService.UpdateSiteInfoAsync(siteInfo);
|
||||||
|
if (OnSiteInfoSaveClicked != null)
|
||||||
|
await OnSiteInfoSaveClicked.Invoke(siteInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
|
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 ContentEditorService _contentEditorService
|
||||||
@inject IHttpContextAccessor HttpContextAccessor
|
@inject IHttpContextAccessor HttpContextAccessor
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
|
@inject ISimpleLogger _logger
|
||||||
|
|
||||||
<nav class="navbar fixed-top navbar-expand-lg" style="z-index: 10005">
|
<nav class="navbar fixed-top navbar-expand-lg" style="z-index: 10005">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
|
@ -32,7 +33,7 @@
|
||||||
{
|
{
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" role="button"
|
<a class="nav-link dropdown-toggle" role="button"
|
||||||
data-bs-toggle="dropdown" aria-expanded="false">
|
data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
@menuItem.Name
|
@menuItem.Name
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
|
|
@ -72,7 +73,7 @@
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<select @onchange="OnLanguageSelected" class="form-select" style="width:100px">
|
<select @onchange="OnLanguageSelected" class="form-select" style="width:100px">
|
||||||
<option value="">Lang</option>
|
<option value="@SelectedLanguage">@SelectedLanguage</option>
|
||||||
@foreach (var Language in Languages)
|
@foreach (var Language in Languages)
|
||||||
{
|
{
|
||||||
<option value="@Language">@Language</option>
|
<option value="@Language">@Language</option>
|
||||||
|
|
@ -84,7 +85,8 @@
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public int SiteId;
|
[Parameter]
|
||||||
|
public int SiteId { get; set; }
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public List<MenuItem> Menu { get; set; }
|
public List<MenuItem> Menu { get; set; }
|
||||||
[Parameter] public string? BrandName { get; set; } = "BLAIzor";
|
[Parameter] public string? BrandName { get; set; } = "BLAIzor";
|
||||||
|
|
@ -114,17 +116,8 @@
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
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()
|
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();
|
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>
|
<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">
|
<div class="form-group">
|
||||||
<label>Sample</label><RadzenButton Click="@(() => ShowSnippetPreview(Snippet))">Preview snippet</RadzenButton>
|
<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 />
|
<RadzenHtmlEditorUndo />
|
||||||
<RadzenHtmlEditorRedo />
|
<RadzenHtmlEditorRedo />
|
||||||
<RadzenHtmlEditorSource />
|
<RadzenHtmlEditorSource />
|
||||||
</RadzenHtmlEditor>
|
</RadzenHtmlEditor> *@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<EventConsole @ref=@console />
|
@* <EventConsole @ref=@console /> *@
|
||||||
@if (IsLoading)
|
@if (IsLoading)
|
||||||
{
|
{
|
||||||
<p>Loading content...</p>
|
<p>Loading content...</p>
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
private string? userName;
|
private string? userName;
|
||||||
private AuthenticationState? authState;
|
private AuthenticationState? authState;
|
||||||
|
|
||||||
EventConsole console;
|
// EventConsole console;
|
||||||
|
|
||||||
public async void ShowSnippetPreview(HtmlSnippet snippet)
|
public async void ShowSnippetPreview(HtmlSnippet snippet)
|
||||||
{
|
{
|
||||||
|
|
@ -117,30 +117,30 @@
|
||||||
|
|
||||||
async Task OnPaste(HtmlEditorPasteEventArgs args)
|
async Task OnPaste(HtmlEditorPasteEventArgs args)
|
||||||
{
|
{
|
||||||
console.Log($"Paste: {args.Html}");
|
// console.Log($"Paste: {args.Html}");
|
||||||
// await OnContentUpdated.InvokeAsync(MenuItem);
|
// await OnContentUpdated.InvokeAsync(MenuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task OnChange(string html)
|
async Task OnChange(string html)
|
||||||
{
|
{
|
||||||
console.Log($"Change: {html}");
|
// console.Log($"Change: {html}");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task OnInput(string html)
|
async Task OnInput(string html)
|
||||||
{
|
{
|
||||||
console.Log($"Input: {html}");
|
// console.Log($"Input: {html}");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnExecute(HtmlEditorExecuteEventArgs args)
|
void OnExecute(HtmlEditorExecuteEventArgs args)
|
||||||
{
|
{
|
||||||
console.Log($"Execute: {args.CommandName}");
|
// console.Log($"Execute: {args.CommandName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnUploadComplete(UploadCompleteEventArgs args)
|
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
|
public static class TextHelper
|
||||||
{
|
{
|
||||||
// Special character replacement map
|
// Special character replacement map
|
||||||
private static readonly Dictionary<string, string> SpecialCharacterMap = new()
|
private static readonly Dictionary<string, string> HungarianSpecialCharacterMap = new()
|
||||||
{
|
{
|
||||||
{ "/", " per " },
|
{ "/", " per " },
|
||||||
{ "@", " kukac " },
|
{ "@", " kukac " },
|
||||||
|
|
@ -17,27 +17,22 @@ public static class TextHelper
|
||||||
//{ " - ", " mínusz " } // Example, you can add more
|
//{ " - ", " 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)
|
// Save parts that should be skipped (emails, URLs, dates)
|
||||||
var protectedParts = new Dictionary<string, string>();
|
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
|
// Protect dates like 2024.05.06
|
||||||
text = Regex.Replace(text, @"\b\d{4}\.\d{2}\.\d{2}\b", match =>
|
text = Regex.Replace(text, @"\b\d{4}\.\d{2}\.\d{2}\b", match =>
|
||||||
{
|
{
|
||||||
|
|
@ -55,31 +50,54 @@ public static class TextHelper
|
||||||
var parts = match.Value.Split('.');
|
var parts = match.Value.Split('.');
|
||||||
var integerPart = int.Parse(parts[0]);
|
var integerPart = int.Parse(parts[0]);
|
||||||
var decimalPart = int.Parse(parts[1]);
|
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")}";
|
{
|
||||||
|
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
|
// Then replace integers
|
||||||
text = Regex.Replace(text, @"\b\d+\b", match =>
|
text = Regex.Replace(text, @"\b\d+\b", match =>
|
||||||
{
|
{
|
||||||
int number = int.Parse(match.Value);
|
int number = int.Parse(match.Value);
|
||||||
return NumberToHungarian(number);
|
if(language == "Hungarian")
|
||||||
|
{
|
||||||
|
return NumberToHungarian(number);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NumberToEnglish(number);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Replace special characters from dictionary
|
// Replace special characters from dictionary
|
||||||
foreach (var kvp in SpecialCharacterMap)
|
if(language == "Hungarian")
|
||||||
{
|
{
|
||||||
text = text.Replace(kvp.Key, kvp.Value);
|
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)
|
// Replace dots surrounded by spaces (optional)
|
||||||
//text = Regex.Replace(text, @" (?=\.)|(?<=\.) ", " pont ");
|
//text = Regex.Replace(text, @" (?=\.)|(?<=\.) ", " pont ");
|
||||||
|
|
||||||
// Restore protected parts
|
// Restore protected parts
|
||||||
foreach (var kvp in protectedParts)
|
foreach (var kvp in protectedParts)
|
||||||
{
|
{
|
||||||
text = text.Replace(kvp.Key, kvp.Value);
|
text = text.Replace(kvp.Key, kvp.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
@ -91,6 +109,7 @@ public static class TextHelper
|
||||||
|
|
||||||
string[] units = { "", "egy", "két", "három", "négy", "öt", "hat", "hét", "nyolc", "kilenc" };
|
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[] 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();
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
|
@ -123,7 +142,7 @@ public static class TextHelper
|
||||||
if (number >= 10)
|
if (number >= 10)
|
||||||
{
|
{
|
||||||
int tensPart = number / 10;
|
int tensPart = number / 10;
|
||||||
result.Append(tens[tensPart]);
|
result.Append(tensAlternate[tensPart]);
|
||||||
number %= 10;
|
number %= 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,6 +158,84 @@ public static class TextHelper
|
||||||
return result.ToString();
|
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)
|
public static string FixJsonWithoutAI(string aiResponse)
|
||||||
{
|
{
|
||||||
if (aiResponse.StartsWith("```"))
|
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()
|
.IsRequired()
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("DesignStyle")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("DomainUrl")
|
b.Property<string>("DomainUrl")
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("EmbeddingService")
|
b.Property<string>("EmbeddingService")
|
||||||
|
|
@ -402,6 +404,12 @@ namespace BLAIzor.Migrations
|
||||||
b.Property<string>("Entity")
|
b.Property<string>("Entity")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("FacebookUrl")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("InstagramUrl")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<bool>("IsPublished")
|
b.Property<bool>("IsPublished")
|
||||||
.HasColumnType("bit");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
|
@ -423,6 +431,9 @@ namespace BLAIzor.Migrations
|
||||||
b.Property<int?>("TemplateId")
|
b.Property<int?>("TemplateId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("TwitterUrl")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("UserId")
|
b.Property<string>("UserId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("nvarchar(450)");
|
.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" +
|
"- 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:" +
|
"X Restrictions:" +
|
||||||
"- DO **NOT** modify the photo urls in any way." +
|
"- 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** generate or assume new photo URLs.\n" +
|
||||||
"- Do **NOT** modify photo URLs in any way.\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" +
|
"- 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";
|
"✅ 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)
|
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" +
|
var sb = new StringBuilder($"You are a helpful assistant generating HTML in {language} using Bootstrap 5.\n\n" +
|
||||||
"### General Instructions:\n" +
|
"### General Instructions:\n" +
|
||||||
|
|
@ -358,13 +291,14 @@ namespace BLAIzor.Models
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine("\n### DO NOT:\n" +
|
sb.AppendLine("\n### DO NOT:\n" +
|
||||||
"- Merge different content blocks.\n" +
|
"- DO **NOT** merge different content blocks.\n" +
|
||||||
"- Remove javascript or <script> tags"+
|
"- DO **NOT** remove javascript or <script> tags" +
|
||||||
"- Wrap the full output in a single `div`.\n" +
|
"- DO **NOT** wrap the full output in a single `div`.\n" +
|
||||||
"- Skip any provided content.\n" +
|
"- DO **NOT** skip any provided content.\n" +
|
||||||
"- Add assumed text (e.g. prices, links).\n" +
|
"- DO **NOT** add assumed text (e.g. prices, links).\n" +
|
||||||
"- Add `<img>` if no image URL exists.\n" +
|
"- DO **NOT** add `<img>` if no image URL exists.\n" +
|
||||||
"- Include explanation, markdown, or ` ```html` markers.\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");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -377,7 +311,7 @@ namespace BLAIzor.Models
|
||||||
"- If there is no matching html snippet, generate the bootstrap 5 based layout for the given block. Else parse the block content into the available code snippet." +
|
"- If there is no matching html snippet, generate the bootstrap 5 based layout for the given block. Else parse the block content into the available code snippet." +
|
||||||
"- For each block, add am opening and closing comment with the name of the block like: <!-- Hero start --> \r \n" +
|
"- For each block, add am opening and closing comment with the name of the block like: <!-- Hero start --> \r \n" +
|
||||||
"- Do not remove script tags from the provided snippets. \r \n" +
|
"- Do not remove script tags from the provided snippets. \r \n" +
|
||||||
"If the block's rawcontent contains photo url, display that photo within that section, not elsewhere.";
|
"If the block's rawcontent contains photo url, display that photo within that section, not elsewhere. ";
|
||||||
|
|
||||||
public static string GetHtmlRenderingAssistantMessageForTextAndErrorResult(LayoutPlan layoutPlan)
|
public static string GetHtmlRenderingAssistantMessageForTextAndErrorResult(LayoutPlan layoutPlan)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,10 @@ namespace BLAIzor.Models
|
||||||
public Message[] Messages { get; set; } = Array.Empty<Message>();
|
public Message[] Messages { get; set; } = Array.Empty<Message>();
|
||||||
[JsonProperty("stream")]
|
[JsonProperty("stream")]
|
||||||
public bool Stream { get; set; } = true;
|
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; }
|
public string UserId { get; set; }
|
||||||
|
|
||||||
[Url(ErrorMessage = "The DomainUrl field must be a valid URL.")]
|
[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.")]
|
[Url(ErrorMessage = "The DefaultUrl field must be a valid URL.")]
|
||||||
public string DefaultUrl { get; set; } // For generated subdomains
|
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 int? TemplateId { get; set; }
|
||||||
public bool TTSActive { get; set; } = false;
|
public bool TTSActive { get; set; } = false;
|
||||||
public bool STTActive { get; set; } = false;
|
public bool STTActive { get; set; } = false;
|
||||||
public string? VoiceId { get; set; }
|
public string? VoiceId { get; set; }
|
||||||
public string? Persona { get; set; }
|
public string? Persona { get; set; }
|
||||||
public string? Entity { get; set; }
|
public string? Entity { get; set; }
|
||||||
|
public string? DesignStyle { get; set; } // e.g. "Modern", "Classic", "Photorealistic"
|
||||||
public string? DefaultLanguage { get; set; }
|
public string? DefaultLanguage { get; set; }
|
||||||
public string? BackgroundVideo { get; set; }
|
public string? BackgroundVideo { get; set; }
|
||||||
public string? VectorCollectionName { get; set; }
|
public string? VectorCollectionName { get; set; }
|
||||||
public string? EmbeddingService { 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();
|
public List<ContentGroup> ContentGroups { get; set; } = new();
|
||||||
|
|
||||||
// Navigation property for IdentityUser
|
// Navigation property for IdentityUser
|
||||||
|
|
|
||||||
40
Program.cs
|
|
@ -1,6 +1,8 @@
|
||||||
using BLAIzor.Components;
|
using BLAIzor.Components;
|
||||||
|
using BLAIzor.Components.Partials;
|
||||||
using BLAIzor.Data;
|
using BLAIzor.Data;
|
||||||
using BLAIzor.Helpers;
|
using BLAIzor.Helpers;
|
||||||
|
using BLAIzor.Interfaces;
|
||||||
using BLAIzor.Services;
|
using BLAIzor.Services;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
|
@ -12,6 +14,8 @@ using Radzen;
|
||||||
using Sidio.Sitemap.AspNetCore;
|
using Sidio.Sitemap.AspNetCore;
|
||||||
using Sidio.Sitemap.Blazor;
|
using Sidio.Sitemap.Blazor;
|
||||||
using Sidio.Sitemap.Core.Services;
|
using Sidio.Sitemap.Core.Services;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
var configuration = builder.Configuration;
|
var configuration = builder.Configuration;
|
||||||
|
|
@ -35,6 +39,12 @@ builder.Services.Configure<KestrelServerOptions>(options =>
|
||||||
options.Limits.MaxRequestBodySize = 50 * 1024 * 1024;
|
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.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorComponents()
|
builder.Services.AddRazorComponents()
|
||||||
|
|
@ -42,9 +52,20 @@ builder.Services.AddRazorComponents()
|
||||||
|
|
||||||
builder.Services.AddRadzenComponents();
|
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.AddHttpClient();
|
||||||
|
builder.Services.AddScoped<ISimpleLogger, SimpleLogger>();
|
||||||
builder.Services.AddScoped<AIService>();
|
builder.Services.AddScoped<AIService>();
|
||||||
builder.Services.AddSingleton<ContentService>();
|
//builder.Services.AddSingleton<ContentService>();
|
||||||
builder.Services.AddScoped<ScopedContentService>();
|
builder.Services.AddScoped<ScopedContentService>();
|
||||||
builder.Services.AddScoped<QDrantService>();
|
builder.Services.AddScoped<QDrantService>();
|
||||||
builder.Services.AddScoped<OpenAIEmbeddingService>();
|
builder.Services.AddScoped<OpenAIEmbeddingService>();
|
||||||
|
|
@ -58,17 +79,32 @@ builder.Services.AddScoped<CustomAuthenticationStateProvider>();
|
||||||
builder.Services.Configure<SmtpSettings>(builder.Configuration.GetSection("Smtp"));
|
builder.Services.Configure<SmtpSettings>(builder.Configuration.GetSection("Smtp"));
|
||||||
builder.Services.AddTransient<IEmailSender, EmailService>();
|
builder.Services.AddTransient<IEmailSender, EmailService>();
|
||||||
builder.Services.AddScoped<ContentEditorService>();
|
builder.Services.AddScoped<ContentEditorService>();
|
||||||
|
builder.Services.AddScoped<ContentEditorAIService>();
|
||||||
builder.Services.AddScoped<CssTemplateService>();
|
builder.Services.AddScoped<CssTemplateService>();
|
||||||
builder.Services.AddScoped<DesignTemplateService>();
|
builder.Services.AddScoped<DesignTemplateService>();
|
||||||
builder.Services.AddScoped<OpenAIApiService>();
|
builder.Services.AddScoped<OpenAIApiService>();
|
||||||
builder.Services.AddScoped<DeepSeekApiService>();
|
builder.Services.AddScoped<DeepSeekApiService>();
|
||||||
builder.Services.AddScoped<OpenAiRealtimeService>();
|
builder.Services.AddScoped<OpenAiRealtimeService>();
|
||||||
builder.Services.AddScoped<CerebrasAPIService>();
|
builder.Services.AddScoped<CerebrasAPIService>();
|
||||||
|
builder.Services.AddScoped<KimiApiService>();
|
||||||
builder.Services.AddScoped<CssInjectorService>();
|
builder.Services.AddScoped<CssInjectorService>();
|
||||||
builder.Services.AddScoped<LocalVectorSearchService>();
|
builder.Services.AddScoped<LocalVectorSearchService>();
|
||||||
builder.Services.AddScoped<WebsiteContentLoaderService>();
|
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.AddHostedService<TempFileCleanupService>();
|
||||||
builder.Services.AddScoped<ISimpleLogger, SimpleLogger>();
|
|
||||||
|
|
||||||
|
|
||||||
builder.Services.AddServerSideBlazor().AddCircuitOptions(options => options.DetailedErrors = true).AddHubOptions(options =>
|
builder.Services.AddServerSideBlazor().AddCircuitOptions(options => options.DetailedErrors = true).AddHubOptions(options =>
|
||||||
{
|
{
|
||||||
options.MaximumReceiveMessageSize = 1024000; // e.g. 100 KB
|
options.MaximumReceiveMessageSize = 1024000; // e.g. 100 KB
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,13 @@ using BLAIzor.Helpers;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using Qdrant.Client.Grpc;
|
using Qdrant.Client.Grpc;
|
||||||
using Microsoft.CodeAnalysis.Elfie.Model.Tree;
|
using Microsoft.CodeAnalysis.Elfie.Model.Tree;
|
||||||
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
|
|
||||||
namespace BLAIzor.Services
|
namespace BLAIzor.Services
|
||||||
{
|
{
|
||||||
public class AIService
|
public class AIService
|
||||||
{
|
{
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly ContentService _contentService;
|
|
||||||
private readonly ContentEditorService _contentEditorService;
|
private readonly ContentEditorService _contentEditorService;
|
||||||
private readonly ScopedContentService _scopedContentService;
|
private readonly ScopedContentService _scopedContentService;
|
||||||
private readonly OpenAIEmbeddingService _openAIEmbeddingService;
|
private readonly OpenAIEmbeddingService _openAIEmbeddingService;
|
||||||
|
|
@ -41,17 +41,19 @@ namespace BLAIzor.Services
|
||||||
private readonly OpenAiRealtimeService _openAIRealtimeService;
|
private readonly OpenAiRealtimeService _openAIRealtimeService;
|
||||||
private readonly DeepSeekApiService _deepSeekApiService;
|
private readonly DeepSeekApiService _deepSeekApiService;
|
||||||
private readonly CerebrasAPIService _cerebrasAPIService;
|
private readonly CerebrasAPIService _cerebrasAPIService;
|
||||||
|
private readonly KimiApiService _kimiApiService;
|
||||||
private readonly QDrantService _qDrantService;
|
private readonly QDrantService _qDrantService;
|
||||||
private readonly NavigationManager _navigationManager;
|
private readonly NavigationManager _navigationManager;
|
||||||
private readonly LocalVectorSearchService _localVectorSearchService;
|
private readonly LocalVectorSearchService _localVectorSearchService;
|
||||||
private readonly WebsiteContentLoaderService _websiteContentLoaderService;
|
private readonly WebsiteContentLoaderService _websiteContentLoaderService;
|
||||||
|
private readonly ISimpleLogger _logger;
|
||||||
|
private readonly CacheService _cacheService;
|
||||||
|
|
||||||
|
|
||||||
public static IConfiguration? _configuration;
|
public static IConfiguration? _configuration;
|
||||||
|
|
||||||
|
|
||||||
public AIService(HttpClient httpClient,
|
public AIService(HttpClient httpClient,
|
||||||
ContentService contentService,
|
|
||||||
ContentEditorService contentEditorService,
|
ContentEditorService contentEditorService,
|
||||||
ScopedContentService scopedContentService,
|
ScopedContentService scopedContentService,
|
||||||
QDrantService qDrantService,
|
QDrantService qDrantService,
|
||||||
|
|
@ -61,13 +63,15 @@ namespace BLAIzor.Services
|
||||||
DeepSeekApiService deepSeekApiService,
|
DeepSeekApiService deepSeekApiService,
|
||||||
OpenAiRealtimeService openAIRealtimeService,
|
OpenAiRealtimeService openAIRealtimeService,
|
||||||
CerebrasAPIService cerebrasAPIService,
|
CerebrasAPIService cerebrasAPIService,
|
||||||
|
KimiApiService kimiApiService,
|
||||||
NavigationManager navigationManager,
|
NavigationManager navigationManager,
|
||||||
IConfiguration? configuration,
|
IConfiguration? configuration,
|
||||||
LocalVectorSearchService localVectorSearchService,
|
LocalVectorSearchService localVectorSearchService,
|
||||||
WebsiteContentLoaderService websiteContentLoaderService)
|
WebsiteContentLoaderService websiteContentLoaderService,
|
||||||
|
ISimpleLogger logger,
|
||||||
|
CacheService cacheService)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_contentService = contentService;
|
|
||||||
_contentEditorService = contentEditorService;
|
_contentEditorService = contentEditorService;
|
||||||
_scopedContentService = scopedContentService;
|
_scopedContentService = scopedContentService;
|
||||||
_qDrantService = qDrantService;
|
_qDrantService = qDrantService;
|
||||||
|
|
@ -77,6 +81,7 @@ namespace BLAIzor.Services
|
||||||
_deepSeekApiService = deepSeekApiService;
|
_deepSeekApiService = deepSeekApiService;
|
||||||
_openAIRealtimeService = openAIRealtimeService;
|
_openAIRealtimeService = openAIRealtimeService;
|
||||||
_cerebrasAPIService = cerebrasAPIService;
|
_cerebrasAPIService = cerebrasAPIService;
|
||||||
|
_kimiApiService = kimiApiService;
|
||||||
_navigationManager = navigationManager;
|
_navigationManager = navigationManager;
|
||||||
_localVectorSearchService = localVectorSearchService;
|
_localVectorSearchService = localVectorSearchService;
|
||||||
_websiteContentLoaderService = websiteContentLoaderService;
|
_websiteContentLoaderService = websiteContentLoaderService;
|
||||||
|
|
@ -85,15 +90,17 @@ namespace BLAIzor.Services
|
||||||
_openAIApiService.RegisterCallback(HandleActionInvoked, HandleFinishedInvoked, HandleErrorInvoked);
|
_openAIApiService.RegisterCallback(HandleActionInvoked, HandleFinishedInvoked, HandleErrorInvoked);
|
||||||
_cerebrasAPIService.RegisterCallback(HandleActionInvoked, HandleFinishedInvoked, HandleErrorInvoked);
|
_cerebrasAPIService.RegisterCallback(HandleActionInvoked, HandleFinishedInvoked, HandleErrorInvoked);
|
||||||
_openAIRealtimeService.RegisterCallback(HandleActionInvoked);
|
_openAIRealtimeService.RegisterCallback(HandleActionInvoked);
|
||||||
|
_logger = logger;
|
||||||
|
_cacheService = cacheService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string OpenAiEndpoint = "https://api.openai.com/v1/chat/completions";
|
private const string OpenAiEndpoint = "https://api.openai.com/v1/chat/completions";
|
||||||
public string _apiKey;
|
public string _apiKey;
|
||||||
public static event Action<string, string, MenuItem?>? OnContentReceived;
|
public event Action<string, string, MenuItem?>? OnContentReceived;
|
||||||
public static event Action<string>? OnContentReceiveFinished;
|
public event Action<string>? OnContentReceiveFinished;
|
||||||
public static event Action<string, string>? OnContentReceivedError;
|
public event Action<string, string>? OnContentReceivedError;
|
||||||
public static event Action<string, string>? OnStatusChangeReceived;
|
public event Action<string, string>? OnStatusChangeReceived;
|
||||||
public static event Action<string, string>? OnTextContentAvailable;
|
public event Action<string, string>? OnTextContentAvailable;
|
||||||
public string Mood = "cool, and professional";
|
public string Mood = "cool, and professional";
|
||||||
private string _workingContent = null;
|
private string _workingContent = null;
|
||||||
public bool UseWebsocket = false;
|
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);
|
_scopedContentService.AvailableTemplateSnippets = await GetSnippetsForDisplay(sessionId, templateCollectionName);
|
||||||
return siteModel;
|
return siteModel;
|
||||||
|
|
@ -154,15 +162,17 @@ namespace BLAIzor.Services
|
||||||
|
|
||||||
public async Task GetChatGptWelcomeMessage(string sessionId, int SiteId, string templateCollectionName, string menuList = "")
|
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);
|
SiteInfo site = await _contentEditorService.GetSiteInfoByIdAsync(SiteId);
|
||||||
|
var siteModel = await InitSite(sessionId, site, templateCollectionName, menuList);
|
||||||
//_apiKey = GetApiKey();
|
//_apiKey = GetApiKey();
|
||||||
//List<string> qdrantPoint = await _qDrantService.GetContentAsync(SiteId, _scopedContentService.WebsiteContent.Items.FirstOrDefault());
|
//List<string> qdrantPoint = await _qDrantService.GetContentAsync(SiteId, _scopedContentService.WebsiteContent.Items.FirstOrDefault());
|
||||||
string extractedText = "";
|
string extractedText = "";
|
||||||
//nullcheck
|
//nullcheck
|
||||||
if(siteModel != null)
|
if (siteModel != null)
|
||||||
{
|
{
|
||||||
if(siteModel.ContentItems != null && siteModel.ContentItems.Count > 0)
|
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)
|
if (siteModel.ContentItems.OrderBy(sm => sm.ContentItem.Id).FirstOrDefault().VectorPoints != null)
|
||||||
{
|
{
|
||||||
|
|
@ -205,17 +215,30 @@ namespace BLAIzor.Services
|
||||||
AiProvider = GetAiSettings();
|
AiProvider = GetAiSettings();
|
||||||
if (AiProvider == "cerebras")
|
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")
|
else if (AiProvider == "chatgpt")
|
||||||
{
|
{
|
||||||
await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage);
|
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")
|
else if (AiProvider == "deepseek")
|
||||||
{
|
{
|
||||||
//await _deepSeekApiService.GetChatGPTStreamedResponse(systemMessage, userMessage);
|
//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
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -234,6 +257,8 @@ namespace BLAIzor.Services
|
||||||
|
|
||||||
public async Task ProcessUserIntent(string sessionId, string userPrompt, int siteId, int templateId, string contentCollectionName, string menuList = "")
|
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}");
|
//Console.WriteLine($"SITE ID: {siteId}");
|
||||||
OnStatusChangeReceived?.Invoke(sessionId, "Understanding your request...");
|
OnStatusChangeReceived?.Invoke(sessionId, "Understanding your request...");
|
||||||
|
|
||||||
|
|
@ -244,7 +269,6 @@ namespace BLAIzor.Services
|
||||||
|
|
||||||
var baseResult = await ValidateAndFixJson<ChatGPTResultBase>(resultJson, FixJsonWithAI);
|
var baseResult = await ValidateAndFixJson<ChatGPTResultBase>(resultJson, FixJsonWithAI);
|
||||||
//var baseResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTResultBase>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
//var baseResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTResultBase>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||||
var fixedResult = System.Text.Json.JsonSerializer.Serialize(baseResult);
|
|
||||||
|
|
||||||
if (baseResult == null)
|
if (baseResult == null)
|
||||||
{
|
{
|
||||||
|
|
@ -252,6 +276,7 @@ namespace BLAIzor.Services
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fixedResult = System.Text.Json.JsonSerializer.Serialize(baseResult);
|
||||||
OnStatusChangeReceived?.Invoke(sessionId, "Making a decision");
|
OnStatusChangeReceived?.Invoke(sessionId, "Making a decision");
|
||||||
|
|
||||||
// Process result based on type
|
// Process result based on type
|
||||||
|
|
@ -302,7 +327,7 @@ namespace BLAIzor.Services
|
||||||
{
|
{
|
||||||
//do we have it cached?
|
//do we have it cached?
|
||||||
var contentId = requestedMenu.ContentItemId;
|
var contentId = requestedMenu.ContentItemId;
|
||||||
if(_scopedContentService.WebsiteContentModel != null)
|
if (_scopedContentService.WebsiteContentModel != null)
|
||||||
{
|
{
|
||||||
if (_scopedContentService.WebsiteContentModel.ContentItems.Any(x => x.ContentItem.Id == contentId))
|
if (_scopedContentService.WebsiteContentModel.ContentItems.Any(x => x.ContentItem.Id == contentId))
|
||||||
{
|
{
|
||||||
|
|
@ -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)
|
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");
|
//Console.Write($"\n\n SessionId: {sessionId}\n\n");
|
||||||
//string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _contentService.SelectedDocument);
|
//string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _contentService.SelectedDocument);
|
||||||
|
|
||||||
|
|
@ -397,7 +422,7 @@ namespace BLAIzor.Services
|
||||||
//PointId[] pointArray = new PointId[intArray.Length];
|
//PointId[] pointArray = new PointId[intArray.Length];
|
||||||
if (pointList.Count > 0)
|
if (pointList.Count > 0)
|
||||||
{
|
{
|
||||||
for(int i=0; i<pointList.Count; i++)
|
for (int i = 0; i < pointList.Count; i++)
|
||||||
{
|
{
|
||||||
extractedText += $"{pointList[i].Name}: {pointList[i].Content}";
|
extractedText += $"{pointList[i].Name}: {pointList[i].Content}";
|
||||||
}
|
}
|
||||||
|
|
@ -438,22 +463,24 @@ namespace BLAIzor.Services
|
||||||
|
|
||||||
private async Task ProcessMethodResult(string sessionId, string resultJson)
|
private async Task ProcessMethodResult(string sessionId, string resultJson)
|
||||||
{
|
{
|
||||||
var fixedResult = await ValidateAndFixJson<ChatGPTMethodResult>(resultJson, FixJsonWithAI);
|
await _logger.InfoAsync($"ProcessMethodResult: method called", $"sessionId: {sessionId}, json: {resultJson}");
|
||||||
//var methodResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTMethodResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
//var fixedResult = await ValidateAndFixJson<ChatGPTMethodResult>(resultJson, FixJsonWithAI); //ANOTHER fixing is useless, json is from fixed format
|
||||||
if (fixedResult != null)
|
var methodResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTMethodResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||||
|
if (methodResult != null)
|
||||||
{
|
{
|
||||||
OnStatusChangeReceived?.Invoke(sessionId, "Initiating the task you requested");
|
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)
|
private async Task ProcessTextResult(string sessionId, string pageTitle, string resultJson, int templateId, string collectionName)
|
||||||
{
|
{
|
||||||
var fixedResult = await ValidateAndFixJson<ChatGPTTextResult>(resultJson, FixJsonWithAI);
|
await _logger.InfoAsync($"ProcessTextResult: method called", $"sessionId: {sessionId}, json: {resultJson}, pageTitle: {pageTitle}, templateId: {templateId}, collectionName: {collectionName}");
|
||||||
//var textResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTTextResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
//var fixedResult = await ValidateAndFixJson<ChatGPTTextResult>(resultJson, FixJsonWithAI); //ANOTHER fixing is useless, json is from fixed format
|
||||||
if (fixedResult != null)
|
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");
|
//Console.Write("\r \n ProcessTextResult: Content: " + contentJson + "\r \n");
|
||||||
await ProcessContent(sessionId, pageTitle, contentJson, templateId, collectionName);
|
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)
|
public async Task<T?> ValidateAndFixJson<T>(string json, Func<string, Task<string>> aiFixer)
|
||||||
{
|
{
|
||||||
|
await _logger.InfoAsync($"ValidateAndFixJson: method called", $"json: {json}");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return System.Text.Json.JsonSerializer.Deserialize<T>(json, new JsonSerializerOptions
|
return System.Text.Json.JsonSerializer.Deserialize<T>(json, new JsonSerializerOptions
|
||||||
|
|
@ -470,8 +498,8 @@ namespace BLAIzor.Services
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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 prompt = BuildJsonFixPrompt(json, ex.Message, typeof(T).Name);
|
||||||
var fixedJson = await aiFixer(prompt);
|
var fixedJson = await aiFixer(prompt);
|
||||||
|
|
||||||
|
|
@ -484,7 +512,8 @@ namespace BLAIzor.Services
|
||||||
}
|
}
|
||||||
catch (Exception ex2)
|
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;
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -509,7 +538,7 @@ namespace BLAIzor.Services
|
||||||
}
|
}
|
||||||
else { return ""; }
|
else { return ""; }
|
||||||
|
|
||||||
//return await _openAIApiService.GetSimpleChatGPTResponseNoSession("You are a JSON-fixing assistant.", prompt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildJsonFixPrompt(string json, string errorMessage, string targetTypeName)
|
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)
|
private async Task ProcessExaminationResult(string sessionId, string resultJson, int templateId, string collectionName)
|
||||||
{
|
{
|
||||||
var fixedResult = await ValidateAndFixJson<ChatGPTExaminationResult>(resultJson, FixJsonWithAI);
|
await _logger.InfoAsync($"ProcessExaminationResult: method called", $"sessionId: {sessionId}, json: {resultJson}, templateId: {templateId}, collectionName: {collectionName}");
|
||||||
//var explanationResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTExaminationResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
//var fixedResult = await ValidateAndFixJson<ChatGPTExaminationResult>(resultJson, FixJsonWithAI);
|
||||||
if (fixedResult != null)
|
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);
|
await ProcessContent(sessionId, "Examination", contentJson, templateId, collectionName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessContent(string sessionId, string pageTitle, string contentJson, int templateId, string contentCollectionName)
|
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
|
try
|
||||||
{
|
{
|
||||||
var fixedResult = await ValidateAndFixJson<ChatGPTContentResult>(contentJson, FixJsonWithAI);
|
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
|
//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
|
//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);
|
Console.WriteLine(removedNumbers);
|
||||||
OnTextContentAvailable?.Invoke(sessionId, removedNumbers);
|
OnTextContentAvailable?.Invoke(sessionId, removedNumbers);
|
||||||
//List<HtmlSnippet> snippets = await GetSnippetsForDisplay(sessionId, collectionName);
|
//List<HtmlSnippet> snippets = await GetSnippetsForDisplay(sessionId, collectionName);
|
||||||
|
|
@ -581,6 +612,7 @@ namespace BLAIzor.Services
|
||||||
//passing menuitem further
|
//passing menuitem further
|
||||||
private async Task ProcessContent(string sessionId, MenuItem requestedMenu, string contentJson, int templateId, string collectionName)
|
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
|
try
|
||||||
{
|
{
|
||||||
var fixedResult = await ValidateAndFixJson<ChatGPTContentResult>(contentJson, FixJsonWithAI);
|
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
|
//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
|
//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);
|
Console.WriteLine(removedNumbers);
|
||||||
OnTextContentAvailable?.Invoke(sessionId, removedNumbers);
|
OnTextContentAvailable?.Invoke(sessionId, removedNumbers);
|
||||||
//List<HtmlSnippet> snippets = await GetSnippetsForDisplay(sessionId, collectionName);
|
//List<HtmlSnippet> snippets = await GetSnippetsForDisplay(sessionId, collectionName);
|
||||||
|
|
@ -618,6 +650,7 @@ namespace BLAIzor.Services
|
||||||
|
|
||||||
private async Task ProcessErrorResult(string sessionId, string resultJson)
|
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);
|
var errorResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTErrorResult>(resultJson);
|
||||||
if (errorResult != null)
|
if (errorResult != null)
|
||||||
{
|
{
|
||||||
|
|
@ -634,6 +667,7 @@ namespace BLAIzor.Services
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<string> GetContentFromQuery(string sessionId, string userPrompt, string content = null, bool forceUnmodified = false)
|
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;
|
string extractedText;
|
||||||
if (content == null)
|
if (content == null)
|
||||||
{
|
{
|
||||||
|
|
@ -803,6 +837,7 @@ namespace BLAIzor.Services
|
||||||
|
|
||||||
public async Task<string> GetJsonResultFromQuery(string sessionId, int siteId, string userPrompt)
|
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);
|
//string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _contentService.SelectedDocument);
|
||||||
//_apiKey = GetApiKey();
|
//_apiKey = GetApiKey();
|
||||||
//start with embeddings
|
//start with embeddings
|
||||||
|
|
@ -900,6 +935,7 @@ namespace BLAIzor.Services
|
||||||
|
|
||||||
public async Task<List<HtmlSnippet>> GetSnippetsForDisplay(string sessionId, string collectionName)
|
public async Task<List<HtmlSnippet>> GetSnippetsForDisplay(string sessionId, string collectionName)
|
||||||
{
|
{
|
||||||
|
await _logger.InfoAsync($"GetSnippetsForDisplay: method called", $"SessionId: {sessionId}, collectionName: {collectionName}");
|
||||||
_apiKey = GetApiKey();
|
_apiKey = GetApiKey();
|
||||||
OnStatusChangeReceived?.Invoke(sessionId, "Looking up the UI template elements for you");
|
OnStatusChangeReceived?.Invoke(sessionId, "Looking up the UI template elements for you");
|
||||||
//string availableSnippetList = "";
|
//string availableSnippetList = "";
|
||||||
|
|
@ -937,6 +973,7 @@ namespace BLAIzor.Services
|
||||||
//for textresult and errorresult
|
//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)
|
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");
|
//Console.Write($"\n SessionId: {sessionId} \n");
|
||||||
OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
|
OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
|
||||||
|
|
||||||
|
|
@ -944,8 +981,9 @@ namespace BLAIzor.Services
|
||||||
//Console.WriteLine($"DISPLAYHTML Topics: {topics} \n\n");
|
//Console.WriteLine($"DISPLAYHTML Topics: {topics} \n\n");
|
||||||
|
|
||||||
string systemMessage = AiPrompts.HtmlRendering.GetHtmlRenderingSystemPromptForTextAndErrorResult(_scopedContentService.SelectedLanguage, pageTitle, htmlToUse, photos, topics);
|
string systemMessage = AiPrompts.HtmlRendering.GetHtmlRenderingSystemPromptForTextAndErrorResult(_scopedContentService.SelectedLanguage, pageTitle, htmlToUse, photos, topics);
|
||||||
string userMessage = AiPrompts.HtmlRendering.HtmlRenderingUserPromptForTextAndErrorResult;
|
//string userMessage = AiPrompts.HtmlRendering.HtmlRenderingUserPromptForTextAndErrorResult;
|
||||||
string assistantMessage = AiPrompts.HtmlRendering.GetHtmlRenderingAssistantMessageForTextAndErrorResult(layoutPlan);
|
//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`:";
|
//string assistantMessage = "`Provided layout plan, that contains the text to be displayed as HTML`:";
|
||||||
|
|
||||||
|
|
@ -961,14 +999,14 @@ namespace BLAIzor.Services
|
||||||
if (AiProvider == "cerebras")
|
if (AiProvider == "cerebras")
|
||||||
{
|
{
|
||||||
//await _cerebrasAPIService.GetCerebrasStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
//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);
|
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
||||||
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
||||||
}
|
}
|
||||||
else if (AiProvider == "chatgpt")
|
else if (AiProvider == "chatgpt")
|
||||||
{
|
{
|
||||||
//await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
//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);
|
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
||||||
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
||||||
}
|
}
|
||||||
|
|
@ -983,12 +1021,13 @@ namespace BLAIzor.Services
|
||||||
}
|
}
|
||||||
else
|
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)
|
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");
|
//Console.Write($"\n SessionId: {sessionId} \n");
|
||||||
OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
|
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)
|
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");
|
OnStatusChangeReceived?.Invoke(sessionId, "Planning layout based on the provided content");
|
||||||
|
|
||||||
//Console.WriteLine($"LAYOUTBUILDER Text: {interMediateResult}\n\n");
|
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: starting text", $"{interMediateResult}");
|
||||||
//Console.WriteLine($"LAYOUTBUILDER Snippets: {htmlToUse.Count}\n\n");
|
|
||||||
//Console.WriteLine($"LAYOUTBUILDER Photos: {photos} \n\n");
|
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: starting snippets count", htmlToUse.Count.ToString());
|
||||||
//Console.WriteLine($"LAYOUTBUILDER Topics: {topics} \n\n");
|
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 systemMessage = AiPrompts.LayoutPlanning.GetLayoutPlanningSystemPrompt(htmlToUse, photos, topics);
|
||||||
string userMessage = AiPrompts.LayoutPlanning.GetLayoutPlanningUserPrompt(interMediateResult, pageTitle, photos);
|
string userMessage = AiPrompts.LayoutPlanning.GetLayoutPlanningUserPrompt(interMediateResult, pageTitle, photos);
|
||||||
|
|
@ -1093,7 +1134,7 @@ namespace BLAIzor.Services
|
||||||
{
|
{
|
||||||
//Console.WriteLine("AI Response:");
|
//Console.WriteLine("AI Response:");
|
||||||
//Console.WriteLine(aiResponse);
|
//Console.WriteLine(aiResponse);
|
||||||
|
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: ai Response", $"{aiResponse}");
|
||||||
aiResponse = TextHelper.FixJsonWithoutAI(aiResponse);
|
aiResponse = TextHelper.FixJsonWithoutAI(aiResponse);
|
||||||
aiResponse = TextHelper.RemoveTabs(aiResponse);
|
aiResponse = TextHelper.RemoveTabs(aiResponse);
|
||||||
layoutPlan = System.Text.Json.JsonSerializer.Deserialize<LayoutPlan>(aiResponse, new JsonSerializerOptions
|
layoutPlan = System.Text.Json.JsonSerializer.Deserialize<LayoutPlan>(aiResponse, new JsonSerializerOptions
|
||||||
|
|
@ -1101,39 +1142,44 @@ namespace BLAIzor.Services
|
||||||
PropertyNameCaseInsensitive = true
|
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)
|
//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);
|
layoutPlan = await ValidateAndFixJson<LayoutPlan>(aiResponse, FixJsonWithAI);
|
||||||
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)))
|
||||||
{
|
{
|
||||||
Console.WriteLine("Invalid block structure in response.");
|
|
||||||
|
await _logger.ErrorAsync($"DisplayLayoutPlanFromContent: Invalid block structure in response.", $"{layoutPlan.Blocks.Count}");
|
||||||
layoutPlan = null;
|
layoutPlan = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Deserialization failed: " + ex.Message);
|
await _logger.ErrorAsync($"DisplayLayoutPlanFromContent: ai Response", ex.Message);
|
||||||
Console.WriteLine("Deserialization failed on json: " + aiResponse);
|
|
||||||
layoutPlan = null;
|
layoutPlan = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layoutPlan == null)
|
if (layoutPlan == null)
|
||||||
{
|
{
|
||||||
retry++;
|
retry++;
|
||||||
Console.WriteLine("Retrying due to invalid format...");
|
await _logger.WarnAsync($"DisplayLayoutPlanFromContent: Retrying due to invalid format...", $"{aiResponse}");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var block in layoutPlan.Blocks)
|
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}");
|
Console.WriteLine($"{block.Order}, {block.Type}, {block.PreferredSnippetId}");
|
||||||
if(block.ContentMap!=null && block.ContentMap.Count > 0)
|
|
||||||
|
if (block.ContentMap != null && block.ContentMap.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var key in block.ContentMap.Keys)
|
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
|
//for methodResult
|
||||||
public async Task DisplayHtml(string sessionId, string interMediateResult, string methodToCall = "", string methodParameter = "")//, string[]? topics = null)
|
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");
|
//Console.Write($"\n SessionId: {sessionId} \n");
|
||||||
OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
|
OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
|
||||||
|
|
||||||
|
|
@ -1162,6 +1209,7 @@ namespace BLAIzor.Services
|
||||||
AiProvider = GetAiSettings();
|
AiProvider = GetAiSettings();
|
||||||
if (AiProvider == "cerebras")
|
if (AiProvider == "cerebras")
|
||||||
{
|
{
|
||||||
|
await _logger.InfoAsync($"DisplayHtml: streamed response", sessionId.ToString());
|
||||||
await _cerebrasAPIService.GetCerebrasStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
await _cerebrasAPIService.GetCerebrasStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
||||||
}
|
}
|
||||||
else if (AiProvider == "chatgpt")
|
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.Data;
|
||||||
using BLAIzor.Models;
|
using BLAIzor.Models;
|
||||||
|
using Microsoft.DotNet.Scaffolding.Shared;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Identity.Client;
|
using Microsoft.Identity.Client;
|
||||||
|
|
||||||
|
|
@ -9,24 +10,50 @@ namespace BLAIzor.Services
|
||||||
{
|
{
|
||||||
//private readonly AIService _aiService;
|
//private readonly AIService _aiService;
|
||||||
private readonly OpenAIApiService _openAIApiService;
|
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 ApplicationDbContext _context;
|
||||||
private readonly QDrantService _qDrantService;
|
private readonly QDrantService _qDrantService;
|
||||||
private readonly HtmlSnippetProcessor _htmlSnippetProcessor;
|
private readonly HtmlSnippetProcessor _htmlSnippetProcessor;
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
private readonly ScopedContentService _scopedContentService;
|
private readonly ScopedContentService _scopedContentService;
|
||||||
private readonly ContentEditorService _contentEditorService;
|
private readonly ContentEditorService _contentEditorService;
|
||||||
|
private readonly ISimpleLogger _logger;
|
||||||
|
|
||||||
public static IConfiguration? _configuration;
|
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;
|
//_aiService = aiService;
|
||||||
_openAIApiService = openAIApiService;
|
_openAIApiService = openAIApiService;
|
||||||
|
_deepSeekApiService = deepSeekApiService;
|
||||||
|
_openAIRealtimeService = openAIRealtimeService;
|
||||||
|
_cerebrasAPIService = cerebrasAPIService;
|
||||||
|
_kimiApiService = kimiApiService;
|
||||||
//_context = context;
|
//_context = context;
|
||||||
_qDrantService = qDrantService;
|
_qDrantService = qDrantService;
|
||||||
_htmlSnippetProcessor = htmlSnippetProcessor;
|
_htmlSnippetProcessor = htmlSnippetProcessor;
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
_scopedContentService = scopedContentService;
|
_scopedContentService = scopedContentService;
|
||||||
|
_logger = logger;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_contentEditorService = contentEditorService;
|
_contentEditorService = contentEditorService;
|
||||||
}
|
}
|
||||||
|
|
@ -34,22 +61,88 @@ namespace BLAIzor.Services
|
||||||
private string GetAiEmbeddingSettings() =>
|
private string GetAiEmbeddingSettings() =>
|
||||||
_configuration?.GetSection("AiSettings")?.GetValue<string>("EmbeddingService") ?? string.Empty;
|
_configuration?.GetSection("AiSettings")?.GetValue<string>("EmbeddingService") ?? string.Empty;
|
||||||
|
|
||||||
|
private string GetAiSettings() =>
|
||||||
|
_configuration?.GetSection("AiSettings")?.GetValue<string>("Provider") ?? string.Empty;
|
||||||
|
|
||||||
// Existing methods
|
// 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.";
|
string systemMessage = "You are a helpful assistant that helps the user in creating a website.";
|
||||||
var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, prompt);
|
||||||
|
|
||||||
return result.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
//return result.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Select(line => line.Trim('-').Trim())
|
// .Select(line => line.Trim('-').Trim())
|
||||||
.ToList() ?? new List<string>();
|
// .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)
|
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.";
|
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);
|
//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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,32 +3,55 @@ using BLAIzor.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Identity.Client;
|
using Microsoft.Identity.Client;
|
||||||
using BLAIzor.Helpers;
|
using BLAIzor.Helpers;
|
||||||
|
|
||||||
|
|
||||||
using Qdrant.Client.Grpc;
|
using Qdrant.Client.Grpc;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
|
||||||
namespace BLAIzor.Services
|
namespace BLAIzor.Services
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
public class ContentEditorService
|
public class ContentEditorService
|
||||||
{
|
{
|
||||||
//private readonly AIService _aiService;
|
#region Private Fields
|
||||||
|
|
||||||
private readonly OpenAIApiService _openAIApiService;
|
private readonly OpenAIApiService _openAIApiService;
|
||||||
//private readonly ApplicationDbContext _context;
|
|
||||||
private readonly QDrantService _qDrantService;
|
private readonly QDrantService _qDrantService;
|
||||||
private readonly HtmlSnippetProcessor _htmlSnippetProcessor;
|
private readonly HtmlSnippetProcessor _htmlSnippetProcessor;
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
private readonly ScopedContentService _scopedContentService;
|
private readonly ScopedContentService _scopedContentService;
|
||||||
private readonly OpenAIEmbeddingService _openAIEmbeddingService;
|
private readonly OpenAIEmbeddingService _openAIEmbeddingService;
|
||||||
private readonly LocalEmbeddingService _localEmbeddingService;
|
private readonly LocalEmbeddingService _localEmbeddingService;
|
||||||
|
private readonly ISimpleLogger _logger;
|
||||||
|
|
||||||
|
|
||||||
public static IConfiguration? _configuration;
|
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;
|
_openAIApiService = openAIApiService;
|
||||||
//_context = context;
|
|
||||||
_qDrantService = qDrantService;
|
_qDrantService = qDrantService;
|
||||||
_openAIEmbeddingService = openAIEmbeddingService;
|
_openAIEmbeddingService = openAIEmbeddingService;
|
||||||
_localEmbeddingService = localEmbeddingService;
|
_localEmbeddingService = localEmbeddingService;
|
||||||
|
|
@ -36,84 +59,132 @@ namespace BLAIzor.Services
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
_scopedContentService = scopedContentService;
|
_scopedContentService = scopedContentService;
|
||||||
_configuration = configuration;
|
_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() =>
|
private string GetAiEmbeddingSettings() =>
|
||||||
_configuration?.GetSection("AiSettings")?.GetValue<string>("EmbeddingService") ?? string.Empty;
|
_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)
|
public async Task<MenuItem> AddMenuItemAsync(MenuItem menuItem)
|
||||||
{
|
{
|
||||||
|
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
// Now use dbContext safely without violating DI rules
|
|
||||||
|
|
||||||
var result = await _context.MenuItems.AddAsync(menuItem);
|
var result = await _context.MenuItems.AddAsync(menuItem);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return result.Entity;
|
return result.Entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds items as list
|
/// Adds multiple menu items to the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="menuItems"></param>
|
/// <param name="menuItems">A list of menu items to add.</param>
|
||||||
/// <returns>the number of modified rows</returns>
|
/// <returns>The number of state entries written to the database.</returns>
|
||||||
public async Task<int> AddMenuItemsAsync(List<MenuItem> menuItems)
|
public async Task<int> AddMenuItemsAsync(List<MenuItem> menuItems)
|
||||||
{
|
{
|
||||||
|
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
// Now use dbContext safely without violating DI rules
|
|
||||||
_context.MenuItems.AddRange(menuItems);
|
_context.MenuItems.AddRange(menuItems);
|
||||||
var result = await _context.SaveChangesAsync();
|
var result = await _context.SaveChangesAsync();
|
||||||
return result;
|
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)
|
public async Task<List<MenuItem>> GetMenuItemsBySiteIdAsync(int siteInfoId)
|
||||||
{
|
{
|
||||||
|
await _logger.InfoAsync($"GetMenuItemsBySiteIdAsync: method called", siteInfoId.ToString());
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
// Now use dbContext safely without violating DI rules
|
|
||||||
var result = await _context.MenuItems
|
var result = await _context.MenuItems
|
||||||
.Where(m => m.SiteInfoId == siteInfoId)
|
.Where(m => m.SiteInfoId == siteInfoId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
return result;
|
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)
|
public async Task<List<MenuItem>> GetMenuItemsBySiteIdWithChildrenAsync(int siteInfoId)
|
||||||
{
|
{
|
||||||
|
await _logger.InfoAsync($"GetMenuItemsBySiteIdWithChildrenAsync: method called", siteInfoId.ToString());
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
// Now use dbContext safely without violating DI rules
|
|
||||||
var result = await _context.MenuItems
|
var result = await _context.MenuItems
|
||||||
.Where(m => m.SiteInfoId == siteInfoId && m.ParentId == null)
|
.Where(m => m.SiteInfoId == siteInfoId && m.ParentId == null)
|
||||||
.Include(m => m.Children)
|
.Include(m => m.Children)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
return result;
|
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)
|
public async Task<MenuItem> UpdateMenuItemAsync(MenuItem menuItem)
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
@ -132,21 +203,21 @@ namespace BLAIzor.Services
|
||||||
existingMenuItem.SortOrder = menuItem.SortOrder;
|
existingMenuItem.SortOrder = menuItem.SortOrder;
|
||||||
existingMenuItem.ShowInMainMenu = menuItem.ShowInMainMenu;
|
existingMenuItem.ShowInMainMenu = menuItem.ShowInMainMenu;
|
||||||
|
|
||||||
// No need for _context.MenuItems.Update(existingMenuItem);
|
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
return existingMenuItem;
|
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)
|
public async Task DeleteMenuItemAsync(int menuItemId)
|
||||||
{
|
{
|
||||||
|
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
// Now use dbContext safely without violating DI rules
|
|
||||||
var menuItem = await _context.MenuItems.FindAsync(menuItemId);
|
var menuItem = await _context.MenuItems.FindAsync(menuItemId);
|
||||||
if (menuItem == null)
|
if (menuItem == null)
|
||||||
{
|
{
|
||||||
|
|
@ -155,32 +226,39 @@ namespace BLAIzor.Services
|
||||||
|
|
||||||
_context.MenuItems.Remove(menuItem);
|
_context.MenuItems.Remove(menuItem);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ContentGroup Operations
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the first ContentGroup associated with the given SiteInfoId.
|
/// Retrieves the first ContentGroup associated with the given SiteInfoId.
|
||||||
/// Returns null if not found.
|
/// Returns null if not found.
|
||||||
/// </summary>
|
/// </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)
|
public async Task<ContentGroup?> GetContentGroupBySiteInfoIdAsync(int siteInfoId)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
// Now use dbContext safely without violating DI rules
|
|
||||||
var result = await _context.ContentGroups
|
var result = await _context.ContentGroups
|
||||||
.Where(cg => cg.SiteInfoId == siteInfoId)
|
.Where(cg => cg.SiteInfoId == siteInfoId)
|
||||||
.OrderBy(cg => cg.Id) // or by LastUpdated, Name, etc.
|
.OrderBy(cg => cg.Id)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </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)
|
public async Task<List<ContentGroup>> GetContentGroupsBySiteInfoIdAsync(int siteInfoId, string? type = null, string? slug = null)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
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)
|
public async Task<ContentGroup> UpdateContentGroupByIdAsync(ContentGroup updatedGroup)
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
@ -228,6 +312,11 @@ namespace BLAIzor.Services
|
||||||
return existingGroup;
|
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)
|
public async Task<bool> DeleteContentGroupByIdAsync(int contentGroupId)
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
@ -243,6 +332,11 @@ namespace BLAIzor.Services
|
||||||
return true;
|
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)
|
public async Task<ContentGroup> CreateContentGroupAsync(ContentGroup newGroup)
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
@ -257,10 +351,12 @@ namespace BLAIzor.Services
|
||||||
return newGroup;
|
return newGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all ContentGroups for a site (can be filtered by type).
|
/// Returns all ContentGroups for a site, with optional filtering by type.
|
||||||
/// </summary>
|
/// </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)
|
public async Task<List<ContentGroup>> GetAllContentGroupsBySiteInfoIdAsync(int siteInfoId, string? type = null)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
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())
|
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)
|
public async Task<ContentItem?> GetContentItemByIdAsync(int id)
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
@ -298,41 +409,25 @@ namespace BLAIzor.Services
|
||||||
return query.FirstOrDefault();
|
return query.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
//public async Task<ContentItem> UpdateContentItemByIdAsync(ContentItem item)
|
/// <summary>
|
||||||
//{
|
/// Creates a new content item, chunks its content, generates embeddings, and inserts them into Qdrant.
|
||||||
// using var scope = _serviceScopeFactory.CreateScope();
|
/// </summary>
|
||||||
// var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
/// <param name="item">The content item to create.</param>
|
||||||
|
/// <param name="collectionName">The name of the Qdrant collection to insert into.</param>
|
||||||
// var existing = await db.ContentItems.FindAsync(item.Id);
|
/// <returns>The created content item.</returns>
|
||||||
// if (existing == null) throw new Exception("ContentItem not found.");
|
/// <exception cref="Exception">Thrown if the saved ContentItem cannot be retrieved.</exception>
|
||||||
|
|
||||||
// // 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;
|
|
||||||
//}
|
|
||||||
|
|
||||||
public async Task<ContentItem> CreateContentItemAsync(ContentItem item, string collectionName)
|
public async Task<ContentItem> CreateContentItemAsync(ContentItem item, string collectionName)
|
||||||
{
|
{
|
||||||
|
await _logger.InfoAsync($"CreateContentItemAsync: method called", item.Id.ToString());
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
item.CreatedAt = DateTime.UtcNow;
|
item.CreatedAt = DateTime.UtcNow;
|
||||||
item.LastUpdated = DateTime.UtcNow;
|
item.LastUpdated = DateTime.UtcNow;
|
||||||
|
|
||||||
// Save to DB first to get ContentItem.Id
|
|
||||||
db.ContentItems.Add(item);
|
db.ContentItems.Add(item);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
// Get the full ContentItem with ContentGroup included (needed for SiteId)
|
|
||||||
var fullItem = await db.ContentItems
|
var fullItem = await db.ContentItems
|
||||||
.Include(ci => ci.ContentGroup)
|
.Include(ci => ci.ContentGroup)
|
||||||
.FirstOrDefaultAsync(ci => ci.Id == item.Id);
|
.FirstOrDefaultAsync(ci => ci.Id == item.Id);
|
||||||
|
|
@ -340,17 +435,15 @@ namespace BLAIzor.Services
|
||||||
if (fullItem == null)
|
if (fullItem == null)
|
||||||
throw new Exception("Failed to retrieve saved ContentItem.");
|
throw new Exception("Failed to retrieve saved ContentItem.");
|
||||||
|
|
||||||
// 🧠 Chunking
|
|
||||||
if (!string.IsNullOrEmpty(item.Content))
|
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);
|
var vectorizedChunks = await VectorizeChunksWithGuidsAsync(chunks, fullItem, collectionName);
|
||||||
|
|
||||||
// 🔗 Save chunk references
|
|
||||||
var chunkEntities = vectorizedChunks.Select((chunk, index) => new ContentChunk
|
var chunkEntities = vectorizedChunks.Select((chunk, index) => new ContentChunk
|
||||||
{
|
{
|
||||||
ContentItemId = item.Id,
|
ContentItemId = item.Id,
|
||||||
QdrantPointId = chunk.UId, // now using GUIDs
|
QdrantPointId = chunk.UId,
|
||||||
ChunkIndex = index,
|
ChunkIndex = index,
|
||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
@ -362,9 +455,13 @@ namespace BLAIzor.Services
|
||||||
return item;
|
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)
|
public async Task<List<WebPageContent>> VectorizeChunksWithGuidsAsync(List<string> chunks, ContentItem item, string collectionName)
|
||||||
{
|
{
|
||||||
var result = new List<WebPageContent>();
|
var result = new List<WebPageContent>();
|
||||||
|
|
@ -388,6 +485,7 @@ namespace BLAIzor.Services
|
||||||
|
|
||||||
result.Add(new WebPageContent
|
result.Add(new WebPageContent
|
||||||
{
|
{
|
||||||
|
Id = Guid.Parse(uid),
|
||||||
UId = uid,
|
UId = uid,
|
||||||
SiteId = item.ContentGroup.SiteInfoId,
|
SiteId = item.ContentGroup.SiteInfoId,
|
||||||
Type = "content-item",
|
Type = "content-item",
|
||||||
|
|
@ -403,14 +501,19 @@ namespace BLAIzor.Services
|
||||||
return result;
|
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)
|
public async Task<List<WebPageContent>> VectorizeChunksAsync(List<string> chunks, ContentItem item, string collectionName)
|
||||||
{
|
{
|
||||||
var result = new List<WebPageContent>();
|
var result = new List<WebPageContent>();
|
||||||
|
|
||||||
foreach (var (chunk, index) in chunks.Select((c, i) => (c, i)))
|
foreach (var (chunk, index) in chunks.Select((c, i) => (c, i)))
|
||||||
{
|
{
|
||||||
//var combinedText = $"{pageContent.Name}: {pageContent.Description} - {chunk}";
|
|
||||||
var combinedText = $"{chunk}";
|
var combinedText = $"{chunk}";
|
||||||
float[] embedding = [];
|
float[] embedding = [];
|
||||||
|
|
||||||
|
|
@ -424,10 +527,6 @@ namespace BLAIzor.Services
|
||||||
embedding = await _openAIEmbeddingService.GenerateEmbeddingAsync(combinedText);
|
embedding = await _openAIEmbeddingService.GenerateEmbeddingAsync(combinedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add data for batch insertion
|
|
||||||
|
|
||||||
var vector = embedding;
|
|
||||||
|
|
||||||
var uid = Guid.NewGuid();
|
var uid = Guid.NewGuid();
|
||||||
|
|
||||||
var webChunk = new WebPageContent
|
var webChunk = new WebPageContent
|
||||||
|
|
@ -439,34 +538,22 @@ namespace BLAIzor.Services
|
||||||
Name = item.Title,
|
Name = item.Title,
|
||||||
Description = item.Description,
|
Description = item.Description,
|
||||||
Content = chunk,
|
Content = chunk,
|
||||||
Vectors = vector,
|
Vectors = embedding,
|
||||||
LastUpdated = DateTime.UtcNow
|
LastUpdated = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
result.Add(webChunk);
|
result.Add(webChunk);
|
||||||
}
|
}
|
||||||
await _qDrantService.QDrantInsertManyAsync(result, collectionName);
|
await _qDrantService.QDrantInsertManyAsync(result, collectionName);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
private async Task RemoveChunksAndQdrantEntriesByIdsAsync(List<int> chunkIds, string collectionName)
|
/// Forces a re-chunking and re-embedding of all content items within a specified content group.
|
||||||
{
|
/// </summary>
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
/// <param name="contentGroupId">The ID of the content group to re-chunk.</param>
|
||||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
/// <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>
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ForceRechunkContentGroupAsync(int contentGroupId, string collectionName = null)
|
public async Task<bool> ForceRechunkContentGroupAsync(int contentGroupId, string collectionName = null)
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
@ -488,12 +575,17 @@ namespace BLAIzor.Services
|
||||||
return true;
|
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)
|
public async Task<bool> ForceRecreateQdrantCollectionAsync(int siteInfoId)
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
// Load site + all content + chunks
|
|
||||||
var site = await db.SiteInfos
|
var site = await db.SiteInfos
|
||||||
.Include(s => s.ContentGroups)
|
.Include(s => s.ContentGroups)
|
||||||
.ThenInclude(g => g.Items)
|
.ThenInclude(g => g.Items)
|
||||||
|
|
@ -505,21 +597,17 @@ namespace BLAIzor.Services
|
||||||
|
|
||||||
string oldCollectionName = site.VectorCollectionName;
|
string oldCollectionName = site.VectorCollectionName;
|
||||||
|
|
||||||
// ❌ Delete old Qdrant collection
|
|
||||||
await _qDrantService.DeleteCollectionAsync(oldCollectionName);
|
await _qDrantService.DeleteCollectionAsync(oldCollectionName);
|
||||||
|
|
||||||
// ✅ Generate and create new collection
|
|
||||||
string newCollectionName = GetGeneratedVectorCollectionName(site);
|
string newCollectionName = GetGeneratedVectorCollectionName(site);
|
||||||
|
|
||||||
bool created = await _qDrantService.CreateQdrantCollectionAsync(newCollectionName);
|
bool created = await _qDrantService.CreateQdrantCollectionAsync(newCollectionName);
|
||||||
if (!created)
|
if (!created)
|
||||||
throw new Exception($"Failed to create Qdrant collection '{newCollectionName}'.");
|
throw new Exception($"Failed to create Qdrant collection '{newCollectionName}'.");
|
||||||
|
|
||||||
// Update and persist new collection name
|
|
||||||
site.VectorCollectionName = newCollectionName;
|
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 group in site.ContentGroups)
|
||||||
{
|
{
|
||||||
foreach (var item in group.Items.ToList())
|
foreach (var item in group.Items.ToList())
|
||||||
|
|
@ -545,8 +633,11 @@ namespace BLAIzor.Services
|
||||||
return true;
|
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)
|
public async Task<List<ContentItem>> GetContentItemsByGroupIdAsync(int contentGroupId)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
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)
|
public async Task<ContentItem?> CreateContentItemAsync(ContentItem newItem)
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
@ -573,6 +696,15 @@ namespace BLAIzor.Services
|
||||||
return newItem;
|
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)
|
public async Task<ContentItem> SaveAndSyncContentItemAsync(ContentItem dto, string collectionName, bool forceRechunk = false)
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
@ -586,10 +718,8 @@ namespace BLAIzor.Services
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
throw new Exception("ContentItem not found.");
|
throw new Exception("ContentItem not found.");
|
||||||
|
|
||||||
// Determine if content has changed
|
|
||||||
bool contentChanged = existing.Content?.Trim() != dto.Content?.Trim();
|
bool contentChanged = existing.Content?.Trim() != dto.Content?.Trim();
|
||||||
|
|
||||||
// Update scalar fields only (don't touch navigation collections directly)
|
|
||||||
existing.Title = dto.Title;
|
existing.Title = dto.Title;
|
||||||
existing.Description = dto.Description;
|
existing.Description = dto.Description;
|
||||||
existing.Content = dto.Content;
|
existing.Content = dto.Content;
|
||||||
|
|
@ -602,26 +732,22 @@ namespace BLAIzor.Services
|
||||||
{
|
{
|
||||||
existing.Version++;
|
existing.Version++;
|
||||||
|
|
||||||
// 🧹 Remove old chunks (track-safe)
|
|
||||||
var chunkIds = existing.Chunks.Select(c => c.Id).ToList();
|
var chunkIds = existing.Chunks.Select(c => c.Id).ToList();
|
||||||
if (chunkIds.Any())
|
if (chunkIds.Any())
|
||||||
{
|
{
|
||||||
await RemoveChunksAndQdrantEntriesByIdsAsync(chunkIds, collectionName);
|
await RemoveChunksAndQdrantEntriesByIdsAsync(chunkIds, collectionName);
|
||||||
|
|
||||||
// Detach removed chunks from current EF context to avoid SaveChanges conflict
|
|
||||||
foreach (var chunk in existing.Chunks.ToList())
|
foreach (var chunk in existing.Chunks.ToList())
|
||||||
{
|
{
|
||||||
db.Entry(chunk).State = EntityState.Detached;
|
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 newChunks = ChunkingHelper.SplitStructuredText(existing.Content, 3000);
|
||||||
var webChunks = await VectorizeChunksAsync(newChunks, existing, collectionName);
|
var webChunks = await VectorizeChunksAsync(newChunks, existing, collectionName);
|
||||||
|
|
||||||
// 💾 Save chunk metadata in SQL
|
|
||||||
var chunkEntities = webChunks.Select((w, i) => new ContentChunk
|
var chunkEntities = webChunks.Select((w, i) => new ContentChunk
|
||||||
{
|
{
|
||||||
ContentItemId = existing.Id,
|
ContentItemId = existing.Id,
|
||||||
|
|
@ -637,7 +763,11 @@ namespace BLAIzor.Services
|
||||||
return existing;
|
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)
|
public static Dictionary<string, List<ContentItemModel>> GroupContentItemsByType(WebsiteContentModel model)
|
||||||
{
|
{
|
||||||
return model.ContentItems
|
return model.ContentItems
|
||||||
|
|
@ -645,8 +775,11 @@ namespace BLAIzor.Services
|
||||||
.ToDictionary(g => g.Key, g => g.ToList());
|
.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)
|
public async Task<bool> DeleteContentItemByIdAsync(int id)
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
@ -663,11 +796,13 @@ namespace BLAIzor.Services
|
||||||
return false;
|
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)
|
public async Task<int[]> GetPointIdsByContentGroupIdAsync(int contentGroupId)
|
||||||
{
|
{
|
||||||
|
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
@ -678,21 +813,20 @@ namespace BLAIzor.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
public string GetGeneratedVectorCollectionName(SiteInfo siteInfo)
|
#region SiteInfo Operations
|
||||||
{
|
|
||||||
var safeName = siteInfo.SiteName?.ToLower().Replace(" ", "_").Replace("-", "_");
|
|
||||||
return $"site_{safeName}_{Guid.NewGuid().ToString().Substring(0, 8)}";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <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)
|
public async Task<SiteInfo> GetSiteInfoByIdAsync(int SiteInfoId)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
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();
|
var result = await _context.SiteInfos.Where(x => x.Id == SiteInfoId).FirstOrDefaultAsync();
|
||||||
if (result == null)
|
if (result == null)
|
||||||
{
|
{
|
||||||
|
|
@ -703,17 +837,18 @@ namespace BLAIzor.Services
|
||||||
return result;
|
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)
|
public async Task<SiteInfo?> GetSiteInfoWithFormsByIdAsync(int siteId)
|
||||||
{
|
{
|
||||||
|
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
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);
|
var result = await _context.SiteInfos.Include(s => s.FormDefinitions).FirstOrDefaultAsync(s => s.Id == siteId);
|
||||||
if (result == null)
|
if (result == null)
|
||||||
{
|
{
|
||||||
|
|
@ -724,16 +859,18 @@ namespace BLAIzor.Services
|
||||||
return result;
|
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)
|
public async Task<SiteInfo> GetSiteInfoByNameAsync(string siteName)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
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();
|
var result = await _context.SiteInfos.Where(x => x.SiteName == siteName).FirstOrDefaultAsync();
|
||||||
if (result == null)
|
if (result == null)
|
||||||
{
|
{
|
||||||
|
|
@ -743,18 +880,19 @@ namespace BLAIzor.Services
|
||||||
{
|
{
|
||||||
return result;
|
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)
|
public async Task<SiteInfo> GetSiteInfoByUrlAsync(string url)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
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();
|
var result = await _context.SiteInfos.Where(x => x.DefaultUrl == url).FirstOrDefaultAsync();
|
||||||
if (result == null)
|
if (result == null)
|
||||||
{
|
{
|
||||||
|
|
@ -764,111 +902,71 @@ namespace BLAIzor.Services
|
||||||
{
|
{
|
||||||
return result;
|
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)
|
public async Task UpdateSiteInfoAsync(SiteInfo siteInfo)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
// Now use dbContext safely without violating DI rules
|
|
||||||
_context.SiteInfos.Update(siteInfo);
|
_context.SiteInfos.Update(siteInfo);
|
||||||
await _context.SaveChangesAsync();
|
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...
|
/// <summary>
|
||||||
//public async Task UpdateSiteInfoAndReChunkIfNeededAsync(SiteInfo updatedSiteInfo)
|
/// Retrieves all sites associated with a specific user.
|
||||||
//{
|
/// </summary>
|
||||||
// using var scope = _serviceScopeFactory.CreateScope();
|
/// <param name="userId">The ID of the user.</param>
|
||||||
// var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
/// <returns>A list of sites belonging to the specified user.</returns>
|
||||||
|
|
||||||
// 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);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<List<SiteInfo>> GetUserSitesAsync(string userId)
|
public async Task<List<SiteInfo>> GetUserSitesAsync(string userId)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
// Now use dbContext safely without violating DI rules
|
|
||||||
return await _context.SiteInfos
|
return await _context.SiteInfos
|
||||||
.Where(s => s.UserId == userId)
|
.Where(s => s.UserId == userId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//public async Task<List<SiteInfo>> GetSitesAsync()
|
/// <summary>
|
||||||
//{
|
/// Retrieves all site information records from the database.
|
||||||
// using (var scope = _serviceScopeFactory.CreateScope())
|
/// </summary>
|
||||||
// {
|
/// <returns>A list of all site information records.</returns>
|
||||||
// var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
public async Task<List<SiteInfo>> GetAllSitesAsync()
|
||||||
|
{
|
||||||
// // Now use dbContext safely without violating DI rules
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
// return await _context.SiteInfos.ToListAsync();
|
{
|
||||||
|
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)
|
public async Task<SiteInfo> AddSiteInfoAsync(SiteInfo siteInfo)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var _context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
// Now use dbContext safely without violating DI rules
|
|
||||||
if (siteInfo == null)
|
if (siteInfo == null)
|
||||||
throw new ArgumentNullException(nameof(siteInfo), "SiteInfo cannot be null.");
|
throw new ArgumentNullException(nameof(siteInfo), "SiteInfo cannot be null.");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(siteInfo.VectorCollectionName))
|
if (string.IsNullOrEmpty(siteInfo.VectorCollectionName))
|
||||||
{
|
{
|
||||||
siteInfo.VectorCollectionName = GetGeneratedVectorCollectionName(siteInfo);
|
siteInfo.VectorCollectionName = GetGeneratedVectorCollectionName(siteInfo);
|
||||||
|
|
@ -878,14 +976,10 @@ namespace BLAIzor.Services
|
||||||
var result = await _context.SaveChangesAsync();
|
var result = await _context.SaveChangesAsync();
|
||||||
if (result > 0)
|
if (result > 0)
|
||||||
{
|
{
|
||||||
//check if collection exists already
|
|
||||||
bool checkResult = await _qDrantService.CollectionExistsAsync(siteInfo.VectorCollectionName);
|
bool checkResult = await _qDrantService.CollectionExistsAsync(siteInfo.VectorCollectionName);
|
||||||
if (checkResult)
|
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);
|
siteInfo.VectorCollectionName = GetGeneratedVectorCollectionName(siteInfo);
|
||||||
//update the site info with the new collection name
|
|
||||||
_context.SiteInfos.Update(siteInfo);
|
_context.SiteInfos.Update(siteInfo);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
bool qresult = await _qDrantService.CreateQdrantCollectionAsync(siteInfo.VectorCollectionName);
|
bool qresult = await _qDrantService.CreateQdrantCollectionAsync(siteInfo.VectorCollectionName);
|
||||||
|
|
@ -896,7 +990,6 @@ namespace BLAIzor.Services
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//collection does not exist, create it
|
|
||||||
bool qresult = await _qDrantService.CreateQdrantCollectionAsync(siteInfo.VectorCollectionName);
|
bool qresult = await _qDrantService.CreateQdrantCollectionAsync(siteInfo.VectorCollectionName);
|
||||||
if (!qresult)
|
if (!qresult)
|
||||||
{
|
{
|
||||||
|
|
@ -908,14 +1001,14 @@ namespace BLAIzor.Services
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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);
|
throw new InvalidOperationException("An error occurred while adding the site info.", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
///TEMPORARY
|
///TEMPORARY
|
||||||
///
|
///
|
||||||
public async Task<bool> MigrateQdrantToContentItemsAsync(int siteId, string collectionName)
|
public async Task<bool> MigrateQdrantToContentItemsAsync(int siteId, string collectionName)
|
||||||
|
|
@ -926,7 +1019,7 @@ namespace BLAIzor.Services
|
||||||
// 1. Get menu items
|
// 1. Get menu items
|
||||||
var menuItems = await GetMenuItemsBySiteIdAsync(siteId);
|
var menuItems = await GetMenuItemsBySiteIdAsync(siteId);
|
||||||
PointId[] pointIds = new PointId[menuItems.Count];
|
PointId[] pointIds = new PointId[menuItems.Count];
|
||||||
for (int i=0; i < menuItems.Count; i++)
|
for (int i = 0; i < menuItems.Count; i++)
|
||||||
{
|
{
|
||||||
// Ensure SortOrder is set to i+1 (1-based index)
|
// Ensure SortOrder is set to i+1 (1-based index)
|
||||||
pointIds[i] = Convert.ToUInt64(menuItems[i].SortOrder);
|
pointIds[i] = Convert.ToUInt64(menuItems[i].SortOrder);
|
||||||
|
|
|
||||||
|
|
@ -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
|
var requestBody = new ChatGPTRequest
|
||||||
{
|
{
|
||||||
Model = modelName,
|
Model = modelName,
|
||||||
Temperature = 0.2,
|
//Temperature = 0.2,
|
||||||
|
Temperature = 1,
|
||||||
Messages = assistantMessage == null || assistantMessage == string.Empty
|
Messages = assistantMessage == null || assistantMessage == string.Empty
|
||||||
? new[]
|
? new[]
|
||||||
{
|
{
|
||||||
|
|
@ -115,7 +116,7 @@ namespace BLAIzor.Services
|
||||||
var requestBody = new ChatGPTRequest
|
var requestBody = new ChatGPTRequest
|
||||||
{
|
{
|
||||||
Model = modelName,
|
Model = modelName,
|
||||||
Temperature = 0.2,
|
Temperature = 1,
|
||||||
Messages = assistantMessage == null || assistantMessage == string.Empty
|
Messages = assistantMessage == null || assistantMessage == string.Empty
|
||||||
? new[]
|
? new[]
|
||||||
{
|
{
|
||||||
|
|
@ -170,7 +171,7 @@ namespace BLAIzor.Services
|
||||||
var requestBody = new ChatGPTRequest
|
var requestBody = new ChatGPTRequest
|
||||||
{
|
{
|
||||||
Model = modelName,
|
Model = modelName,
|
||||||
Temperature = 0.2,
|
Temperature = 1,
|
||||||
Messages = assistantMessage == null
|
Messages = assistantMessage == null
|
||||||
? new[]
|
? 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)
|
public ScopedContentService(ApplicationDbContext context, IServiceScopeFactory serviceScopeFactory)
|
||||||
{
|
{
|
||||||
//_context = context;
|
//_context = context;
|
||||||
|
SessionId = Guid.NewGuid().ToString();
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,10 +46,10 @@ namespace BLAIzor.Services
|
||||||
|
|
||||||
// set { }
|
// set { }
|
||||||
//}
|
//}
|
||||||
|
public string? WebsiteDefaultLanguage { get; set; }
|
||||||
public string SelectedLanguage { get; set; } = "English";
|
public string SelectedLanguage { get; set; } = "English";
|
||||||
|
|
||||||
public string SessionId { get; set; }
|
public string SessionId { get; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
using BLAIzor.Data;
|
using BLAIzor.Data;
|
||||||
using BLAIzor.Models;
|
using BLAIzor.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace BLAIzor.Services
|
namespace BLAIzor.Services
|
||||||
{
|
{
|
||||||
public class SimpleLogger : ISimpleLogger
|
public class SimpleLogger : ISimpleLogger
|
||||||
{
|
{
|
||||||
private readonly ApplicationDbContext _dbContext;
|
private readonly IDbContextFactory<ApplicationDbContext> _dbFactory;
|
||||||
private readonly IWebHostEnvironment _env;
|
private readonly IWebHostEnvironment _env;
|
||||||
private LogLevel _currentLevel = LogLevel.Info;
|
private LogLevel _currentLevel = LogLevel.Info;
|
||||||
private bool _consoleEnabled = true;
|
private bool _consoleEnabled = true;
|
||||||
|
|
||||||
public SimpleLogger(ApplicationDbContext dbContext, IWebHostEnvironment env)
|
public SimpleLogger(IDbContextFactory<ApplicationDbContext> dbFactory, IWebHostEnvironment env)
|
||||||
{
|
{
|
||||||
_dbContext = dbContext;
|
_dbFactory = dbFactory;
|
||||||
_env = env;
|
_env = env;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,10 +43,20 @@ namespace BLAIzor.Services
|
||||||
Timestamp = DateTime.UtcNow
|
Timestamp = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_env.IsProduction())
|
if (_env.IsDevelopment())
|
||||||
{
|
{
|
||||||
_dbContext.Logs.Add(log);
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
await _dbContext.SaveChangesAsync();
|
|
||||||
|
log = new AppLog
|
||||||
|
{
|
||||||
|
Severity = level.ToString(),
|
||||||
|
Message = message,
|
||||||
|
Details = details,
|
||||||
|
Timestamp = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
db.Logs.Add(log);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_consoleEnabled)
|
if (_consoleEnabled)
|
||||||
|
|
@ -53,7 +64,7 @@ namespace BLAIzor.Services
|
||||||
var color = Console.ForegroundColor;
|
var color = Console.ForegroundColor;
|
||||||
Console.ForegroundColor = level switch
|
Console.ForegroundColor = level switch
|
||||||
{
|
{
|
||||||
LogLevel.Info => ConsoleColor.Gray,
|
LogLevel.Info => ConsoleColor.Green,
|
||||||
LogLevel.Warning => ConsoleColor.Yellow,
|
LogLevel.Warning => ConsoleColor.Yellow,
|
||||||
LogLevel.Error => ConsoleColor.Red,
|
LogLevel.Error => ConsoleColor.Red,
|
||||||
_ => ConsoleColor.White
|
_ => 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": {
|
"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": "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"
|
//"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,9 +22,10 @@
|
||||||
},
|
},
|
||||||
"AiSettings": {
|
"AiSettings": {
|
||||||
"Provider": "cerebras",
|
"Provider": "cerebras",
|
||||||
|
//"Provider": "kimi",
|
||||||
//"Provider": "chatgpt",
|
//"Provider": "chatgpt",
|
||||||
"VoiceActivated": true,
|
"VoiceActivated": true,
|
||||||
"EmbeddingService": "openai"
|
"EmbeddingService": "openai"
|
||||||
//"EmbeddingService": "local"
|
//"EmbeddingService": "local"
|
||||||
},
|
},
|
||||||
"DeepSeek": {
|
"DeepSeek": {
|
||||||
|
|
@ -32,19 +33,24 @@
|
||||||
},
|
},
|
||||||
"Cerebras": {
|
"Cerebras": {
|
||||||
"ApiKey": "csk-3pwm3pjjrpcmmt6rm6k8f43n6rhh3h5pjn6jn9m9j4pyevrp",
|
"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": "llama-4-scout-17b-16e-instruct"
|
||||||
//"Model": "qwen-3-32b"
|
//"Model": "qwen-3-32b"
|
||||||
//"Model": "deepseek-r1-distill-llama-70b"
|
//"Model": "deepseek-r1-distill-llama-70b"
|
||||||
//"Model": "llama3.1-8b"
|
//"Model": "llama3.1-8b"
|
||||||
},
|
},
|
||||||
|
"Kimi": {
|
||||||
|
"ApiKey": "sk-GSHABNe1qCpfNmMTQfjtb57j1OOMyJcyMYJqVRV5EXZmcaBM"
|
||||||
|
},
|
||||||
"OpenAI": {
|
"OpenAI": {
|
||||||
//"CredentialsPath": "D:\\GOOGLECREDENTIALS\\client_secret_359861037120-m3mjvr3kg51i2c2qb38dav62uuqoqs5k.apps.googleusercontent.com.json"
|
//"CredentialsPath": "D:\\GOOGLECREDENTIALS\\client_secret_359861037120-m3mjvr3kg51i2c2qb38dav62uuqoqs5k.apps.googleusercontent.com.json"
|
||||||
"ApiKey": "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",
|
"ApiKey": "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",
|
||||||
//"ApiKey": "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" //VG3Law
|
//"ApiKey": "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" //VG3Law
|
||||||
//"Model": "gpt-4.1-mini"
|
//"Model": "gpt-4.1-mini"
|
||||||
"Model": "gpt-4o-mini"
|
//"Model": "gpt-4o-mini"
|
||||||
//"Model": "gpt-4.1-nano"
|
//"Model": "gpt-4.1-nano"
|
||||||
|
"Model": "gpt-5-nano"
|
||||||
},
|
},
|
||||||
"QDrant": {
|
"QDrant": {
|
||||||
//"CredentialsPath": "D:\\GOOGLECREDENTIALS\\client_secret_359861037120-m3mjvr3kg51i2c2qb38dav62uuqoqs5k.apps.googleusercontent.com.json"
|
//"CredentialsPath": "D:\\GOOGLECREDENTIALS\\client_secret_359861037120-m3mjvr3kg51i2c2qb38dav62uuqoqs5k.apps.googleusercontent.com.json"
|
||||||
|
|
@ -57,5 +63,9 @@
|
||||||
"ElevenLabsAPI": {
|
"ElevenLabsAPI": {
|
||||||
"ApiKey": "sk_adaa84dce6ed60504c71aff230f2b8bdbd0effa347f715b6"
|
"ApiKey": "sk_adaa84dce6ed60504c71aff230f2b8bdbd0effa347f715b6"
|
||||||
},
|
},
|
||||||
|
"ScraperSettings": {
|
||||||
|
"Provider": "BrightData",
|
||||||
|
"ApiKey": "2137725d-f768-49fd-9c85-f9caf90518e7"
|
||||||
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,63 @@
|
||||||
/*@import url('https://fonts.googleapis.com/css2?family=Quicksand&display=swap');
|
/*@import url('https://fonts.googleapis.com/css2?family=Quicksand&display=swap');
|
||||||
@import url('https://fonts.googleapis.com/css?family=Comfortaa:400,700,300');*/
|
@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 {
|
label {
|
||||||
display: unset !important;
|
display: unset !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
|
|
@ -12,20 +69,20 @@ label {
|
||||||
scrollbar-color: #87b1d6 #ffffff00;
|
scrollbar-color: #87b1d6 #ffffff00;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chrome, Edge, and Safari */
|
/* Chrome, Edge, and Safari */
|
||||||
*::-webkit-scrollbar {
|
*::-webkit-scrollbar {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
*::-webkit-scrollbar-track {
|
*::-webkit-scrollbar-track {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
*::-webkit-scrollbar-thumb {
|
*::-webkit-scrollbar-thumb {
|
||||||
background-color: #3e9fa3;
|
background-color: #3e9fa3;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 3px solid #ffffff;
|
border: 3px solid #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.reference-button {
|
.reference-button {
|
||||||
|
|
@ -85,6 +142,10 @@ label {
|
||||||
background: linear-gradient(to bottom, #533e7e, #3c3666) !important;
|
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 {
|
.bg-panel-gradient-highlight {
|
||||||
background: linear-gradient(to bottom, #63358d, #7d3d7b) !important;
|
background: linear-gradient(to bottom, #63358d, #7d3d7b) !important;
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +158,7 @@ label {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rz-dialog-wrapper{
|
.rz-dialog-wrapper {
|
||||||
z-index: 10008 !important;
|
z-index: 10008 !important;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +179,6 @@ label {
|
||||||
box-shadow: 8px 6px 8px 2px rgba(0, 0, 0, 0.4);
|
box-shadow: 8px 6px 8px 2px rgba(0, 0, 0, 0.4);
|
||||||
--rz-dialog-title-background-color: transparent;
|
--rz-dialog-title-background-color: transparent;
|
||||||
--rz-primary: #87b1d6;
|
--rz-primary: #87b1d6;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rz-dialog .rz-button {
|
.rz-dialog .rz-button {
|
||||||
|
|
@ -135,25 +195,25 @@ label {
|
||||||
color: #608AAD !important;
|
color: #608AAD !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-panel .list-group {
|
.bg-panel .list-group {
|
||||||
--bs-list-group-color: unset !important;
|
--bs-list-group-color: unset !important;
|
||||||
--bs-list-group-bg: unset !important;
|
--bs-list-group-bg: unset !important;
|
||||||
}
|
|
||||||
|
|
||||||
.bg-panel .list-group-item {
|
|
||||||
background-color: unset !important;
|
|
||||||
border: unset !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-panel .list-group-item .btn {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-panel .content-item-list {
|
.bg-panel .list-group-item {
|
||||||
max-height: 300px;
|
background-color: unset !important;
|
||||||
overflow-y: scroll;
|
border: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-panel .list-group-item .btn {
|
||||||
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-panel .content-item-list {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
.rz-dialog-confirm-buttons .rz-base {
|
.rz-dialog-confirm-buttons .rz-base {
|
||||||
background-color: #87B1D6 !important;
|
background-color: #87B1D6 !important;
|
||||||
}
|
}
|
||||||
|
|
@ -204,19 +264,68 @@ Don't show
|
||||||
backdrop-filter: blur(6px);
|
backdrop-filter: blur(6px);
|
||||||
color: #b0daff;
|
color: #b0daff;
|
||||||
z-index: 10008 !important;
|
z-index: 10008 !important;
|
||||||
top: 150px;
|
top: 200px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
max-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;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
margin-left: 10px;
|
||||||
box-shadow: 8px 6px 8px 2px rgba(0, 0, 0, 0.4);
|
box-shadow: 8px 6px 8px 2px rgba(0, 0, 0, 0.4);
|
||||||
--rz-primary: #87b1d6;
|
--rz-primary: #87b1d6;
|
||||||
overflow-y: scroll;
|
font-size: 0.9rem;
|
||||||
}
|
|
||||||
|
|
||||||
.editor-window .btn {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.radzen-popup {
|
.radzen-popup {
|
||||||
|
|
@ -236,9 +345,9 @@ Don't show
|
||||||
/*background-color: #b0daff;*/
|
/*background-color: #b0daff;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.radzen-popup p {
|
.radzen-popup p {
|
||||||
color: #fff
|
color: #fff
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.rz-panel {
|
.rz-panel {
|
||||||
|
|
@ -249,3 +358,123 @@ Don't show
|
||||||
border-radius: 0px;
|
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;
|
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;
|
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-top: 1.1rem;
|
padding-top: 1.1rem;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,16 @@
|
||||||
@import url('https://fonts.googleapis.com/css?family=Comfortaa:400,700,300');*/
|
@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 {
|
.radzen-popup {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -125,7 +135,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchBox:hover > .searchInput {
|
.searchBox:hover > .searchInput {
|
||||||
width: calc(100% - 60px);
|
width: calc(100% - 120px);
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,6 +144,10 @@
|
||||||
color: #2f3640;
|
color: #2f3640;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.searchBox:hover > .voicebutton {
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.searchButton {
|
.searchButton {
|
||||||
color: white;
|
color: white;
|
||||||
float: right;
|
float: right;
|
||||||
|
|
@ -309,9 +323,7 @@ ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rz-html-editor {
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
h1 {
|
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 |