diff --git a/BotSharp.sln b/BotSharp.sln
index 76d19498..645b6060 100644
--- a/BotSharp.sln
+++ b/BotSharp.sln
@@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.SparkDesk",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.MetaGLM", "src\Plugins\BotSharp.Plugin.MetaGLM\BotSharp.Plugin.MetaGLM.csproj", "{CCF745F2-0C95-4ED0-983B-507C528B39EA}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.AnthropicAI", "src\Plugins\BotSharp.Plugin.AnthropicAI\BotSharp.Plugin.AnthropicAI.csproj", "{806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -343,6 +345,14 @@ Global
{CCF745F2-0C95-4ED0-983B-507C528B39EA}.Release|Any CPU.Build.0 = Release|Any CPU
{CCF745F2-0C95-4ED0-983B-507C528B39EA}.Release|x64.ActiveCfg = Release|Any CPU
{CCF745F2-0C95-4ED0-983B-507C528B39EA}.Release|x64.Build.0 = Release|Any CPU
+ {806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Debug|x64.Build.0 = Debug|Any CPU
+ {806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Release|x64.ActiveCfg = Release|Any CPU
+ {806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -386,6 +396,7 @@ Global
{267998C1-55C2-4ADC-8361-2CDFA5EA6D6C} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{289E25C8-63F1-4D52-9909-207724DB40CB} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
{CCF745F2-0C95-4ED0-983B-507C528B39EA} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
+ {806A0B0E-FEFF-420E-B5B2-C9FCBF890A8C} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
diff --git a/Directory.Build.props b/Directory.Build.props
index 56e1a108..4ca87cf3 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,7 +2,7 @@
net8.0
10.0
- 1.2.1
+ 1.5.1
true
false
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
index c8aee271..b435a4d1 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
@@ -10,7 +10,7 @@ namespace BotSharp.Abstraction.Agents;
public interface IAgentService
{
Task CreateAgent(Agent agent);
- Task RefreshAgents();
+ Task RefreshAgents();
Task> GetAgents(AgentFilter filter);
///
@@ -26,6 +26,8 @@ public interface IAgentService
bool RenderFunction(Agent agent, FunctionDef def);
+ FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def);
+
///
/// Get agent detail without trigger any hook.
///
@@ -35,9 +37,18 @@ public interface IAgentService
Task DeleteAgent(string id);
Task UpdateAgent(Agent agent, AgentField updateField);
- Task UpdateAgentFromFile(string id);
+
+ ///
+ /// Path existing templates of agent, cannot create new or delete templates
+ ///
+ ///
+ ///
+ Task PatchAgentTemplate(Agent agent);
+ Task UpdateAgentFromFile(string id);
string GetDataDir();
string GetAgentDataDir(string agentId);
+ List GetAgentsByUser(string userId);
+
PluginDef GetPlugin(string agentId);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs
index e95f72bc..fefda240 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs
@@ -5,6 +5,8 @@ public class AgentSettings
public string DataDir { get; set; } = string.Empty;
public string TemplateFormat { get; set; } = "liquid";
public string HostAgentId { get; set; } = string.Empty;
+ public bool EnableTranslator { get; set; } = false;
+ public bool EnableHttpHandler { get; set; } = false;
///
/// This is the default LLM config for agent
diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
index bbade11e..65424adb 100644
--- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
+++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
@@ -22,6 +22,7 @@
+
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs
new file mode 100644
index 00000000..6dd0fb63
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.Abstraction.Browsing.Enums;
+
+public enum BroswerActionEnum
+{
+ Click = 1,
+ InputText = 2,
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs
index 376c7cac..e2581ba9 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs
@@ -4,8 +4,8 @@ namespace BotSharp.Abstraction.Browsing;
public interface IWebBrowser
{
- Task LaunchBrowser(string conversationId, string? url);
- Task ScreenshotAsync(string conversationId, string path);
+ Task LaunchBrowser(string contextId, string? url);
+ Task ScreenshotAsync(string contextId, string path);
Task ScrollPageAsync(BrowserActionParams actionParams);
Task ActionOnElement(MessageInfo message, ElementLocatingArgs location, ElementActionArgs action);
@@ -19,10 +19,11 @@ public interface IWebBrowser
Task ChangeListValue(BrowserActionParams actionParams);
Task CheckRadioButton(BrowserActionParams actionParams);
Task ChangeCheckbox(BrowserActionParams actionParams);
- Task GoToPage(string conversationId, string url);
+ Task GoToPage(string contextId, string url, bool openNewTab = false);
Task ExtractData(BrowserActionParams actionParams);
- Task EvaluateScript(string conversationId, string script);
- Task CloseBrowser(string conversationId);
- Task SendHttpRequest(BrowserActionParams actionParams);
- Task GetAttributeValue(MessageInfo message, ElementLocatingArgs location, BrowserActionResult result);
+ Task EvaluateScript(string contextId, string script);
+ Task CloseBrowser(string contextId);
+ Task CloseCurrentPage(string contextId);
+ Task SendHttpRequest(MessageInfo message, HttpRequestParams actionParams);
+ Task GetAttributeValue(MessageInfo message, ElementLocatingArgs location);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionParams.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionParams.cs
index 37c820a2..04ba82b2 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionParams.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionParams.cs
@@ -4,14 +4,14 @@ public class BrowserActionParams
{
public Agent Agent { get; set; }
public BrowsingContextIn Context { get; set; }
- public string ConversationId { get; set; }
+ public string ContextId { get; set; }
public string MessageId { get; set; }
- public BrowserActionParams(Agent agent, BrowsingContextIn context, string conversationId, string messageId)
+ public BrowserActionParams(Agent agent, BrowsingContextIn context, string contextId, string messageId)
{
Agent = agent;
Context = context;
- ConversationId = conversationId;
+ ContextId = contextId;
MessageId = messageId;
}
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs
index 9e45cf97..b3576b90 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs
@@ -3,8 +3,14 @@ namespace BotSharp.Abstraction.Browsing.Models;
public class BrowserActionResult
{
public bool IsSuccess { get; set; }
- public string ErrorMessage { get; set; }
- public string StackTrace { get; set; }
+ public string? Message { get; set; }
+ public string? StackTrace { get; set; }
public string Selector { get; set; }
public string Body { get; set; }
+ public bool IsHighlighted { get; set; }
+
+ public override string ToString()
+ {
+ return $"{IsSuccess} - {Selector}";
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowsingContextIn.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowsingContextIn.cs
index f613738b..ff3b94f3 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowsingContextIn.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowsingContextIn.cs
@@ -40,10 +40,4 @@ public class BrowsingContextIn
[JsonPropertyName("direction")]
public string? Direction { get; set; }
-
- ///
- /// Http request payload
- ///
- [JsonPropertyName("payload")]
- public string? Payload { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs
index 32d13c5c..0d44066a 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs
@@ -1,12 +1,34 @@
+using BotSharp.Abstraction.Browsing.Enums;
+
namespace BotSharp.Abstraction.Browsing.Models;
public class ElementActionArgs
{
- private string _action;
- public string Action => _action;
+ public BroswerActionEnum Action { get; set; }
- public ElementActionArgs(string action)
+ public string? Content { get; set; }
+
+ public ElementPosition? Position { get; set; }
+
+ public string? PressKey { get; set; }
+
+ ///
+ /// Required for deserialization
+ ///
+ public ElementActionArgs()
{
- _action = action;
+
+ }
+
+ public ElementActionArgs(BroswerActionEnum action, ElementPosition? position = null)
+ {
+ Action = action;
+ Position = position;
+ }
+
+ public ElementActionArgs(BroswerActionEnum action, string content)
+ {
+ Action = action;
+ Content = content;
}
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
index d043ca54..3b96c760 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
@@ -5,6 +5,9 @@ public class ElementLocatingArgs
[JsonPropertyName("match_rule")]
public string MatchRule { get; set; } = string.Empty;
+ [JsonPropertyName("tag")]
+ public string? Tag { get; set; } = null!;
+
[JsonPropertyName("text")]
public string? Text { get; set; }
@@ -20,5 +23,12 @@ public class ElementLocatingArgs
[JsonPropertyName("selector")]
public string? Selector { get; set; }
+ public bool Parent { get; set; }
+
public bool FailIfMultiple { get; set; }
+
+ ///
+ /// Draw outline around the element
+ ///
+ public bool Highlight { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementPosition.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementPosition.cs
new file mode 100644
index 00000000..fd95bd66
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementPosition.cs
@@ -0,0 +1,8 @@
+namespace BotSharp.Abstraction.Browsing.Models;
+
+public class ElementPosition
+{
+ public float X { get; set; } = default!;
+
+ public float Y { get; set; } = default!;
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/HttpRequestParams.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/HttpRequestParams.cs
new file mode 100644
index 00000000..e6d24a8e
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/HttpRequestParams.cs
@@ -0,0 +1,25 @@
+using System.Net.Http;
+
+namespace BotSharp.Abstraction.Browsing.Models;
+
+public class HttpRequestParams
+{
+ [JsonPropertyName("url")]
+ public string Url { get; set; } = string.Empty;
+
+ [JsonPropertyName("method")]
+ public HttpMethod Method { get; set; }
+
+ ///
+ /// Http request payload
+ ///
+ [JsonPropertyName("payload")]
+ public string? Payload { get; set; }
+
+ public HttpRequestParams(string url, HttpMethod method, string? payload = null)
+ {
+ Method = method;
+ Url = url;
+ Payload = payload;
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs
index dc530e6d..336d255c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs
@@ -3,6 +3,6 @@ namespace BotSharp.Abstraction.Browsing.Models;
public class MessageInfo
{
public string AgentId { get; set; }
- public string ConversationId { get; set; }
+ public string ContextId { get; set; }
public string MessageId { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Settings/WebBrowsingSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Settings/WebBrowsingSettings.cs
new file mode 100644
index 00000000..25149fe0
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Settings/WebBrowsingSettings.cs
@@ -0,0 +1,6 @@
+namespace BotSharp.Abstraction.Browsing.Settings;
+
+public class WebBrowsingSettings
+{
+ public string Driver { get; set; } = "Playwright";
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
index 36f065fd..06a97174 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs
@@ -44,6 +44,9 @@ public abstract class ConversationHookBase : IConversationHook
public virtual Task OnConversationEnding(RoleDialogModel message)
=> Task.CompletedTask;
+ public virtual Task OnNewTaskDetected(RoleDialogModel message, string reason)
+ => Task.CompletedTask;
+
public virtual Task OnTaskCompleted(RoleDialogModel message)
=> Task.CompletedTask;
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateDataType.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateDataType.cs
new file mode 100644
index 00000000..20d635f4
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateDataType.cs
@@ -0,0 +1,10 @@
+namespace BotSharp.Abstraction.Conversations.Enums;
+
+public class StateDataType
+{
+ public const string String = "string";
+ public const string Boolean = "boolean";
+ public const string Number = "number";
+ public const string Currency = "currency";
+ public const string Date = "date";
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateSource.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateSource.cs
new file mode 100644
index 00000000..31f32e16
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/StateSource.cs
@@ -0,0 +1,8 @@
+namespace BotSharp.Abstraction.Conversations.Enums;
+
+public class StateSource
+{
+ public const string External = "external";
+ public const string Application = "application";
+ public const string User = "user";
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationAttachmentService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationAttachmentService.cs
deleted file mode 100644
index feec3fab..00000000
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationAttachmentService.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace BotSharp.Abstraction.Conversations;
-
-public interface IConversationAttachmentService
-{
- string GetDirectory(string conversationId);
-}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs
index b9f0f3b8..9ed47c61 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs
@@ -64,6 +64,13 @@ public interface IConversationHook
Task OnResponseGenerated(RoleDialogModel message);
+ ///
+ /// LLM detected user requested a new task different from previous topic.
+ ///
+ ///
+ ///
+ Task OnNewTaskDetected(RoleDialogModel message, string reason);
+
///
/// LLM detected the current task is completed.
/// It's useful for the situation of multiple tasks in the same conversation.
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
index effb8a94..c8b997ec 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs
@@ -15,7 +15,15 @@ public interface IConversationService
Task> GetLastConversations();
Task> GetIdleConversations(int batchSize, int messageLimit, int bufferHours);
Task DeleteConversations(IEnumerable ids);
- Task TruncateConversation(string conversationId, string messageId);
+
+ ///
+ /// Truncate conversation
+ ///
+ /// Target conversation id
+ /// Target message id to delete
+ /// If not null, delete messages while input a new message; otherwise delete messages only
+ ///
+ Task TruncateConversation(string conversationId, string messageId, string? newMessageId = null);
Task> GetConversationContentLogs(string conversationId);
Task> GetConversationStateLogs(string conversationId);
@@ -42,6 +50,10 @@ public interface IConversationService
/// Use this feature when you want to hide some context from LLM.
///
/// Whether to reset all states
+ /// Append user init words
+ ///
///
- Task UpdateBreakpoint(bool resetStates = false);
+ Task UpdateBreakpoint(bool resetStates = false, string? reason = null, params string[] excludedStates);
+
+ Task GetConversationSummary(IEnumerable conversationId);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs
index 07eab6c1..de26ef5b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Conversations.Enums;
using System.Text.Json;
namespace BotSharp.Abstraction.Conversations;
@@ -8,12 +9,14 @@ namespace BotSharp.Abstraction.Conversations;
public interface IConversationStateService
{
string GetConversationId();
- Dictionary Load(string conversationId);
+ Dictionary Load(string conversationId, bool isReadOnly = false);
string GetState(string name, string defaultValue = "");
bool ContainsState(string name);
Dictionary GetStates();
- IConversationStateService SetState(string name, T value, bool isNeedVersion = true, int activeRounds = -1);
+ IConversationStateService SetState(string name, T value, bool isNeedVersion = true,
+ int activeRounds = -1, string valueType = StateDataType.String, string source = StateSource.User, bool readOnly = false);
void SaveStateByArgs(JsonDocument args);
- void CleanStates();
+ bool RemoveState(string name);
+ void CleanStates(params string[] excludedStates);
void Save();
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
index 530f425d..ad7ffd04 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs
@@ -32,34 +32,63 @@ public class Conversation
public class DialogElement
{
+ [JsonPropertyName("meta_data")]
public DialogMetaData MetaData { get; set; }
+
+ [JsonPropertyName("content")]
public string Content { get; set; }
+
+ [JsonPropertyName("secondary_content")]
+ public string? SecondaryContent { get; set; }
+
+ [JsonPropertyName("rich_content")]
public string? RichContent { get; set; }
+ [JsonPropertyName("secondary_rich_content")]
+ public string? SecondaryRichContent { get; set; }
+
+ [JsonPropertyName("payload")]
+ public string? Payload { get; set; }
+
public DialogElement()
{
}
- public DialogElement(DialogMetaData meta, string content, string? richContent = null)
+ public DialogElement(DialogMetaData meta, string content, string? richContent = null,
+ string? secondaryContent = null, string? secondaryRichContent = null, string? payload = null)
{
MetaData = meta;
Content = content;
RichContent = richContent;
+ SecondaryContent = secondaryContent;
+ SecondaryRichContent = secondaryRichContent;
+ Payload = payload;
}
public override string ToString()
{
- return $"{MetaData.Role}: {Content} [{MetaData.CreateTime}]";
+ return $"{MetaData.Role}: {Content} [{MetaData?.CreateTime}]";
}
}
public class DialogMetaData
{
+ [JsonPropertyName("role")]
public string Role { get; set; }
+
+ [JsonPropertyName("agent_id")]
public string AgentId { get; set; }
+
+ [JsonPropertyName("message_id")]
public string MessageId { get; set; }
+
+ [JsonPropertyName("function_name")]
public string? FunctionName { get; set; }
+
+ [JsonPropertyName("sender_id")]
public string? SenderId { get; set; }
+
+ [JsonPropertyName("create_at")]
public DateTime CreateTime { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationBreakpoint.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationBreakpoint.cs
index 69a88d13..0d819353 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationBreakpoint.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationBreakpoint.cs
@@ -10,4 +10,7 @@ public class ConversationBreakpoint
[JsonPropertyName("created_time")]
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
+
+ [JsonPropertyName("reason")]
+ public string? Reason { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationSenderActionModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationSenderActionModel.cs
index a153472c..b49fc55d 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationSenderActionModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationSenderActionModel.cs
@@ -6,6 +6,10 @@ public class ConversationSenderActionModel
{
[JsonPropertyName("conversation_id")]
public string ConversationId { get; set; }
+
[JsonPropertyName("sender_action")]
public SenderActionEnum SenderAction { get; set; }
+
+ [JsonPropertyName("indication")]
+ public string? Indication { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs
index 2176c7fa..cd4aad61 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs
@@ -9,4 +9,6 @@ public class IncomingMessageModel : MessageConfig
/// Postback message
///
public PostbackMessageModel? Postback { get; set; }
+
+ public List Files { get; set; } = new List();
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
index 96d933f5..a4aba84d 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs
@@ -26,6 +26,20 @@ public class RoleDialogModel : ITrackableMessage
public string Content { get; set; }
+ public string? SecondaryContent { get; set; }
+
+ ///
+ /// Postback content
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("payload")]
+ public string? Payload { get; set; }
+
+ ///
+ /// Indicator message used to provide UI feedback for function execution
+ ///
+ public string? Indication { get; set; }
+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string CurrentAgentId { get; set; }
@@ -35,6 +49,12 @@ public class RoleDialogModel : ITrackableMessage
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? FunctionName { get; set; }
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? ToolCallId { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? PostbackFunctionName { get; set; }
+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? FunctionArgs { get; set; }
@@ -54,6 +74,9 @@ public class RoleDialogModel : ITrackableMessage
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public RichContent? RichContent { get; set; }
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public RichContent? SecondaryRichContent { get; set; }
+
///
/// Stop conversation completion
///
@@ -62,6 +85,8 @@ public class RoleDialogModel : ITrackableMessage
public FunctionCallFromLlm Instruction { get; set; }
+ public List Files { get; set; } = new List();
+
private RoleDialogModel()
{
}
@@ -95,6 +120,8 @@ public class RoleDialogModel : ITrackableMessage
MessageId = source.MessageId,
FunctionArgs = source.FunctionArgs,
FunctionName = source.FunctionName,
+ ToolCallId = source.ToolCallId,
+ PostbackFunctionName = source.PostbackFunctionName,
RichContent = source.RichContent,
StopCompletion = source.StopCompletion,
Instruction = source.Instruction,
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateChangeModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateChangeModel.cs
index f98a896d..de84692f 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateChangeModel.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateChangeModel.cs
@@ -12,14 +12,23 @@ public class StateChangeModel
public string Name { get; set; }
[JsonPropertyName("before_value")]
- public string BeforeValue { get; set; }
+ public string? BeforeValue { get; set; }
[JsonPropertyName("before_active_rounds")]
public int? BeforeActiveRounds { get; set; }
[JsonPropertyName("after_value")]
- public string AfterValue { get; set; }
+ public string? AfterValue { get; set; }
[JsonPropertyName("after_active_rounds")]
public int? AfterActiveRounds { get; set; }
+
+ [JsonPropertyName("data_type")]
+ public string DataType { get; set; }
+
+ [JsonPropertyName("source")]
+ public string Source { get; set; }
+
+ [JsonPropertyName("readonly")]
+ public bool Readonly { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateKeyValue.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateKeyValue.cs
index 53618242..4afea9da 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateKeyValue.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/StateKeyValue.cs
@@ -1,9 +1,12 @@
+using BotSharp.Abstraction.Conversations.Enums;
+
namespace BotSharp.Abstraction.Conversations.Models;
public class StateKeyValue
{
public string Key { get; set; }
public bool Versioning { get; set; }
+ public bool Readonly { get; set; }
public List Values { get; set; } = new List();
public StateKeyValue()
@@ -16,6 +19,12 @@ public class StateKeyValue
Key = key;
Values = values;
}
+
+ public override string ToString()
+ {
+ var lastValue = Values.LastOrDefault();
+ return $"{Key} => ({lastValue?.ToString()})";
+ }
}
public class StateValue
@@ -30,6 +39,12 @@ public class StateValue
[JsonPropertyName("active_rounds")]
public int ActiveRounds { get; set; }
+ [JsonPropertyName("data_type")]
+ public string DataType { get; set; } = StateDataType.String;
+
+ [JsonPropertyName("source")]
+ public string Source { get; set; }
+
[JsonPropertyName("update_time")]
public DateTime UpdateTime { get; set; }
@@ -37,4 +52,11 @@ public class StateValue
{
}
+
+ public override string ToString()
+ {
+ var isActive = Active ? "Yes" : "No";
+ var activeRounds = ActiveRounds <= 0 ? "infinity" : ActiveRounds.ToString();
+ return $"Data: {Data}, Active: {isActive}, Active rounds: {activeRounds}, Source: {Source}";
+ }
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs
index 0c9a7436..566f7bef 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/RateLimitSetting.cs
@@ -3,6 +3,6 @@ namespace BotSharp.Abstraction.Conversations.Settings;
public class RateLimitSetting
{
public int MaxConversationPerDay { get; set; } = 100;
- public int MaxInputLengthPerRequest { get; set; } = 256;
+ public int MaxInputLengthPerRequest { get; set; } = 512;
public int MinTimeSecondsBetweenMessages { get; set; } = 2;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs b/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs
new file mode 100644
index 00000000..16197b9c
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs
@@ -0,0 +1,31 @@
+namespace BotSharp.Abstraction.Files;
+
+public interface IBotSharpFileService
+{
+ string GetDirectory(string conversationId);
+ IEnumerable GetChatImages(string conversationId, List conversations, int offset = 2);
+ IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, bool imageOnly = false);
+ string GetMessageFile(string conversationId, string messageId, string fileName);
+ bool SaveMessageFiles(string conversationId, string messageId, List files);
+
+ string GetUserAvatar();
+ bool SaveUserAvatar(BotSharpFile file);
+
+ ///
+ /// Delete files under messages
+ ///
+ /// Conversation Id
+ /// Files in these messages will be deleted
+ /// The starting message to delete
+ /// If not null, delete messages while input a new message; otherwise, delete messages only
+ ///
+ bool DeleteMessageFiles(string conversationId, IEnumerable messageIds, string targetMessageId, string? newMessageId = null);
+ bool DeleteConversationFiles(IEnumerable conversationIds);
+
+ ///
+ /// Get file bytes and content type from data, e.g., "data:image/png;base64,aaaaaaaaa"
+ ///
+ ///
+ ///
+ (string, byte[]) GetFileInfoFromData(string data);
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/BotSharpFile.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Models/BotSharpFile.cs
new file mode 100644
index 00000000..de226f58
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/Models/BotSharpFile.cs
@@ -0,0 +1,17 @@
+
+namespace BotSharp.Abstraction.Files.Models;
+
+public class BotSharpFile
+{
+ [JsonPropertyName("file_name")]
+ public string FileName { get; set; } = string.Empty;
+
+ ///
+ /// File data, e.g., "data:image/png;base64,aaaaaaaa"
+ ///
+ [JsonPropertyName("file_data")]
+ public string FileData { get; set; } = string.Empty;
+
+ [JsonPropertyName("file_url")]
+ public string FileUrl { get; set; } = string.Empty;
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs
new file mode 100644
index 00000000..3ec63fd8
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs
@@ -0,0 +1,32 @@
+namespace BotSharp.Abstraction.Files.Models;
+
+public class MessageFileModel
+{
+ [JsonPropertyName("message_id")]
+ public string MessageId { get; set; }
+
+ [JsonPropertyName("file_url")]
+ public string FileUrl { get; set; }
+
+ [JsonPropertyName("file_storage_url")]
+ public string FileStorageUrl { get; set; }
+
+ [JsonPropertyName("file_name")]
+ public string FileName { get; set; }
+
+ [JsonPropertyName("file_type")]
+ public string FileType { get; set; }
+
+ [JsonPropertyName("content_type")]
+ public string ContentType { get; set; }
+
+ public MessageFileModel()
+ {
+
+ }
+
+ public override string ToString()
+ {
+ return $"File name: {FileName}, File type: {FileType}, Content type: {ContentType}";
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs
index 3973cb9d..ffc25f33 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs
@@ -3,5 +3,11 @@ namespace BotSharp.Abstraction.Functions;
public interface IFunctionCallback
{
string Name { get; }
+
+ ///
+ /// Indicator message used to provide UI feedback for function execution
+ ///
+ string Indication => string.Empty;
+
Task Execute(RoleDialogModel message);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
index fefade10..661a1437 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs
@@ -1,4 +1,3 @@
-using BotSharp.Abstraction.Routing.Models;
using System.Text.Json;
namespace BotSharp.Abstraction.Functions.Models;
diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallingResponse.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallingResponse.cs
index d5bd5979..d437d425 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallingResponse.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallingResponse.cs
@@ -13,7 +13,7 @@ public class FunctionCallingResponse
[JsonPropertyName("content")]
public string? Content { get; set; }
- [JsonPropertyName("function_name")]
+ [JsonPropertyName("function")]
public string? FunctionName { get; set; }
[JsonPropertyName("args")]
diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs
index 8458adf6..4366d323 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs
@@ -2,8 +2,11 @@ namespace BotSharp.Abstraction.Functions.Models;
public class FunctionDef
{
- public string Name { get; set; }
- public string Description { get; set; }
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = null!;
+
+ [JsonPropertyName("description")]
+ public string Description { get; set; } = null!;
[JsonPropertyName("visibility_expression")]
public string? VisibilityExpression { get; set; }
@@ -11,6 +14,7 @@ public class FunctionDef
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Impact { get; set; }
+ [JsonPropertyName("parameters")]
public FunctionParametersDef Parameters { get; set; } = new FunctionParametersDef();
public override string ToString()
diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionParametersDef.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionParametersDef.cs
index 975a17d6..12c313fd 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionParametersDef.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionParametersDef.cs
@@ -19,6 +19,11 @@ public class FunctionParametersDef
[JsonPropertyName("required")]
public List Required { get; set; } = new List();
+ public override string ToString()
+ {
+ return $"{{\"type\":\"{Type}\", \"properties\":{JsonSerializer.Serialize(Properties)}, \"required\":[{string.Join(",", Required.Select(x => "\"" + x + "\""))}]}}";
+ }
+
public FunctionParametersDef()
{
diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs
index fd927bbc..4f948aa1 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/ParameterPropertyDef.cs
@@ -2,10 +2,11 @@ namespace BotSharp.Abstraction.Functions.Models;
public class ParameterPropertyDef : NameDesc
{
- public ParameterPropertyDef(string name, string description, string type = "string")
+ public ParameterPropertyDef(string name, string description, string type = "string", bool required = false)
: base(name, description)
{
Type = type;
+ Required = required;
}
[JsonPropertyName("required")]
diff --git a/src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleVideoResult.cs b/src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleVideoResult.cs
index 442290aa..49e50c30 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleVideoResult.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Google/Models/GoogleVideoResult.cs
@@ -3,7 +3,7 @@ namespace BotSharp.Abstraction.Google.Models;
public class GoogleVideoResult
{
public string Kind { get; set; }
- public IList Items { get; set; } = new List();
+ public List Items { get; set; } = new List();
}
public class VideoItem
@@ -25,6 +25,7 @@ public class VideoSnippet
{
public string Title { get; set; }
public string Description { get; set; }
+ public string ChannelId { get; set; }
public string ChannelTitle { get; set; }
public VideoThumbnails Thumbnails { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Google/Settings/GoogleApiSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Google/Settings/GoogleApiSettings.cs
index be7ef6a2..dfa66f60 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Google/Settings/GoogleApiSettings.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Google/Settings/GoogleApiSettings.cs
@@ -19,4 +19,5 @@ public class YoutubeSettings
public string Endpoint { get; set; }
public string Part { get; set; }
public string RegionCode { get; set; }
+ public IList Channels { get; set; }
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Http/Settings/HttpSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Http/Settings/HttpSettings.cs
new file mode 100644
index 00000000..8fc14988
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Http/Settings/HttpSettings.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.Abstraction.Http.Settings;
+
+public class HttpSettings
+{
+ public string BaseAddress { get; set; } = string.Empty;
+ public string Origin { get; set; } = string.Empty;
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/LanguageType.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/LanguageType.cs
new file mode 100644
index 00000000..64351e45
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/LanguageType.cs
@@ -0,0 +1,9 @@
+namespace BotSharp.Abstraction.Infrastructures.Enums;
+
+public class LanguageType
+{
+ public const string UNKNOWN = "Unknown";
+ public const string ENGLISH = "English";
+ public const string SPANISH = "Spanish";
+ public const string CHINESE = "Chinese";
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/StateConst.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/StateConst.cs
index a4039bf7..442c8ef8 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/StateConst.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/StateConst.cs
@@ -5,5 +5,8 @@ public class StateConst
public const string EXPECTED_ACTION_AGENT = "expected_next_action_agent";
public const string EXPECTED_GOAL_AGENT = "expected_user_goal_agent";
public const string NEXT_ACTION_AGENT = "next_action_agent";
+ public const string NEXT_ACTION_REASON = "next_action_reason";
public const string USER_GOAL_AGENT = "user_goal_agent";
+
+ public const string LANGUAGE = "language";
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/ILlmProviderService.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ILlmProviderService.cs
index 75fd60e6..20762fe0 100644
--- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/ILlmProviderService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ILlmProviderService.cs
@@ -6,6 +6,6 @@ public interface ILlmProviderService
{
LlmModelSetting GetSetting(string provider, string model);
List GetProviders();
- LlmModelSetting GetProviderModel(string provider, string id);
+ LlmModelSetting GetProviderModel(string provider, string id, bool? multiModal = null);
List GetProviderModels(string provider);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs
index 1faf3c52..b86578fe 100644
--- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/Settings/LlmModelSetting.cs
@@ -27,6 +27,11 @@ public class LlmModelSetting
public string Endpoint { get; set; }
public LlmModelType Type { get; set; } = LlmModelType.Chat;
+ ///
+ /// If true, allow sending images/vidoes to this model
+ ///
+ public bool MultiModal { get; set; }
+
///
/// Prompt cost per 1K token
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/MessageParser.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/BotSharpMessageParser.cs
similarity index 55%
rename from src/Infrastructure/BotSharp.Abstraction/Messaging/MessageParser.cs
rename to src/Infrastructure/BotSharp.Abstraction/Messaging/BotSharpMessageParser.cs
index b739709c..1f495a44 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/MessageParser.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/BotSharpMessageParser.cs
@@ -1,17 +1,17 @@
-using BotSharp.Abstraction.Messaging;
-using BotSharp.Abstraction.Messaging.Enums;
using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
using BotSharp.Abstraction.Messaging.Models.RichContent;
using System.Text.Json;
+using System.Reflection;
-namespace BotSharp.Core.Messaging;
+namespace BotSharp.Abstraction.Messaging;
-public static class MessageParser
+public static class BotSharpMessageParser
{
public static IRichMessage? ParseRichMessage(JsonElement root, JsonSerializerOptions options)
{
IRichMessage? res = null;
+ Type? targetType = null;
JsonElement element;
var jsonText = root.GetRawText();
@@ -20,47 +20,52 @@ public static class MessageParser
var richType = element.GetString();
if (richType == RichTypeEnum.ButtonTemplate)
{
- res = JsonSerializer.Deserialize(jsonText, options);
+ targetType = typeof(ButtonTemplateMessage);
}
else if (richType == RichTypeEnum.MultiSelectTemplate)
{
- res = JsonSerializer.Deserialize(jsonText, options);
+ targetType = typeof(MultiSelectTemplateMessage);
}
else if (richType == RichTypeEnum.QuickReply)
{
- res = JsonSerializer.Deserialize(jsonText, options);
+ targetType = typeof(QuickReplyMessage);
}
else if (richType == RichTypeEnum.CouponTemplate)
{
- res = JsonSerializer.Deserialize(jsonText, options);
+ targetType = typeof(CouponTemplateMessage);
}
else if (richType == RichTypeEnum.Text)
{
- res = JsonSerializer.Deserialize(jsonText, options);
+ targetType = typeof(TextMessage);
}
else if (richType == RichTypeEnum.GenericTemplate)
{
if (root.TryGetProperty("element_type", out element))
{
var elementType = element.GetString();
- if (elementType == typeof(GenericElement).Name)
+ var wrapperType = typeof(GenericTemplateMessage<>);
+ var genericType = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(x => x.Name == elementType);
+
+ if (wrapperType != null && genericType != null)
{
- res = JsonSerializer.Deserialize>(jsonText, options);
- }
- else if (elementType == typeof(ButtonElement).Name)
- {
- res = JsonSerializer.Deserialize>(jsonText, options);
+ targetType = wrapperType.MakeGenericType(genericType);
}
}
}
}
+ if (targetType != null)
+ {
+ res = JsonSerializer.Deserialize(jsonText, targetType, options) as IRichMessage;
+ }
+
return res;
}
public static ITemplateMessage? ParseTemplateMessage(JsonElement root, JsonSerializerOptions options)
{
ITemplateMessage? res = null;
+ Type? targetType = null;
JsonElement element;
var jsonText = root.GetRawText();
@@ -69,37 +74,41 @@ public static class MessageParser
var templateType = element.GetString();
if (templateType == TemplateTypeEnum.Button)
{
- res = JsonSerializer.Deserialize(jsonText, options);
+ targetType = typeof(ButtonTemplateMessage);
}
else if (templateType == TemplateTypeEnum.MultiSelect)
{
- res = JsonSerializer.Deserialize(jsonText, options);
+ targetType = typeof(MultiSelectTemplateMessage);
}
else if (templateType == TemplateTypeEnum.Coupon)
{
- res = JsonSerializer.Deserialize(jsonText, options);
+ targetType = typeof(CouponTemplateMessage);
}
else if (templateType == TemplateTypeEnum.Product)
{
- res = JsonSerializer.Deserialize(jsonText, options);
+ targetType = typeof(ProductTemplateMessage);
}
else if (templateType == TemplateTypeEnum.Generic)
{
if (root.TryGetProperty("element_type", out element))
{
var elementType = element.GetString();
- if (elementType == typeof(GenericElement).Name)
+ var wrapperType = typeof(GenericTemplateMessage<>);
+ var genericType = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(x => x.Name == elementType);
+
+ if (wrapperType != null && genericType != null)
{
- res = JsonSerializer.Deserialize>(jsonText, options);
- }
- else if (elementType == typeof(ButtonElement).Name)
- {
- res = JsonSerializer.Deserialize>(jsonText, options);
+ targetType = wrapperType.MakeGenericType(genericType);
}
}
}
}
+ if (targetType != null)
+ {
+ res = JsonSerializer.Deserialize(jsonText, targetType, options) as ITemplateMessage;
+ }
+
return res;
}
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/EditorTypeEnum.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/EditorTypeEnum.cs
index f190e5e5..9516bb13 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/EditorTypeEnum.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Enums/EditorTypeEnum.cs
@@ -12,6 +12,7 @@ public static class EditorTypeEnum
public const string DateTimePicker = "datetime-picker";
public const string DateTimeRangePicker = "datetime-range-picker";
public const string Email = "email";
+ public const string File = "file";
///
/// Regex, set the expression in editor_attributes
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/IRichMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/IRichMessage.cs
index e04692e6..605fc3f7 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/IRichMessage.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/IRichMessage.cs
@@ -1,5 +1,3 @@
-using BotSharp.Abstraction.Messaging.Enums;
-
namespace BotSharp.Abstraction.Messaging;
public interface IRichMessage
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs
index 502b0785..bc456aca 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/RichContentJsonConverter .cs
@@ -1,4 +1,3 @@
-using BotSharp.Core.Messaging;
using System.Text.Json;
namespace BotSharp.Abstraction.Messaging.JsonConverters;
@@ -9,8 +8,7 @@ public class RichContentJsonConverter : JsonConverter
{
using var jsonDoc = JsonDocument.ParseValue(ref reader);
var root = jsonDoc.RootElement;
- var jsonText = root.GetRawText();
- var res = MessageParser.ParseRichMessage(root, options);
+ var res = BotSharpMessageParser.ParseRichMessage(root, options);
return res;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/TemplateMessageJsonConverter.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/TemplateMessageJsonConverter.cs
index f0f14730..69fb6c28 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/TemplateMessageJsonConverter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/JsonConverters/TemplateMessageJsonConverter.cs
@@ -1,4 +1,3 @@
-using BotSharp.Core.Messaging;
using System.Text.Json;
namespace BotSharp.Abstraction.Messaging.JsonConverters;
@@ -9,8 +8,7 @@ public class TemplateMessageJsonConverter : JsonConverter
{
using var jsonDoc = JsonDocument.ParseValue(ref reader);
var root = jsonDoc.RootElement;
- var jsonText = root.GetRawText();
- var res = MessageParser.ParseTemplateMessage(root, options);
+ var res = BotSharpMessageParser.ParseTemplateMessage(root, options);
return res;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/ElementButton.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/ElementButton.cs
index b3642194..daf3de65 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/ElementButton.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/ElementButton.cs
@@ -5,13 +5,30 @@ namespace BotSharp.Abstraction.Messaging.Models.RichContent;
///
public class ElementButton
{
- public string Type { get; set; }
+ public string Type { get; set; } = "web_url";
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public string Url { get; set; }
+ public string? Url { get; set; }
- public string Title { get; set; }
+ [Translate]
+ public string Title { get; set; } = string.Empty;
+
+ [JsonPropertyName("description")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [Translate]
+ public string? Description { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public string Payload { get; set; }
+ public string? Payload { get; set; }
+
+ [JsonPropertyName("is_primary")]
+ public bool IsPrimary { get; set; }
+
+ [JsonPropertyName("is_secondary")]
+ public bool IsSecondary { get; set; }
+
+ [JsonPropertyName("post_action_disclaimer")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [Translate]
+ public string? PostActionDisclaimer { get; set; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyMessage.cs
index efb177f0..c81fe73e 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyMessage.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyMessage.cs
@@ -1,5 +1,3 @@
-using BotSharp.Abstraction.Messaging.Enums;
-
namespace BotSharp.Abstraction.Messaging.Models.RichContent;
public class QuickReplyMessage : IRichMessage
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/RichContent.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/RichContent.cs
index c8c3b3fe..1f812bf8 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/RichContent.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/RichContent.cs
@@ -1,5 +1,3 @@
-using BotSharp.Abstraction.Messaging.Enums;
-
namespace BotSharp.Abstraction.Messaging.Models.RichContent;
public class RichContent where T : IRichMessage
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs
index 4c92a3fb..0d8470d7 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplateMessage.cs
@@ -1,5 +1,3 @@
-using BotSharp.Abstraction.Messaging.Enums;
-
namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template;
///
@@ -11,33 +9,15 @@ public class ButtonTemplateMessage : IRichMessage, ITemplateMessage
public string RichType => RichTypeEnum.ButtonTemplate;
[JsonPropertyName("text")]
+ [Translate]
public string Text { get; set; } = string.Empty;
[JsonPropertyName("template_type")]
public string TemplateType => TemplateTypeEnum.Button;
[JsonPropertyName("buttons")]
- public ButtonElement[] Buttons { get; set; } = new ButtonElement[0];
+ public ElementButton[] Buttons { get; set; } = new ElementButton[0];
[JsonPropertyName("is_horizontal")]
public bool IsHorizontal { get; set; }
}
-
-public class ButtonElement
-{
- ///
- /// web_url, postback, phone_number
- ///
- public string Type { get; set; } = "web_url";
-
- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public string? Url { get; set; }
-
- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public string? Payload { get; set; }
-
- public string Title { get; set; } = string.Empty;
-
- [JsonPropertyName("is_primary")]
- public bool IsPrimary { get; set; }
-}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/CouponTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/CouponTemplateMessage.cs
index 1ca47b5b..a3bf116d 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/CouponTemplateMessage.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/CouponTemplateMessage.cs
@@ -1,5 +1,3 @@
-using BotSharp.Abstraction.Messaging.Enums;
-
namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template;
///
@@ -10,6 +8,7 @@ public class CouponTemplateMessage : IRichMessage, ITemplateMessage
{
[JsonPropertyName("rich_type")]
public string RichType => RichTypeEnum.CouponTemplate;
+
[JsonPropertyName("text")]
public string Text { get; set; }
public string Title { get; set; }
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/GenericTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/GenericTemplateMessage.cs
index c86bca59..f165dbc1 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/GenericTemplateMessage.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/GenericTemplateMessage.cs
@@ -1,5 +1,3 @@
-using BotSharp.Abstraction.Messaging.Enums;
-
namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template;
public class GenericTemplateMessage : IRichMessage, ITemplateMessage
@@ -8,6 +6,7 @@ public class GenericTemplateMessage : IRichMessage, ITemplateMessage
public string RichType => RichTypeEnum.GenericTemplate;
[JsonPropertyName("text")]
+ [Translate]
public string Text { get; set; } = string.Empty;
[JsonPropertyName("template_type")]
@@ -19,16 +18,24 @@ public class GenericTemplateMessage : IRichMessage, ITemplateMessage
[JsonPropertyName("is_horizontal")]
public bool IsHorizontal { get; set; }
+ [JsonPropertyName("is_popup")]
+ public bool IsPopup { get; set; }
+
[JsonPropertyName("element_type")]
public string ElementType => typeof(T).Name;
}
public class GenericElement
{
+ [Translate]
public string Title { get; set; }
+
+ [Translate]
public string Subtitle { get; set; }
+
[JsonPropertyName("image_url")]
public string ImageUrl { get; set; }
+
[JsonPropertyName("default_action")]
public ElementAction DefaultAction { get; set; }
public ElementButton[] Buttons { get; set; }
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplateMessage.cs
index 6919a1a4..4ef62fc1 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplateMessage.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplateMessage.cs
@@ -1,12 +1,12 @@
-using BotSharp.Abstraction.Messaging.Enums;
-
namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template;
public class MultiSelectTemplateMessage : IRichMessage, ITemplateMessage
{
[JsonPropertyName("rich_type")]
public string RichType => RichTypeEnum.MultiSelectTemplate;
+
[JsonPropertyName("text")]
+ [Translate]
public string Text { get; set; } = string.Empty;
[JsonPropertyName("template_type")]
@@ -21,6 +21,7 @@ public class MultiSelectTemplateMessage : IRichMessage, ITemplateMessage
public class OptionElement
{
+ [Translate]
public string Title { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty;
public string? Payload { get; set; }
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProductTemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProductTemplateMessage.cs
index 59a1e911..05af097a 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProductTemplateMessage.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ProductTemplateMessage.cs
@@ -1,5 +1,3 @@
-using BotSharp.Abstraction.Messaging.Enums;
-
namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template;
public class ProductTemplateMessage : IRichMessage, ITemplateMessage
@@ -8,6 +6,7 @@ public class ProductTemplateMessage : IRichMessage, ITemplateMessage
public string RichType => RichTypeEnum.GenericTemplate;
[JsonPropertyName("text")]
+ [Translate]
public string Text { get; set; } = string.Empty;
[JsonPropertyName("template_type")]
diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/TextMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/TextMessage.cs
index 899a9265..3a37ad54 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/TextMessage.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/TextMessage.cs
@@ -1,5 +1,3 @@
-using BotSharp.Abstraction.Messaging.Enums;
-
namespace BotSharp.Abstraction.Messaging.Models.RichContent;
public class TextMessage : IRichMessage
@@ -7,6 +5,7 @@ public class TextMessage : IRichMessage
[JsonPropertyName("rich_type")]
public string RichType => RichTypeEnum.Text;
+ [Translate]
public string Text { get; set; } = string.Empty;
public TextMessage(string text)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginMenuDef.cs b/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginMenuDef.cs
index bd5a1d88..5f8a59b9 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginMenuDef.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginMenuDef.cs
@@ -19,6 +19,9 @@ public class PluginMenuDef
[JsonIgnore]
public int Weight { get; set; }
+ [JsonIgnore]
+ public List? Roles { get; set; }
+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List? SubMenu { get; set; }
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/BotSharpDatabaseSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/BotSharpDatabaseSettings.cs
index 458aa83e..ff077296 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/BotSharpDatabaseSettings.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/BotSharpDatabaseSettings.cs
@@ -7,6 +7,7 @@ public class BotSharpDatabaseSettings : DatabaseBasicSettings
public string BotSharpMongoDb { get; set; }
public string TablePrefix { get; set; }
public DbConnectionSetting BotSharp { get; set; }
+ public string Redis { get; set; }
}
public class DatabaseBasicSettings
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Enums/RepositoryEnum.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Enums/RepositoryEnum.cs
new file mode 100644
index 00000000..f71845a5
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Enums/RepositoryEnum.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.Abstraction.Repositories.Enums;
+
+public static class RepositoryEnum
+{
+ public const string FileRepository = nameof(FileRepository);
+ public const string MongoRepository = nameof(MongoRepository);
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
index 64e92a66..6adcc8ad 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
@@ -1,7 +1,6 @@
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Repositories.Filters;
-using BotSharp.Abstraction.Repositories.Models;
using BotSharp.Abstraction.Tasks.Models;
using BotSharp.Abstraction.Users.Models;
@@ -18,10 +17,11 @@ public interface IBotSharpRepository
#endregion
#region User
- User? GetUserByEmail(string email);
- User? GetUserById(string id);
- User? GetUserByUserName(string userName);
- void CreateUser(User user);
+ User? GetUserByEmail(string email) => throw new NotImplementedException();
+ User? GetUserById(string id) => throw new NotImplementedException();
+ User? GetUserByUserName(string userName) => throw new NotImplementedException();
+ void CreateUser(User user) => throw new NotImplementedException();
+ void UpdateUserVerified(string userId) => throw new NotImplementedException();
#endregion
#region Agent
@@ -32,8 +32,10 @@ public interface IBotSharpRepository
void BulkInsertAgents(List agents);
void BulkInsertUserAgents(List userAgents);
bool DeleteAgents();
+ bool DeleteAgent(string agentId);
List GetAgentResponses(string agentId, string prefix, string intent);
string GetAgentTemplate(string agentId, string templateName);
+ bool PatchAgentTemplate(string agentId, AgentTemplate template);
#endregion
#region Agent Task
@@ -42,7 +44,7 @@ public interface IBotSharpRepository
void InsertAgentTask(AgentTask task);
void BulkInsertAgentTasks(List tasks);
void UpdateAgentTask(AgentTask task, AgentTaskField field);
- bool DeleteAgentTask(string agentId, string taskId);
+ bool DeleteAgentTask(string agentId, List taskIds);
bool DeleteAgentTasks();
#endregion
@@ -50,7 +52,6 @@ public interface IBotSharpRepository
void CreateNewConversation(Conversation conversation);
bool DeleteConversations(IEnumerable conversationIds);
List GetConversationDialogs(string conversationId);
- void UpdateConversationDialogElements(string conversationId, List updateElements);
void AppendConversationDialogs(string conversationId, List dialogs);
ConversationState GetConversationStates(string conversationId);
void UpdateConversationStates(string conversationId, List states);
@@ -58,11 +59,11 @@ public interface IBotSharpRepository
Conversation GetConversation(string conversationId);
PagedItems GetConversations(ConversationFilter filter);
void UpdateConversationTitle(string conversationId, string title);
- void UpdateConversationBreakpoint(string conversationId, string messageId, DateTime breakpoint);
- DateTime GetConversationBreakpoint(string conversationId);
+ void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint);
+ ConversationBreakpoint? GetConversationBreakpoint(string conversationId);
List GetLastConversations();
List GetIdleConversations(int batchSize, int messageLimit, int bufferHours);
- bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false);
+ IEnumerable TruncateConversation(string conversationId, string messageId, bool cleanLog = false);
#endregion
#region Execution Log
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs
index 2c0aa15e..3c93976b 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs
@@ -3,7 +3,8 @@ namespace BotSharp.Abstraction.Routing;
public interface IRoutingContext
{
string GetCurrentAgentId();
- string PreviousAgentId();
+ string FirstGoalAgentId();
+ bool ContainsAgentId(string agentId);
string OriginAgentId { get; }
string ConversationId { get; }
string MessageId { get; }
@@ -13,6 +14,7 @@ public interface IRoutingContext
int AgentCount { get; }
void Push(string agentId, string? reason = null);
void Pop(string? reason = null);
+ void PopTo(string agentId, string reason);
void Replace(string agentId, string? reason = null);
void Empty(string? reason = null);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs
index 95cddf2e..e82ea706 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs
@@ -16,5 +16,5 @@ public interface IRoutingHandler
void SetDialogs(List dialogs);
- Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message);
+ Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message, Func onFunctionExecuting);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs
index da84ffa3..5a12c0ed 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs
@@ -1,5 +1,3 @@
-using BotSharp.Abstraction.Routing.Models;
-
namespace BotSharp.Abstraction.Routing;
public interface IRoutingService
@@ -30,9 +28,9 @@ public interface IRoutingService
List GetHandlers(Agent router);
void ResetRecursiveCounter();
- Task InvokeAgent(string agentId, List dialogs);
- Task InvokeFunction(string name, RoleDialogModel message);
- Task InstructLoop(RoleDialogModel message, List dialogs);
+ Task InvokeAgent(string agentId, List dialogs, Func onFunctionExecuting);
+ Task InvokeFunction(string name, RoleDialogModel messages, Func? onFunctionExecuting = null);
+ Task InstructLoop(RoleDialogModel message, List dialogs, Func onFunctionExecuting);
///
/// Talk to a specific Agent directly, bypassing the Router
@@ -44,5 +42,5 @@ public interface IRoutingService
Task GetConversationContent(List dialogs, int maxDialogCount = 50);
- bool HasMissingRequiredField(RoleDialogModel message, out string agentId);
+ (bool, string) HasMissingRequiredField(RoleDialogModel message, out string agentId);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs
index 82de1a81..dad45cb9 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingArgs.cs
@@ -3,7 +3,7 @@ namespace BotSharp.Abstraction.Routing.Models;
public class RoutingArgs
{
[JsonPropertyName("function")]
- public string Function { get; set; }
+ public string Function { get; set; } = string.Empty;
///
/// The reason why you select this function or agent
@@ -19,30 +19,36 @@ public class RoutingArgs
[JsonPropertyName("conversation_end")]
public bool ConversationEnd { get; set; }
+ [JsonPropertyName("task_completed")]
+ public bool TaskCompleted { get; set; }
+
+ [JsonPropertyName("is_new_task")]
+ public bool IsNewTask { get; set; }
+
///
/// The content of replying to user
///
[JsonPropertyName("response")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public string Response { get; set; }
+ public string Response { get; set; } = string.Empty;
///
/// Agent for next action based on user latest response
///
[JsonPropertyName("next_action_agent")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public string AgentName { get; set; }
+ public string AgentName { get; set; } = string.Empty;
///
/// Agent who can achieve user original goal
///
[JsonPropertyName("user_goal_agent")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public string OriginalAgent { get; set; }
+ public string OriginalAgent { get; set; } = string.Empty;
[JsonPropertyName("user_goal_description")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public string UserGoal { get; set; }
+ public string UserGoal { get; set; } = string.Empty;
public override string ToString()
{
diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs
index c8bffe6c..14c02034 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs
@@ -7,5 +7,6 @@ public interface IExecutor
Task Execute(IRoutingService routing,
FunctionCallFromLlm inst,
RoleDialogModel message,
- List dialogs);
+ List dialogs,
+ Func onFunctionExecuting);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Translation/Attributes/TranslateAttribute.cs b/src/Infrastructure/BotSharp.Abstraction/Translation/Attributes/TranslateAttribute.cs
new file mode 100644
index 00000000..1c39c415
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Translation/Attributes/TranslateAttribute.cs
@@ -0,0 +1,10 @@
+namespace BotSharp.Abstraction.Translation.Attributes;
+
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false)]
+public class TranslateAttribute : Attribute
+{
+ public TranslateAttribute()
+ {
+
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Translation/ITranslationService.cs b/src/Infrastructure/BotSharp.Abstraction/Translation/ITranslationService.cs
new file mode 100644
index 00000000..e69e35b1
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Translation/ITranslationService.cs
@@ -0,0 +1,6 @@
+namespace BotSharp.Abstraction.Translation;
+
+public interface ITranslationService
+{
+ Task Translate(Agent router, string messageId, T data, string language = "Spanish", bool clone = true) where T : class;
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationInput.cs b/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationInput.cs
new file mode 100644
index 00000000..6897ca42
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationInput.cs
@@ -0,0 +1,10 @@
+namespace BotSharp.Abstraction.Translation.Models;
+
+public class TranslationInput
+{
+ [JsonPropertyName("id")]
+ public int Id { get; set; } = -1;
+
+ [JsonPropertyName("text")]
+ public string Text { get; set; } = null!;
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationOutput.cs b/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationOutput.cs
new file mode 100644
index 00000000..b15bfef4
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationOutput.cs
@@ -0,0 +1,13 @@
+namespace BotSharp.Abstraction.Translation.Models;
+
+public class TranslationOutput
+{
+ [JsonPropertyName("input_lang")]
+ public string InputLanguage { get; set; } = null!;
+
+ [JsonPropertyName("output_lang")]
+ public string OutputLanguage { get; set; } = LanguageType.ENGLISH;
+
+ [JsonPropertyName("texts")]
+ public TranslationInput[] Texts { get; set; } = Array.Empty();
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
index 33b8086f..0de10328 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
@@ -8,4 +8,5 @@ public interface IAuthenticationHook
Task Authenticate(string id, string password);
void AddClaims(List claims);
void BeforeSending(Token token);
+ Task UserCreated(User user);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs
index a0998117..4fecb48c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs
@@ -8,4 +8,5 @@ public interface IUserIdentity
string FirstName { get; }
string LastName { get; }
string FullName { get; }
+ string? UserLanguage { get; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
index 35d74aa4..722917cc 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
@@ -1,4 +1,5 @@
using BotSharp.Abstraction.Users.Models;
+using BotSharp.OpenAPI.ViewModels.Users;
namespace BotSharp.Abstraction.Users;
@@ -6,6 +7,9 @@ public interface IUserService
{
Task GetUser(string id);
Task CreateUser(User user);
- Task GetToken(string authorization);
+ Task ActiveUser(UserActivationModel model);
+ Task GetToken(string authorization);
Task GetMyProfile();
+ Task VerifyUserNameExisting(string userName);
+ Task VerifyEmailExisting(string email);
}
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
index 7ca441fe..4e6ca265 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
@@ -14,6 +14,8 @@ public class User
public string Source { get; set; } = "internal";
public string? ExternalId { get; set; }
public string Role { get; set; } = UserRole.Client;
+ public string? VerificationCode { get; set; }
+ public bool Verified { get; set; }
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserActivationModel.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserActivationModel.cs
new file mode 100644
index 00000000..904bd936
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserActivationModel.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.OpenAPI.ViewModels.Users;
+
+public class UserActivationModel
+{
+ public string UserName { get; set; }
+ public string VerificationCode { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Settings/AccountSetting.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Settings/AccountSetting.cs
new file mode 100644
index 00000000..06176ce6
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Settings/AccountSetting.cs
@@ -0,0 +1,9 @@
+namespace BotSharp.Abstraction.Users.Settings;
+
+public class AccountSetting
+{
+ ///
+ /// Whether to enable verification code to verify the authenticity of new users
+ ///
+ public bool NewUserVerification { get; set; }
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Using.cs b/src/Infrastructure/BotSharp.Abstraction/Using.cs
index f9b708dc..a5bbc188 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Using.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Using.cs
@@ -14,3 +14,6 @@ global using BotSharp.Abstraction.Models;
global using BotSharp.Abstraction.Routing.Models;
global using BotSharp.Abstraction.Routing.Planning;
global using BotSharp.Abstraction.Templating;
+global using BotSharp.Abstraction.Translation.Attributes;
+global using BotSharp.Abstraction.Messaging.Enums;
+global using BotSharp.Abstraction.Files.Models;
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs
index d11752f6..9ada5f63 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/Pagination.cs
@@ -13,19 +13,26 @@ public class Pagination
public int Size
{
- get
+ get
{
- if (_size <= 0) return 20;
- if (_size > 100) return 100;
-
- return _size;
- }
- set
+ return _size > 0 ? _size : 1;
+ }
+ set
{
_size = value;
- }
+ }
}
+ ///
+ /// Sort by field
+ ///
+ public string? Sort { get; set; }
+
+ ///
+ /// Sort order: asc or desc
+ ///
+ public string Order { get; set; } = "asc";
+
public int Offset
{
get { return (Page - 1) * Size; }
diff --git a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs
index 7df5fa02..83ce823c 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs
@@ -1,6 +1,7 @@
using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Settings;
+using BotSharp.Abstraction.Users.Enums;
using Microsoft.Extensions.Configuration;
namespace BotSharp.Core.Agents;
@@ -14,6 +15,13 @@ public class AgentPlugin : IBotSharpPlugin
public SettingsMeta Settings =>
new SettingsMeta("Agent");
+ public string[] AgentIds => new string[]
+ {
+ "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
+ "01e2fc5c-2c89-4ec7-8470-7688608b496c",
+ "01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b"
+ };
+
public object GetNewSettingsInstance() =>
new AgentSettings();
@@ -36,8 +44,8 @@ public class AgentPlugin : IBotSharpPlugin
{
SubMenu = new List
{
- new PluginMenuDef("Routing", link: "page/agent/router"), // icon: "bx bx-map-pin"
- new PluginMenuDef("Evaluating", link: "page/agent/evaluator"), // icon: "bx bx-task"
+ new PluginMenuDef("Routing", link: "page/agent/router") { Roles = new List { UserRole.Admin } }, // icon: "bx bx-map-pin"
+ new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List { UserRole.Admin } }, // icon: "bx bx-task"
new PluginMenuDef("Agents", link: "page/agent"), // icon: "bx bx-bot"
}
});
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
index ce6512af..ae429484 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs
@@ -1,8 +1,4 @@
-using BotSharp.Abstraction.Agents.Models;
-using BotSharp.Abstraction.Functions.Models;
-using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Tasks.Models;
-using BotSharp.Abstraction.Users.Models;
using System.IO;
using System.Text.RegularExpressions;
@@ -26,32 +22,13 @@ public partial class AgentService
var dbSettings = _services.GetRequiredService();
var agentSettings = _services.GetRequiredService();
- var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir);
- var foundAgent = FetchAgentFileByName(agent.Name, filePath);
-
- if (foundAgent != null)
- {
- agentRecord.SetId(foundAgent.Id)
- .SetName(foundAgent.Name)
- .SetDescription(foundAgent.Description)
- .SetIsPublic(foundAgent.IsPublic)
- .SetDisabled(foundAgent.Disabled)
- .SetAgentType(foundAgent.Type)
- .SetProfiles(foundAgent.Profiles)
- .SetRoutingRules(foundAgent.RoutingRules)
- .SetInstruction(foundAgent.Instruction)
- .SetTemplates(foundAgent.Templates)
- .SetFunctions(foundAgent.Functions)
- .SetResponses(foundAgent.Responses)
- .SetLlmConfig(foundAgent.LlmConfig);
- }
var user = _db.GetUserById(_user.Id);
var userAgentRecord = new UserAgent
{
Id = Guid.NewGuid().ToString(),
UserId = user.Id,
- AgentId = foundAgent?.Id ?? agentRecord.Id,
+ AgentId = agentRecord.Id,
Editable = false,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
@@ -65,7 +42,7 @@ public partial class AgentService
Utilities.ClearCache();
- return agentRecord;
+ return await Task.FromResult(agentRecord);
}
private Agent FetchAgentFileByName(string agentName, string filePath)
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs
index 23111fd2..1fe5c6e1 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs
@@ -1,9 +1,20 @@
+using BotSharp.Abstraction.Users.Enums;
+
namespace BotSharp.Core.Agents.Services;
public partial class AgentService
{
public async Task DeleteAgent(string id)
{
- throw new NotImplementedException();
+ var user = _db.GetUserById(_user.Id);
+ var agent = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Id.IsEqualTo(id));
+
+ if (user?.Role != UserRole.Admin && agent == null)
+ {
+ return false;
+ }
+
+ var deleted = _db.DeleteAgent(id);
+ return await Task.FromResult(deleted);
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs
index 9c3b34f9..0b61977e 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs
@@ -1,55 +1,89 @@
-using BotSharp.Abstraction.Tasks.Models;
+using BotSharp.Abstraction.Repositories.Enums;
using System.IO;
namespace BotSharp.Core.Agents.Services;
public partial class AgentService
{
- public async Task RefreshAgents()
+ public async Task RefreshAgents()
{
- var isAgentDeleted = _db.DeleteAgents();
- var isTaskDeleted = _db.DeleteAgentTasks();
- if (!isAgentDeleted) return;
-
+ string refreshResult;
var dbSettings = _services.GetRequiredService();
+ if (dbSettings.Default == RepositoryEnum.FileRepository)
+ {
+ refreshResult = $"Invalid database repository setting: {dbSettings.Default}";
+ _logger.LogWarning(refreshResult);
+ return refreshResult;
+ }
+
var agentDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
dbSettings.FileRepository,
_agentSettings.DataDir);
+ if (!Directory.Exists(agentDir))
+ {
+ refreshResult = $"Cannot find the directory: {agentDir}";
+ return refreshResult;
+ }
+
var user = _db.GetUserById(_user.Id);
- var agents = new List();
- var userAgents = new List();
- var agentTasks = new List();
+ var refreshedAgents = new List();
foreach (var dir in Directory.GetDirectories(agentDir))
{
- var agentJson = File.ReadAllText(Path.Combine(dir, "agent.json"));
- var agent = JsonSerializer.Deserialize(agentJson, _options);
- if (agent == null) continue;
+ try
+ {
+ var agentJson = File.ReadAllText(Path.Combine(dir, "agent.json"));
+ var agent = JsonSerializer.Deserialize(agentJson, _options);
+
+ if (agent == null)
+ {
+ _logger.LogError($"Cannot find agent in file directory: {dir}");
+ continue;
+ }
- var functions = FetchFunctionsFromFile(dir);
- var instruction = FetchInstructionFromFile(dir);
- var responses = FetchResponsesFromFile(dir);
- var templates = FetchTemplatesFromFile(dir);
- var samples = FetchSamplesFromFile(dir);
- agent.SetInstruction(instruction)
- .SetTemplates(templates)
- .SetFunctions(functions)
- .SetResponses(responses)
- .SetSamples(samples);
- agents.Add(agent);
+ var functions = FetchFunctionsFromFile(dir);
+ var instruction = FetchInstructionFromFile(dir);
+ var responses = FetchResponsesFromFile(dir);
+ var templates = FetchTemplatesFromFile(dir);
+ var samples = FetchSamplesFromFile(dir);
+ agent.SetInstruction(instruction)
+ .SetTemplates(templates)
+ .SetFunctions(functions)
+ .SetResponses(responses)
+ .SetSamples(samples);
- var userAgent = BuildUserAgent(agent.Id, user.Id);
- userAgents.Add(userAgent);
+ var userAgent = BuildUserAgent(agent.Id, user.Id);
+ var tasks = FetchTasksFromFile(dir);
- var tasks = FetchTasksFromFile(dir);
- agentTasks.AddRange(tasks);
+ var isAgentDeleted = _db.DeleteAgent(agent.Id);
+ if (isAgentDeleted)
+ {
+ await Task.Delay(100);
+ _db.BulkInsertAgents(new List { agent });
+ _db.BulkInsertUserAgents(new List { userAgent });
+ _db.BulkInsertAgentTasks(tasks);
+ refreshedAgents.Add(agent.Name);
+ _logger.LogInformation($"Agent {agent.Name} has been migrated.");
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"Failed to migrate agent in file directory: {dir}\r\nError: {ex.Message}");
+ }
}
- _db.BulkInsertAgents(agents);
- _db.BulkInsertUserAgents(userAgents);
- _db.BulkInsertAgentTasks(agentTasks);
+ if (!refreshedAgents.IsNullOrEmpty())
+ {
+ Utilities.ClearCache();
+ refreshResult = $"Agents are migrated!\r\n{string.Join("\r\n", refreshedAgents)}";
+ }
+ else
+ {
+ refreshResult = "No agent gets refreshed!";
+ }
- Utilities.ClearCache();
+ _logger.LogInformation(refreshResult);
+ return refreshResult;
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
index 9ce7a7a8..a0b2f130 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
@@ -1,5 +1,6 @@
using BotSharp.Abstraction.Loggers;
using BotSharp.Abstraction.Templating;
+using Newtonsoft.Json.Linq;
namespace BotSharp.Core.Agents.Services;
@@ -32,6 +33,64 @@ public partial class AgentService
return true;
}
+ public FunctionParametersDef? RenderFunctionProperty(Agent agent, FunctionDef def)
+ {
+ var parameterDef = def?.Parameters;
+ var propertyDef = parameterDef?.Properties;
+ if (propertyDef == null) return null;
+
+ var visibleExpress = "visibility_expression";
+ var root = propertyDef.RootElement;
+ var iterator = root.EnumerateObject();
+ var visibleProps = new List();
+ while (iterator.MoveNext())
+ {
+ var prop = iterator.Current;
+ var name = prop.Name;
+ var node = prop.Value;
+ var matched = true;
+ if (node.TryGetProperty(visibleExpress, out var element))
+ {
+ var expression = element.GetString();
+ var render = _services.GetRequiredService();
+ var result = render.Render(expression, new Dictionary
+ {
+ { "states", agent.TemplateDict }
+ });
+ matched = result == "visible";
+ }
+
+ if (matched)
+ {
+ visibleProps.Add(name);
+ }
+ }
+
+ var rootObject = JObject.Parse(root.GetRawText());
+ var clonedRoot = rootObject.DeepClone() as JObject;
+ var required = parameterDef?.Required ?? new List();
+ foreach (var property in rootObject.Properties())
+ {
+ if (visibleProps.Contains(property.Name))
+ {
+ var value = clonedRoot.GetValue(property.Name) as JObject;
+ if (value != null && value.ContainsKey(visibleExpress))
+ {
+ value.Remove(visibleExpress);
+ }
+ }
+ else
+ {
+ clonedRoot.Remove(property.Name);
+ required.Remove(property.Name);
+ }
+ }
+
+ parameterDef.Properties = JsonSerializer.Deserialize(clonedRoot.ToString());
+ parameterDef.Required = required;
+ return parameterDef; ;
+ }
+
public string RenderedTemplate(Agent agent, string templateName)
{
// render liquid template
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
index 4577af6d..af6cb65e 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs
@@ -1,7 +1,6 @@
-using BotSharp.Abstraction.Agents.Models;
-using BotSharp.Abstraction.Functions.Models;
-using BotSharp.Abstraction.Repositories;
+using BotSharp.Abstraction.Repositories.Enums;
using BotSharp.Abstraction.Routing.Models;
+using BotSharp.Abstraction.Users.Enums;
using System.IO;
namespace BotSharp.Core.Agents.Services;
@@ -10,6 +9,10 @@ public partial class AgentService
{
public async Task UpdateAgent(Agent agent, AgentField updateField)
{
+ var userService = _services.GetRequiredService();
+ var user = await userService.GetUser(_user.Id);
+ if (user?.Role != UserRole.Admin) return;
+
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
var record = _db.GetAgent(agent.Id);
@@ -39,21 +42,41 @@ public partial class AgentService
await Task.CompletedTask;
}
- public async Task UpdateAgentFromFile(string id)
+ public async Task UpdateAgentFromFile(string id)
{
- var agent = _db.GetAgent(id);
-
- if (agent == null) return;
-
+ string updateResult;
var dbSettings = _services.GetRequiredService();
var agentSettings = _services.GetRequiredService();
+
+ if (dbSettings.Default == RepositoryEnum.FileRepository)
+ {
+ updateResult = $"Invalid database repository setting: {dbSettings.Default}";
+ _logger.LogWarning(updateResult);
+ return updateResult;
+ }
+
+ var agent = _db.GetAgent(id);
+ if (agent == null)
+ {
+ updateResult = $"Cannot find agent ${id}";
+ _logger.LogError(updateResult);
+ return updateResult;
+ }
+
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
dbSettings.FileRepository,
agentSettings.DataDir);
var clonedAgent = Agent.Clone(agent);
var foundAgent = FetchAgentFileById(agent.Id, filePath);
- if (foundAgent != null)
+ if (foundAgent == null)
+ {
+ updateResult = $"Cannot find agent {agent.Name} in file directory: {filePath}";
+ _logger.LogError(updateResult);
+ return updateResult;
+ }
+
+ try
{
clonedAgent.SetId(foundAgent.Id)
.SetName(foundAgent.Name)
@@ -71,15 +94,77 @@ public partial class AgentService
.SetLlmConfig(foundAgent.LlmConfig);
_db.UpdateAgent(clonedAgent, AgentField.All);
-
Utilities.ClearCache();
- }
- await Task.CompletedTask;
+ updateResult = $"Agent {agent.Name} has been migrated!";
+ _logger.LogInformation(updateResult);
+ return updateResult;
+ }
+ catch (Exception ex)
+ {
+ updateResult = $"Failed to migrate agent {agent.Name} in file directory {filePath}.\r\nError: {ex.Message}";
+ _logger.LogError(updateResult);
+ return updateResult;
+ }
}
- private Agent FetchAgentFileById(string agentId, string filePath)
+
+ public async Task PatchAgentTemplate(Agent agent)
{
+ var patchResult = string.Empty;
+ if (agent == null || agent.Templates.IsNullOrEmpty())
+ {
+ patchResult = $"Null agent instance or empty input templates";
+ _logger.LogWarning(patchResult);
+ return patchResult;
+ }
+
+ var record = _db.GetAgent(agent.Id);
+ if (record == null)
+ {
+ patchResult = $"Cannot find agent {agent.Id}";
+ _logger.LogWarning(patchResult);
+ return patchResult;
+ }
+
+ var successTemplates = new List();
+ var failTemplates = new List();
+ foreach (var template in agent.Templates)
+ {
+ if (template == null) continue;
+
+ var result = _db.PatchAgentTemplate(agent.Id, template);
+ if (result)
+ {
+ successTemplates.Add(template.Name);
+ _logger.LogInformation($"Template {template.Name} is updated successfully!");
+ }
+ else
+ {
+ failTemplates.Add(template.Name);
+ _logger.LogWarning($"Template {template.Name} is failed to be updated!");
+ }
+ }
+
+ Utilities.ClearCache();
+
+ if (!successTemplates.IsNullOrEmpty())
+ {
+ patchResult += $"Success templates:\n{string.Join('\n', successTemplates)}\n\n";
+ }
+
+ if (!failTemplates.IsNullOrEmpty())
+ {
+ patchResult += $"Failed templates:\n{string.Join('\n', failTemplates)}";
+ }
+
+ return patchResult;
+ }
+
+ private Agent? FetchAgentFileById(string agentId, string filePath)
+ {
+ if (!Directory.Exists(filePath)) return null;
+
foreach (var dir in Directory.GetDirectories(filePath))
{
var agentJson = File.ReadAllText(Path.Combine(dir, "agent.json"));
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
index bd009db0..da1b8cf1 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
@@ -47,4 +47,10 @@ public partial class AgentService : IAgentService
}
return dir;
}
+
+ public List GetAgentsByUser(string userId)
+ {
+ var agents = _db.GetAgentsByUser(userId);
+ return agents;
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
index da47ccb5..10e21b3c 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
+++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj
@@ -46,11 +46,17 @@
+
+
+
+
+
+
@@ -59,6 +65,7 @@
+
@@ -67,6 +74,21 @@
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
PreserveNewest
@@ -106,6 +128,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
@@ -118,6 +143,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
@@ -133,9 +161,11 @@
-
-
+
+
+
+
diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
index 2b56281c..2dd2dc7f 100644
--- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
+++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs
@@ -5,6 +5,7 @@ using BotSharp.Core.Plugins;
using BotSharp.Abstraction.Settings;
using BotSharp.Abstraction.Options;
using BotSharp.Abstraction.Messaging.JsonConverters;
+using BotSharp.Abstraction.Users.Settings;
namespace BotSharp.Core;
@@ -14,9 +15,11 @@ public static class BotSharpCoreExtensions
{
services.AddScoped();
services.AddScoped();
+ services.AddSingleton();
RegisterPlugins(services, config);
ConfigureBotSharpOptions(services, configOptions);
+
return services;
}
@@ -82,6 +85,10 @@ public static class BotSharpCoreExtensions
return settingService.Bind("PluginLoader");
});
+ var accountSettings = new AccountSetting();
+ config.Bind("Account", accountSettings);
+ services.AddScoped(x => accountSettings);
+
var loader = new PluginLoader(services, config, pluginSettings);
loader.Load(assembly =>
{
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
index 5c215884..90eb3298 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs
@@ -1,13 +1,17 @@
+using BotSharp.Abstraction.Files;
+using BotSharp.Abstraction.Google.Settings;
using BotSharp.Abstraction.Instructs;
using BotSharp.Abstraction.Messaging;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Abstraction.Settings;
using BotSharp.Abstraction.Templating;
+using BotSharp.Core.Files;
using BotSharp.Core.Instructs;
using BotSharp.Core.Messaging;
using BotSharp.Core.Routing.Planning;
using BotSharp.Core.Templating;
+using BotSharp.Core.Translation;
using Microsoft.Extensions.Configuration;
namespace BotSharp.Core.Conversations;
@@ -31,10 +35,17 @@ public class ConversationPlugin : IBotSharpPlugin
return settingService.Bind("Conversation");
});
+ services.AddScoped(provider =>
+ {
+ var settingService = provider.GetRequiredService();
+ return settingService.Bind("GoogleApi");
+ });
+
services.AddScoped();
services.AddScoped();
services.AddScoped();
- services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
// Rich content messaging
services.AddScoped();
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationAttachmentService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationAttachmentService.cs
deleted file mode 100644
index b0625fa8..00000000
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationAttachmentService.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using BotSharp.Abstraction.Repositories;
-using System.IO;
-
-namespace BotSharp.Core.Conversations.Services;
-
-public class ConversationAttachmentService : IConversationAttachmentService
-{
- private readonly BotSharpDatabaseSettings _dbSettings;
- private readonly IServiceProvider _services;
-
- public ConversationAttachmentService(
- BotSharpDatabaseSettings dbSettings,
- IServiceProvider services)
- {
- _dbSettings = dbSettings;
- _services = services;
- }
-
- public string GetDirectory(string conversationId)
- {
- var dir = Path.Combine(_dbSettings.FileRepository, "conversations", conversationId, "attachments");
- if (!Directory.Exists(dir))
- {
- Directory.CreateDirectory(dir);
- }
- return dir;
- }
-}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
index 9d01f355..d6940fe1 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs
@@ -27,7 +27,6 @@ public partial class ConversationService
#endif
message.CurrentAgentId = agent.Id;
- message.CreatedAt = DateTime.UtcNow;
if (string.IsNullOrEmpty(message.SenderId))
{
message.SenderId = _user.Id;
@@ -47,6 +46,17 @@ public partial class ConversationService
routing.Context.SetMessageId(_conversationId, message.MessageId);
routing.Context.Push(agent.Id);
+ // Save message files
+ var fileService = _services.GetRequiredService();
+ fileService.SaveMessageFiles(_conversationId, message.MessageId, message.Files);
+ message.Files?.Clear();
+
+ // Save payload
+ if (replyMessage != null && !string.IsNullOrEmpty(replyMessage.Payload))
+ {
+ message.Payload = replyMessage.Payload;
+ }
+
// Before chat completion hook
foreach (var hook in hooks)
{
@@ -71,19 +81,13 @@ public partial class ConversationService
}
}
- // Persist to storage
- _storage.Append(_conversationId, message);
-
- // Add to thread
- dialogs.Add(RoleDialogModel.From(message));
-
if (!stopCompletion)
{
// Routing with reasoning
var settings = _services.GetRequiredService();
response = agent.Type == AgentType.Routing ?
- await routing.InstructLoop(message, dialogs) :
+ await routing.InstructLoop(message, dialogs, onFunctionExecuting) :
await routing.InstructDirect(agent, message);
routing.ResetRecursiveCounter();
@@ -136,7 +140,7 @@ public partial class ConversationService
response.RichContent is RichContent template &&
string.IsNullOrEmpty(template.Message.Text))
{
- template.Message.Text = response.Content;
+ template.Message.Text = response.SecondaryContent ?? response.Content;
}
// Only read content from RichContent for UI rendering. When richContent is null, create a basic text message for richContent.
@@ -144,30 +148,40 @@ public partial class ConversationService
response.RichContent = response.RichContent ?? new RichContent
{
Recipient = new Recipient { Id = state.GetConversationId() },
- Message = new TextMessage(response.Content)
+ Message = new TextMessage(response.SecondaryContent ?? response.Content)
};
- var hooks = _services.GetServices().ToList();
+ // Patch return function name
+ if (response.PostbackFunctionName != null)
+ {
+ response.FunctionName = response.PostbackFunctionName;
+ }
if (response.Instruction != null)
{
var conversation = _services.GetRequiredService();
var updatedConversation = await conversation.UpdateConversationTitle(_conversationId, response.Instruction.NextActionReason);
+ // Emit conversation task completed hook
+ if (response.Instruction.TaskCompleted)
+ {
+ await HookEmitter.Emit(_services, async hook =>
+ await hook.OnTaskCompleted(response)
+ );
+ }
+
// Emit conversation ending hook
if (response.Instruction.ConversationEnd)
{
- foreach (var hook in hooks)
- {
- await hook.OnConversationEnding(response);
- }
+ await HookEmitter.Emit(_services, async hook =>
+ await hook.OnConversationEnding(response)
+ );
}
}
- foreach (var hook in hooks)
- {
- await hook.OnResponseGenerated(response);
- }
+ await HookEmitter.Emit(_services, async hook =>
+ await hook.OnResponseGenerated(response)
+ );
await onResponseReceived(response);
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs
new file mode 100644
index 00000000..2e7657ba
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs
@@ -0,0 +1,119 @@
+using BotSharp.Abstraction.MLTasks;
+using BotSharp.Abstraction.Templating;
+
+namespace BotSharp.Core.Conversations.Services;
+
+public partial class ConversationService
+{
+ public async Task GetConversationSummary(IEnumerable conversationIds)
+ {
+ if (conversationIds.IsNullOrEmpty()) return string.Empty;
+
+ var routing = _services.GetRequiredService();
+ var agentService = _services.GetRequiredService();
+
+ var contents = new List();
+ foreach ( var conversationId in conversationIds)
+ {
+ if (string.IsNullOrEmpty(conversationId)) continue;
+
+ var dialogs = _storage.GetDialogs(conversationId);
+
+ if (dialogs.IsNullOrEmpty()) continue;
+
+ var content = GetConversationContent(dialogs);
+ if (string.IsNullOrWhiteSpace(content)) continue;
+
+ contents.Add(content);
+ }
+
+ if (contents.IsNullOrEmpty()) return string.Empty;
+
+ var router = await agentService.LoadAgent(AIAssistant);
+ var prompt = GetPrompt(router, contents);
+ var summary = await Summarize(router, prompt);
+
+ return summary;
+ }
+
+ private string GetPrompt(Agent agent, List contents)
+ {
+ var template = agent.Templates.First(x => x.Name == "conversation.summary").Content;
+ var render = _services.GetRequiredService();
+
+ var texts = new List();
+ for (int i = 0; i < contents.Count; i++)
+ {
+ texts.Add($"{contents[i]}");
+ }
+
+ return render.Render(template, new Dictionary
+ {
+ { "texts", texts }
+ });
+ }
+
+ private async Task Summarize(Agent agent, string prompt)
+ {
+ var provider = "openai";
+ string? model;
+
+ var providerService = _services.GetRequiredService();
+ var modelSettings = providerService.GetProviderModels(provider);
+ var modelSetting = modelSettings.FirstOrDefault(x => x.Name.IsEqualTo("gpt4-turbo") || x.Name.IsEqualTo("gpt-4o"));
+
+ if (modelSetting != null)
+ {
+ model = modelSetting.Name;
+ }
+ else
+ {
+ provider = agent?.LlmConfig?.Provider;
+ model = agent?.LlmConfig?.Model;
+ if (provider == null || model == null)
+ {
+ var agentSettings = _services.GetRequiredService();
+ provider = agentSettings.LlmConfig.Provider;
+ model = agentSettings.LlmConfig.Model;
+ }
+ }
+
+ var chatCompletion = CompletionProvider.GetChatCompletion(_services, provider, model);
+ var response = await chatCompletion.GetChatCompletions(new Agent
+ {
+ Id = agent.Id,
+ Name = agent.Name,
+ Instruction = prompt
+ }, new List
+ {
+ new RoleDialogModel(AgentRole.User, "Please summarize the conversations.")
+ });
+
+ return response.Content;
+ }
+
+ private string GetConversationContent(List dialogs, int maxDialogCount = 50)
+ {
+ var conversation = "";
+
+ foreach (var dialog in dialogs.TakeLast(maxDialogCount))
+ {
+ var role = dialog.Role;
+ if (role == AgentRole.Function) continue;
+
+ if (role != AgentRole.User)
+ {
+ role = AgentRole.Assistant;
+ }
+
+ conversation += $"{role}: {dialog.Payload ?? dialog.Content}\r\n";
+ }
+
+ if (string.IsNullOrEmpty(conversation))
+ {
+ return string.Empty;
+ }
+
+ return conversation + "\r\n";
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs
index e4b81f31..451cdeed 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.TruncateMessage.cs
@@ -2,15 +2,19 @@ namespace BotSharp.Core.Conversations.Services;
public partial class ConversationService : IConversationService
{
- public async Task TruncateConversation(string conversationId, string messageId)
+ public async Task TruncateConversation(string conversationId, string messageId, string? newMessageId = null)
{
var db = _services.GetRequiredService();
- var isSaved = db.TruncateConversation(conversationId, messageId, true);
+ var fileService = _services.GetRequiredService();
+ var deleteMessageIds = db.TruncateConversation(conversationId, messageId, cleanLog: true);
+
+ fileService.DeleteMessageFiles(conversationId, deleteMessageIds, messageId, newMessageId);
+
var hooks = _services.GetServices().ToList();
foreach (var hook in hooks)
{
await hook.OnMessageDeleted(conversationId, messageId);
}
- return await Task.FromResult(isSaved);
+ return await Task.FromResult(true);
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs
index ea10dac0..8f88f44f 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs
@@ -1,19 +1,34 @@
+using BotSharp.Abstraction.Infrastructures.Enums;
+
namespace BotSharp.Core.Conversations.Services;
public partial class ConversationService : IConversationService
{
- public async Task UpdateBreakpoint(bool resetStates = false)
+ public async Task UpdateBreakpoint(bool resetStates = false, string? reason = null, params string[] excludedStates)
{
var db = _services.GetRequiredService();
var routingCtx = _services.GetRequiredService();
var messageId = routingCtx.MessageId;
- db.UpdateConversationBreakpoint(_conversationId, messageId, DateTime.UtcNow);
+
+ db.UpdateConversationBreakpoint(_conversationId, new ConversationBreakpoint
+ {
+ MessageId = messageId,
+ Breakpoint = DateTime.UtcNow,
+ Reason = reason
+ });
// Reset states
if (resetStates)
{
var states = _services.GetRequiredService();
- states.CleanStates();
+ // keep language state
+ if (excludedStates == null) excludedStates = new string[] { };
+ if (!excludedStates.Contains(StateConst.LANGUAGE))
+ {
+ excludedStates = excludedStates.Append(StateConst.LANGUAGE).ToArray();
+ }
+
+ states.CleanStates(excludedStates);
}
var hooks = _services.GetServices()
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
index 8b3bb0fc..8e113226 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.Models;
namespace BotSharp.Core.Conversations.Services;
@@ -11,6 +12,8 @@ public partial class ConversationService : IConversationService
private readonly IConversationStorage _storage;
private readonly IConversationStateService _state;
private string _conversationId;
+ private const string AIAssistant = "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a";
+
public string ConversationId => _conversationId;
public IConversationStateService States => _state;
@@ -34,7 +37,9 @@ public partial class ConversationService : IConversationService
public async Task DeleteConversations(IEnumerable ids)
{
var db = _services.GetRequiredService();
+ var fileService = _services.GetRequiredService();
var isDeleted = db.DeleteConversations(ids);
+ fileService.DeleteConversationFiles(ids);
return await Task.FromResult(isDeleted);
}
@@ -114,7 +119,14 @@ public partial class ConversationService : IConversationService
{
var db = _services.GetRequiredService();
var breakpoint = db.GetConversationBreakpoint(_conversationId);
- dialogs = dialogs.Where(x => x.CreatedAt >= breakpoint).ToList();
+ if (breakpoint != null)
+ {
+ dialogs = dialogs.Where(x => x.CreatedAt >= breakpoint.Breakpoint).ToList();
+ if (!string.IsNullOrEmpty(breakpoint.Reason))
+ {
+ dialogs.Insert(0, new RoleDialogModel(AgentRole.User, breakpoint.Reason));
+ }
+ }
}
return dialogs
@@ -126,6 +138,6 @@ public partial class ConversationService : IConversationService
{
_conversationId = conversationId;
_state.Load(_conversationId);
- states.ForEach(x => _state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds));
+ states.ForEach(x => _state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
index 9356e2f5..57f09c71 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.Users.Enums;
namespace BotSharp.Core.Conversations.Services;
@@ -9,9 +10,16 @@ public class ConversationStateService : IConversationStateService, IDisposable
{
private readonly ILogger _logger;
private readonly IServiceProvider _services;
- private ConversationState _states;
- private string _conversationId;
private readonly IBotSharpRepository _db;
+ private string _conversationId;
+ ///
+ /// States in the current round of conversation
+ ///
+ private ConversationState _curStates;
+ ///
+ /// States in the previous rounds of conversation
+ ///
+ private ConversationState _historyStates;
public ConversationStateService(ILogger logger,
IServiceProvider services,
@@ -20,7 +28,8 @@ public class ConversationStateService : IConversationStateService, IDisposable
_logger = logger;
_services = services;
_db = db;
- _states = new ConversationState();
+ _curStates = new ConversationState();
+ _historyStates = new ConversationState();
}
public string GetConversationId() => _conversationId;
@@ -33,7 +42,8 @@ public class ConversationStateService : IConversationStateService, IDisposable
///
/// whether the state is related to message or not
///
- public IConversationStateService SetState(string name, T value, bool isNeedVersion = true, int activeRounds = -1)
+ public IConversationStateService SetState(string name, T value, bool isNeedVersion = true,
+ int activeRounds = -1, string valueType = StateDataType.String, string source = StateSource.User, bool readOnly = false)
{
if (value == null)
{
@@ -46,34 +56,41 @@ public class ConversationStateService : IConversationStateService, IDisposable
var curActiveRounds = activeRounds > 0 ? activeRounds : -1;
int? preActiveRounds = null;
- if (ContainsState(name) && _states.TryGetValue(name, out var pair))
+ if (ContainsState(name) && _curStates.TryGetValue(name, out var pair))
{
- var lastNode = pair?.Values?.LastOrDefault();
- preActiveRounds = lastNode?.ActiveRounds;
- preValue = lastNode?.Data ?? string.Empty;
+ var leafNode = pair?.Values?.LastOrDefault();
+ preActiveRounds = leafNode?.ActiveRounds;
+ preValue = leafNode?.Data ?? string.Empty;
}
_logger.LogInformation($"[STATE] {name} = {value}");
var routingCtx = _services.GetRequiredService();
- foreach (var hook in hooks)
+ if (!ContainsState(name) || preValue != currentValue || preActiveRounds != curActiveRounds)
{
- hook.OnStateChanged(new StateChangeModel
+ foreach (var hook in hooks)
{
- ConversationId = _conversationId,
- MessageId = routingCtx.MessageId,
- Name = name,
- BeforeValue = preValue,
- BeforeActiveRounds = preActiveRounds,
- AfterValue = currentValue,
- AfterActiveRounds = curActiveRounds
- }).Wait();
+ hook.OnStateChanged(new StateChangeModel
+ {
+ ConversationId = _conversationId,
+ MessageId = routingCtx.MessageId,
+ Name = name,
+ BeforeValue = preValue,
+ BeforeActiveRounds = preActiveRounds,
+ AfterValue = currentValue,
+ AfterActiveRounds = curActiveRounds,
+ DataType = valueType,
+ Source = source,
+ Readonly = readOnly
+ }).Wait();
+ }
}
var newPair = new StateKeyValue
{
Key = name,
- Versioning = isNeedVersion
+ Versioning = isNeedVersion,
+ Readonly = readOnly
};
var newValue = new StateValue
@@ -82,76 +99,94 @@ public class ConversationStateService : IConversationStateService, IDisposable
MessageId = routingCtx.MessageId,
Active = true,
ActiveRounds = curActiveRounds,
+ DataType = valueType,
+ Source = source,
UpdateTime = DateTime.UtcNow,
};
- if (!isNeedVersion || !_states.ContainsKey(name))
+ if (!isNeedVersion || !_curStates.ContainsKey(name))
{
newPair.Values = new List { newValue };
- _states[name] = newPair;
+ _curStates[name] = newPair;
}
else
{
- _states[name].Values.Add(newValue);
+ _curStates[name].Values.Add(newValue);
}
return this;
}
- public Dictionary Load(string conversationId)
+ public Dictionary Load(string conversationId, bool isReadOnly = false)
{
- _conversationId = conversationId;
+ _conversationId = !isReadOnly ? conversationId : null;
var routingCtx = _services.GetRequiredService();
var curMsgId = routingCtx.MessageId;
- _states = _db.GetConversationStates(_conversationId);
- var dialogs = _db.GetConversationDialogs(_conversationId);
+
+ _historyStates = _db.GetConversationStates(conversationId);
+ var dialogs = _db.GetConversationDialogs(conversationId);
var userDialogs = dialogs.Where(x => x.MetaData?.Role == AgentRole.User || x.MetaData?.Role == UserRole.Client)
+ .GroupBy(x => x.MetaData?.MessageId)
+ .Select(g => g.First())
.OrderBy(x => x.MetaData?.CreateTime)
.ToList();
-
var curMsgIndex = userDialogs.FindIndex(x => !string.IsNullOrEmpty(curMsgId) && x.MetaData?.MessageId == curMsgId);
curMsgIndex = curMsgIndex < 0 ? userDialogs.Count() : curMsgIndex;
- var curStates = new Dictionary();
- if (!_states.IsNullOrEmpty())
+ var endNodes = new Dictionary();
+ if (_historyStates.IsNullOrEmpty()) return endNodes;
+
+ foreach (var state in _historyStates)
{
- foreach (var state in _states)
+ var key = state.Key;
+ var value = state.Value;
+ var leafNode = value?.Values?.LastOrDefault();
+ if (leafNode == null) continue;
+
+ _curStates[key] = new StateKeyValue
{
- var value = state.Value?.Values?.LastOrDefault();
- if (value == null || !value.Active) continue;
+ Key = key,
+ Versioning = value.Versioning,
+ Readonly = value.Readonly,
+ Values = new List { leafNode }
+ };
- if (value.ActiveRounds > 0)
+ if (!leafNode.Active) continue;
+
+ // Handle state active rounds
+ if (leafNode.ActiveRounds > 0)
+ {
+ var stateMsgIndex = userDialogs.FindIndex(x => !string.IsNullOrEmpty(x.MetaData?.MessageId) && x.MetaData.MessageId == leafNode.MessageId);
+ if (stateMsgIndex >= 0 && curMsgIndex - stateMsgIndex >= leafNode.ActiveRounds)
{
- var stateMsgIndex = userDialogs.FindIndex(x => !string.IsNullOrEmpty(x.MetaData?.MessageId) && x.MetaData.MessageId == value.MessageId);
- if (stateMsgIndex >= 0 && curMsgIndex - stateMsgIndex >= value.ActiveRounds)
+ _curStates[key].Values.Add(new StateValue
{
- state.Value.Values.Add(new StateValue
- {
- Data = value.Data,
- MessageId = curMsgId,
- Active = false,
- ActiveRounds = value.ActiveRounds,
- UpdateTime = DateTime.UtcNow
- });
- continue;
- }
+ Data = leafNode.Data,
+ MessageId = curMsgId,
+ Active = false,
+ ActiveRounds = leafNode.ActiveRounds,
+ DataType = leafNode.DataType,
+ Source = leafNode.Source,
+ UpdateTime = DateTime.UtcNow
+ });
+ continue;
}
-
- var data = value.Data ?? string.Empty;
- curStates[state.Key] = data;
- _logger.LogInformation($"[STATE] {state.Key} : {data}");
}
+
+ var data = leafNode.Data ?? string.Empty;
+ endNodes[state.Key] = data;
+ _logger.LogInformation($"[STATE] {key} : {data}");
}
- _logger.LogInformation($"Loaded conversation states: {_conversationId}");
+ _logger.LogInformation($"Loaded conversation states: {conversationId}");
var hooks = _services.GetServices();
foreach (var hook in hooks)
{
- hook.OnStateLoaded(_states).Wait();
+ hook.OnStateLoaded(_curStates).Wait();
}
- return curStates;
+ return endNodes;
}
public void Save()
@@ -163,35 +198,106 @@ public class ConversationStateService : IConversationStateService, IDisposable
var states = new List();
- foreach (var dic in _states)
+ foreach (var pair in _curStates)
{
- states.Add(dic.Value);
+ var key = pair.Key;
+ var curValue = pair.Value;
+
+ if (!_historyStates.TryGetValue(key, out var historyValue)
+ || historyValue == null
+ || historyValue.Values.IsNullOrEmpty()
+ || !curValue.Versioning)
+ {
+ states.Add(curValue);
+ }
+ else
+ {
+ var historyValues = historyValue.Values.Take(historyValue.Values.Count - 1).ToList();
+ var newValues = historyValues.Concat(curValue.Values).ToList();
+ var updatedNode = new StateKeyValue
+ {
+ Key = pair.Key,
+ Versioning = curValue.Versioning,
+ Readonly = curValue.Readonly,
+ Values = newValues
+ };
+ states.Add(updatedNode);
+ }
}
_db.UpdateConversationStates(_conversationId, states);
_logger.LogInformation($"Saved states of conversation {_conversationId}");
}
- public void CleanStates()
+ public bool RemoveState(string name)
+ {
+ if (!ContainsState(name)) return false;
+
+ var routingCtx = _services.GetRequiredService();
+ var value = _curStates[name];
+ var leafNode = value?.Values?.LastOrDefault();
+ if (value == null || !value.Versioning || leafNode == null) return false;
+
+ _curStates[name].Values.Add(new StateValue
+ {
+ Data = leafNode.Data,
+ MessageId = routingCtx.MessageId,
+ Active = false,
+ ActiveRounds = leafNode.ActiveRounds,
+ DataType = leafNode.DataType,
+ Source = leafNode.Source,
+ UpdateTime = DateTime.UtcNow
+ });
+
+ var hooks = _services.GetServices();
+ foreach (var hook in hooks)
+ {
+ hook.OnStateChanged(new StateChangeModel
+ {
+ ConversationId = _conversationId,
+ MessageId = routingCtx.MessageId,
+ Name = name,
+ BeforeValue = leafNode.Data,
+ BeforeActiveRounds = leafNode.ActiveRounds,
+ AfterValue = null,
+ AfterActiveRounds = leafNode.ActiveRounds,
+ DataType = leafNode.DataType,
+ Source = leafNode.Source,
+ Readonly = value.Readonly
+ }).Wait();
+ }
+
+ return true;
+ }
+
+ public void CleanStates(params string[] excludedStates)
{
var routingCtx = _services.GetRequiredService();
var curMsgId = routingCtx.MessageId;
var utcNow = DateTime.UtcNow;
- foreach (var key in _states.Keys)
+ foreach (var key in _curStates.Keys)
{
- var value = _states[key];
+ // skip state
+ if (excludedStates.Contains(key))
+ {
+ continue;
+ }
+
+ var value = _curStates[key];
if (value == null || !value.Versioning || value.Values.IsNullOrEmpty()) continue;
- var lastValue = value.Values.LastOrDefault();
- if (lastValue == null || !lastValue.Active) continue;
+ var leafNode = value.Values.LastOrDefault();
+ if (leafNode == null || !leafNode.Active) continue;
value.Values.Add(new StateValue
{
- Data = lastValue.Data,
+ Data = leafNode.Data,
MessageId = curMsgId,
Active = false,
- ActiveRounds = lastValue.ActiveRounds,
+ ActiveRounds = leafNode.ActiveRounds,
+ DataType = leafNode.DataType,
+ Source = leafNode.Source,
UpdateTime = utcNow
});
}
@@ -199,25 +305,25 @@ public class ConversationStateService : IConversationStateService, IDisposable
public Dictionary GetStates()
{
- var curStates = new Dictionary();
- foreach (var state in _states)
+ var endNodes = new Dictionary();
+ foreach (var state in _curStates)
{
var value = state.Value?.Values?.LastOrDefault();
if (value == null || !value.Active) continue;
- curStates[state.Key] = value.Data ?? string.Empty;
+ endNodes[state.Key] = value.Data ?? string.Empty;
}
- return curStates;
+ return endNodes;
}
public string GetState(string name, string defaultValue = "")
{
- if (!_states.ContainsKey(name) || _states[name].Values.IsNullOrEmpty() || !_states[name].Values.Last().Active)
+ if (!_curStates.ContainsKey(name) || _curStates[name].Values.IsNullOrEmpty() || !_curStates[name].Values.Last().Active)
{
return defaultValue;
}
- return _states[name].Values.Last().Data;
+ return _curStates[name].Values.Last().Data;
}
public void Dispose()
@@ -227,10 +333,10 @@ public class ConversationStateService : IConversationStateService, IDisposable
public bool ContainsState(string name)
{
- return _states.ContainsKey(name)
- && !_states[name].Values.IsNullOrEmpty()
- && _states[name].Values.LastOrDefault()?.Active == true
- && !string.IsNullOrEmpty(_states[name].Values.Last().Data);
+ return _curStates.ContainsKey(name)
+ && !_curStates[name].Values.IsNullOrEmpty()
+ && _curStates[name].Values.LastOrDefault()?.Active == true
+ && !string.IsNullOrEmpty(_curStates[name].Values.Last().Data);
}
public void SaveStateByArgs(JsonDocument args)
@@ -244,9 +350,17 @@ public class ConversationStateService : IConversationStateService, IDisposable
{
foreach (JsonProperty property in root.EnumerateObject())
{
- if (!string.IsNullOrEmpty(property.Value.ToString()))
+ var propertyValue = property.Value;
+ var stateValue = propertyValue.ToString();
+ if (!string.IsNullOrEmpty(stateValue))
{
- SetState(property.Name, property.Value);
+ if (propertyValue.ValueKind == JsonValueKind.True ||
+ propertyValue.ValueKind == JsonValueKind.False)
+ {
+ stateValue = stateValue?.ToLower();
+ }
+
+ SetState(property.Name, stateValue, source: StateSource.Application);
}
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
index e523ce48..5abc4fc5 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs
@@ -8,8 +8,8 @@ namespace BotSharp.Core.Conversations.Services;
public class ConversationStorage : IConversationStorage
{
private readonly BotSharpDatabaseSettings _dbSettings;
+ private readonly BotSharpOptions _options;
private readonly IServiceProvider _services;
- private readonly JsonSerializerOptions _jsonOptions;
public ConversationStorage(
BotSharpDatabaseSettings dbSettings,
@@ -18,7 +18,7 @@ public class ConversationStorage : IConversationStorage
{
_dbSettings = dbSettings;
_services = services;
- _jsonOptions = InitJsonSerilizerOptions(options);
+ _options = options;
}
public void Append(string conversationId, RoleDialogModel dialog)
@@ -28,11 +28,11 @@ public class ConversationStorage : IConversationStorage
var dialogElements = new List();
// Prevent duplicate record to be inserted
- var dialogs = db.GetConversationDialogs(conversationId);
+ /*var dialogs = db.GetConversationDialogs(conversationId);
if (dialogs.Any(x => x.MetaData.MessageId == dialog.MessageId && x.Content == dialog.Content))
{
return;
- }
+ }*/
if (dialog.Role == AgentRole.Function)
{
@@ -50,7 +50,13 @@ public class ConversationStorage : IConversationStorage
{
return;
}
- dialogElements.Add(new DialogElement(meta, content));
+ dialogElements.Add(new DialogElement
+ {
+ MetaData = meta,
+ Content = dialog.Content,
+ SecondaryContent = dialog.SecondaryContent,
+ Payload = dialog.Payload
+ });
}
else
{
@@ -70,8 +76,17 @@ public class ConversationStorage : IConversationStorage
return;
}
- var richContent = dialog.RichContent != null ? JsonSerializer.Serialize(dialog.RichContent, _jsonOptions) : null;
- dialogElements.Add(new DialogElement(meta, content, richContent));
+ var richContent = dialog.RichContent != null ? JsonSerializer.Serialize(dialog.RichContent, _options.JsonSerializerOptions) : null;
+ var secondaryRichContent = dialog.SecondaryRichContent != null ? JsonSerializer.Serialize(dialog.SecondaryRichContent, _options.JsonSerializerOptions) : null;
+ dialogElements.Add(new DialogElement
+ {
+ MetaData = meta,
+ Content = dialog.Content,
+ SecondaryContent = dialog.SecondaryContent,
+ RichContent = richContent,
+ SecondaryRichContent = secondaryRichContent,
+ Payload = dialog.Payload
+ });
}
db.AppendConversationDialogs(conversationId, dialogElements);
@@ -88,6 +103,8 @@ public class ConversationStorage : IConversationStorage
{
var meta = dialog.MetaData;
var content = dialog.Content;
+ var secondaryContent = dialog.SecondaryContent;
+ var payload = string.IsNullOrEmpty(dialog.Payload) ? null : dialog.Payload;
var role = meta.Role;
var currentAgentId = meta.AgentId;
var messageId = meta.MessageId;
@@ -95,7 +112,9 @@ public class ConversationStorage : IConversationStorage
var senderId = role == AgentRole.Function ? currentAgentId : meta.SenderId;
var createdAt = meta.CreateTime;
var richContent = !string.IsNullOrEmpty(dialog.RichContent) ?
- JsonSerializer.Deserialize>(dialog.RichContent, _jsonOptions) : null;
+ JsonSerializer.Deserialize>(dialog.RichContent, _options.JsonSerializerOptions) : null;
+ var secondaryRichContent = !string.IsNullOrEmpty(dialog.SecondaryRichContent) ?
+ JsonSerializer.Deserialize>(dialog.SecondaryRichContent, _options.JsonSerializerOptions) : null;
var record = new RoleDialogModel(role, content)
{
@@ -104,7 +123,10 @@ public class ConversationStorage : IConversationStorage
CreatedAt = createdAt,
SenderId = senderId,
FunctionName = function,
- RichContent = richContent
+ RichContent = richContent,
+ SecondaryContent = secondaryContent,
+ SecondaryRichContent = secondaryRichContent,
+ Payload = payload
};
results.Add(record);
@@ -140,21 +162,4 @@ public class ConversationStorage : IConversationStorage
}
return Path.Combine(dir, "dialogs.txt");
}
-
- private JsonSerializerOptions InitJsonSerilizerOptions(BotSharpOptions botSharOptions)
- {
- var options = botSharOptions.JsonSerializerOptions;
- var jsonOptions = new JsonSerializerOptions
- {
- PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive,
- PropertyNamingPolicy = options.PropertyNamingPolicy ?? JsonNamingPolicy.CamelCase,
- AllowTrailingCommas = options.AllowTrailingCommas,
- };
-
- foreach (var converter in options.Converters)
- {
- jsonOptions.Converters.Add(converter);
- }
- return jsonOptions;
- }
}
diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs
index 1ee38e2e..ca230ff3 100644
--- a/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs
+++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Conversations.Enums;
using BotSharp.Abstraction.MLTasks;
using System.Diagnostics;
using System.Drawing;
@@ -47,14 +48,14 @@ public class TokenStatistics : ITokenStatistics
// Accumulated Token
var stat = _services.GetRequiredService();
var inputCount = int.Parse(stat.GetState("prompt_total", "0"));
- stat.SetState("prompt_total", stats.PromptCount + inputCount, false);
+ stat.SetState("prompt_total", stats.PromptCount + inputCount, isNeedVersion: false, source: StateSource.Application);
var outputCount = int.Parse(stat.GetState("completion_total", "0"));
- stat.SetState("completion_total", stats.CompletionCount + outputCount, false);
+ stat.SetState("completion_total", stats.CompletionCount + outputCount, isNeedVersion: false, source: StateSource.Application);
// Total cost
var total_cost = float.Parse(stat.GetState("llm_total_cost", "0"));
total_cost += Cost;
- stat.SetState("llm_total_cost", total_cost, false);
+ stat.SetState("llm_total_cost", total_cost, isNeedVersion: false, source: StateSource.Application);
}
public void PrintStatistics()
diff --git a/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.Conversation.cs b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.Conversation.cs
new file mode 100644
index 00000000..71e34549
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.Conversation.cs
@@ -0,0 +1,188 @@
+using Microsoft.AspNetCore.StaticFiles;
+using System.IO;
+using System.Threading;
+
+namespace BotSharp.Core.Files;
+
+public partial class BotSharpFileService
+{
+ public IEnumerable GetChatImages(string conversationId, List conversations, int offset = 1)
+ {
+ var files = new List();
+ if (string.IsNullOrEmpty(conversationId) || conversations.IsNullOrEmpty())
+ {
+ return files;
+ }
+
+ if (offset <= 0)
+ {
+ offset = MIN_OFFSET;
+ }
+ else if (offset > MAX_OFFSET)
+ {
+ offset = MAX_OFFSET;
+ }
+
+ var messageIds = conversations.Select(x => x.MessageId).Distinct().TakeLast(offset).ToList();
+ files = GetMessageFiles(conversationId, messageIds, imageOnly: true).ToList();
+ return files;
+ }
+
+ public IEnumerable GetMessageFiles(string conversationId, IEnumerable messageIds, bool imageOnly = false)
+ {
+ var files = new List();
+ if (messageIds.IsNullOrEmpty()) return files;
+
+ foreach (var messageId in messageIds)
+ {
+ var dir = GetConversationFileDirectory(conversationId, messageId);
+ if (!ExistDirectory(dir))
+ {
+ continue;
+ }
+
+ foreach (var file in Directory.GetFiles(dir))
+ {
+ var contentType = GetFileContentType(file);
+ if (imageOnly && !_allowedTypes.Contains(contentType))
+ {
+ continue;
+ }
+
+ var fileName = Path.GetFileNameWithoutExtension(file);
+ var extension = Path.GetExtension(file);
+ var fileType = extension.Substring(1);
+
+ var model = new MessageFileModel()
+ {
+ MessageId = messageId,
+ FileUrl = $"/conversation/{conversationId}/message/{messageId}/file/{fileName}",
+ FileStorageUrl = file,
+ FileName = fileName,
+ FileType = fileType,
+ ContentType = contentType
+ };
+ files.Add(model);
+ }
+ }
+
+ return files;
+ }
+
+ public string GetMessageFile(string conversationId, string messageId, string fileName)
+ {
+ var dir = GetConversationFileDirectory(conversationId, messageId);
+ if (!ExistDirectory(dir))
+ {
+ return string.Empty;
+ }
+
+ var found = Directory.GetFiles(dir).FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).IsEqualTo(fileName));
+ return found;
+ }
+
+ public bool SaveMessageFiles(string conversationId, string messageId, List files)
+ {
+ if (files.IsNullOrEmpty()) return false;
+
+ var dir = GetConversationFileDirectory(conversationId, messageId, createNewDir: true);
+ if (!ExistDirectory(dir)) return false;
+
+ try
+ {
+ for (int i = 0; i < files.Count; i++)
+ {
+ var file = files[i];
+ if (string.IsNullOrEmpty(file.FileData))
+ {
+ continue;
+ }
+
+ var (_, bytes) = GetFileInfoFromData(file.FileData);
+ var fileType = Path.GetExtension(file.FileName);
+ var fileName = $"{i + 1}{fileType}";
+ Thread.Sleep(100);
+ File.WriteAllBytes(Path.Combine(dir, fileName), bytes);
+ }
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning($"Error when saving conversation files: {ex.Message}");
+ return false;
+ }
+ }
+
+
+
+ public bool DeleteMessageFiles(string conversationId, IEnumerable messageIds, string targetMessageId, string? newMessageId = null)
+ {
+ if (string.IsNullOrEmpty(conversationId) || messageIds == null) return false;
+
+ if (!string.IsNullOrEmpty(targetMessageId) && !string.IsNullOrEmpty(newMessageId))
+ {
+ var prevDir = GetConversationFileDirectory(conversationId, targetMessageId);
+ var newDir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, newMessageId);
+
+ if (ExistDirectory(prevDir))
+ {
+ if (ExistDirectory(newDir))
+ {
+ Directory.Delete(newDir, true);
+ }
+
+ Directory.Move(prevDir, newDir);
+ }
+ }
+
+ foreach (var messageId in messageIds)
+ {
+ var dir = GetConversationFileDirectory(conversationId, messageId);
+ if (!ExistDirectory(dir)) continue;
+
+ Thread.Sleep(100);
+ Directory.Delete(dir, true);
+ }
+
+ return true;
+ }
+
+ public bool DeleteConversationFiles(IEnumerable conversationIds)
+ {
+ if (conversationIds.IsNullOrEmpty()) return false;
+
+ foreach (var conversationId in conversationIds)
+ {
+ var convDir = FindConversationDirectory(conversationId);
+ if (!ExistDirectory(convDir)) continue;
+
+ Directory.Delete(convDir, true);
+ }
+ return true;
+ }
+
+ #region Private methods
+ private string GetConversationFileDirectory(string? conversationId, string? messageId, bool createNewDir = false)
+ {
+ if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId))
+ {
+ return string.Empty;
+ }
+
+ var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId);
+ if (!Directory.Exists(dir) && createNewDir)
+ {
+ Directory.CreateDirectory(dir);
+ }
+ return dir;
+ }
+
+ private string? FindConversationDirectory(string conversationId)
+ {
+ if (string.IsNullOrEmpty(conversationId)) return null;
+
+ var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId);
+ return dir;
+ }
+ #endregion
+}
diff --git a/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.User.cs b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.User.cs
new file mode 100644
index 00000000..b6a87993
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.User.cs
@@ -0,0 +1,65 @@
+using System.IO;
+
+namespace BotSharp.Core.Files;
+
+public partial class BotSharpFileService
+{
+ public string GetUserAvatar()
+ {
+ var db = _services.GetRequiredService();
+ var user = db.GetUserById(_user.Id);
+ var dir = GetUserAvatarDir(user?.Id);
+
+ if (!ExistDirectory(dir)) return string.Empty;
+
+ var found = Directory.GetFiles(dir).FirstOrDefault() ?? string.Empty;
+ return found;
+ }
+
+ public bool SaveUserAvatar(BotSharpFile file)
+ {
+ if (file == null || string.IsNullOrEmpty(file.FileData)) return false;
+
+ try
+ {
+ var db = _services.GetRequiredService();
+ var user = db.GetUserById(_user.Id);
+ var dir = GetUserAvatarDir(user?.Id);
+
+ if (string.IsNullOrEmpty(dir)) return false;
+
+ if (Directory.Exists(dir))
+ {
+ Directory.Delete(dir, true);
+ }
+
+ dir = GetUserAvatarDir(user?.Id, createNewDir: true);
+ var (_, bytes) = GetFileInfoFromData(file.FileData);
+ File.WriteAllBytes(Path.Combine(dir, file.FileName), bytes);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning($"Error when saving user avatar: {ex.Message}");
+ return false;
+ }
+ }
+
+
+ #region Private methods
+ private string GetUserAvatarDir(string? userId, bool createNewDir = false)
+ {
+ if (string.IsNullOrEmpty(userId))
+ {
+ return string.Empty;
+ }
+
+ var dir = Path.Combine(_baseDir, USERS_FOLDER, userId, USER_AVATAR_FOLDER);
+ if (!Directory.Exists(dir) && createNewDir)
+ {
+ Directory.CreateDirectory(dir);
+ }
+ return dir;
+ }
+ #endregion
+}
diff --git a/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs
new file mode 100644
index 00000000..76d26dbc
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs
@@ -0,0 +1,83 @@
+using Microsoft.AspNetCore.StaticFiles;
+using System;
+using System.IO;
+using System.Threading;
+
+namespace BotSharp.Core.Files;
+
+public partial class BotSharpFileService : IBotSharpFileService
+{
+ private readonly BotSharpDatabaseSettings _dbSettings;
+ private readonly IServiceProvider _services;
+ private readonly IUserIdentity _user;
+ private readonly ILogger _logger;
+ private readonly string _baseDir;
+ private readonly IEnumerable _allowedTypes = new List { "image/png", "image/jpeg" };
+
+ private const string CONVERSATION_FOLDER = "conversations";
+ private const string FILE_FOLDER = "files";
+ private const string USERS_FOLDER = "users";
+ private const string USER_AVATAR_FOLDER = "avatar";
+
+ private const int MIN_OFFSET = 1;
+ private const int MAX_OFFSET = 5;
+
+ public BotSharpFileService(
+ BotSharpDatabaseSettings dbSettings,
+ IUserIdentity user,
+ ILogger logger,
+ IServiceProvider services)
+ {
+ _dbSettings = dbSettings;
+ _user = user;
+ _logger = logger;
+ _services = services;
+ _baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dbSettings.FileRepository);
+ }
+
+ public string GetDirectory(string conversationId)
+ {
+ var dir = Path.Combine(_dbSettings.FileRepository, CONVERSATION_FOLDER, conversationId, "attachments");
+ if (!Directory.Exists(dir))
+ {
+ Directory.CreateDirectory(dir);
+ }
+ return dir;
+ }
+
+ public (string, byte[]) GetFileInfoFromData(string data)
+ {
+ if (string.IsNullOrEmpty(data))
+ {
+ return (string.Empty, new byte[0]);
+ }
+
+ var typeStartIdx = data.IndexOf(':');
+ var typeEndIdx = data.IndexOf(';');
+ var contentType = data.Substring(typeStartIdx + 1, typeEndIdx - typeStartIdx - 1);
+
+ var base64startIdx = data.IndexOf(',');
+ var base64Str = data.Substring(base64startIdx + 1);
+
+ return (contentType, Convert.FromBase64String(base64Str));
+ }
+
+ #region Private methods
+ private string GetFileContentType(string filePath)
+ {
+ string contentType;
+ var provider = new FileExtensionContentTypeProvider();
+ if (!provider.TryGetContentType(filePath, out contentType))
+ {
+ contentType = string.Empty;
+ }
+
+ return contentType;
+ }
+
+ private bool ExistDirectory(string? dir)
+ {
+ return !string.IsNullOrEmpty(dir) && Directory.Exists(dir);
+ }
+ #endregion
+}
diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs
index c55ed9a8..4655b1de 100644
--- a/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs
+++ b/src/Infrastructure/BotSharp.Core/Infrastructures/CompletionProvider.cs
@@ -35,10 +35,13 @@ public class CompletionProvider
public static IChatCompletion GetChatCompletion(IServiceProvider services,
string? provider = null,
string? model = null,
+ string? modelId = null,
+ bool? multiModal = null,
AgentLlmConfig? agentConfig = null)
{
var completions = services.GetServices();
- (provider, model) = GetProviderAndModel(services, provider: provider, model: model, agentConfig: agentConfig);
+ (provider, model) = GetProviderAndModel(services, provider: provider, model: model, modelId: modelId,
+ multiModal: multiModal, agentConfig: agentConfig);
var completer = completions.FirstOrDefault(x => x.Provider == provider);
if (completer == null)
@@ -47,7 +50,7 @@ public class CompletionProvider
logger.LogError($"Can't resolve completion provider by {provider}");
}
- completer.SetModelName(model);
+ completer?.SetModelName(model);
return completer;
}
@@ -55,6 +58,8 @@ public class CompletionProvider
private static (string, string) GetProviderAndModel(IServiceProvider services,
string? provider = null,
string? model = null,
+ string? modelId = null,
+ bool? multiModal = null,
AgentLlmConfig? agentConfig = null)
{
var agentSetting = services.GetRequiredService();
@@ -73,11 +78,11 @@ public class CompletionProvider
{
model = state.GetState("model", model ?? "gpt-35-turbo-4k");
}
- else if (state.ContainsState("model_id"))
+ else if (state.ContainsState("model_id") || !string.IsNullOrEmpty(modelId))
{
- var modelId = state.GetState("model_id");
+ var modelIdentity = state.ContainsState("model_id") ? state.GetState("model_id") : modelId;
var llmProviderService = services.GetRequiredService();
- model = llmProviderService.GetProviderModel(provider, modelId)?.Name;
+ model = llmProviderService.GetProviderModel(provider, modelIdentity, multiModal: multiModal)?.Name;
}
}
diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs
new file mode 100644
index 00000000..82b655c7
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs
@@ -0,0 +1,50 @@
+using RedLockNet;
+using RedLockNet.SERedis;
+using RedLockNet.SERedis.Configuration;
+using StackExchange.Redis;
+
+namespace BotSharp.Core.Infrastructures;
+
+public class DistributedLocker
+{
+ private readonly BotSharpDatabaseSettings _settings;
+ private readonly RedLockFactory _lockFactory;
+
+ public DistributedLocker(/*BotSharpDatabaseSettings settings*/)
+ {
+ // _settings = settings;
+
+ var multiplexers = new List();
+ foreach (var x in "".Split(';'))
+ {
+ var option = new ConfigurationOptions
+ {
+ AbortOnConnectFail = false,
+ EndPoints = { x }
+ };
+ var _connMuliplexer = ConnectionMultiplexer.Connect(option);
+ multiplexers.Add(_connMuliplexer);
+ }
+
+ _lockFactory = RedLockFactory.Create(multiplexers);
+ }
+
+ public async Task Lock(string resource, Func action)
+ {
+ var expiry = TimeSpan.FromSeconds(60);
+ var wait = TimeSpan.FromSeconds(30);
+ var retry = TimeSpan.FromSeconds(3);
+
+ await using (var redLock = await _lockFactory.CreateLockAsync(resource, expiry, wait, retry))
+ {
+ if (redLock.IsAcquired)
+ {
+ await action();
+ }
+ else
+ {
+ Console.WriteLine($"Acquire locak failed due to {resource} after {wait}s timeout.");
+ }
+ }
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/LlmProviderService.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/LlmProviderService.cs
index eb92ac51..8320bdb7 100644
--- a/src/Infrastructure/BotSharp.Core/Infrastructures/LlmProviderService.cs
+++ b/src/Infrastructure/BotSharp.Core/Infrastructures/LlmProviderService.cs
@@ -44,11 +44,15 @@ public class LlmProviderService : ILlmProviderService
?.Models ?? new List();
}
- public LlmModelSetting GetProviderModel(string provider, string id)
+ public LlmModelSetting GetProviderModel(string provider, string id, bool? multiModal = null)
{
var models = GetProviderModels(provider)
- .Where(x => x.Id == id)
- .ToList();
+ .Where(x => x.Id == id);
+
+ if (multiModal.HasValue)
+ {
+ models = models.Where(x => x.MultiModal == multiModal);
+ }
var random = new Random();
var index = random.Next(0, models.Count());
diff --git a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs
index 0932041d..1b883657 100644
--- a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs
+++ b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs
@@ -5,7 +5,6 @@ using System.Drawing;
using System.IO;
using System.Reflection;
using System.Xml;
-using BotSharp.Abstraction.Repositories;
namespace BotSharp.Core.Plugins;
@@ -144,15 +143,19 @@ public class PluginLoader
var config = db.GetPluginConfig();
if (enable)
{
- if (!config.EnabledPlugins.Exists(x => x == id))
+ var dependentPlugins = new HashSet();
+ var dependentAgentIds = new HashSet();
+ FindPluginDependency(id, enable, ref dependentPlugins, ref dependentAgentIds);
+ var missingPlugins = dependentPlugins.Where(x => !config.EnabledPlugins.Contains(x)).ToList();
+ if (!missingPlugins.IsNullOrEmpty())
{
- config.EnabledPlugins.Add(id);
+ config.EnabledPlugins.AddRange(missingPlugins);
db.SavePluginConfig(config);
}
// enable agents
var agentService = services.GetRequiredService();
- foreach (var agentId in plugin.AgentIds)
+ foreach (var agentId in dependentAgentIds)
{
var agent = agentService.LoadAgent(agentId).Result;
agent.Disabled = false;
@@ -186,6 +189,43 @@ public class PluginLoader
return plugin;
}
+ private void FindPluginDependency(string pluginId, bool enabled, ref HashSet dependentPlugins, ref HashSet dependentAgentIds)
+ {
+ var pluginDef = _plugins.FirstOrDefault(x => x.Id == pluginId);
+ if (pluginDef == null) return;
+
+ if (!pluginDef.IsCore)
+ {
+ pluginDef.Enabled = enabled;
+ dependentPlugins.Add(pluginId);
+ if (!pluginDef.AgentIds.IsNullOrEmpty())
+ {
+ foreach (var agentId in pluginDef.AgentIds)
+ {
+ dependentAgentIds.Add(agentId);
+ }
+ }
+ }
+
+ var foundPlugin = _modules.FirstOrDefault(x => x.Id == pluginId);
+ if (foundPlugin == null) return;
+
+ var attr = foundPlugin.GetType().GetCustomAttribute();
+ if (attr != null && !attr.PluginNames.IsNullOrEmpty())
+ {
+ foreach (var name in attr.PluginNames)
+ {
+ var plugins = _plugins.Where(x => x.Assembly == name).ToList();
+ if (plugins.IsNullOrEmpty()) return;
+
+ foreach (var plugin in plugins)
+ {
+ FindPluginDependency(plugin.Id, enabled, ref dependentPlugins, ref dependentAgentIds);
+ }
+ }
+ }
+ }
+
public string GetSummaryComment(Type member)
{
string summary = string.Empty;
@@ -229,4 +269,20 @@ public class PluginLoader
}
});
}
+
+ public List GetPluginMenuByRoles(List plugins, string userRole)
+ {
+ if (plugins.IsNullOrEmpty()) return plugins;
+
+ var filtered = new List();
+ foreach (var plugin in plugins)
+ {
+ if (plugin.Roles.IsNullOrEmpty() || plugin.Roles.Contains(userRole))
+ {
+ plugin.SubMenu = GetPluginMenuByRoles(plugin.SubMenu, userRole);
+ filtered.Add(plugin);
+ }
+ }
+ return filtered;
+ }
}
diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
index 904048a8..6bba6c2a 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs
@@ -1,6 +1,5 @@
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Plugins.Models;
-using BotSharp.Abstraction.Repositories.Models;
using BotSharp.Abstraction.Tasks.Models;
using BotSharp.Abstraction.Users.Models;
using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -73,86 +72,60 @@ public class BotSharpDbContext : Database, IBotSharpRepository
#region Agent
public Agent GetAgent(string agentId)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
public List GetAgents(AgentFilter filter)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
public List GetAgentsByUser(string userId)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
public void UpdateAgent(Agent agent, AgentField field)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
public string GetAgentTemplate(string agentId, string templateName)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
+
+ public bool PatchAgentTemplate(string agentId, AgentTemplate template)
+ => throw new NotImplementedException();
public List GetAgentResponses(string agentId, string prefix, string intent)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
public void BulkInsertAgents(List agents)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
public void BulkInsertUserAgents(List userAgents)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
public bool DeleteAgents()
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
+
+ public bool DeleteAgent(string agentId)
+ => throw new NotImplementedException();
#endregion
#region Agent Task
public PagedItems GetAgentTasks(AgentTaskFilter filter)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
public AgentTask? GetAgentTask(string agentId, string taskId)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
public void InsertAgentTask(AgentTask task)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
public void BulkInsertAgentTasks(List tasks)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
public void UpdateAgentTask(AgentTask task, AgentTaskField field)
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
- public bool DeleteAgentTask(string agentId, string taskId)
- {
- throw new NotImplementedException();
- }
+ public bool DeleteAgentTask(string agentId, List taskIds)
+ => throw new NotImplementedException();
public bool DeleteAgentTasks()
- {
- throw new NotImplementedException();
- }
+ => throw new NotImplementedException();
#endregion
#region Conversation
@@ -177,9 +150,6 @@ public class BotSharpDbContext : Database, IBotSharpRepository
public List GetConversationDialogs(string conversationId)
=> throw new NotImplementedException();
- public void UpdateConversationDialogElements(string conversationId, List updateElements)
- => new NotImplementedException();
-
public ConversationState GetConversationStates(string conversationId)
=> throw new NotImplementedException();
@@ -189,10 +159,10 @@ public class BotSharpDbContext : Database, IBotSharpRepository
public void UpdateConversationTitle(string conversationId, string title)
=> new NotImplementedException();
- public void UpdateConversationBreakpoint(string conversationId, string messageId, DateTime breakpoint)
+ public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint)
=> new NotImplementedException();
- public DateTime GetConversationBreakpoint(string conversationId)
+ public ConversationBreakpoint? GetConversationBreakpoint(string conversationId)
=> throw new NotImplementedException();
public void UpdateConversationStates(string conversationId, List states)
@@ -201,21 +171,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository
public void UpdateConversationStatus(string conversationId, string status)
=> new NotImplementedException();
- public bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
- => throw new NotImplementedException();
- #endregion
-
- #region User
- public User? GetUserByEmail(string email)
- => throw new NotImplementedException();
-
- public User? GetUserById(string id)
- => throw new NotImplementedException();
-
- public User? GetUserByUserName(string userName)
- => throw new NotImplementedException();
-
- public void CreateUser(User user)
+ public IEnumerable