diff --git a/AICore/AICore.csproj b/AICore/AICore.csproj
index 69e0c021d..07a0c5f6a 100644
--- a/AICore/AICore.csproj
+++ b/AICore/AICore.csproj
@@ -39,6 +39,7 @@
..\packages\System.IO.Pipelines.9.0.2\lib\net462\System.IO.Pipelines.dll
+
..\packages\System.Text.Encodings.Web.9.0.2\lib\net462\System.Text.Encodings.Web.dll
@@ -92,6 +93,10 @@
+
+
+
+
@@ -111,6 +116,10 @@
{B0D73E3D-4AE7-4024-93A6-DB1F46D7CCEE}
Data
+
+ {4B2F3959-3CDA-4514-83A2-FA3C24E57BB7}
+ ApiFacade
+
{EC2349FB-7FE0-4AD1-B28B-A7A27AF80A57}
ServiceUtils
diff --git a/AICore/Context/SummaryParser/CsvContextSummaryParser.cs b/AICore/Context/SummaryParser/CsvContextSummaryParser.cs
index 874332c57..9a7a900e3 100644
--- a/AICore/Context/SummaryParser/CsvContextSummaryParser.cs
+++ b/AICore/Context/SummaryParser/CsvContextSummaryParser.cs
@@ -15,11 +15,12 @@ namespace AICore.Context.SummaryParser
public class CsvContextSummaryParser : BaseContextSummaryParser
{
private string _Delimiter;
+ private bool _StrictEscapeCsvField;
- public CsvContextSummaryParser() : this(",") { }
- public CsvContextSummaryParser(string delimiter)
+ public CsvContextSummaryParser(string delimiter = ",", bool strictEscapeCsvField = false)
{
_Delimiter = delimiter;
+ _StrictEscapeCsvField = strictEscapeCsvField;
}
public override string Parse(AiContextType uicontext, BaseContextSummary context)
@@ -111,14 +112,21 @@ namespace AICore.Context.SummaryParser
if (string.IsNullOrEmpty(field))
return string.Empty;
- // Wenn das Feld Kommas, Anführungszeichen oder Zeilumbrüche enthält,
- // muss es in Anführungszeichen gesetzt werden
- if (field.Contains(_Delimiter) || field.Contains("\"") || field.Contains("\n") || field.Contains("\r"))
+ if (_StrictEscapeCsvField)
{
- // Anführungszeichen im Feld verdoppeln
- field = field.Replace("\"", "\"\"");
return $"\"{field}\"";
}
+ else
+ {
+ // Wenn das Feld Kommas, Anführungszeichen oder Zeilumbrüche enthält,
+ // muss es in Anführungszeichen gesetzt werden
+ if (field.Contains(_Delimiter) || field.Contains("\"") || field.Contains("\n") || field.Contains("\r"))
+ {
+ // Anführungszeichen im Feld verdoppeln
+ field = field.Replace("\"", "\"\"");
+ return $"\"{field}\"";
+ }
+ }
return field;
}
diff --git a/Service/Api/ErrorExtractor/DefaultErrorExtractor.cs b/AICore/Facade/DefaultErrorExtractor.cs
similarity index 92%
rename from Service/Api/ErrorExtractor/DefaultErrorExtractor.cs
rename to AICore/Facade/DefaultErrorExtractor.cs
index 1a2e8ab4e..812dc5b39 100644
--- a/Service/Api/ErrorExtractor/DefaultErrorExtractor.cs
+++ b/AICore/Facade/DefaultErrorExtractor.cs
@@ -6,7 +6,7 @@ using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
-namespace BeWo.Service.Api.ErrorExtractor
+namespace AICore.Facade
{
public class DefaultErrorExtractor : IApiErrorExtractor
{
diff --git a/AICore/Facade/LLM/BaseLLMApiClient.cs b/AICore/Facade/LLM/BaseLLMApiClient.cs
new file mode 100644
index 000000000..970c4240c
--- /dev/null
+++ b/AICore/Facade/LLM/BaseLLMApiClient.cs
@@ -0,0 +1,30 @@
+using BeWo.Data.Entities;
+using BeWo.ServiceUtils.Core;
+using BS.Shared.DataContracts.Feature.AI;
+using BS.Shared.DataContracts;
+using BS.Shared.Interface;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Text.Json;
+
+namespace AICore.Facade.LLM
+{
+ public abstract class BaseLLMApiClient
+ {
+ public string BaseUrl { get; set; }
+ public string ApiKey { get; set; }
+
+ protected BaseLLMApiClient(string base_url, string api_key)
+ {
+ base_url = BaseUrl;
+ ApiKey = api_key;
+ }
+
+ public abstract ApiResponse> GetAiModelle();
+ public abstract ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration);
+ }
+}
diff --git a/AICore/Facade/LLM/OllamaApiClient.cs b/AICore/Facade/LLM/OllamaApiClient.cs
new file mode 100644
index 000000000..5fc33c845
--- /dev/null
+++ b/AICore/Facade/LLM/OllamaApiClient.cs
@@ -0,0 +1,124 @@
+using BeWo.Data.Entities;
+using BS.Shared.DataContracts;
+using BS.Shared.DataContracts.Feature.AI;
+using BS.Shared.Extensions;
+using BS.Shared.Interface;
+using System.Collections.Generic;
+using System;
+using System.Linq;
+using System.Net.Sockets;
+using System.Net;
+using BeWo.Server.ApiFacade.Core;
+
+namespace AICore.Facade.LLM
+{
+ public class OllamaApiClient : BaseLLMApiClient
+ {
+ private protected readonly HttpClientFacade _GetClientFacade;
+ private protected readonly JsonHttpClientFacade _PostClientFacade;
+ private protected readonly IApiErrorExtractor _ErrorExtractor;
+
+ public OllamaApiClient(string base_url, string api_key) : base(base_url, api_key)
+ {
+ var authorization = $"Bearer {api_key}";
+ _GetClientFacade = new HttpClientFacade(base_url);
+ _GetClientFacade.SetToken(authorization);
+ _PostClientFacade = new JsonHttpClientFacade(base_url);
+ _PostClientFacade.SetToken(authorization);
+ _PostClientFacade.Timeout = TimeSpan.FromSeconds(300);
+ _ErrorExtractor = new DefaultErrorExtractor();
+
+ try
+ {
+ var addresses = Dns.GetHostAddresses("w2.ownchat.de");
+ foreach (var addr in addresses)
+ Console.WriteLine(addr);
+ }
+ catch (SocketException ex)
+ {
+ Console.WriteLine($"DNS resolution failed: {ex.Message}");
+ }
+ }
+
+ public override ApiResponse> GetAiModelle()
+ {
+ var models = new List();
+
+ var response_format = new
+ {
+ models = new[]
+ {
+ new
+ {
+ name = "",
+ model = ""
+ }
+
+ }
+ };
+
+ var response = _GetClientFacade.GetAnonymousTypeAsync("api/ollama/models", response_format, _ErrorExtractor).GetAwaiter().GetResult();
+
+ if (!response.Success)
+ {
+ throw new NotImplementedException();
+ }
+
+ var data = response.Data.models.Select(x => new AiModel(x.name, 0));
+
+ return ApiResponse>.SuccessResponse(data);
+ }
+
+ public override ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration)
+ {
+ message = null;
+ duration = null;
+ model = null;
+
+ _PostClientFacade.JsonObject = payload;
+
+ var response_format = new
+ {
+ id = string.Empty,
+ model = string.Empty,
+ message = new
+ {
+ content = string.Empty,
+ role = string.Empty
+ },
+ done_reason = "stop",
+ done = true,
+ total_duration = 1316754573L,
+ load_duration = 31739136L,
+ prompt_eval_count = 22L,
+ prompt_eval_duration = 2132887L,
+ eval_count = 66L,
+ eval_duration = 1282438235L
+ };
+
+ var response = _PostClientFacade.GetAnonymousTypeAsync("api/ollama/chat/completions", response_format, _ErrorExtractor).GetAwaiter().GetResult();
+
+ if (!response.Success)
+ return response.Copy();
+
+ var json = response.GetResponseData();
+
+ if (json.message is object && !string.IsNullOrEmpty(json.message.content))
+ {
+ var total_ns = json.total_duration;
+ var total_ms = total_ns / 1000000;
+
+ duration = (int)total_ms;
+ model = json.model;
+
+ var assi_msg = BS.Shared.Core.Utils.ConvertToISO88591(json.message.content);
+
+ message = assi_msg;
+
+ return ApiResponse.SuccessResponse(assi_msg);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/Service/ServiceProxy/OpenWebApiClient.cs b/AICore/Facade/LLM/OpenWebApiClient.cs
similarity index 55%
rename from Service/ServiceProxy/OpenWebApiClient.cs
rename to AICore/Facade/LLM/OpenWebApiClient.cs
index 9fad4453d..2778472e3 100644
--- a/Service/ServiceProxy/OpenWebApiClient.cs
+++ b/AICore/Facade/LLM/OpenWebApiClient.cs
@@ -1,36 +1,23 @@
using BeWo.Data.Entities;
-using BeWo.Service.Api.ErrorExtractor;
-using BeWo.Service.Core;
-using BeWo.Service.DCEntityMapper;
-using BS.Shared.Core;
-using BS.Shared.Core.Facade;
+using BeWo.Server.ApiFacade.Core;
using BS.Shared.DataContracts;
using BS.Shared.DataContracts.Feature.AI;
-using BS.Shared.Exceptions;
using BS.Shared.Extensions;
using BS.Shared.Interface;
-using DevExpress.DataProcessing.InMemoryDataProcessor;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
-using System.Configuration;
using System.IO;
using System.Linq;
-using System.Net;
-using System.Text;
-using System.Text.Json.Nodes;
-using System.Threading.Tasks;
-using static DevExpress.Xpo.Helpers.AssociatedCollectionCriteriaHelper;
+using System.Text.Json;
-namespace BeWo.Service.ServiceProxy
+namespace AICore.Facade.LLM
{
- public class OpenWebApiClient
+ public class OpenWebApiClient : BaseLLMApiClient
{
- private readonly HttpClientFacade _GetClientFacade;
- private readonly JsonHttpClientFacade _PostClientFacade;
- private readonly IApiErrorExtractor _ErrorExtractor;
+ private protected readonly HttpClientFacade _GetClientFacade;
+ private protected readonly JsonHttpClientFacade _PostClientFacade;
+ private protected readonly IApiErrorExtractor _ErrorExtractor;
- public OpenWebApiClient() : this(MergedConfig.GetSetting("OpenWebUIUrl"), MergedConfig.GetSetting("OpenWebUIKey")) { }
public OpenWebApiClient(string base_url, string api_key)
{
var authorization = $"Bearer {api_key}";
@@ -42,13 +29,21 @@ namespace BeWo.Service.ServiceProxy
_ErrorExtractor = new DefaultErrorExtractor();
}
- public ApiResponse> GetAiModelle()
+ public override ApiResponse> GetAiModelle()
{
var models = new List();
var response_format = new
{
- data = models
+ data = new[]
+ {
+ new
+ {
+ id = "",
+ name = ""
+ }
+
+ }
};
var response = _GetClientFacade.GetAnonymousTypeAsync("api/models", response_format, _ErrorExtractor).GetAwaiter().GetResult();
@@ -58,18 +53,18 @@ namespace BeWo.Service.ServiceProxy
throw new NotImplementedException();
}
- var data = MapperFactory.AiModel.MapToNewEntities(response.Data.data);
+ var data = response.Data.data.Select(x => new AiModel(x.name, 0));
return ApiResponse>.SuccessResponse(data);
}
- public ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration)
+ public override ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration)
{
message = null;
duration = null;
model = null;
- var sub_url = "api/chat/completions";
+ _PostClientFacade.JsonObject = payload;
var response_format = new
{
@@ -94,20 +89,7 @@ namespace BeWo.Service.ServiceProxy
}
};
- _PostClientFacade.JsonObject = payload;
-
- if (MergedConfig.GetSetting("AiServiceLastRequestEnabled") is string str && bool.TryParse(str, out bool b) && b)
- {
- var path = MergedConfig.GetSetting("AiServiceLastRequestFilePath");
- if (Directory.GetParent(path) == null)
- throw new ArgumentException("AiServiceLastRequestFilePath ungültig");
-
- var payload_ser = JsonConvert.SerializeObject(_PostClientFacade.JsonObject, Formatting.Indented);
-
- File.WriteAllText(path, payload_ser);
- }
-
- var response = _PostClientFacade.GetAnonymousTypeAsync(sub_url, response_format, _ErrorExtractor).GetAwaiter().GetResult();
+ var response = _PostClientFacade.GetAnonymousTypeAsync("api/chat/completions", response_format, _ErrorExtractor).GetAwaiter().GetResult();
if (!response.Success)
return response.Copy();
@@ -120,7 +102,7 @@ namespace BeWo.Service.ServiceProxy
{
var total_ns = json.usage.total_duration;
var total_ms = total_ns / 1000000;
-
+
duration = (int)total_ms;
model = json.model;
diff --git a/AICore/Prompt/Factories/AiPromptFactory.cs b/AICore/Prompt/Factories/AiPromptFactory.cs
index 13eef7e58..cc79e3ab7 100644
--- a/AICore/Prompt/Factories/AiPromptFactory.cs
+++ b/AICore/Prompt/Factories/AiPromptFactory.cs
@@ -60,6 +60,10 @@ namespace AICore.Prompt.Factories
return new CsvContextSummaryParser();
case AiDataContextType.tsv:
return new CsvContextSummaryParser("\t");
+ case AiDataContextType.csv2:
+ return new CsvContextSummaryParser(";", true);
+ case AiDataContextType.tsv2:
+ return new CsvContextSummaryParser("\t", true);
default:
break;
}
diff --git a/AiCoreUnitTest/AiCoreUnitTest.csproj b/AiCoreUnitTest/AiCoreUnitTest.csproj
new file mode 100644
index 000000000..0723a5f99
--- /dev/null
+++ b/AiCoreUnitTest/AiCoreUnitTest.csproj
@@ -0,0 +1,83 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {68C1F8EA-7828-4E46-8F04-91105C55860E}
+ Library
+ Properties
+ AiCoreUnitTest
+ AiCoreUnitTest
+ v4.7.2
+ 512
+ {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 15.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
+ False
+ UnitTest
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
+
+
+ ..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {69D70CA9-0DF9-419E-BFAF-F4539F129BD9}
+ AICore
+
+
+ {B0D73E3D-4AE7-4024-93A6-DB1F46D7CCEE}
+ Data
+
+
+ {2F50B83D-A3F0-4EC4-979A-3F9B7E3D8ED4}
+ Shared
+
+
+
+
+
+
+ Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}".
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AiCoreUnitTest/OllamaFacadeTest.cs b/AiCoreUnitTest/OllamaFacadeTest.cs
new file mode 100644
index 000000000..761d8758e
--- /dev/null
+++ b/AiCoreUnitTest/OllamaFacadeTest.cs
@@ -0,0 +1,34 @@
+using AICore.Facade.LLM;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Net;
+
+namespace AiCoreUnitTest
+{
+ [TestClass]
+ public class OllamaFacadeTest
+ {
+ public string OllamaUrl { get; set; }
+ public string OllamaKey { get; set; }
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ OllamaUrl = "https://w2.owchat.de";
+ OllamaKey = "mu9ZPuUXJ4aVGLU1WL2RfPCeSMRmJREwyjVJBP7bHfSz8quZRqN7e7UwnwCDi8GZ";
+ }
+
+ public OllamaApiClient GetOllamaApiClient()
+ => new OllamaApiClient(OllamaUrl, OllamaKey);
+
+ [TestMethod]
+ public void GetModels()
+ {
+ var client = GetOllamaApiClient();
+
+ var models = client.GetAiModelle();
+
+ Assert.IsNotNull(models);
+ }
+ }
+}
diff --git a/AiCoreUnitTest/OpenWebUIFacadeTest.cs b/AiCoreUnitTest/OpenWebUIFacadeTest.cs
new file mode 100644
index 000000000..9b3c67f70
--- /dev/null
+++ b/AiCoreUnitTest/OpenWebUIFacadeTest.cs
@@ -0,0 +1,34 @@
+using AICore.Facade.LLM;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Net;
+
+namespace AiCoreUnitTest
+{
+ [TestClass]
+ public class OpenWebUIFacadeTest
+ {
+ public string OpenWebUIUrl { get; set; }
+ public string OpenWebUIKey { get; set; }
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ OpenWebUIUrl = "https://owui1.ownsoft.de/";
+ OpenWebUIKey = "sk-7f5d8c418630448dbe31ab1c7e8b100a";
+ }
+
+ public OpenWebApiClient GetOpenWebApiClient()
+ => new OpenWebApiClient(OpenWebUIUrl, OpenWebUIKey);
+
+ [TestMethod]
+ public void GetModels()
+ {
+ var client = GetOpenWebApiClient();
+
+ var models = client.GetAiModelle();
+
+ Assert.IsNotNull(models);
+ }
+ }
+}
diff --git a/AiCoreUnitTest/Properties/AssemblyInfo.cs b/AiCoreUnitTest/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..e6db92c1a
--- /dev/null
+++ b/AiCoreUnitTest/Properties/AssemblyInfo.cs
@@ -0,0 +1,20 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("AiCoreUnitTest")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("AiCoreUnitTest")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+[assembly: Guid("68c1f8ea-7828-4e46-8f04-91105c55860e")]
+
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/AiCoreUnitTest/packages.config b/AiCoreUnitTest/packages.config
new file mode 100644
index 000000000..36cbc3e9e
--- /dev/null
+++ b/AiCoreUnitTest/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/BeWo/BeWoApp.xaml.cs b/BeWo/BeWoApp.xaml.cs
index 74bf1e253..dca813ca8 100644
--- a/BeWo/BeWoApp.xaml.cs
+++ b/BeWo/BeWoApp.xaml.cs
@@ -59,7 +59,7 @@ namespace BeWo
public static string ShortVersion = "3.26";
- public static string Version = "Version 3.26 - 1.2 Test";
+ public static string Version = "Version 3.26 - 1.2.1 Test";
public static int RenderTier = 0;
public static bool IsInDesignMode = DesignerProperties.GetIsInDesignMode(new DependencyObject());
diff --git a/BeWo/View/Detail/AI/AiConfigView.xaml b/BeWo/View/Detail/AI/AiConfigView.xaml
index 03b7f3b1a..bb8c9ab97 100644
--- a/BeWo/View/Detail/AI/AiConfigView.xaml
+++ b/BeWo/View/Detail/AI/AiConfigView.xaml
@@ -6,11 +6,11 @@
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:listviewmodel="clr-namespace:BeWo.ViewModel.ListViewModel"
xmlns:local="clr-namespace:BeWo.View.Detail.AI"
- xmlns:shared="clr-namespace:BS.Shared;assembly=BS.Shared"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:shared="clr-namespace:BS.Shared;assembly=BS.Shared"
xmlns:vmv="clr-namespace:BeWo.ViewModel.View"
d:DataContext="{d:DesignInstance Type=vmv:AiConversationChatViewModel,
- IsDesignTimeCreatable=True}"
+ IsDesignTimeCreatable=True}"
mc:Ignorable="d">
@@ -25,7 +25,7 @@
-
+
@@ -91,14 +91,19 @@
IsEnabled="False"
Style="{StaticResource TextEditNumber}" />
-