1240 lines
64 KiB
C#
1240 lines
64 KiB
C#
using System.Net.Http;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using System.Text;
|
|
using DocumentFormat.OpenXml.Bibliography;
|
|
using DocumentFormat.OpenXml.Wordprocessing;
|
|
using BLAIzor.Models;
|
|
using Newtonsoft.Json;
|
|
using Microsoft.AspNetCore.Components.Routing;
|
|
using Microsoft.AspNetCore.Components;
|
|
using static Google.Apis.Requests.BatchRequest;
|
|
using DocumentFormat.OpenXml.Spreadsheet;
|
|
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
|
using NuGet.Packaging;
|
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
|
using Google.Api;
|
|
using System.CodeDom;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using System.Text.Json.Serialization;
|
|
using BLAIzor.Services;
|
|
using Microsoft.DotNet.Scaffolding.Shared;
|
|
using System.Xml.XPath;
|
|
using Azure.Identity;
|
|
using BLAIzor.Helpers;
|
|
using System.Collections;
|
|
using Qdrant.Client.Grpc;
|
|
using Microsoft.CodeAnalysis.Elfie.Model.Tree;
|
|
using Microsoft.AspNetCore.Components.Web;
|
|
|
|
namespace BLAIzor.Services
|
|
{
|
|
public class AIService
|
|
{
|
|
private readonly HttpClient _httpClient;
|
|
private readonly ContentEditorService _contentEditorService;
|
|
private readonly ScopedContentService _scopedContentService;
|
|
private readonly OpenAIEmbeddingService _openAIEmbeddingService;
|
|
private readonly LocalEmbeddingService _localEmbeddingService;
|
|
private readonly OpenAIApiService _openAIApiService;
|
|
private readonly OpenAiRealtimeService _openAIRealtimeService;
|
|
private readonly DeepSeekApiService _deepSeekApiService;
|
|
private readonly CerebrasAPIService _cerebrasAPIService;
|
|
private readonly KimiApiService _kimiApiService;
|
|
private readonly QDrantService _qDrantService;
|
|
private readonly NavigationManager _navigationManager;
|
|
private readonly LocalVectorSearchService _localVectorSearchService;
|
|
private readonly WebsiteContentLoaderService _websiteContentLoaderService;
|
|
private readonly ISimpleLogger _logger;
|
|
private readonly CacheService _cacheService;
|
|
|
|
|
|
public static IConfiguration? _configuration;
|
|
|
|
|
|
public AIService(HttpClient httpClient,
|
|
ContentEditorService contentEditorService,
|
|
ScopedContentService scopedContentService,
|
|
QDrantService qDrantService,
|
|
OpenAIEmbeddingService openAIEmbeddingService,
|
|
LocalEmbeddingService localEmbeddingService,
|
|
OpenAIApiService openAIApiService,
|
|
DeepSeekApiService deepSeekApiService,
|
|
OpenAiRealtimeService openAIRealtimeService,
|
|
CerebrasAPIService cerebrasAPIService,
|
|
KimiApiService kimiApiService,
|
|
NavigationManager navigationManager,
|
|
IConfiguration? configuration,
|
|
LocalVectorSearchService localVectorSearchService,
|
|
WebsiteContentLoaderService websiteContentLoaderService,
|
|
ISimpleLogger logger,
|
|
CacheService cacheService)
|
|
{
|
|
_httpClient = httpClient;
|
|
_contentEditorService = contentEditorService;
|
|
_scopedContentService = scopedContentService;
|
|
_qDrantService = qDrantService;
|
|
_openAIEmbeddingService = openAIEmbeddingService;
|
|
_localEmbeddingService = localEmbeddingService;
|
|
_openAIApiService = openAIApiService;
|
|
_deepSeekApiService = deepSeekApiService;
|
|
_openAIRealtimeService = openAIRealtimeService;
|
|
_cerebrasAPIService = cerebrasAPIService;
|
|
_kimiApiService = kimiApiService;
|
|
_navigationManager = navigationManager;
|
|
_localVectorSearchService = localVectorSearchService;
|
|
_websiteContentLoaderService = websiteContentLoaderService;
|
|
_configuration = configuration;
|
|
|
|
_openAIApiService.RegisterCallback(HandleActionInvoked, HandleFinishedInvoked, HandleErrorInvoked);
|
|
_cerebrasAPIService.RegisterCallback(HandleActionInvoked, HandleFinishedInvoked, HandleErrorInvoked);
|
|
_openAIRealtimeService.RegisterCallback(HandleActionInvoked);
|
|
_logger = logger;
|
|
_cacheService = cacheService;
|
|
}
|
|
|
|
private const string OpenAiEndpoint = "https://api.openai.com/v1/chat/completions";
|
|
public string _apiKey;
|
|
public event Action<string, string, MenuItem?>? OnContentReceived;
|
|
public event Action<string>? OnContentReceiveFinished;
|
|
public event Action<string, string>? OnContentReceivedError;
|
|
public event Action<string, string>? OnStatusChangeReceived;
|
|
public event Action<string, string>? OnTextContentAvailable;
|
|
public string Mood = "cool, and professional";
|
|
private string _workingContent = null;
|
|
public bool UseWebsocket = false;
|
|
private string AiProvider = "";
|
|
|
|
private string GetAiSettings() =>
|
|
_configuration?.GetSection("AiSettings")?.GetValue<string>("Provider") ?? string.Empty;
|
|
|
|
private string GetAiEmbeddingSettings() =>
|
|
_configuration?.GetSection("AiSettings")?.GetValue<string>("EmbeddingService") ?? string.Empty;
|
|
|
|
private void HandleActionInvoked(string sessionId, string streamedHtmlContent)
|
|
{
|
|
OnContentReceived?.Invoke(sessionId, streamedHtmlContent, null);
|
|
}
|
|
|
|
private void HandleErrorInvoked(string sessionId, string streamedHtmlContent)
|
|
{
|
|
OnContentReceivedError?.Invoke(sessionId, streamedHtmlContent);
|
|
}
|
|
|
|
private void HandleFinishedInvoked(string sessionId)
|
|
{
|
|
OnContentReceiveFinished?.Invoke(sessionId);
|
|
}
|
|
|
|
public string GetApiKey()
|
|
{
|
|
if (_configuration == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
if (_configuration.GetSection("OpenAI") == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
return _configuration.GetSection("OpenAI").GetValue<string>("ApiKey")!;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async Task<WebsiteContentModel> InitSite(string sessionId, SiteInfo site, string templateCollectionName, string menuList = "")
|
|
{
|
|
await _logger.InfoAsync($"InitSite: method called", templateCollectionName);
|
|
|
|
string currentUri = _navigationManager.Uri;
|
|
if (site.DefaultLanguage != null)
|
|
{
|
|
_scopedContentService.WebsiteDefaultLanguage = site.DefaultLanguage;
|
|
}
|
|
WebsiteContentModel siteModel = await _cacheService.UpdateContentCache(sessionId, site.Id);
|
|
//_scopedContentService.SessionId = sessionId;
|
|
|
|
_scopedContentService.AvailableTemplateSnippets = await GetSnippetsForDisplay(sessionId, templateCollectionName);
|
|
return siteModel;
|
|
}
|
|
|
|
public async Task GetChatGptWelcomeMessage(string sessionId, int SiteId, string templateCollectionName, string menuList = "")
|
|
{
|
|
await _logger.InfoAsync($"GetChatGptWelcomeMessage: method called", templateCollectionName);
|
|
SiteInfo site = await _contentEditorService.GetSiteInfoByIdAsync(SiteId);
|
|
var siteModel = await InitSite(sessionId, site, templateCollectionName, menuList);
|
|
//_apiKey = GetApiKey();
|
|
//List<string> qdrantPoint = await _qDrantService.GetContentAsync(SiteId, _scopedContentService.WebsiteContent.Items.FirstOrDefault());
|
|
string extractedText = "";
|
|
//nullcheck
|
|
if (siteModel != null)
|
|
{
|
|
await _logger.InfoAsync($"GetChatGptWelcomeMessage: sitemodel not null", siteModel.SiteInfoId.ToString());
|
|
if (siteModel.ContentItems != null && siteModel.ContentItems.Count > 0)
|
|
{
|
|
if (siteModel.ContentItems.OrderBy(sm => sm.ContentItem.Id).FirstOrDefault().VectorPoints != null)
|
|
{
|
|
foreach (var contentPoint in siteModel.ContentItems.OrderBy(sm => sm.ContentItem.Id).FirstOrDefault().VectorPoints)
|
|
{
|
|
extractedText += contentPoint.Content;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
extractedText = "Content not available";
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var siteContent = await _qDrantService.GetPointFromQdrantAsyncByPointId(SiteId, 0);
|
|
|
|
extractedText += siteContent.FirstOrDefault().Content;
|
|
}
|
|
//OnContentReceived?.Invoke(sessionId, extractedText);
|
|
|
|
string siteEntity;
|
|
if (!string.IsNullOrWhiteSpace(site.Entity))
|
|
{
|
|
siteEntity = site.Entity;
|
|
}
|
|
else
|
|
{
|
|
siteEntity = "brand or company";
|
|
}
|
|
string systemMessage = AiPrompts.WelcomeContent.GetSystemMessageForWelcomeMessage(Mood, siteEntity, extractedText, _scopedContentService.SelectedBrandName, _scopedContentService.SelectedLanguage, menuList);
|
|
|
|
string userMessage = "Hello";
|
|
string streamedHtmlContent = string.Empty;
|
|
|
|
if (!UseWebsocket)
|
|
{
|
|
|
|
AiProvider = GetAiSettings();
|
|
if (AiProvider == "cerebras")
|
|
{
|
|
var result = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, userMessage);
|
|
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
|
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
|
|
|
}
|
|
else if (AiProvider == "chatgpt")
|
|
{
|
|
await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage);
|
|
//var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userMessage);
|
|
//var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
|
//OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
|
|
|
}
|
|
else if (AiProvider == "deepseek")
|
|
{
|
|
//await _deepSeekApiService.GetChatGPTStreamedResponse(systemMessage, userMessage);
|
|
}
|
|
if (AiProvider == "kimi")
|
|
{
|
|
var result = await _kimiApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userMessage);
|
|
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
|
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await _openAIRealtimeService.GetChatGPTResponseAsync(sessionId, systemMessage, userMessage);
|
|
|
|
}
|
|
|
|
//_scopedContentService.CurrentDOM = streamedHtmlContent;
|
|
////Console.Write("Answer: " + streamedHtmlContent);
|
|
//return streamedHtmlContent;
|
|
|
|
_scopedContentService.AvailableTemplateSnippets = await GetSnippetsForDisplay(sessionId, templateCollectionName);
|
|
|
|
//Console.WriteLine(_scopedContentService.WebsiteContentModel.ContentItems.Count);
|
|
}
|
|
|
|
public async Task ProcessUserIntent(string sessionId, string userPrompt, int siteId, int templateId, string contentCollectionName, string menuList = "")
|
|
{
|
|
await _logger.InfoAsync($"ProcessUserIntent: method called", $"sessionId: {sessionId}, userprompt: {userPrompt}, siteId: {siteId}, templateId: {templateId}, contentCollectionName: {contentCollectionName}");
|
|
|
|
//Console.WriteLine($"SITE ID: {siteId}");
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Understanding your request...");
|
|
|
|
// Get JSON result based on siteId presence
|
|
string resultJson = await GetJsonResultFromQuery(sessionId, siteId, userPrompt);
|
|
|
|
//Console.WriteLine(resultJson);
|
|
|
|
var baseResult = await ValidateAndFixJson<ChatGPTResultBase>(resultJson, FixJsonWithAI);
|
|
//var baseResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTResultBase>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
|
|
if (baseResult == null)
|
|
{
|
|
//Console.WriteLine("Invalid JSON response.");
|
|
return;
|
|
}
|
|
|
|
var fixedResult = System.Text.Json.JsonSerializer.Serialize(baseResult);
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Making a decision");
|
|
|
|
// Process result based on type
|
|
switch (baseResult.Type.ToLower())
|
|
{
|
|
case "methodresult":
|
|
await ProcessMethodResult(sessionId, resultJson);
|
|
break;
|
|
|
|
case "textresult":
|
|
await ProcessTextResult(sessionId, "Your answer", fixedResult, templateId, contentCollectionName);
|
|
break;
|
|
|
|
case "examinationresult":
|
|
await ProcessExaminationResult(sessionId, fixedResult, templateId, contentCollectionName);
|
|
break;
|
|
|
|
case "errorresult":
|
|
await ProcessErrorResult(sessionId, fixedResult);
|
|
break;
|
|
|
|
default:
|
|
//Console.WriteLine("Unknown result type.");
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// No reasoning needed just content retrieved and displayed as html
|
|
/// </summary>
|
|
/// <param name="sessionId"></param>
|
|
/// <param name="userPrompt"></param>
|
|
/// <param name="siteId"></param>
|
|
/// <param name="templateId"></param>
|
|
/// <param name="collectionName"></param>
|
|
/// <param name="menuList"></param>
|
|
/// <returns></returns>
|
|
public async Task ProcessContentRequest(string sessionId, MenuItem requestedMenu, int siteId, int templateId, string contentCollectionName, string menuList = "", bool forceUnmodified = false)
|
|
{
|
|
|
|
//Console.Write($"\n\n SessionId: {sessionId}\n\n");
|
|
//string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _contentService.SelectedDocument);
|
|
|
|
string extractedText = "";
|
|
|
|
if (requestedMenu != null)
|
|
{
|
|
//do we have it cached?
|
|
var contentId = requestedMenu.ContentItemId;
|
|
if (_scopedContentService.WebsiteContentModel != null)
|
|
{
|
|
if (_scopedContentService.WebsiteContentModel.ContentItems.Any(x => x.ContentItem.Id == contentId))
|
|
{
|
|
//we have the content cached
|
|
//checkversion TODO
|
|
var thisContent = _scopedContentService.WebsiteContentModel.ContentItems.Where(x => x.ContentItem.Id == contentId).FirstOrDefault();
|
|
//this should be only 1 for now (and we shouldn't use this, but the save content itself...)
|
|
//foreach (var chunk in thisContent.VectorPoints)
|
|
//{
|
|
// extractedText += $"{chunk.Name}: {chunk.Content}";
|
|
//}
|
|
extractedText = thisContent.ContentItem.Content;
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//get vector of menuItem
|
|
|
|
if (requestedMenu.ContentGroupId != null)
|
|
{
|
|
//here we need something that turns the menuitem into contentitem... oh wait we have that..
|
|
|
|
var contentItem = await _contentEditorService.GetContentItemByIdAsync((int)requestedMenu.ContentItemId);
|
|
var vectorList = contentItem.Chunks.ToList();
|
|
//var vectors = await _contentEditorService.GetPointIdsByContentGroupIdAsync((int)requestedMenu.ContentGroupId);
|
|
PointId[] points = new PointId[vectorList.Count];
|
|
for (int i = 0; i < vectorList.Count; i++)
|
|
{
|
|
points[i] = Convert.ToUInt64(vectorList[i].ChunkIndex);
|
|
}
|
|
var site = await _contentEditorService.GetSiteInfoByIdAsync(siteId);
|
|
List<WebPageContent> qDrantData = await _qDrantService.GetPointsFromQdrantAsyncByPointIds(site, points); //FIXXXXXXX
|
|
|
|
foreach (var point in qDrantData)
|
|
{
|
|
//extractedText += point.result.payload.name + ": " + point.result.payload.content + ", ";
|
|
extractedText += $"{point.Name}: {point.Content},";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//error, no contentgroup yet
|
|
}
|
|
//TODO
|
|
|
|
}
|
|
}
|
|
|
|
string contentJson = await GetContentFromQuery(sessionId,
|
|
"Enhance this text if needed, making its style and grammar suitable to be displayed as the content of a webpage",
|
|
extractedText,
|
|
forceUnmodified);
|
|
|
|
await ProcessContent(sessionId, requestedMenu, contentJson, templateId, contentCollectionName);
|
|
}
|
|
|
|
public async Task ProcessContentRequest(string sessionId, string requestedMenu, int siteId, int templateId, string contentCollectionName, string menuList = "", bool forceUnmodified = false)
|
|
{
|
|
await _logger.InfoAsync($"ProcessContentRequest: method called", $"sessionId: {sessionId}, requestedMenu: {requestedMenu}, siteId: {siteId}, templateId: {templateId}, contentCollectionName: {contentCollectionName}");
|
|
//Console.Write($"\n\n SessionId: {sessionId}\n\n");
|
|
//string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _contentService.SelectedDocument);
|
|
|
|
string extractedText = "";
|
|
|
|
float[] vector = [];
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Determining search vectors");
|
|
var embeddingServiceProvider = GetAiEmbeddingSettings();
|
|
if (embeddingServiceProvider == "local")
|
|
{
|
|
vector = await _localEmbeddingService.GenerateEmbeddingAsync(requestedMenu);
|
|
}
|
|
else
|
|
{
|
|
vector = await _openAIEmbeddingService.GenerateEmbeddingAsync(requestedMenu);
|
|
}
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Looking up content in the knowledge database");
|
|
|
|
//get current version
|
|
ContentGroup contentGroup = await _contentEditorService.GetContentGroupBySiteInfoIdAsync(siteId);
|
|
int version = 0;
|
|
if (contentGroup != null)
|
|
{
|
|
version = contentGroup.Version;
|
|
}
|
|
//no cache
|
|
//if (_scopedContentService.WebsiteContentModel == null || _scopedContentService.WebsiteContentModel.ContentGroup.Version != version)
|
|
if (_scopedContentService.WebsiteContentModel == null)
|
|
{
|
|
//have to get from qdrant
|
|
var pointList = await _qDrantService.QueryContentAsync(contentCollectionName, vector, 3);
|
|
//PointId[] pointArray = new PointId[intArray.Length];
|
|
if (pointList.Count > 0)
|
|
{
|
|
for (int i = 0; i < pointList.Count; i++)
|
|
{
|
|
extractedText += $"{pointList[i].Name}: {pointList[i].Content}";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
extractedText = "VECTOR ERROR: ZERO INFORMATION FOUND";
|
|
}
|
|
}
|
|
//cached content
|
|
else
|
|
{
|
|
List<WebPageContent> pagePoints = new List<WebPageContent>();
|
|
|
|
foreach (var valami in _scopedContentService.WebsiteContentModel.ContentItems)
|
|
{
|
|
pagePoints.AddRange(valami.VectorPoints);
|
|
}
|
|
|
|
var resultContent = _localVectorSearchService.SearchContent(vector, pagePoints, 3);
|
|
|
|
foreach (var item in resultContent)
|
|
{
|
|
extractedText += item.Name + ": " + item.Content + ", ";
|
|
}
|
|
}
|
|
|
|
Console.WriteLine($"Points from cache or qdrant: {extractedText}");
|
|
string contentJson = await GetContentFromQuery(sessionId,
|
|
requestedMenu,
|
|
extractedText,
|
|
forceUnmodified);
|
|
Console.WriteLine($"AI representation: {contentJson}");
|
|
await ProcessContent(sessionId, requestedMenu, contentJson, templateId, contentCollectionName);
|
|
}
|
|
|
|
// Refactored helper methods
|
|
|
|
private async Task ProcessMethodResult(string sessionId, string resultJson)
|
|
{
|
|
await _logger.InfoAsync($"ProcessMethodResult: method called", $"sessionId: {sessionId}, json: {resultJson}");
|
|
//var fixedResult = await ValidateAndFixJson<ChatGPTMethodResult>(resultJson, FixJsonWithAI); //ANOTHER fixing is useless, json is from fixed format
|
|
var methodResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTMethodResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
if (methodResult != null)
|
|
{
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Initiating the task you requested");
|
|
await DisplayHtml(sessionId, methodResult.Text, methodResult.MethodToCall, methodResult.Parameter);
|
|
}
|
|
}
|
|
|
|
private async Task ProcessTextResult(string sessionId, string pageTitle, string resultJson, int templateId, string collectionName)
|
|
{
|
|
await _logger.InfoAsync($"ProcessTextResult: method called", $"sessionId: {sessionId}, json: {resultJson}, pageTitle: {pageTitle}, templateId: {templateId}, collectionName: {collectionName}");
|
|
//var fixedResult = await ValidateAndFixJson<ChatGPTTextResult>(resultJson, FixJsonWithAI); //ANOTHER fixing is useless, json is from fixed format
|
|
var textResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTTextResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
if (textResult != null)
|
|
{
|
|
string contentJson = await GetContentFromQuery(sessionId, textResult.Text, _workingContent);
|
|
//Console.Write("\r \n ProcessTextResult: Content: " + contentJson + "\r \n");
|
|
await ProcessContent(sessionId, pageTitle, contentJson, templateId, collectionName);
|
|
}
|
|
}
|
|
|
|
public async Task<T?> ValidateAndFixJson<T>(string json, Func<string, Task<string>> aiFixer)
|
|
{
|
|
await _logger.InfoAsync($"ValidateAndFixJson: method called", $"json: {json}");
|
|
try
|
|
{
|
|
return System.Text.Json.JsonSerializer.Deserialize<T>(json, new JsonSerializerOptions
|
|
{
|
|
PropertyNameCaseInsensitive = true
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
|
|
await _logger.ErrorAsync($"ValidateAndFixJson: failed first!", $"message: {ex.Message}");
|
|
var prompt = BuildJsonFixPrompt(json, ex.Message, typeof(T).Name);
|
|
var fixedJson = await aiFixer(prompt);
|
|
|
|
try
|
|
{
|
|
return System.Text.Json.JsonSerializer.Deserialize<T>(fixedJson, new JsonSerializerOptions
|
|
{
|
|
PropertyNameCaseInsensitive = true
|
|
});
|
|
}
|
|
catch (Exception ex2)
|
|
{
|
|
await _logger.ErrorAsync($"ValidateAndFixJson: failed again!", $"❌ AI-fix parse failed: {ex2.Message}");
|
|
|
|
return default;
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task<string> FixJsonWithAI(string prompt)
|
|
{
|
|
|
|
AiProvider = GetAiSettings();
|
|
if (AiProvider == "cerebras")
|
|
{
|
|
return await _cerebrasAPIService.GetSimpleCerebrasResponseNoSession("You are a JSON-fixing assistant.", prompt);
|
|
}
|
|
else if (AiProvider == "chatgpt")
|
|
{
|
|
return await _openAIApiService.GetSimpleChatGPTResponseNoSession("You are a JSON-fixing assistant.", prompt);
|
|
|
|
}
|
|
else if (AiProvider == "deepseek")
|
|
{
|
|
return await _deepSeekApiService.GetSimpleChatGPTResponse("You are a JSON-fixing assistant.", prompt);
|
|
}
|
|
else { return ""; }
|
|
|
|
|
|
}
|
|
|
|
private string BuildJsonFixPrompt(string json, string errorMessage, string targetTypeName)
|
|
{
|
|
return $"""
|
|
The following JSON was supposed to be parsed into an object of type {targetTypeName}, but it failed with this error:
|
|
|
|
{errorMessage}
|
|
|
|
Please fix the formatting of the JSON so that it becomes valid and deserializable:
|
|
|
|
--- JSON START ---
|
|
{json}
|
|
--- JSON END ---
|
|
|
|
Only return the fixed JSON, with no explanation or formatting like ```json.
|
|
""";
|
|
}
|
|
|
|
|
|
private async Task ProcessExaminationResult(string sessionId, string resultJson, int templateId, string collectionName)
|
|
{
|
|
await _logger.InfoAsync($"ProcessExaminationResult: method called", $"sessionId: {sessionId}, json: {resultJson}, templateId: {templateId}, collectionName: {collectionName}");
|
|
//var fixedResult = await ValidateAndFixJson<ChatGPTExaminationResult>(resultJson, FixJsonWithAI);
|
|
var explanationResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTExaminationResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
if (explanationResult != null)
|
|
{
|
|
string contentJson = await GetExplanationFromQuery(sessionId, explanationResult.Text, _scopedContentService.CurrentDOM);
|
|
await ProcessContent(sessionId, "Examination", contentJson, templateId, collectionName);
|
|
}
|
|
}
|
|
|
|
private async Task ProcessContent(string sessionId, string pageTitle, string contentJson, int templateId, string contentCollectionName)
|
|
{
|
|
await _logger.InfoAsync($"ProcessContent: method called", $"sessionId: {sessionId}, pageTitle: {pageTitle}, json: {contentJson}, templateId: {templateId}, contentCollectionName: {contentCollectionName}");
|
|
try
|
|
{
|
|
var fixedResult = await ValidateAndFixJson<ChatGPTContentResult>(contentJson, FixJsonWithAI);
|
|
//var contentResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTContentResult>(contentJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
//var fixedResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTContentResult>(contentJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
|
|
if (fixedResult != null)
|
|
{
|
|
Console.WriteLine($"\n\n Actual content: {fixedResult.Text} \n\n");
|
|
|
|
// Add reaction GIFs
|
|
//contentResult.Photos.Add("Clarification request", "https://www.reactiongifs.com/r/martin.gif");
|
|
//contentResult.Photos.Add("Compliment response", "https://www.reactiongifs.com/r/review.gif");
|
|
|
|
//We have the text all available now, let's pass it to the voice generator
|
|
//TODO modify photos handling, move audio generation to the layout area
|
|
string removedNumbers = TextHelper.ReplaceNumbersAndSpecialCharacters(fixedResult.Text, _scopedContentService.SelectedLanguage);
|
|
Console.WriteLine(removedNumbers);
|
|
OnTextContentAvailable?.Invoke(sessionId, removedNumbers);
|
|
//List<HtmlSnippet> snippets = await GetSnippetsForDisplay(sessionId, collectionName);
|
|
List<HtmlSnippet> snippets = _scopedContentService.AvailableTemplateSnippets;
|
|
//await DisplayLayoutPlanFromContent(sessionId, fixedResult.Text, snippets, fixedResult.Topics, fixedResult.Photos);
|
|
var result = await DisplayLayoutPlanFromContent(sessionId, pageTitle, fixedResult.Text, snippets, fixedResult.Topics, fixedResult.Photos);
|
|
if (result == null) result = new LayoutPlan();
|
|
await DisplayHtml(sessionId, pageTitle, result, snippets, fixedResult.Topics, fixedResult.Photos);
|
|
//OnContentReceived?.Invoke(sessionId, result.Blocks.Count.ToString());
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//Console.WriteLine($"Error processing content: {ex.Message}");
|
|
OnContentReceived?.Invoke(sessionId, ex.Message, null);
|
|
}
|
|
}
|
|
|
|
//passing menuitem further
|
|
private async Task ProcessContent(string sessionId, MenuItem requestedMenu, string contentJson, int templateId, string collectionName)
|
|
{
|
|
await _logger.InfoAsync($"ProcessContent: method called", $"sessionId: {sessionId}, menuItem: {requestedMenu.Name}, json: {contentJson}, templateId: {templateId}, contentCollectionName: {collectionName}");
|
|
try
|
|
{
|
|
var fixedResult = await ValidateAndFixJson<ChatGPTContentResult>(contentJson, FixJsonWithAI);
|
|
//var contentResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTContentResult>(contentJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
//var fixedResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTContentResult>(contentJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
|
|
if (fixedResult != null)
|
|
{
|
|
Console.WriteLine($"\n\n Actual content: {fixedResult.Text} \n\n");
|
|
|
|
// Add reaction GIFs
|
|
//contentResult.Photos.Add("Clarification request", "https://www.reactiongifs.com/r/martin.gif");
|
|
//contentResult.Photos.Add("Compliment response", "https://www.reactiongifs.com/r/review.gif");
|
|
|
|
//We have the text all available now, let's pass it to the voice generator
|
|
//TODO modify photos handling, move audio generation to the layout area
|
|
string removedNumbers = TextHelper.ReplaceNumbersAndSpecialCharacters(fixedResult.Text, _scopedContentService.SelectedLanguage);
|
|
Console.WriteLine(removedNumbers);
|
|
OnTextContentAvailable?.Invoke(sessionId, removedNumbers);
|
|
//List<HtmlSnippet> snippets = await GetSnippetsForDisplay(sessionId, collectionName);
|
|
List<HtmlSnippet> snippets = _scopedContentService.AvailableTemplateSnippets;
|
|
//await DisplayLayoutPlanFromContent(sessionId, fixedResult.Text, snippets, fixedResult.Topics, fixedResult.Photos);
|
|
var result = await DisplayLayoutPlanFromContent(sessionId, requestedMenu.Name, fixedResult.Text, snippets, fixedResult.Topics, fixedResult.Photos);
|
|
if (result == null) result = new LayoutPlan();
|
|
await DisplayHtmlForMenu(sessionId, requestedMenu, result, snippets, fixedResult.Topics, fixedResult.Photos);
|
|
//OnContentReceived?.Invoke(sessionId, result.Blocks.Count.ToString());
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//Console.WriteLine($"Error processing content: {ex.Message}");
|
|
OnContentReceived?.Invoke(sessionId, ex.Message, requestedMenu);
|
|
}
|
|
}
|
|
|
|
private async Task ProcessErrorResult(string sessionId, string resultJson)
|
|
{
|
|
await _logger.InfoAsync($"ProcessErrorResult: method called", $"sessionId: {sessionId}, json: {resultJson}");
|
|
var errorResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTErrorResult>(resultJson);
|
|
if (errorResult != null)
|
|
{
|
|
//Console.WriteLine($"Error Result: {errorResult.Text}");
|
|
await DisplayLayoutPlanFromContent(sessionId, errorResult.Text, null, null, null);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Let's get the actual content
|
|
/// </summary>
|
|
/// <param name="userPrompt"></param>
|
|
/// <returns></returns>
|
|
public async Task<string> GetContentFromQuery(string sessionId, string userPrompt, string content = null, bool forceUnmodified = false)
|
|
{
|
|
await _logger.InfoAsync($"GetContentFromQuery: method called", $"sessionId: {sessionId}, userPrompt: {userPrompt}, content: {content}, forceUnmodified: {forceUnmodified}");
|
|
string extractedText;
|
|
if (content == null)
|
|
{
|
|
string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _scopedContentService.SelectedDocument);
|
|
extractedText = WordFileReader.ExtractText(rootpath);
|
|
}
|
|
else extractedText = content;
|
|
|
|
_apiKey = GetApiKey();
|
|
|
|
////Console.Write("GetJSONResult called: " +extractedText);
|
|
string systemMessage = "";
|
|
if (forceUnmodified)
|
|
{
|
|
systemMessage = "You are a helpful assistant of a website. Display the Content STRICTLY in " + _scopedContentService.SelectedLanguage + " with a plain JSON object in the following format:\r\n\r\n1. " +
|
|
//"**chatGPTContentResult**:\r\n " +
|
|
"- `type`: A string with value `contentresult`.\r\n " +
|
|
"- `text`: A string with the actual response.\r\n " +
|
|
"- `topics`: A list of sections of the initial document." +
|
|
"- `photos`: A dictionary of string key and string values, where the keys are the name of the subject that the photo is related to (like a person's name, or a section)," +
|
|
" and the value is the actual, unmodified photo url.\r\n" +
|
|
"**Document-Specific Instructions**:\r\n" +
|
|
"Step 1: Start with defining above mentioned key topics of the initial document, and making the list of them. " +
|
|
"Step 2: After that add the above mentioned relevant image urls list." +
|
|
"Step 3: " +
|
|
"- Turn the following content into a nice informative webpage content (DO NOT REMOVE URLS, PHOTO URLS though).\r\n " +
|
|
"- Start with the page title.\r\n" +
|
|
"- Structure the text nicely without missing any information.\r\n " +
|
|
//"*** CONTENT START *** {" + extractedText + "} *** CONTENT END ***.\r\n" +
|
|
"**Style and Image Handling**:\r\n" +
|
|
"- Make sure the json is valid json." +
|
|
"- Do NOT include extraneous text outside the JSON structure.\r\n\r\n" +
|
|
"When you understand the input, follow these rules strictly. Otherwise, seek clarification.\r\n" +
|
|
"Do not include linebreaks or any formatting, just the plain json string. Make sure it is valid json, and every objects is closed properly" +
|
|
"Do NOT mark your answer with anything like `````json, and do not add any explanation.";
|
|
userPrompt = "Give me a formatted json from this content, without modifying the text: " + extractedText;
|
|
}
|
|
else
|
|
{
|
|
systemMessage = "You are a helpful assistant of a website. Respond in the name of the brand or person in the content, strictly in " + _scopedContentService.SelectedLanguage + " with a plain JSON object in the following format:\r\n\r\n1. " +
|
|
//"**chatGPTContentResult**:\r\n " +
|
|
"- `type`: A string with value `contentresult`.\r\n " +
|
|
"- `text`: A string with the actual response.\r\n " +
|
|
"- `topics`: A list of sections of the initial document." +
|
|
"- `photos`: A dictionary of string key and string values, where the keys are the name of the subject that the photo is related to (like a person's name, or a section)," +
|
|
" and the value is the actual, unmodified photo url.\r\n" +
|
|
"**Document-Specific Instructions**:\r\n" +
|
|
"Step 1: Start with defining above mentioned key topics of the initial document, and making the list of them. " +
|
|
"Step 2: After that add the above mentioned relevant image urls list." +
|
|
"Step 3: " +
|
|
"- Base a detailed response solely on the initial document provided below. " +
|
|
"- In your response, summarize ALL relevant information in the document, that is connected to the question." +
|
|
"*** CONTENT START *** {" + extractedText + "} *** CONTENT END ***.\r\n" +
|
|
"- For missing information: Inform the user and ask if you can help with something else. " +
|
|
//"- Do not generate lengthy answers." +
|
|
"- If the user prompt is clear and they ask specific, well defined question, do not add other infromation or welcome message." +
|
|
"- If the user prompt is unclear, or makes no sense, ask for clarification." +
|
|
//"You can decorate your clarification" +
|
|
//"request with this image URL: `https://www.reactiongifs.com/r/martin.gif` added to the photo dictionary.\r\n\r\n" +
|
|
//"- For compliments from the user: Express our happiness about it " +
|
|
//"and apply this image URL: `https://www.reactiongifs.com/r/review.gif` in the photo dictionary.\r\n\r\n" +
|
|
"**Style and Image Handling**:\r\n" +
|
|
"- Make sure the json is valid json." +
|
|
"- Do NOT include extraneous text outside the JSON structure.\r\n\r\n" +
|
|
"When you understand the input, follow these rules strictly. Otherwise, seek clarification.\r\n" +
|
|
"Do not include linebreaks or any formatting, just the plain json string. Make sure it is valid json, and every objects is closed properly" +
|
|
"Do NOT mark your answer with anything like `````json, and do not add any explanation.";
|
|
}
|
|
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Constructing the answer");
|
|
string interMediateResult = string.Empty;
|
|
if (!UseWebsocket)
|
|
{
|
|
|
|
AiProvider = GetAiSettings();
|
|
if (AiProvider == "cerebras")
|
|
{
|
|
interMediateResult = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, userPrompt);
|
|
}
|
|
else if (AiProvider == "chatgpt")
|
|
{
|
|
interMediateResult = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userPrompt);
|
|
|
|
}
|
|
else if (AiProvider == "deepseek")
|
|
{
|
|
interMediateResult = await _deepSeekApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userPrompt);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
interMediateResult = await _openAIRealtimeService.GetFullChatGPTResponseAsync(sessionId, systemMessage, userPrompt);
|
|
}
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Mkay, I know now");
|
|
//Console.Write("GetContentFromQuery: Result decision: " + interMediateResult);
|
|
return interMediateResult;
|
|
}
|
|
|
|
public async Task<string> GetExplanationFromQuery(string sessionId, string userPrompt, string content = null)
|
|
{
|
|
string extractedText;
|
|
if (content == null)
|
|
{
|
|
string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _scopedContentService.SelectedDocument);
|
|
extractedText = WordFileReader.ExtractText(rootpath);
|
|
}
|
|
else extractedText = content;
|
|
|
|
_apiKey = GetApiKey();
|
|
|
|
////Console.Write("GetJSONResult called: " +extractedText);
|
|
|
|
var systemMessage = "You are a helpful assistant. Respond strictly in " + _scopedContentService.SelectedLanguage + " as a JSON object in the following format:\r\n\r\n1. " +
|
|
//"**chatGPTContentResult**:\r\n " +
|
|
"- `type`: A string with value `contentresult`.\r\n " +
|
|
"- `text`: A string with the actual response.\r\n " +
|
|
"- `topics`: A list of sections of the initial document." +
|
|
"- `photos`: A dictionary of string key and string values, where the keys are the name of the subject that the photo is related to (like a person's name, or a section)," +
|
|
" and the value is the actual, unmodified photo url.\r\n" +
|
|
"**Document-Specific Instructions**:\r\n- Base responses solely on the initial document: {" + extractedText + "}.\r\n" +
|
|
"- For missing information: Inform the user and provide a clarification. " +
|
|
"- If the user prompt is clear and they ask specific, well defined question, do not add other infromation or welcome message." +
|
|
"- If the user prompt is unclear, or makes no sense, ask for clarification. " +
|
|
"You may decorate your clarification" +
|
|
"request with this image URL: `https://www.reactiongifs.com/r/martin.gif` added to the photo dictionary.\r\n\r\n" +
|
|
"- For compliments from the user: Express our happiness about it " +
|
|
"and apply this image URL: `https://www.reactiongifs.com/r/review.gif` in the photo dictionary.\r\n\r\n" +
|
|
"**Style and Image Handling**:\r\n" +
|
|
//"- Copy styles explicitly from the document into the response.\r\n" +
|
|
//"- Only use image URLs found in the document for relevant content.\r\n" +
|
|
"- Do NOT include extraneous text outside the JSON structure.\r\n\r\n" +
|
|
"When you understand the input, follow these rules strictly. Otherwise, seek clarification.\r\n" +
|
|
"Do not include linebreaks or any formatting, just the plain json string. Make sure it is valid json, and every objects is closed properly" +
|
|
"Do NOT mark your answer with anything like `````json or such.";
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Constructing the answer");
|
|
string interMediateResult = string.Empty;
|
|
if (!UseWebsocket)
|
|
{
|
|
|
|
AiProvider = GetAiSettings();
|
|
if (AiProvider == "cerebras")
|
|
{
|
|
interMediateResult = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, userPrompt);
|
|
}
|
|
else if (AiProvider == "chatgpt")
|
|
{
|
|
interMediateResult = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userPrompt);
|
|
|
|
}
|
|
else if (AiProvider == "deepseek")
|
|
{
|
|
interMediateResult = await _deepSeekApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userPrompt);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
interMediateResult = await _openAIRealtimeService.GetFullChatGPTResponseAsync(sessionId, systemMessage, userPrompt);
|
|
}
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Mkay, I know now");
|
|
//Console.Write("GetExaminationResult: Result decision: " + interMediateResult);
|
|
return interMediateResult;
|
|
}
|
|
|
|
public async Task<string> GetJsonResultFromQuery(string sessionId, int siteId, string userPrompt)
|
|
{
|
|
await _logger.InfoAsync($"GetJsonResultFromQuery: method called", $"SessionId: {sessionId}, userPrompt: {userPrompt}, siteId: {siteId}");
|
|
//string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _contentService.SelectedDocument);
|
|
//_apiKey = GetApiKey();
|
|
//start with embeddings
|
|
|
|
var site = await _contentEditorService.GetSiteInfoByIdAsync(siteId);
|
|
|
|
float[] vector = [];
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Determining search vectors");
|
|
var embeddingServiceProvider = GetAiEmbeddingSettings();
|
|
if (embeddingServiceProvider == "local")
|
|
{
|
|
vector = await _localEmbeddingService.GenerateEmbeddingAsync(userPrompt);
|
|
}
|
|
else
|
|
{
|
|
vector = await _openAIEmbeddingService.GenerateEmbeddingAsync(userPrompt);
|
|
}
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Looking up content in the knowledge database");
|
|
|
|
|
|
string extractedText = "Sections: ";
|
|
|
|
|
|
if (_scopedContentService.WebsiteContentModel == null)
|
|
{
|
|
|
|
//have to get from qdrant
|
|
var pointList = await _qDrantService.QueryContentAsync(site.VectorCollectionName, vector, 3);
|
|
|
|
if (pointList.Count > 0)
|
|
{
|
|
for (int i = 0; i < pointList.Count; i++)
|
|
{
|
|
extractedText += pointList[i].Name + ": " + pointList[i].Content + ", ";
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
extractedText = "VECTOR ERROR: ZERO INFORMATION FOUND";
|
|
}
|
|
}
|
|
//cached content
|
|
else
|
|
{
|
|
List<WebPageContent> pagePoints = new List<WebPageContent>();
|
|
//TODO nullcheck... how did we get here,
|
|
foreach (var valami in _scopedContentService.WebsiteContentModel.ContentItems)
|
|
{
|
|
pagePoints.AddRange(valami.VectorPoints);
|
|
}
|
|
var resultContent = _localVectorSearchService.SearchContent(vector, pagePoints, 3);
|
|
|
|
foreach (var item in resultContent)
|
|
{
|
|
extractedText += item.Name + ": " + item.Content + ", ";
|
|
}
|
|
}
|
|
|
|
|
|
_workingContent = extractedText.Replace("\"", "'");
|
|
//Console.Write("\r \n GetJsonResultFromQuery: Working content: " + _workingContent + "\r \n");
|
|
//string extractedText = WordFileReader.ExtractText(rootpath);
|
|
|
|
//Console.Write("GetJSONResult called!");
|
|
string systemMessage = AiPrompts.ContentProcessing.GetSystemMessageForJsonResultDecision(_scopedContentService.SelectedLanguage, extractedText, _scopedContentService.CurrentDOM);
|
|
|
|
string interMediateResult = string.Empty;
|
|
if (!UseWebsocket)
|
|
{
|
|
AiProvider = GetAiSettings();
|
|
if (AiProvider == "cerebras")
|
|
{
|
|
interMediateResult = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, userPrompt);
|
|
}
|
|
else if (AiProvider == "chatgpt")
|
|
{
|
|
interMediateResult = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userPrompt);
|
|
|
|
}
|
|
else if (AiProvider == "deepseek")
|
|
{
|
|
interMediateResult = await _deepSeekApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userPrompt);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
interMediateResult = await _openAIRealtimeService.GetFullChatGPTResponseAsync(sessionId, systemMessage, userPrompt);
|
|
}
|
|
|
|
//Console.Write("\r \n GetJsonResultFromQuery: Result decision: " + interMediateResult + "\r \n");
|
|
return interMediateResult;
|
|
}
|
|
|
|
public async Task<List<HtmlSnippet>> GetSnippetsForDisplay(string sessionId, string collectionName)
|
|
{
|
|
await _logger.InfoAsync($"GetSnippetsForDisplay: method called", $"SessionId: {sessionId}, collectionName: {collectionName}");
|
|
_apiKey = GetApiKey();
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Looking up the UI template elements for you");
|
|
//string availableSnippetList = "";
|
|
|
|
List<HtmlSnippet> snippets = new List<HtmlSnippet>();
|
|
|
|
var snippetscount = await _qDrantService.GetCollectionCount(collectionName);
|
|
for (int j = 1; j <= snippetscount; j++)
|
|
{
|
|
var snippet = await _qDrantService.GetSnippetAsync(j, collectionName);
|
|
QDrantGetPointResult x = JsonConvert.DeserializeObject<QDrantGetPointResult>(snippet);
|
|
|
|
snippets.Add(new HtmlSnippet
|
|
{
|
|
Id = x.result.payload.Id,
|
|
Name = x.result.payload.Name,
|
|
Description = x.result.payload.Description,
|
|
Type = x.result.payload.Type,
|
|
Variant = x.result.payload.Variant,
|
|
Tags = x.result.payload.Tags,
|
|
Slots = x.result.payload.Slots,
|
|
Html = x.result.payload.Html,
|
|
SampleHtml = x.result.payload.SampleHtml,
|
|
Vectors = x.result.GetFloatVector()
|
|
});
|
|
//availableSnippetList += ("- " + x.result.payload.Name + ": " + x.result.payload.Description + ".\r\n");
|
|
}
|
|
////Console.Write(availableSnippetList);
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Loading UI elements from the design database");
|
|
|
|
return snippets;
|
|
|
|
}
|
|
|
|
//for textresult and errorresult
|
|
public async Task DisplayHtml(string sessionId, string pageTitle, LayoutPlan layoutPlan, List<HtmlSnippet> htmlToUse, string[]? topics = null, Dictionary<string, string>? photos = null)
|
|
{
|
|
await _logger.InfoAsync($"DisplayHtml: method called", $"SessionId: {sessionId}, pageTitle: {pageTitle}, layoutPlan: {layoutPlan.Blocks.Count}, htmlToUse: {htmlToUse.Count}, topics: {topics?.Length}, photos: {photos?.Count}");
|
|
//Console.Write($"\n SessionId: {sessionId} \n");
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
|
|
|
|
//Console.WriteLine($"DISPLAYHTML Snippets: {htmlToUse.Count}\n\n");
|
|
//Console.WriteLine($"DISPLAYHTML Topics: {topics} \n\n");
|
|
|
|
string systemMessage = AiPrompts.HtmlRendering.GetHtmlRenderingSystemPromptForTextAndErrorResult(_scopedContentService.SelectedLanguage, pageTitle, htmlToUse, photos, topics);
|
|
//string userMessage = AiPrompts.HtmlRendering.HtmlRenderingUserPromptForTextAndErrorResult;
|
|
//string assistantMessage = AiPrompts.HtmlRendering.GetHtmlRenderingAssistantMessageForTextAndErrorResult(layoutPlan);
|
|
string userMessage = AiPrompts.HtmlRendering.HtmlRenderingUserPromptForTextAndErrorResult + AiPrompts.HtmlRendering.GetHtmlRenderingAssistantMessageForTextAndErrorResult(layoutPlan);
|
|
|
|
//string assistantMessage = "`Provided layout plan, that contains the text to be displayed as HTML`:";
|
|
|
|
//foreach (var block in layoutPlan.Blocks)
|
|
//{
|
|
// assistantMessage += $"Block type: {block.Type}, block content: {block.RawContent}";
|
|
//}
|
|
//int mode = -1;
|
|
|
|
if (!UseWebsocket)
|
|
{
|
|
AiProvider = GetAiSettings();
|
|
if (AiProvider == "cerebras")
|
|
{
|
|
//await _cerebrasAPIService.GetCerebrasStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
var result = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, userMessage, "", -1);
|
|
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
|
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
|
}
|
|
else if (AiProvider == "chatgpt")
|
|
{
|
|
//await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userMessage, "", -1);
|
|
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
|
OnContentReceived?.Invoke(sessionId, fixedForColons, null);
|
|
}
|
|
else if (AiProvider == "deepseek")
|
|
{
|
|
//await _deepSeekApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
}
|
|
|
|
|
|
//await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
|
|
}
|
|
else
|
|
{
|
|
await _openAIRealtimeService.GetChatGPTResponseAsync(sessionId, systemMessage, userMessage, "");
|
|
}
|
|
}
|
|
|
|
public async Task DisplayHtmlForMenu(string sessionId, MenuItem requestedMenu, LayoutPlan layoutPlan, List<HtmlSnippet> htmlToUse, string[]? topics = null, Dictionary<string, string>? photos = null)
|
|
{
|
|
await _logger.InfoAsync($"DisplayHtmlForMenu: method called", $"SessionId: {sessionId}, requestedMenu: {requestedMenu.Name}, layoutPlan: {layoutPlan.Blocks.Count}, htmlToUse: {htmlToUse.Count}, topics: {topics?.Length}, photos: {photos?.Count}");
|
|
//Console.Write($"\n SessionId: {sessionId} \n");
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
|
|
|
|
//Console.WriteLine($"DISPLAYHTML Snippets: {htmlToUse.Count}\n\n");
|
|
//Console.WriteLine($"DISPLAYHTML Topics: {topics} \n\n");
|
|
|
|
string systemMessage = AiPrompts.HtmlRendering.GetHtmlRenderingSystemPromptForTextAndErrorResult(_scopedContentService.SelectedLanguage, requestedMenu.Name, htmlToUse, photos, topics);
|
|
string userMessage = AiPrompts.HtmlRendering.HtmlRenderingUserPromptForTextAndErrorResult;
|
|
string assistantMessage = AiPrompts.HtmlRendering.GetHtmlRenderingAssistantMessageForTextAndErrorResult(layoutPlan);
|
|
|
|
//string assistantMessage = "`Provided layout plan, that contains the text to be displayed as HTML`:";
|
|
|
|
//foreach (var block in layoutPlan.Blocks)
|
|
//{
|
|
// assistantMessage += $"Block type: {block.Type}, block content: {block.RawContent}";
|
|
//}
|
|
//int mode = -1;
|
|
|
|
if (!UseWebsocket)
|
|
{
|
|
AiProvider = GetAiSettings();
|
|
if (AiProvider == "cerebras")
|
|
{
|
|
//await _cerebrasAPIService.GetCerebrasStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
var result = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
|
OnContentReceived?.Invoke(sessionId, fixedForColons, requestedMenu);
|
|
}
|
|
else if (AiProvider == "chatgpt")
|
|
{
|
|
//await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
var result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
var fixedForColons = TextHelper.FixJsonWithoutAI(result);
|
|
OnContentReceived?.Invoke(sessionId, fixedForColons, requestedMenu);
|
|
}
|
|
else if (AiProvider == "deepseek")
|
|
{
|
|
//await _deepSeekApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
}
|
|
|
|
|
|
//await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
|
|
}
|
|
else
|
|
{
|
|
await _openAIRealtimeService.GetChatGPTResponseAsync(sessionId, systemMessage, userMessage + assistantMessage);
|
|
}
|
|
}
|
|
|
|
|
|
public async Task<LayoutPlan?> DisplayLayoutPlanFromContent(string sessionId, string pageTitle, string interMediateResult, List<HtmlSnippet> htmlToUse, string[]? topics = null, Dictionary<string, string>? photos = null)
|
|
{
|
|
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: method called for methodResult", sessionId.ToString());
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Planning layout based on the provided content");
|
|
|
|
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: starting text", $"{interMediateResult}");
|
|
|
|
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: starting snippets count", htmlToUse.Count.ToString());
|
|
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: starting photos count", photos.Count.ToString());
|
|
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: starting topics count", topics.Length.ToString());
|
|
|
|
|
|
string systemMessage = AiPrompts.LayoutPlanning.GetLayoutPlanningSystemPrompt(htmlToUse, photos, topics);
|
|
string userMessage = AiPrompts.LayoutPlanning.GetLayoutPlanningUserPrompt(interMediateResult, pageTitle, photos);
|
|
|
|
string assistantMessage = "";
|
|
|
|
//Console.WriteLine($"LAYOUTBUILDER PROMPT: {systemMessage}\n\n");
|
|
|
|
// 🛡 Retry logic
|
|
string aiResponse = string.Empty;
|
|
LayoutPlan? layoutPlan = null;
|
|
int retry = 0;
|
|
|
|
AiProvider = GetAiSettings();
|
|
|
|
while (layoutPlan == null && retry < 2)
|
|
{
|
|
if (AiProvider == "chatgpt")
|
|
{
|
|
aiResponse = await _openAIApiService.GetSimpleChatGPTResponse(systemMessage, userMessage, assistantMessage);
|
|
}
|
|
else if (AiProvider == "cerebras")
|
|
{
|
|
aiResponse = await _cerebrasAPIService.GetSimpleCerebrasResponse(systemMessage, userMessage, assistantMessage);
|
|
}
|
|
else if (AiProvider == "deepseek")
|
|
{
|
|
aiResponse = await _deepSeekApiService.GetSimpleChatGPTResponse(systemMessage, userMessage, assistantMessage);
|
|
}
|
|
else
|
|
{
|
|
aiResponse = await _openAIRealtimeService.GetFullChatGPTResponseAsync(sessionId, systemMessage, userMessage, null);
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(aiResponse))
|
|
{
|
|
//Console.WriteLine("Empty response from AI.");
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
//Console.WriteLine("AI Response:");
|
|
//Console.WriteLine(aiResponse);
|
|
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: ai Response", $"{aiResponse}");
|
|
aiResponse = TextHelper.FixJsonWithoutAI(aiResponse);
|
|
aiResponse = TextHelper.RemoveTabs(aiResponse);
|
|
layoutPlan = System.Text.Json.JsonSerializer.Deserialize<LayoutPlan>(aiResponse, new JsonSerializerOptions
|
|
{
|
|
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)))
|
|
{
|
|
//try to fix with AI)
|
|
await _logger.WarnAsync($"DisplayLayoutPlanFromContent: trying to fix with AI", $"{aiResponse}");
|
|
layoutPlan = await ValidateAndFixJson<LayoutPlan>(aiResponse, FixJsonWithAI);
|
|
if (layoutPlan?.Blocks == null || layoutPlan.Blocks.Any(b => string.IsNullOrEmpty(b.Type) || string.IsNullOrEmpty(b.RawContent)))
|
|
{
|
|
|
|
await _logger.ErrorAsync($"DisplayLayoutPlanFromContent: Invalid block structure in response.", $"{layoutPlan.Blocks.Count}");
|
|
layoutPlan = null;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await _logger.ErrorAsync($"DisplayLayoutPlanFromContent: ai Response", ex.Message);
|
|
layoutPlan = null;
|
|
}
|
|
|
|
if (layoutPlan == null)
|
|
{
|
|
retry++;
|
|
await _logger.WarnAsync($"DisplayLayoutPlanFromContent: Retrying due to invalid format...", $"{aiResponse}");
|
|
|
|
}
|
|
}
|
|
foreach (var block in layoutPlan.Blocks)
|
|
{
|
|
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: reading blocks", $"{block.Order}, {block.Type}, {block.PreferredSnippetId}");
|
|
Console.WriteLine($"{block.Order}, {block.Type}, {block.PreferredSnippetId}");
|
|
|
|
if (block.ContentMap != null && block.ContentMap.Count > 0)
|
|
{
|
|
foreach (var key in block.ContentMap.Keys)
|
|
{
|
|
await _logger.InfoAsync($"DisplayLayoutPlanFromContent: reading ContentMap", $"key {key} : value: {block.ContentMap[key]}");
|
|
//Console.WriteLine($"key {key} : value: {block.ContentMap[key]}");
|
|
}
|
|
}
|
|
|
|
}
|
|
return layoutPlan;
|
|
}
|
|
|
|
|
|
|
|
//for methodResult
|
|
public async Task DisplayHtml(string sessionId, string interMediateResult, string methodToCall = "", string methodParameter = "")//, string[]? topics = null)
|
|
{
|
|
await _logger.InfoAsync($"DisplayHtml: method called for methodResult", sessionId.ToString());
|
|
//Console.Write($"\n SessionId: {sessionId} \n");
|
|
OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
|
|
|
|
////Console.WriteLine($"DISPLAYHTML Text: {interMediateResult}\n\n");
|
|
////Console.WriteLine($"DISPLAYHTML Topics: {topics} \n\n");
|
|
|
|
string systemMessage = AiPrompts.HtmlRendering.GetHtmlRenderingSystemPromptForMethodResult(_scopedContentService.SelectedLanguage, methodToCall, methodParameter);
|
|
string userMessage = "Create a perfect, production ready, structured, responsive Bootstrap 5 webpage content from the provided text, please.";
|
|
string assistantMessage = "`Provided text to display as HTML`: `" + interMediateResult + "`";
|
|
//int mode = -1;
|
|
|
|
if (!UseWebsocket)
|
|
{
|
|
AiProvider = GetAiSettings();
|
|
if (AiProvider == "cerebras")
|
|
{
|
|
await _logger.InfoAsync($"DisplayHtml: streamed response", sessionId.ToString());
|
|
await _cerebrasAPIService.GetCerebrasStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
}
|
|
else if (AiProvider == "chatgpt")
|
|
{
|
|
await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
|
|
}
|
|
else if (AiProvider == "deepseek")
|
|
{
|
|
//await _deepSeekApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
}
|
|
|
|
|
|
//await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage, assistantMessage, -1);
|
|
|
|
}
|
|
else
|
|
{
|
|
await _openAIRealtimeService.GetChatGPTResponseAsync(sessionId, systemMessage, userMessage + assistantMessage);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|