diff --git a/BotSharp.sln b/BotSharp.sln index e161ed0d8..0c99deee5 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -81,6 +81,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.HttpHandler EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.SqlDriver", "src\Plugins\BotSharp.Plugin.SqlDriver\BotSharp.Plugin.SqlDriver.csproj", "{D775DB67-A4B4-44E5-9144-522689590057}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.Dashboard", "src\Plugins\BotSharp.Plugin.Dashboard\BotSharp.Plugin.Dashboard.csproj", "{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -313,6 +315,14 @@ Global {D775DB67-A4B4-44E5-9144-522689590057}.Release|Any CPU.Build.0 = Release|Any CPU {D775DB67-A4B4-44E5-9144-522689590057}.Release|x64.ActiveCfg = Release|Any CPU {D775DB67-A4B4-44E5-9144-522689590057}.Release|x64.Build.0 = Release|Any CPU + {267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Debug|x64.ActiveCfg = Debug|Any CPU + {267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Debug|x64.Build.0 = Debug|Any CPU + {267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Release|Any CPU.Build.0 = Release|Any CPU + {267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Release|x64.ActiveCfg = Release|Any CPU + {267998C1-55C2-4ADC-8361-2CDFA5EA6D6C}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -353,6 +363,7 @@ Global {5CA3335E-E6AD-46FD-B277-29BBC3A16500} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {32D9E720-6FE6-4F29-94B1-B10B05BFAD75} = {51AFE054-AE99-497D-A593-69BAEFB5106F} {D775DB67-A4B4-44E5-9144-522689590057} = {51AFE054-AE99-497D-A593-69BAEFB5106F} + {267998C1-55C2-4ADC-8361-2CDFA5EA6D6C} = {51AFE054-AE99-497D-A593-69BAEFB5106F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index e74ad6a8e..5ca241e67 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -48,7 +48,9 @@ public interface IBotSharpRepository List GetLastConversations(); bool TruncateConversation(string conversationId, string messageId); #endregion - + #region Statistics + void IncrementConversationCount(); + #endregion #region Execution Log void AddExecutionLogs(string conversationId, List logs); List GetExecutionLogs(string conversationId); diff --git a/src/Infrastructure/BotSharp.Abstraction/Statistics/Model/Statistics.cs b/src/Infrastructure/BotSharp.Abstraction/Statistics/Model/Statistics.cs new file mode 100644 index 000000000..5f4f1e5c7 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Statistics/Model/Statistics.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Abstraction.Statistics.Model +{ + public class Statistics + { + public string Id { get; set; } = string.Empty; + public int ConversationCount { get; set; } + public DateTime UpdatedDateTime { get; set; } + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Statistics/Settings/StatisticsSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Statistics/Settings/StatisticsSettings.cs new file mode 100644 index 000000000..2758dfc3e --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Statistics/Settings/StatisticsSettings.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Abstraction.Statistics.Settings +{ + public class StatisticsSettings + { + public string DataDir { get; set; } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index 43e44c7a1..8adf711a0 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -184,7 +184,12 @@ public bool TruncateConversation(string conversationId, string messageId) throw new NotImplementedException(); } #endregion - + #region Stats + public void IncrementConversationCount() + { + throw new NotImplementedException(); + } + #endregion #region User public User? GetUserByEmail(string email) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Stats.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Stats.cs new file mode 100644 index 000000000..b9c3b664e --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Stats.cs @@ -0,0 +1,57 @@ +using BotSharp.Abstraction.Statistics.Model; +using System.IO; + +namespace BotSharp.Core.Repository +{ + public partial class FileRepository + { + public void IncrementConversationCount() + { + var statsFileDirectory = FindCurrentStatsDirectory(); + if (statsFileDirectory == null) + { + statsFileDirectory = CreateStatsFileDirectory(); + } + var fileName = GenerateStatsFileName(); + var statsFile = Path.Combine(statsFileDirectory, fileName); + if (!File.Exists(statsFile)) + { + File.WriteAllText(statsFile, JsonSerializer.Serialize(new Statistics() + { + Id = Guid.NewGuid().ToString(), + UpdatedDateTime = DateTime.UtcNow + }, _options)); + } + var json = File.ReadAllText(statsFile); + var stats = JsonSerializer.Deserialize(json, _options); + stats.ConversationCount += 1; + stats.UpdatedDateTime = DateTime.UtcNow; + File.WriteAllText(statsFile, JsonSerializer.Serialize(stats, _options)); + } + public string? CreateStatsFileDirectory() + { + var dir = GenerateStatsDirectoryName(); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + return dir; + } + private string? FindCurrentStatsDirectory() + { + var dir = GenerateStatsDirectoryName(); + if (!Directory.Exists(dir)) return null; + + return dir; + } + private string GenerateStatsDirectoryName() + { + return Path.Combine(_dbSettings.FileRepository, _statisticsSetting.DataDir, DateTime.UtcNow.Year.ToString(), DateTime.UtcNow.ToString("MM")); + } + private string GenerateStatsFileName() + { + var fileName = DateTime.UtcNow.ToString("MMdd"); + return $"{fileName}-{STATS_FILE}"; + } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs index 6874ed022..8c9041096 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs @@ -11,6 +11,7 @@ using BotSharp.Abstraction.Evaluations.Settings; using System.Text.Encodings.Web; using BotSharp.Abstraction.Plugins.Models; +using BotSharp.Abstraction.Statistics.Settings; namespace BotSharp.Core.Repository; @@ -20,6 +21,7 @@ public partial class FileRepository : IBotSharpRepository private readonly BotSharpDatabaseSettings _dbSettings; private readonly AgentSettings _agentSettings; private readonly ConversationSetting _conversationSettings; + private readonly StatisticsSettings _statisticsSetting; private JsonSerializerOptions _options; private const string AGENT_FILE = "agent.json"; @@ -29,6 +31,7 @@ public partial class FileRepository : IBotSharpRepository private const string USER_FILE = "user.json"; private const string USER_AGENT_FILE = "agents.json"; private const string CONVERSATION_FILE = "conversation.json"; + private const string STATS_FILE = "stats.json"; private const string DIALOG_FILE = "dialogs.txt"; private const string STATE_FILE = "state.json"; private const string EXECUTION_LOG_FILE = "execution.log"; @@ -38,12 +41,14 @@ public FileRepository( IServiceProvider services, BotSharpDatabaseSettings dbSettings, AgentSettings agentSettings, - ConversationSetting conversationSettings) + ConversationSetting conversationSettings, + StatisticsSettings statisticsSettings) { _services = services; _dbSettings = dbSettings; _agentSettings = agentSettings; _conversationSettings = conversationSettings; + _statisticsSetting = statisticsSettings; _options = new JsonSerializerOptions { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs index 54cdae14d..3b3256c5c 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs @@ -28,7 +28,6 @@ public List GetPluginMenu() { var menu = new List { - new PluginMenuDef("Dashboard", link: "/page/dashboard", icon: "bx bx-home-circle", weight: 1), new PluginMenuDef("Apps", weight: 5) { IsHeader = true, diff --git a/src/Plugins/BotSharp.Plugin.Dashboard/BotSharp.Plugin.Dashboard.csproj b/src/Plugins/BotSharp.Plugin.Dashboard/BotSharp.Plugin.Dashboard.csproj new file mode 100644 index 000000000..5736c64f3 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Dashboard/BotSharp.Plugin.Dashboard.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.1 + enable + + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.Dashboard/DashboardPlugin.cs b/src/Plugins/BotSharp.Plugin.Dashboard/DashboardPlugin.cs new file mode 100644 index 000000000..572bff90b --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Dashboard/DashboardPlugin.cs @@ -0,0 +1,32 @@ +using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.Plugins; +using BotSharp.Abstraction.Plugins.Models; +using BotSharp.Abstraction.Settings; +using BotSharp.Abstraction.Statistics.Settings; +using BotSharp.Plugin.Dashboard.Hooks; + +namespace BotSharp.Plugin.Dashboard; + +public class DashboardPlugin : IBotSharpPlugin +{ + public string Id => "d42a0c21-b461-44f6-ada2-499510d260af"; + public string Name => "Dashboard"; + public string Description => "Dashboard that offers real-time statistics on model performance, usage trends, and user feedback"; + + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + services.AddScoped(); + services.AddScoped(provider => + { + var settingService = provider.GetRequiredService(); + return settingService.Bind("Statistics"); + }); + } + + public bool AttachMenu(List menu) + { + var section = menu.First(x => x.Label == "Apps"); + menu.Add(new PluginMenuDef("Dashboard", link: "/page/dashboard", icon: "bx bx-home-circle", weight: section.Weight - 1)); + return true; + } +} diff --git a/src/Plugins/BotSharp.Plugin.Dashboard/Hooks/StatsConversationHook.cs b/src/Plugins/BotSharp.Plugin.Dashboard/Hooks/StatsConversationHook.cs new file mode 100644 index 000000000..5c16a1736 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Dashboard/Hooks/StatsConversationHook.cs @@ -0,0 +1,20 @@ +using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.Plugins.Models; +using BotSharp.Abstraction.Repositories; + +namespace BotSharp.Plugin.Dashboard.Hooks; + +public class StatsConversationHook : ConversationHookBase +{ + private readonly IServiceProvider _services; + public StatsConversationHook(IServiceProvider services) + { + _services = services; + } + + public override async Task OnConversationInitialized(Conversation conversation) + { + var db = _services.GetRequiredService(); + db.IncrementConversationCount(); + } +} diff --git a/src/Plugins/BotSharp.Plugin.Dashboard/Using.cs b/src/Plugins/BotSharp.Plugin.Dashboard/Using.cs new file mode 100644 index 000000000..8cb9a723c --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Dashboard/Using.cs @@ -0,0 +1,13 @@ +global using System; +global using System.Collections.Generic; +global using System.Text; +global using System.Threading.Tasks; +global using System.Linq; +global using System.Text.Json; +global using BotSharp.Abstraction.Conversations.Models; +global using BotSharp.Abstraction.Agents.Models; +global using BotSharp.Abstraction.MLTasks; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using System.Text.Json.Serialization; +global using BotSharp.Abstraction.Utilities; \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Stats.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Stats.cs new file mode 100644 index 000000000..11f74e692 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Stats.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Plugin.MongoStorage.Repository +{ + public partial class MongoRepository + { + #region Statistics + public void IncrementConversationCount() + { + throw new NotImplementedException(); + } + #endregion + } +} diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index d4e6e764b..8a612ae49 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -27,6 +27,7 @@ + diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 67952c9ab..9c59875f5 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -82,7 +82,9 @@ "EnableLlmCompletionLog": false, "EnableExecutionLog": true }, - + "Stats": { + "DataDir": "stats" + }, "LlamaSharp": { "Interactive": true, "ModelDir": "C:/Users/haipi/Downloads", @@ -163,6 +165,7 @@ "Assemblies": [ "BotSharp.Plugin.MongoStorage", "BotSharp.Core", + "BotSharp.Plugin.Dashboard", "BotSharp.Plugin.AzureOpenAI", "BotSharp.Plugin.GoogleAI", "BotSharp.Plugin.MetaAI",