SeemGen/Services/AIService.cs

1082 lines
57 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
namespace BLAIzor.Services
{
public class AIService
{
private readonly HttpClient _httpClient;
private readonly ContentService _contentService;
private readonly ScopedContentService _scopedContentService;
private readonly OpenAIEmbeddingService _openAIEmbeddingService;
private readonly OpenAIApiService _openAIApiService;
private readonly OpenAiRealtimeService _openAIRealtimeService;
private readonly DeepSeekApiService _deepSeekApiService;
private readonly CerebrasAPIService _cerebrasAPIService;
private readonly QDrantService _qDrantService;
private readonly NavigationManager _navigationManager;
public static IConfiguration? _configuration;
public AIService(HttpClient httpClient, ContentService contentService, ScopedContentService scopedContentService, QDrantService qDrantService, OpenAIEmbeddingService openAIEmbeddingService, OpenAIApiService openAIApiService, DeepSeekApiService deepSeekApiService, OpenAiRealtimeService openAIRealtimeService, CerebrasAPIService cerebrasAPIService, NavigationManager navigationManager, IConfiguration? configuration)
{
_httpClient = httpClient;
_contentService = contentService;
_scopedContentService = scopedContentService;
_qDrantService = qDrantService;
_openAIEmbeddingService = openAIEmbeddingService;
_openAIApiService = openAIApiService;
_deepSeekApiService = deepSeekApiService;
_openAIRealtimeService = openAIRealtimeService;
_cerebrasAPIService = cerebrasAPIService;
_navigationManager = navigationManager;
_configuration = configuration;
_openAIApiService.RegisterCallback(HandleActionInvoked, HandleFinishedInvoked, HandleErrorInvoked);
_cerebrasAPIService.RegisterCallback(HandleActionInvoked, HandleFinishedInvoked, HandleErrorInvoked);
_openAIRealtimeService.RegisterCallback(HandleActionInvoked);
}
private const string OpenAiEndpoint = "https://api.openai.com/v1/chat/completions";
public string _apiKey;
public static event Action<string, string>? OnContentReceived;
public static event Action<string>? OnContentReceiveFinished;
public static event Action<string, string>? OnContentReceivedError;
public static event Action<string, string>? OnStatusChangeReceived;
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 void HandleActionInvoked(string sessionId, string streamedHtmlContent)
{
OnContentReceived?.Invoke(sessionId, streamedHtmlContent);
}
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 GetChatGptWelcomeMessage(string sessionId, int SiteId, string menuList = "")
{
string currentUri = _navigationManager.Uri;
Console.Write($"\n\n SessionId: {sessionId}\n\n");
//string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _contentService.SelectedDocument);
_apiKey = GetApiKey();
string qdrantPoint = await _qDrantService.GetContentAsync(SiteId, 0);
string extractedText = "";
//TODO: this is the full object, should get the text from it, it sends the vectors too at the moment
var selectedPoint = JsonConvert.DeserializeObject<QDrantGetContentPointResult>(qdrantPoint)!;
if (selectedPoint != null)
{
extractedText = selectedPoint.result.payload.content;
//Console.Write($"\n -------------------------------- Found point: {selectedPoint.result.payload.content} \n");
Console.Write($"\n -------------------------------- Found point: {selectedPoint.result.id} \n");
}
string systemMessage = "You are a helpful, " + Mood + " assistant that welcomes the user in the name of the brand or person described by the content, on a website of " + _scopedContentService.SelectedBrandName + " in " + _scopedContentService.SelectedLanguage + ". Use the following content: `" +
extractedText + "` " +
//"and generate a short" +Mood+ "but kind marketing-oriented welcome message and introduction of the brand for the user, constructed as simple Bootstrap HTML codeblock with a <h1> tagged title and a paragraph." +
"and generate a" + Mood + " marketing-oriented welcome message and a summary of the content and introduction of the brand for the user, aiming to explain clearly, what does the company/person offer, constructed as simple Bootstrap HTML codeblock with a <h1> tagged title and a paragraph." +
"If there is any logo, or not logo but main brand image in the document use that url, and add that as a bootstrap responsive ('img-fluid py-3') image, with the maximum height of 30vh." +
"Here is a list of topics " + menuList +
", make a new bootstrap clearfix and after that make a clickable bootstrap styled (btn btn-primary) button from each of the determined topics, " +
"that calls the javascript function 'callAI({the name of the topic})' on click. " +
"Do not include anything else than the html title and text elements, no css, no scripts, no head or other tags." +
"Do not mark your answer with ```html or any other mark.";
string userMessage = "Hello";
string streamedHtmlContent = string.Empty;
if (!UseWebsocket)
{
AiProvider = GetAiSettings();
if (AiProvider == "cerebras")
{
await _cerebrasAPIService.GetCerebrasStreamedResponse(sessionId, systemMessage, userMessage);
}
else if (AiProvider == "chatgpt")
{
await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMessage, userMessage);
}
else if (AiProvider == "deepseek")
{
//await _deepSeekApiService.GetChatGPTStreamedResponse(systemMessage, userMessage);
}
}
else
{
await _openAIRealtimeService.GetChatGPTResponseAsync(sessionId, systemMessage, userMessage);
}
//_scopedContentService.CurrentDOM = streamedHtmlContent;
//Console.Write("Answer: " + streamedHtmlContent);
//return streamedHtmlContent;
}
public async Task ProcessUserIntent(string sessionId, string userPrompt, int siteId, int templateId, string collectionName, string menuList = "")
{
Console.WriteLine($"SITE ID: {siteId}");
OnStatusChangeReceived?.Invoke(sessionId, "Understanding your request...");
// Get JSON result based on siteId presence
string resultJson = siteId >= 0
? await GetJsonResultFromQuery(sessionId, siteId, userPrompt)
: await GetJsonResultFromQuery(sessionId, 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;
}
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, resultJson, templateId, collectionName);
break;
case "examinationresult":
await ProcessExaminationResult(sessionId, resultJson, templateId, collectionName);
break;
case "errorresult":
await ProcessErrorResult(sessionId, resultJson);
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, string requestedMenuName, int siteId, int templateId, string collectionName, 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);
float[] vector = [];
OnStatusChangeReceived?.Invoke(sessionId, "Determining search vectors");
vector = await _openAIEmbeddingService.GenerateEmbeddingAsync(requestedMenuName);
OnStatusChangeReceived?.Invoke(sessionId, "Looking up content in the knowledge database");
var pointId = await _qDrantService.QueryContentAsync(siteId, vector, 1);
string extractedText = "";
if (pointId.Length > 0)
{
foreach (var item in pointId)
{
string qDrantData = await _qDrantService.GetContentAsync(siteId, item);
QDrantGetContentPointResult _selectedPoint = new QDrantGetContentPointResult();
if (qDrantData != null)
{
_selectedPoint = JsonConvert.DeserializeObject<QDrantGetContentPointResult>(qDrantData)!;
}
extractedText += _selectedPoint.result.payload.name + ": " + _selectedPoint.result.payload.content + ", ";
}
}
else
{
extractedText = "VECTOR ERROR: ZERO INFORMATION FOUND";
}
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, contentJson, templateId, collectionName);
}
// Refactored helper methods
private async Task ProcessMethodResult(string sessionId, string resultJson)
{
var fixedResult = await ValidateAndFixJson<ChatGPTMethodResult>(resultJson, FixJsonWithAI);
//var methodResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTMethodResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (fixedResult != null)
{
OnStatusChangeReceived?.Invoke(sessionId, "Initiating the task you requested");
await DisplayHtml(sessionId, fixedResult.Text, fixedResult.MethodToCall, fixedResult.Parameter, "");
}
}
private async Task ProcessTextResult(string sessionId, string resultJson, int templateId, string collectionName)
{
var fixedResult = await ValidateAndFixJson<ChatGPTTextResult>(resultJson, FixJsonWithAI);
//var textResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTTextResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (fixedResult != null)
{
string contentJson = await GetContentFromQuery(sessionId, fixedResult.Text, _workingContent);
Console.Write("\r \n ProcessTextResult: Content: " + contentJson + "\r \n");
await ProcessContent(sessionId, contentJson, templateId, collectionName);
}
}
public async Task<T?> ValidateAndFixJson<T>(string json, Func<string, Task<string>> aiFixer)
{
try
{
return System.Text.Json.JsonSerializer.Deserialize<T>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
}
catch (Exception ex)
{
Console.WriteLine($"❌ JSON parse failed: {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)
{
Console.WriteLine($"❌ 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 ""; }
//return await _openAIApiService.GetSimpleChatGPTResponseNoSession("You are a JSON-fixing assistant.", prompt);
}
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)
{
var fixedResult = await ValidateAndFixJson<ChatGPTExaminationResult>(resultJson, FixJsonWithAI);
//var explanationResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTExaminationResult>(resultJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (fixedResult != null)
{
string contentJson = await GetExplanationFromQuery(sessionId, fixedResult.Text, _scopedContentService.CurrentDOM);
await ProcessContent(sessionId, contentJson, templateId, collectionName);
}
}
private async Task ProcessContent(string sessionId, string contentJson, int templateId, string collectionName)
{
try
{
var fixedResult = await ValidateAndFixJson<ChatGPTContentResult>(contentJson, FixJsonWithAI);
//var contentResult = 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");
string snippets = await GetSnippetsForDisplay(sessionId, templateId, fixedResult.Text, collectionName);
await DisplayHtml(sessionId, fixedResult.Text, "", "", snippets, fixedResult.Topics, fixedResult.Photos);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error processing content: {ex.Message}");
OnContentReceived?.Invoke(sessionId, ex.Message);
}
}
private async Task ProcessErrorResult(string sessionId, string resultJson)
{
var errorResult = System.Text.Json.JsonSerializer.Deserialize<ChatGPTErrorResult>(resultJson);
if (errorResult != null)
{
Console.WriteLine($"Error Result: {errorResult.Text}");
await DisplayHtml(sessionId, errorResult.Text, "", "", "");
}
}
/// <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)
{
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. 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: " +
"- Turn the following content into a nice informative webpage content. " +
"- Structure it nicely without leaving out any information. " +
"*** 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.";
}
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, but not lengthy 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 provide a clarification. " +
//"- 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;
}
/// <summary>
/// What does the user want? Answer or action?
/// </summary>
/// <param name="userPrompt"></param>
/// <returns></returns>
public async Task<string> GetJsonResultFromQuery(string sessionId, string userPrompt)
{
string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _scopedContentService.SelectedDocument);
_apiKey = GetApiKey();
string extractedText = WordFileReader.ExtractText(rootpath);
Console.Write("GetJSONResult called!");
var systemMessage = "You are a helpful assistant. Respond strictly in " + _scopedContentService.SelectedLanguage + " as a JSON object in the following formats:\r\n\r\n1. " +
"1. MethodResult:\r\n " +
"- `type`: A string with value `methodresult`.\r\n " +
"- `text`: A string explaining the result.\r\n " +
"- `methodToCall`: One of these values: " +
"[openContactForm, openCalendar, openApplicationForm].\r\n" +
"- `parameter`: One of these: \r\n" +
"[email address for openContactForm, calendlyUserName for openCalendar, empty string for openApplicationForm]" +
"2. TextResult:\r\n " +
"- `type`: A string with value `textresult`.\r\n " +
"- `text`: Contains the user query without any modification.\r\n " +
"3. ExaminationResult:\r\n " +
"- `type`: A string with value `examinationresult`.\r\n " +
"- `text`: Contains the user query without any modification.\r\n " +
"4. ErrorResult:\r\n " +
"- `type`: A string with value `errorresult`. \r\n " +
"- `text`: The description of the problem you found. " +
"**Document-Specific Instructions**:\r\n- Base responses solely on the following initial document: {" + extractedText + "}.\r\n" +
"**Rules for Decision Making**:\r\n" +
"- If the users input indicates a method invocation, and you find the relevant parameter in the initial document, generate a `methodresult`.\r\n" +
" In the explanation, put a short sentence about what the user has requested by your understanding. \r\n" +
"- If the user asks about the current content displayed for them, generate an examinationResult. \r\n" +
"- If you don't find the relevant parameter in the initial document, generate an errorResult. \r\n" +
//"- If the user asks for contact form but the initial document doesn't contain contact email, generate an errorResult. \r\n"+
"- Otherwise, create a `textresult` with the unmoddified user query.\r\n\r\n" +
"Do NOT mark your answer with anything like `````json or such.";
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("Result decision: " + interMediateResult);
return interMediateResult;
}
public async Task<string> GetJsonResultFromQuery(string sessionId, int siteId, string userPrompt)
{
//string rootpath = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot/Documents/" + _contentService.SelectedDocument);
//_apiKey = GetApiKey();
//start with embeddings
float[] vector = [];
OnStatusChangeReceived?.Invoke(sessionId, "Determining search vectors");
vector = await _openAIEmbeddingService.GenerateEmbeddingAsync(userPrompt);
OnStatusChangeReceived?.Invoke(sessionId, "Looking up content in the knowledge database");
var pointId = await _qDrantService.QueryContentAsync(siteId, vector, 3);
string extractedText = "Sections: ";
if (pointId.Length > 0)
{
foreach (var item in pointId)
{
string qDrantData = await _qDrantService.GetContentAsync(siteId, item);
QDrantGetContentPointResult selectedPoint = new QDrantGetContentPointResult();
if (qDrantData != null)
{
selectedPoint = JsonConvert.DeserializeObject<QDrantGetContentPointResult>(qDrantData)!;
}
extractedText += selectedPoint.result.payload.name + ": " + selectedPoint.result.payload.content + ", ";
}
}
else
{
extractedText = "VECTOR ERROR: ZERO INFORMATION FOUND";
}
_workingContent = extractedText;
Console.Write("\r \n GetJsonResultFromQuery: Working content: " + _workingContent + "\r \n");
//string extractedText = WordFileReader.ExtractText(rootpath);
Console.Write("GetJSONResult called!");
string systemMessage = $"You are a helpful assistant built in a website, trying to figure out what the User wants to do or know about.\r\n" +
"Your job is to classify the user's request into one of the following categories:\r\n" +
"1. **Browse the websites content** (Return a 'Text result')\r\n" +
"2. **Analyze the currently displayed HTML content** (Return an 'Examination result')\r\n" +
"3. **Initiate an action** (Return a 'Method result')\r\n" +
"If none of the above applies, return an 'Error result'.\r\n\r\n" +
"**Response format:**\r\n" +
"Strictly respond in " + _scopedContentService.SelectedLanguage + " as a JSON object, using one of the following formats:\r\n" +
"1. **chatGPTMethodResult** (for initiating actions):\r\n" +
" - `type`: \"methodresult\"\r\n" +
" - `text`: A short explanation of what the user wants to do.\r\n" +
" - `methodToCall`: One of: [openContactForm, openCalendar, openApplicationForm]\r\n" +
" - `parameter`: [email address for openContactForm, calendlyUserName for openCalendar, empty string for openApplicationForm]\r\n\r\n" +
"2. **chatGPTTextResult** (for general website content searches):\r\n" +
" - `type`: \"textresult\"\r\n" +
" - `text`: The users unmodified query.\r\n\r\n" +
"3. **chatGPTExaminationResult** (for analyzing the currently displayed page only):\r\n" +
" - `type`: \"examinationresult\"\r\n" +
" - `text`: The users unmodified query.\r\n\r\n" +
"4. **chatGPTErrorResult** (for errors):\r\n" +
" - `type`: \"errorresult\"\r\n" +
" - `text`: A description of the issue encountered.\r\n\r\n" +
"**Decision Rules:**\r\n" +
"- If the user is **searching for website content** beyond what is currently displayed (e.g., 'Find information about our services'), return a `textresult`.\r\n" +
"- If the user is **asking about the currently visible content** (e.g., 'What is shown on the page?'), return an `examinationresult`.\r\n" +
"- If the user wants to **perform an action**, return a `methodresult`.\r\n" +
"- If the required parameter is missing, return an `errorresult`.\r\n\r\n" +
"**Examples:**\r\n" +
"- User asks: 'Show me information about pricing' → `textresult`\r\n" +
"- User asks: 'What is displayed right now?' → `examinationresult`\r\n" +
"- User asks: 'Open the contact form' → `methodresult`\r\n" +
"- User asks: 'Contact support' but no email is found → `errorresult`\r\n\r\n" +
"**Context:**\r\n" +
"- Base responses on this initial document: {" + extractedText + "}\r\n" +
"- Current displayed HTML: {" + _scopedContentService.CurrentDOM + "}\r\n" +
"**IMPORTANT:**\r\n" +
"- If the request is about general content, **DO NOT use 'examinationresult'**.\r\n" +
"- If the request is about the currently displayed page, **DO NOT use 'textresult'**.\r\n" +
"- Do NOT format the response with markdown, code blocks, or `json` tags, do not add any title, or explanation besides the plain json object";
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<string> GetSnippetsForDisplay(string sessionId, int templateId, string interMediateResult, string collectionName)
{
_apiKey = GetApiKey();
OnStatusChangeReceived?.Invoke(sessionId, "Looking up the UI template elements for you");
string availableSnippetList = "";
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);
availableSnippetList += ("- " + x.result.payload.Name + ": " + x.result.payload.Description + ".\r\n");
}
Console.Write(availableSnippetList);
OnStatusChangeReceived?.Invoke(sessionId, "Fitting content to available UI elements");
var systemMessage = "You are a helpful assistant for generating responses using HTML templates. Analyze the user query and choose the most suitable snippet type(s) for rendering the response." +
"Respond with only the snippet name(s), comma-separated. No explanation. Identify the most suitable " +
"HTML code snippet type to use for rendering your response based on user queries. The available snippet types are: " +
availableSnippetList +
//"The content to answer from from is here: " + extractedText + ". " +
"If multiple snippets apply, list them in order of priority. ";
var userMessage = "How would you render the following text in html: " + interMediateResult + " ? ";
string result = string.Empty;
if (!UseWebsocket)
{
AiProvider = GetAiSettings();
if (AiProvider == "cerebras")
{
result = await _cerebrasAPIService.GetSimpleCerebrasResponse(sessionId, systemMessage, userMessage);
}
else if (AiProvider == "chatgpt")
{
result = await _openAIApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userMessage);
}
else if (AiProvider == "deepseek")
{
result = await _deepSeekApiService.GetSimpleChatGPTResponse(sessionId, systemMessage, userMessage);
}
}
else
{
result = await _openAIRealtimeService.GetFullChatGPTResponseAsync(sessionId, systemMessage, userMessage);
}
//Console.Write(result);
List<string> snippetList = result.Split(new[] { ',' }).ToList();
int i = 0;
OnStatusChangeReceived?.Invoke(sessionId, "Oooh got it! Loading UI elements from the design database");
//Console.Write("ChatGPT decided!!!! ");
//let's send result to embeddingservice
List<float[]> vectorsList = new List<float[]>();
foreach (var snippet in snippetList)
{
var vectors = await _openAIEmbeddingService.GenerateEmbeddingAsync(result);
vectorsList.Add(vectors);
}
List<int> pointIds = new List<int>();
var collectionCount = await _qDrantService.GetCollectionCount(collectionName);
if (collectionCount > 0)
{
foreach (var vector in vectorsList)
{
var qDrantresult = await _qDrantService.QuerySnippetAsync(vector, 1, collectionName);
pointIds.Add(qDrantresult);
}
List<string> qDrantDataList = new List<string>();
foreach (var pointId in pointIds)
{
var qDrantData = await _qDrantService.GetSnippetAsync(pointId, collectionName);
qDrantDataList.Add(qDrantData);
}
List<QDrantGetPointResult> selectedPointList = new List<QDrantGetPointResult>();
if (qDrantDataList != null)
{
foreach (var x in qDrantDataList)
{
var selectedPoint = JsonConvert.DeserializeObject<QDrantGetPointResult>(x)!;
selectedPointList.Add(selectedPoint);
}
}
string htmlToUse = "Your snippets to use: ";
foreach (var selectedPoint in selectedPointList)
{
htmlToUse += selectedPoint.result.payload.Name + ": " + selectedPoint.result.payload.Html + ", ";
}
//Console.Write(htmlToUse);
return htmlToUse;
}
else return "No snippets found, use bootstrap html components";
}
//public async Task DisplayHtmlOld(string sessionId, string interMediateResult, string methodToCall = "", string methodParameter = "", string htmlToUse = "", string[]? topics = null, Dictionary<string, string>? photos = null)
//{
// Console.Write($"\n SessionId: {sessionId} \n");
// OnStatusChangeReceived?.Invoke(sessionId, "Casting spells to draw customized UI");
// _apiKey = GetApiKey();
// StringBuilder lst = new StringBuilder("You are a helpful assistant generating HTML responses in " + _scopedContentService.SelectedLanguage + " using Bootstrap. " +
// "Rules to follow:" +
// "- Based on the user's query and retrieved content, generate HTML that fits into a Bootstrap container. \r\n" +
// "- Begin with an `<h1>` tag styled with the 'p-3' class, and include a title based on the user's query. \r\n" +
// "- Use separate div element with `row` class for different logical sections." +
// "- Do not include elements outside the container (e.g., `<head>`, `<body>`, or `<script>` tags). ");
// if (!string.IsNullOrEmpty(methodToCall))
// {
// lst.AppendLine("Create a single clickable Bootstrap button (`btn btn-primary`) that calls the JavaScript function `" + methodToCall + "` with '" + methodParameter + "' on click. " +
// "Place this button at the end of the generated HTML. " +
// "Do NOT mark your answer with anything like `````html or such.");
// }
// else
// {
// if (!string.IsNullOrEmpty(htmlToUse))
// {
// lst.AppendLine("Use the following snippets when applicable: \n \n " + htmlToUse + ". \n \n For other content, use appropriate Bootstrap elements. " +
// "Structure the content using Bootstrap layouts: " +
// //"If there is a `styles to be applied` section in the content, include it as a `<style>` tag at the beginning of the HTML. " +
// " - Use 'row justify-content-center' for layout, 'col-xs-12 col-sm-6 col-md-3 text-center' for product cards, and 'img-fluid' for images. \r\n" +
// " - Separate logical sections using Bootstrap utilities like `clearfix`. Avoid unrelated elements and the use of the `d-grid` class inside `col` elements. \r\n" +
// " - DO NOT use d-grid class. Ever. \r\n" +
// "If you render a button you have two options: \r\n" +
// "1. Render a button for a url you found in the text\r\n" +
// "2. Any other button, which is not based on a url found in the content should call the JavaScript function `callAI({topic})` on click. \r\n" +
// "Do NOT generate fake image urls if there is no image url provided. \r\n");
// }
// if (photos != null)
// {
// lst.AppendLine("- Include related photo URLs from the following list where applicable without modifying the urls. " +
// "Photo urls: " + string.Join(", ", photos.Select(kv => $"{kv.Key}: {kv.Value}")) + ". \r\n" +
// "DO NOT modifiy the photo urls in any way.");
// }
// lst.AppendLine("- NEVER render image elements if you didn't find any image url in the `photo list`" +
// "- Maintain the original text content; do not moddify names, or mix information, only convert it into Bootstrap-styled HTML. \r\n");
// if (topics != null && topics.Any())
// {
// lst.AppendLine("- Here are the topics: " + string.Join(", ", topics) + ". Create a button for each topic using the `btn btn-primary` class." +
// "Each button should call the JavaScript function `callAI({topicName})` on click, and these buttons should appear at the end of the generated HTML. \r\n");
// }
// else
// {
// lst.AppendLine("- No topics are provided. Do not generate navigational topic buttons.");
// }
// }
// lst.AppendLine("- Do not add explanations and do not mark your response with ```html or similar syntax.");
// string systemMesage = lst.ToString();
// string userMessage = "Make a nice bootstrap 5 page content from the provided text, please.";
// string assistantMessage = "`Provided text to display as html`: `" + interMediateResult + "`";
// if (!UseWebsocket)
// {
// await _openAIApiService.GetChatGPTStreamedResponse(sessionId, systemMesage, userMessage, assistantMessage);
// }
// else
// {
// await _openAIRealtimeService.GetChatGPTResponseAsync(sessionId, systemMesage, userMessage + assistantMessage);
// }
// //return response;
//}
public async Task DisplayHtml(string sessionId, string interMediateResult, string methodToCall = "", string methodParameter = "", string htmlToUse = "", string[]? topics = null, Dictionary<string, string>? photos = null)
{
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 Snippets: {htmlToUse}\n\n");
Console.WriteLine($"DISPLAYHTML Photos: {photos} \n\n");
Console.WriteLine($"DISPLAYHTML Topics: {topics} \n\n");
StringBuilder lst = new StringBuilder("You are a helpful assistant generating HTML content in " + _scopedContentService.SelectedLanguage + " using Bootstrap. \n\n" +
"### Rules to Follow: \n" +
"- Please generate clean and structured HTML that fits inside a Bootstrap container.\n" +
"- DO NOT include `<head>`, `<body>`, or `<script>` tags—only content inside the Bootstrap container.\n" +
"- Use `<h1 class='p-3'>` for the title, based on the user's query.\n" +
"- Structure content using **separate `row` elements** for different sections.\n" +
"- Ensure clear **separation of content into multiple sections if multiple snippets are provided**.\n");
if (!string.IsNullOrEmpty(methodToCall))
{
lst.AppendLine("- At the END of the content, include a **single** Bootstrap button (`btn btn-primary`) that calls `" + methodToCall + "` with `" + methodParameter + "` on click.\n");
}
else
{
if (!string.IsNullOrEmpty(htmlToUse))
{
lst.AppendLine(
"### Using Provided Snippets:\n" +
"- You have been given **multiple HTML snippets**:\n \n " + htmlToUse + ". \n \n **DO NOT merge them into one**.\n" +
"- Use each snippet **as a separate section** inside its own `div.row`.\n" +
"- Maintain their order and **DO NOT omit any snippet**.\n" +
"- Example structure:\n\n" +
" ```html\n" +
" <div class='container'>\n" +
" <div class='row'>\n" +
" <!-- First snippet -->\n" +
" </div>\n" +
" <div class='row'>\n" +
" <!-- Second snippet -->\n" +
" </div>\n" +
" </div>\n" +
" ```\n\n" +
"- Use Bootstrap classes to ensure proper spacing and alignment.\n" +
" - Use 'row justify-content-center' for layout, 'col-xx-x' classes for columns (IF multiple columns are needed), and always use 'img-fluid' class for images. \r\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())
{
lst.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" +
//" ```html\n" +
" <img src='" + photos.First().Value + "' class='img-fluid' alt='" + photos.First().Key + "' />\n" +
//" ```\n\n" +
"DO NOT modifiy the photo urls in any way."
);
}
if (topics != null && topics.Any())
{
lst.AppendLine(
"### Generating Topic Buttons:\n" +
"Start this section with a title `Related` " +
"- Create a **separate button** for each topic.\n" +
$"- Make sure the topics are in {_scopedContentService.SelectedLanguage}, if not, translate them." +
"- Each button should use `btn btn-primary` and call `callAI('{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
{
lst.AppendLine("- **No topics provided** → **Do NOT generate topic buttons.**");
}
}
lst.AppendLine(
"- DO **NOT** merge different content sections.\n" +
"- DO **NOT** wrap the entire content in a single `div`—use separate `row` divs.\n" +
"- DO **NOT** modify the photo urls in any way." +
"- Do **NOT** generate or assume new image URLs.\n" +
"- If the snippet contains an image, but there is no photo url available, skip the image element." +
"- **Never** add explanations or start with ```html syntax markers.\n");
string systemMessage = lst.ToString();
string userMessage = "Create a perfect, production ready, structured Bootstrap 5 page 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 _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);
}
}
}
}