Files
BeWoPlaner/Service/Plugins/AiService2.cs
2026-01-23 16:28:47 +01:00

373 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IdentityModel.Metadata;
using System.IO;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.Text;
using System.Web.Hosting;
using AICore.Prompt.Core;
using AICore.Prompt.SystemPrompts;
using BeWo.Data.Access;
using BeWo.Data.Entities;
using BeWo.Data.Security;
using BeWo.Service.Core;
using BeWo.Service.DCEntityMapper;
using BeWo.Service.ServiceProxy;
using BeWo.Service.ServiceUtils;
using BS.Shared;
using BS.Shared.Core;
using BS.Shared.DataContracts.Feature.AI;
using BS.Shared.Exceptions;
using BS.Shared.Extensions;
using DevExpress.Charts.Native;
using DevExpress.CodeParser;
using Newtonsoft.Json;
namespace BeWo.Service.Plugins
{
public class AiService2
{
// Was wird alles benötigt:
// - WebConfig
// - DBs
// ======================[ Config ]======================
public virtual AiConfigDC GetAiConfig()
{
var configs = DAOFactory.GenericDAO.GetAllActive<AiConfig>();
var config = configs.FirstOrDefault();
if (config == null)
{
config = getDefaultAiConfig();
DAOFactory.GenericDAO.Insert(config);
}
if (config.SelectedModel is null)
{
config.SelectedModel = getDefaultAiModel();
}
var config_dc = MapperFactory.AiConfig.MapToNewDC(config);
return config_dc;
}
public virtual AiConfigDC UpdateAiConfig(AiConfigDC toUpdate)
{
if (toUpdate?.Oid is null)
throw new InvalidOperationException();
var oid = toUpdate.Oid.Value;
var entity = DAOFactory.GenericDAO.LoadByID<AiConfig>(oid);
var update = MapperFactory.AiConfig.MergeWithEntity(toUpdate, entity);
DAOFactory.GenericDAO.Update(update);
var dc = MapperFactory.AiConfig.MapToNewDC(update);
return dc;
}
// ======================[ Models ]======================
public virtual IEnumerable<AiModelDC> GetAiModels()
{
var models = getAiModels();
return MapperFactory.AiModel.MapToNewDCs(models);
}
// ======================[ Conversations ]======================
public virtual IEnumerable<AiConversationDC> GetAiConversations(AiContextType uicontext, long? oid)
{
// SearchDAO
var current_user = UserRightHelper.GetLoggedInUserWithOid();
var convs = DAOFactory.GenericDAO.GetAllActive<AiConversation>();
var filter = new List<AiConversation>();
foreach (var conv in convs)
{
if (conv.UIContext != uicontext)
continue;
if (conv.ApplicationUserOid != current_user.Oid)
continue;
if (oid is long ref_oid)
{
var main_ref_obj = conv.ContextBeWoObjects?.FirstOrDefault();
// ServiceRecord
if (uicontext == AiContextType.ServiceRecord)
main_ref_obj = conv.ContextBeWoObjects.FirstOrDefault(x => x.Tid == TableID.SupportConcept);
if (main_ref_obj?.Oid != ref_oid)
continue;
}
filter.Add(conv);
}
var dcs = MapperFactory.AiConversation.MapToNewDCs(filter);
return dcs;
}
public virtual AiConversationDC CreateAiConversation(AiConversationDC conversation, long modell_oid)
{
var config = GetAiConfig();
var conv = createAiConversation(conversation.UIContext, conversation.ContextBeWoObjects, modell_oid, config.Context_Type);
DAOFactory.GenericDAO.Insert(conv);
var conv_dc = MapperFactory.AiConversation.MapToNewDC(conv);
return conv_dc;
}
public virtual AiConversationDC CreateAiConversationWithMessage(AiConversationDC conversation, long modell_oid, string message)
{
var config = GetAiConfig();
var conv = createAiConversation(conversation.UIContext, conversation.ContextBeWoObjects, modell_oid, config.Context_Type);
var new_messages = sendNewMessage(conv, message, config);
DAOFactory.GenericDAO.Insert(conv);
var conv_dc = MapperFactory.AiConversation.MapToNewDC(conv);
return conv_dc;
}
public virtual AiConversationDC CloneAiConversation(long conversation_oid, long? last_message_oid)
{
var parent_conv = DAOFactory.GenericDAO.GetByID<AiConversation>(conversation_oid);
if (parent_conv is null)
throw new InvalidOperationException("conv_oid unknown");
var clone_conv = new AiConversation();
clone_conv.Created = DateTime.Now;
clone_conv.ContextBeWoObjects = parent_conv.ContextBeWoObjects.ToList();
clone_conv.Displayname = "Klon von " + parent_conv.Displayname;
clone_conv.Messages = new List<AiConversationMessage>();
clone_conv.Modell = parent_conv.Modell;
clone_conv.UIContext = parent_conv.UIContext;
clone_conv.Updated = DateTime.Now;
clone_conv.ApplicationUserOid = parent_conv.ApplicationUserOid;
if (last_message_oid is null)
last_message_oid = parent_conv.Messages[0].Oid;
foreach (var msg in parent_conv.Messages)
{
var clone_msg = new AiConversationMessage();
clone_msg.Created = msg.Created;
clone_msg.Message = msg.Message;
clone_msg.Role = msg.Role;
clone_msg.Duration = msg.Duration;
clone_msg.ModelName = msg.ModelName;
clone_conv.Messages.Add(clone_msg);
clone_msg.AiConversation = clone_conv;
if (msg.Oid == last_message_oid)
break;
}
DAOFactory.GenericDAO.Insert(clone_conv);
var dc = MapperFactory.AiConversation.MapToNewDC(clone_conv);
return dc;
}
public virtual long UpdateAiConversationDisplayname(long oid, long version, string displayname)
{
var conv = DAOFactory.GenericDAO.GetByID<AiConversation>(oid);
ServiceLogic.ConcurrencyCheck(version, conv);
conv.Displayname = displayname;
DAOFactory.GenericDAO.Update(conv);
return conv.Version.Value;
}
public virtual void DeleteAiConversation(long conversation_oid)
{
var conversation = DAOFactory.GenericDAO.LoadByID<AiConversation>(conversation_oid);
DAOFactory.GenericDAO.SetActivationType(conversation, ActivationTypeId.Deleted);
}
// ======================[ Conversationmessages ]======================
public virtual AiConversationMessageDC[] SendNewMessage(long conversation, string message)
{
var conv = DAOFactory.GenericDAO.LoadByID<AiConversation>(conversation);
return sendNewMessage(conv, message);
}
// ======================[ private ]======================
protected virtual AiConfig getDefaultAiConfig()
{
var config = new AiConfig();
config.Temperature = 1;
config.Top_P = 1;
config.Max_Gen_Len = 1000;
config.SelectedModel = getDefaultAiModel();
return config;
}
protected virtual AiModel getDefaultAiModel()
{
var models = getAiModels();
var model = models.FirstOrDefault(x => x.ModelName.Contains("gemma3") && x.ModelName.Contains("12b") && x.ModelName.Contains("max-context"))
?? models.FirstOrDefault(x => x.ModelName.Contains("gemma3") && x.ModelName.Contains("12b"))
?? models.FirstOrDefault(x => x.ModelName.Contains("gemma3"))
?? models.FirstOrDefault(x => x.ModelName.Contains("llama"))
?? models.FirstOrDefault();
return model;
}
protected virtual IEnumerable<AiModel> getAiModels()
{
var current_models = DAOFactory.GenericDAO.GetAll<AiModel>();
var current_models_actives = current_models.Where(x => x.IsActive == ActivationTypeId.Active);
var current_models_deactives = current_models.Where(x => x.IsActive != ActivationTypeId.Active);
var actives_llm_clients = ServiceFacade.GetActiveApiClients();
var actives_llm_clients_models = actives_llm_clients.SelectMany(x => x.GetAiModelle().GetResponseData());
var request_models = MapperFactory.AiModel.MapToNewEntities(actives_llm_clients_models);
//var request_models_dc_owui = ServiceFacade.OpenWebApiClient.GetAiModelle().GetResponseData();
//var request_models_dc = request_models_dc_ollama.Union(request_models_dc_owui);
//var request_models = MapperFactory.AiModel.MapToNewEntities(request_models_dc);
var toadd = request_models.Except(current_models);
var toremove = current_models_actives.Except(request_models);
var toreadd = current_models_deactives.Intersect(request_models);
DAOFactory.GenericDAO.Insert(toadd);
DAOFactory.GenericDAO.Deactivate(toremove);
DAOFactory.GenericDAO.SetActivationType(toreadd, ActivationTypeId.Active);
var models = DAOFactory.GenericDAO.GetAllActive<AiModel>();
return models;
}
protected virtual AiConversation createAiConversation(AiContextType uicontext, Dictionary<TableID, long[]> context_reference, long modell_oid, AiDataContextType context_Type)
{
var current_user = UserRightHelper.GetLoggedInUserWithOid();
var systemPromptFolderPath = BeWoServerPathHelper.GetAiSystemPromptPath();
var factory = new AiConversationSystemPromptBuilder(systemPromptFolderPath);
var context_loaded = AiContextLoader.LoadContext(uicontext, context_reference);
var system_prompt = factory.CreateSystemPrompt(uicontext, context_loaded, context_Type);
var conv = new AiConversation();
conv.Created = DateTime.Now;
// RETODO Muss noch bzgl Sicherheit geprüft werden.
conv.ContextBeWoObjects = MapperFactory.AiConversation.Parse(context_reference);
conv.Displayname = "Neue Unterhaltung";
conv.Messages = new List<AiConversationMessage>();
conv.Modell = DAOFactory.GenericDAO.LoadByID<AiModel>(modell_oid);
conv.UIContext = uicontext;
conv.ContextType = context_Type;
conv.Updated = DateTime.Now;
conv.ApplicationUserOid = current_user.Oid;
var msg = new AiConversationMessage();
msg.Message = system_prompt;
msg.AiConversation = conv;
msg.Role = AiConversationMessageRole.System;
msg.Created = DateTime.Now;
conv.Messages.Add(msg);
return conv;
}
protected virtual AiConversationMessageDC[] sendNewMessage(AiConversation conv, string message, AiConfigDC config = null)
{
// Hole Config
if (config == null)
config = GetAiConfig();
var configModel = config.SelectedModel;
var configModelName = config.SelectedModel.ModelName;
// Build Payload
var payload = new
{
model = configModelName,
messages = conv.Messages.Select(m => new { role = m.Role.ToString().ToLower(), content = m.Message })
};
// Updated
conv.Updated = DateTime.Now;
// Füge Message hinzu
var user_msg = new AiConversationMessage();
user_msg.Message = message;
user_msg.AiConversation = conv;
user_msg.Role = BS.Shared.AiConversationMessageRole.User;
user_msg.Created = DateTime.Now;
conv.Messages.Add(user_msg);
// Check Token Size
if (-1 < configModel.Value)
{
AiUtils.CheckTokenSize(conv.Messages, configModelName, configModel.Value);
}
// Update DB Entity
// DAOFactory.GenericDAO.Update(conv);
var conv_dc = MapperFactory.AiConversation.MapToNewDC(conv);
var user_msg_dc = MapperFactory.AiConversationMessage.MapToNewDC(user_msg);
// Send Request, wait for Response
var dt_before_request = DateTime.Now;
var logger = ServiceLogger.GetRequestLogger();
var client = ServiceFacade.GetLLMApiClientByConfig(config.SelectedModel.ModelSource);
var response = client.SendAiMessageRequest(payload, out var message2, out var model, out var duration, logger);
var result = response.GetResponseData();
// Antwort
var assi_msg = new AiConversationMessage();
assi_msg.Message = message2;
assi_msg.AiConversation = conv;
assi_msg.Role = BS.Shared.AiConversationMessageRole.Assistent;
assi_msg.Created = dt_before_request;
assi_msg.Duration = duration ?? 0;
assi_msg.ModelName = model;
conv.Messages.Add(assi_msg);
// Update conv.
DAOFactory.GenericDAO.Update(conv);
var assi_msg_dc = MapperFactory.AiConversationMessage.MapToNewDC(assi_msg);
return new AiConversationMessageDC[] { user_msg_dc, assi_msg_dc };
}
}
}