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 BeWo.Data.Access; using BeWo.Data.Entities; using BeWo.Data.Security; using BeWo.Service.Core; using BeWo.Service.DCEntityMapper; using BeWo.Service.Invoicing; using BeWo.Service.ServiceContracts; using BeWo.Service.ServiceImplementations; using BeWo.Service.ServiceProxy; using BeWo.Service.ServiceUtils; using BS.Shared; using BS.Shared.Core; using BS.Shared.DataContracts; using BS.Shared.DataContracts.Compact; 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(); 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(oid); var update = MapperFactory.AiConfig.MergeWithEntity(toUpdate, entity); DAOFactory.GenericDAO.Update(update); var dc = MapperFactory.AiConfig.MapToNewDC(update); return dc; } // ======================[ Models ]====================== public virtual IEnumerable GetAiModels() { var models = getAiModels(); return MapperFactory.AiModel.MapToNewDCs(models); } // ======================[ Conversations ]====================== public virtual IEnumerable GetAiConversations(AiContextType uicontext, long? oid) { // SearchDAO var current_user = UserRightHelper.GetLoggedInUserWithOid(); var convs = DAOFactory.GenericDAO.GetAllActive(); var filter = new List(); 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(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(); 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(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(conversation_oid); DAOFactory.GenericDAO.SetActivationType(conversation, ActivationTypeId.Deleted); } // ======================[ Conversationmessages ]====================== public virtual AiConversationMessageDC[] SendNewMessage(long conversation, string message) { var conv = DAOFactory.GenericDAO.LoadByID(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 getAiModels() { var current_models = DAOFactory.GenericDAO.GetAll(); 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 request_models_dc_ollama = ServiceFacade.OllamaApiClient.GetAiModelle().GetResponseData(); 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(); return models; } protected virtual AiConversation createAiConversation(AiContextType uicontext, Dictionary context_reference, long modell_oid, AiDataContextType context_Type) { var current_user = UserRightHelper.GetLoggedInUserWithOid(); var factory = new AiPromptBuilder(); var context_loaded = AiContextLoader.LoadContext(uicontext, context_reference); var system_prompt = factory.CreateSystemInstructions(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(); conv.Modell = DAOFactory.GenericDAO.LoadByID(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; ApiResponse response; string message2, model; int? duration; if (config.SelectedModel.ModelSource == AiModelSource.openwebui) response = ServiceFacade.OpenWebApiClient.SendAiMessageRequest(payload, out message2, out model, out duration); else if (config.SelectedModel.ModelSource == AiModelSource.ollama) response = ServiceFacade.OllamaApiClient.SendAiMessageRequest(payload, out message2, out model, out duration); else throw new NotImplementedException(); if (!response.Success) { var json = JsonConvert.SerializeObject(response.Copy()); File.WriteAllText(MergedConfig.GetSetting("AiServiceLogFilePath"), json); } 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 }; } } }