temp save

This commit is contained in:
Jicheng Lu 2024-10-31 14:36:26 -05:00
parent e3da73b417
commit 170d9ffc1e
37 changed files with 435 additions and 405 deletions

View file

@ -55,7 +55,7 @@ public interface IAgentService
string GetDataDir();
string GetAgentDataDir(string agentId);
List<Agent> GetAgentsByUser(string userId);
Task<List<UserAgent>> GetUserAgents(string userId);
PluginDef GetPlugin(string agentId);

View file

@ -1,11 +1,27 @@
using BotSharp.Abstraction.Users.Models;
namespace BotSharp.Abstraction.Agents.Models;
public class UserAgent
{
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("user_id")]
public string UserId { get; set; } = string.Empty;
public string AgentId { get; set; } = string.Empty;
public bool Editable { get; set; }
[JsonPropertyName("agent_id")]
public string AgentId { get; set; }
[JsonPropertyName("actions")]
public IEnumerable<string> Actions { get; set; } = [];
[JsonIgnore]
public Agent? Agent { get; set; }
[JsonPropertyName("updated_time")]
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
[JsonPropertyName("created_time")]
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
}

View file

@ -10,9 +10,6 @@ namespace BotSharp.Abstraction.Repositories;
public interface IBotSharpRepository
{
int Transaction<TTableInterface>(Action action);
void Add<TTableInterface>(object entity);
#region Plugin
PluginConfig GetPluginConfig();
void SavePluginConfig(PluginConfig config);
@ -35,13 +32,14 @@ public interface IBotSharpRepository
void UpdateUserPhone(string userId, string Iphone) => throw new NotImplementedException();
void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException();
void UpdateUsersIsDisable(List<string> userIds, bool isDisable) => throw new NotImplementedException();
PagedItems<User> GetUsers(UserFilter filter) => throw new NotImplementedException();
#endregion
#region Agent
void UpdateAgent(Agent agent, AgentField field);
Agent? GetAgent(string agentId);
List<Agent> GetAgents(AgentFilter filter);
List<Agent> GetAgentsByUser(string userId);
List<UserAgent> GetUserAgents(string userId);
void BulkInsertAgents(List<Agent> agents);
void BulkInsertUserAgents(List<UserAgent> userAgents);
bool DeleteAgents();

View file

@ -0,0 +1,7 @@
namespace BotSharp.Abstraction.Users.Enums;
public static class UserAction
{
public const string Edit = "edit";
public const string Chat = "chat";
}

View file

@ -0,0 +1,10 @@
namespace BotSharp.Abstraction.Users.Enums;
public static class UserConstant
{
public static IEnumerable<string> AdminRoles = new List<string>
{
UserRole.Admin,
UserRole.Root
};
}

View file

@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.Users.Enums;
public static class UserPermission
{
public const string CreateAgent = "create-agent";
}

View file

@ -35,4 +35,4 @@ public class UserRole
public const string Assistant = "assistant";
public const string Root = "root";
}
}

View file

@ -6,6 +6,7 @@ namespace BotSharp.Abstraction.Users;
public interface IUserService
{
Task<User> GetUser(string id);
Task<PagedItems<User>> GetUsers(UserFilter filter);
Task<User> CreateUser(User user);
Task<Token> ActiveUser(UserActivationModel model);
Task<Token?> GetAffiliateToken(string authorization);

View file

@ -23,6 +23,10 @@ public class User
public bool Verified { get; set; }
public string? AffiliateId { get; set; }
public bool IsDisabled { get; set; }
public IEnumerable<string> Permissions { get; set; } = [];
[JsonIgnore]
public IEnumerable<UserAgentAction> AgentActions { get; set; } = [];
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
}

View file

@ -0,0 +1,16 @@
namespace BotSharp.Abstraction.Users.Models;
public class UserAgentAction
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("agent_id")]
public string AgentId { get; set; }
[JsonIgnore]
public Agent? Agent { get; set; }
[JsonPropertyName("actions")]
public IEnumerable<string> Actions { get; set; } = [];
}

View file

@ -0,0 +1,19 @@
namespace BotSharp.Abstraction.Users.Models;
public class UserFilter : Pagination
{
[JsonPropertyName("user_ids")]
public IEnumerable<string>? UserIds { get; set; }
[JsonPropertyName("user_names")]
public IEnumerable<string>? UserNames { get; set; }
[JsonPropertyName("external_ids")]
public IEnumerable<string>? ExternalIds { get; set; }
[JsonPropertyName("roles")]
public IEnumerable<string>? Roles { get; set; }
[JsonPropertyName("sources")]
public IEnumerable<string>? Sources { get; set; }
}

View file

@ -45,7 +45,7 @@ public class AgentPlugin : IBotSharpPlugin
SubMenu = new List<PluginMenuDef>
{
new PluginMenuDef("Routing", link: "page/agent/router"), // icon: "bx bx-map-pin"
new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List<string> { UserRole.Admin } }, // icon: "bx bx-task"
new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List<string> { UserRole.Root, UserRole.Admin } }, // icon: "bx bx-task"
new PluginMenuDef("Agents", link: "page/agent"), // icon: "bx bx-bot"
}
});

View file

@ -8,14 +8,14 @@ public partial class AgentService
{
public async Task<Agent> CreateAgent(Agent agent)
{
var agentRecord = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Name.IsEqualTo(agent.Name));
if (agentRecord != null)
var userAgents = _db.GetUserAgents(_user.Id);
var found = userAgents?.FirstOrDefault(x => x.Agent != null && x.Agent.Name.IsEqualTo(agent.Name));
if (found != null)
{
return agentRecord;
return found.Agent;
}
agentRecord = Agent.Clone(agent);
var agentRecord = Agent.Clone(agent);
agentRecord.Id = Guid.NewGuid().ToString();
agentRecord.CreatedDateTime = DateTime.UtcNow;
agentRecord.UpdatedDateTime = DateTime.UtcNow;
@ -24,21 +24,7 @@ public partial class AgentService
var agentSettings = _services.GetRequiredService<AgentSettings>();
var user = _db.GetUserById(_user.Id);
var userAgentRecord = new UserAgent
{
Id = Guid.NewGuid().ToString(),
UserId = user.Id,
AgentId = agentRecord.Id,
Editable = false,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
};
_db.Transaction<IBotSharpTable>(delegate
{
_db.Add<IBotSharpTable>(agentRecord);
_db.Add<IBotSharpTable>(userAgentRecord);
});
_db.BulkInsertAgents(new List<Agent> { agentRecord });
Utilities.ClearCache();
return await Task.FromResult(agentRecord);
@ -213,17 +199,4 @@ public partial class AgentService
task.Content = content.Substring(suffix.Length).Trim();
return task;
}
private UserAgent BuildUserAgent(string agentId, string userId, bool editable = false)
{
return new UserAgent
{
Id = Guid.NewGuid().ToString(),
UserId = userId,
AgentId = agentId,
Editable = editable,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
};
}
}

View file

@ -7,9 +7,10 @@ public partial class AgentService
public async Task<bool> DeleteAgent(string id)
{
var user = _db.GetUserById(_user.Id);
var agent = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Id.IsEqualTo(id));
var userAgents = await GetUserAgents(user?.Id);
var found = userAgents?.FirstOrDefault(x => x.AgentId == id);
if (user?.Role != UserRole.Admin && agent == null)
if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || !found.Actions.Contains(UserAction.Edit)))
{
return false;
}

View file

@ -54,7 +54,6 @@ public partial class AgentService
.SetResponses(responses)
.SetSamples(samples);
var userAgent = BuildUserAgent(agent.Id, user.Id);
var tasks = GetTasksFromFile(dir);
var isAgentDeleted = _db.DeleteAgent(agent.Id);
@ -62,7 +61,6 @@ public partial class AgentService
{
await Task.Delay(100);
_db.BulkInsertAgents(new List<Agent> { agent });
_db.BulkInsertUserAgents(new List<UserAgent> { userAgent });
_db.BulkInsertAgentTasks(tasks);
refreshedAgents.Add(agent.Name);
_logger.LogInformation($"Agent {agent.Name} has been migrated.");

View file

@ -9,13 +9,18 @@ public partial class AgentService
{
public async Task UpdateAgent(Agent agent, AgentField updateField)
{
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
var userAgents = GetAgentsByUser(user?.Id);
var editable = userAgents?.Select(x => x.Id)?.Contains(agent.Id) ?? false;
if (user?.Role != UserRole.Admin && !editable) return;
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
var userAgents = await GetUserAgents(user.Id);
var found = userAgents?.FirstOrDefault(x => x.AgentId == agent.Id);
if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || found.Actions.Contains(UserAction.Edit)))
{
return;
}
var record = _db.GetAgent(agent.Id);
if (record == null) return;

View file

@ -49,10 +49,12 @@ public partial class AgentService : IAgentService
return dir;
}
public List<Agent> GetAgentsByUser(string userId)
public async Task<List<UserAgent>> GetUserAgents(string userId)
{
var agents = _db.GetAgentsByUser(userId);
return agents;
if (string.IsNullOrEmpty(userId)) return [];
var userAgents = _db.GetUserAgents(userId);
return userAgents;
}
public IEnumerable<string> GetAgentUtilities()

View file

@ -2,71 +2,12 @@ using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Tasks.Models;
using BotSharp.Abstraction.Translation.Models;
using BotSharp.Abstraction.Users.Models;
using BotSharp.Abstraction.VectorStorage.Models;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BotSharp.Core.Repository;
public class BotSharpDbContext : Database, IBotSharpRepository
{
public IQueryable<User> Users => throw new NotImplementedException();
public IQueryable<Agent> Agents => throw new NotImplementedException();
public IQueryable<UserAgent> UserAgents => throw new NotImplementedException();
public IQueryable<Conversation> Conversations => throw new NotImplementedException();
public int Transaction<TTableInterface>(Action action)
{
DatabaseFacade database = base.GetMaster(typeof(TTableInterface)).Database;
int num = 0;
if (database.CurrentTransaction == null)
{
using (Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction dbContextTransaction = database.BeginTransaction())
{
try
{
action();
num = base.SaveChanges();
dbContextTransaction.Commit();
return num;
}
catch (Exception ex)
{
dbContextTransaction.Rollback();
if (ex.Message.Contains("See the inner exception for details"))
{
throw ex.InnerException;
}
throw ex;
}
}
}
try
{
action();
return base.SaveChanges();
}
catch (Exception ex2)
{
if (database.CurrentTransaction != null)
{
database.CurrentTransaction.Rollback();
}
if (ex2.Message.Contains("See the inner exception for details"))
{
throw ex2.InnerException;
}
throw ex2;
}
}
#region Plugin
public PluginConfig GetPluginConfig() => throw new NotImplementedException();
public void SavePluginConfig(PluginConfig config) => throw new NotImplementedException();
@ -79,7 +20,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository
public List<Agent> GetAgents(AgentFilter filter)
=> throw new NotImplementedException();
public List<Agent> GetAgentsByUser(string userId)
public List<UserAgent> GetUserAgents(string userId)
=> throw new NotImplementedException();
public void UpdateAgent(Agent agent, AgentField field)

View file

@ -1,5 +1,5 @@
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Users.Models;
using System.IO;
namespace BotSharp.Core.Repository
@ -386,19 +386,26 @@ namespace BotSharp.Core.Repository
return query.ToList();
}
public List<Agent> GetAgentsByUser(string userId)
public List<UserAgent> GetUserAgents(string userId)
{
var agentIds = (from ua in UserAgents
join u in Users on ua.UserId equals u.Id
where ua.UserId == userId || u.ExternalId == userId
select ua.AgentId).ToList();
var found = (from ua in UserAgents
join u in Users on ua.UserId equals u.Id
where ua.UserId == userId || u.ExternalId == userId
select ua).ToList();
var filter = new AgentFilter
if (found.IsNullOrEmpty()) return [];
var agentIds = found.Select(x => x.AgentId).Distinct().ToList();
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
foreach (var item in found)
{
AgentIds = agentIds
};
var agents = GetAgents(filter);
return agents;
var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
if (agent == null) continue;
item.Agent = agent;
}
return found;
}
@ -473,14 +480,13 @@ namespace BotSharp.Core.Repository
var userAgents = JsonSerializer.Deserialize<List<UserAgent>>(text, _options);
if (userAgents.IsNullOrEmpty()) continue;
userAgents = userAgents.Where(x => x.AgentId != agentId).ToList();
userAgents = userAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? [];
File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options));
}
}
// Delete agent folder
Directory.Delete(agentDir, true);
return true;
}
catch

View file

@ -1,82 +0,0 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Users.Models;
using System.IO;
namespace BotSharp.Core.Repository;
public partial class FileRepository
{
public void Add<TTableInterface>(object entity)
{
if (entity is Agent agent)
{
_agents.Add(agent);
_changedTableNames.Add(nameof(Agent));
}
else if (entity is User user)
{
_users.Add(user);
_changedTableNames.Add(nameof(User));
}
else if (entity is UserAgent userAgent)
{
_userAgents.Add(userAgent);
_changedTableNames.Add(nameof(UserAgent));
}
}
private readonly List<string> _changedTableNames = new List<string>();
public int Transaction<TTableInterface>(Action action)
{
_changedTableNames.Clear();
action();
// Persist to disk
foreach (var table in _changedTableNames)
{
if (table == nameof(Agent))
{
foreach (var agent in _agents)
{
var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agent.Id);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
var path = Path.Combine(dir, AGENT_FILE);
File.WriteAllText(path, JsonSerializer.Serialize(agent, _options));
}
}
else if (table == nameof(User))
{
foreach (var user in _users)
{
var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, user.Id);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
var path = Path.Combine(dir, USER_FILE);
File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
}
}
else if (table == nameof(UserAgent))
{
_userAgents.GroupBy(x => x.UserId)
.Select(x => x.Key).ToList()
.ForEach(uid =>
{
var agents = _userAgents.Where(x => x.UserId == uid).ToList();
if (agents.Any())
{
var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, uid);
var path = Path.Combine(dir, USER_AGENT_FILE);
File.WriteAllText(path, JsonSerializer.Serialize(agents, _options));
}
});
}
}
return _changedTableNames.Count;
}
}

View file

@ -1,5 +1,6 @@
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
using System;
using System.IO;
namespace BotSharp.Core.Repository;
@ -68,4 +69,65 @@ public partial class FileRepository
var path = Path.Combine(dir, USER_FILE);
File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
}
public PagedItems<User> GetUsers(UserFilter filter)
{
var users = Users;
// Apply filters
if (!filter.UserIds.IsNullOrEmpty())
{
users = users.Where(x => filter.UserIds.Contains(x.Id));
}
if (!filter.UserNames.IsNullOrEmpty())
{
users = users.Where(x => filter.UserNames.Contains(x.UserName));
}
if (!filter.ExternalIds.IsNullOrEmpty())
{
users = users.Where(x => filter.ExternalIds.Contains(x.ExternalId));
}
if (!filter.Roles.IsNullOrEmpty())
{
users = users.Where(x => filter.Roles.Contains(x.Role));
}
if (!filter.Sources.IsNullOrEmpty())
{
users = users.Where(x => filter.Sources.Contains(x.Source));
}
// Get user agents
var userIds = users.Select(x => x.Id).ToList();
var userAgents = UserAgents.Where(x => userIds.Contains(x.UserId)).ToList();
var agentIds = userAgents?.Select(x => x.AgentId)?.Distinct()?.ToList() ?? [];
if (!agentIds.IsNullOrEmpty())
{
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
foreach (var item in userAgents)
{
item.Agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
}
foreach (var user in users)
{
var found = userAgents.Where(x => x.UserId == user.Id).ToList();
if (found.IsNullOrEmpty()) continue;
user.AgentActions = found.Select(x => new UserAgentAction
{
Id = x.Id,
AgentId = x.AgentId,
Agent = x.Agent,
Actions = x.Actions
});
}
}
return new PagedItems<User>
{
Items = users.OrderByDescending(x => x.CreatedTime).Skip(filter.Offset).Take(filter.Size),
Count = users.Count()
};
}
}

View file

@ -22,7 +22,7 @@ public class TaskPlugin : IBotSharpPlugin
var section = menu.First(x => x.Label == "Apps");
menu.Add(new PluginMenuDef("Task", link: "page/task", icon: "bx bx-task", weight: section.Weight + 8)
{
Roles = new List<string> { UserRole.Admin }
Roles = new List<string> { UserRole.Root, UserRole.Admin }
});
return true;

View file

@ -377,6 +377,13 @@ public class UserService : IUserService
return user;
}
public async Task<PagedItems<User>> GetUsers(UserFilter filter)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var users = db.GetUsers(filter);
return users;
}
public async Task<Token> ActiveUser(UserActivationModel model)
{
var id = model.UserName;

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Users.Enums;
namespace BotSharp.OpenAPI.Controllers;
@ -58,15 +59,19 @@ public class AgentController : ControllerBase
}
var editable = true;
var chatable = true;
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (user?.Role != UserRole.Admin)
if (!UserConstant.AdminRoles.Contains(user?.Role))
{
var userAgents = _agentService.GetAgentsByUser(user?.Id);
editable = userAgents?.Select(x => x.Id)?.Contains(targetAgent.Id) ?? false;
var userAgents = await _agentService.GetUserAgents(user?.Id);
var actions = userAgents?.FirstOrDefault(x => x.AgentId == targetAgent.Id)?.Actions ?? [];
editable = actions.Contains(UserAction.Edit);
chatable = actions.Contains(UserAction.Chat);
}
targetAgent.Editable = editable;
targetAgent.Chatable = chatable;
return targetAgent;
}
@ -74,8 +79,29 @@ public class AgentController : ControllerBase
public async Task<PagedItems<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter)
{
var agentSetting = _services.GetRequiredService<AgentSettings>();
var userService = _services.GetRequiredService<IUserService>();
var pagedAgents = await _agentService.GetAgents(filter);
var agents = pagedAgents?.Items?.Select(x => AgentViewModel.FromAgent(x))?.ToList() ?? new List<AgentViewModel>();
var userAgents = new List<UserAgent>();
var user = await userService.GetUser(_user.Id);
if (!UserConstant.AdminRoles.Contains(user.Role))
{
userAgents = await _agentService.GetUserAgents(user.Id);
}
var agents = pagedAgents?.Items?.Select(x =>
{
var chatable = true;
if (!UserConstant.AdminRoles.Contains(user.Role))
{
var actions = userAgents.FirstOrDefault(a => a.AgentId == x.Id)?.Actions ?? [];
chatable = actions.Contains(UserAction.Chat);
}
var model = AgentViewModel.FromAgent(x);
model.Chatable = chatable;
return model;
})?.ToList() ?? [];
return new PagedItems<AgentViewModel>
{

View file

@ -55,7 +55,7 @@ public class ConversationController : ControllerBase
return new PagedItems<ConversationViewModel>();
}
filter.UserId = user.Role != UserRole.Admin ? user.Id : filter.UserId;
filter.UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : filter.UserId;
var conversations = await convService.GetConversations(filter);
var agentService = _services.GetRequiredService<IAgentService>();
var list = conversations.Items.Select(x => ConversationViewModel.FromSession(x)).ToList();
@ -146,7 +146,7 @@ public class ConversationController : ControllerBase
var filter = new ConversationFilter
{
Id = conversationId,
UserId = user.Role != UserRole.Admin ? user.Id : null
UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null
};
var conversations = await service.GetConversations(filter);
if (conversations.Items.IsNullOrEmpty())
@ -209,7 +209,7 @@ public class ConversationController : ControllerBase
var filter = new ConversationFilter
{
Id = conversationId,
UserId = user.Role != UserRole.Admin ? user.Id : null
UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null
};
var conversations = await conv.GetConversations(filter);
@ -262,7 +262,7 @@ public class ConversationController : ControllerBase
var filter = new ConversationFilter
{
Id = conversationId,
UserId = user.Role != UserRole.Admin ? user.Id : null
UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null
};
var conversations = await conversationService.GetConversations(filter);

View file

@ -24,7 +24,7 @@ public class PluginController : ControllerBase
{
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (user?.Role != UserRole.Admin)
if (!UserConstant.AdminRoles.Contains(user?.Role))
{
return new PagedItems<PluginDef>();
}
@ -45,15 +45,19 @@ public class PluginController : ControllerBase
new PluginMenuDef("System", weight: 30)
{
IsHeader = true,
Roles = new List<string> { UserRole.Admin }
Roles = new List<string> { UserRole.Root, UserRole.Admin }
},
new PluginMenuDef("Plugins", link: "page/plugin", icon: "bx bx-plug", weight: 31)
{
Roles = new List<string> { UserRole.Admin }
Roles = new List<string> { UserRole.Root, UserRole.Admin }
},
new PluginMenuDef("Settings", link: "page/setting", icon: "bx bx-cog", weight: 32)
{
Roles = new List<string> { UserRole.Admin }
Roles = new List<string> { UserRole.Root, UserRole.Admin }
},
new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 33)
{
Roles = new List<string> { UserRole.Root, UserRole.Admin }
}
};

View file

@ -1,3 +1,5 @@
using BotSharp.Abstraction.Users.Enums;
using EntityFrameworkCore.BootKit;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using System.ComponentModel.DataAnnotations;
@ -10,10 +12,16 @@ public class UserController : ControllerBase
{
private readonly IServiceProvider _services;
private readonly IUserService _userService;
public UserController(IUserService userService, IServiceProvider services)
private readonly IUserIdentity _user;
public UserController(
IUserService userService,
IServiceProvider services,
IUserIdentity user)
{
_services = services;
_userService = userService;
_user = user;
}
[AllowAnonymous]
@ -164,6 +172,29 @@ public class UserController : ControllerBase
return await _userService.UpdateUsersIsDisable(userIds, isDisable);
}
#region User management
[HttpPost("/users")]
public async Task<PagedItems<UserViewModel>> GetUsers([FromBody] UserFilter filter)
{
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (user == null || !UserConstant.AdminRoles.Contains(user.Role))
{
return new PagedItems<UserViewModel>();
}
var users = await userService.GetUsers(filter);
var views = users.Items.Select(x => UserViewModel.FromUser(x)).ToList();
return new PagedItems<UserViewModel>
{
Count = users.Count,
Items = views
};
}
#endregion
#region Avatar
[HttpPost("/user/avatar")]
public bool UploadUserAvatar([FromBody] UserAvatarModel input)

View file

@ -47,6 +47,7 @@ public class AgentViewModel
public PluginDef Plugin { get; set; }
public bool Editable { get; set; }
public bool Chatable { get; set; }
[JsonPropertyName("created_datetime")]
public DateTime CreatedDateTime { get; set; }

View file

@ -0,0 +1,30 @@
using BotSharp.Abstraction.Agents.Models;
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Users;
public class UserAgentActionViewModel
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("agent_id")]
public string AgentId { get; set; }
[JsonPropertyName("agent")]
public Agent? Agent { get; set; }
[JsonPropertyName("actions")]
public IEnumerable<string> Actions { get; set; } = [];
public static UserAgentActionViewModel ToViewModel(UserAgentAction action)
{
return new UserAgentActionViewModel
{
Id = action.Id,
AgentId = action.AgentId,
Agent = action.Agent,
Actions = action.Actions
};
}
}

View file

@ -16,14 +16,23 @@ public class UserViewModel
public string? Phone { get; set; }
public string Type { get; set; } = UserType.Client;
public string Role { get; set; } = UserRole.User;
[JsonPropertyName("full_name")]
public string FullName => $"{FirstName} {LastName}".Trim();
public string? Source { get; set; }
[JsonPropertyName("external_id")]
public string? ExternalId { get; set; }
public string Avatar { get; set; } = "/user/avatar";
public IEnumerable<string> Permissions { get; set; } = [];
[JsonPropertyName("agent_actions")]
public IEnumerable<UserAgentActionViewModel> AgentActions { get; set; } = [];
[JsonPropertyName("create_date")]
public DateTime CreateDate { get; set; }
[JsonPropertyName("update_date")]
public DateTime UpdateDate { get; set; }
@ -52,6 +61,8 @@ public class UserViewModel
Role = user.Role,
Source = user.Source,
ExternalId = user.ExternalId,
Permissions = user.Permissions,
AgentActions = user.AgentActions?.Select(x => UserAgentActionViewModel.ToViewModel(x)) ?? [],
CreateDate = user.CreatedTime,
UpdateDate = user.UpdatedTime,
Avatar = "/user/avatar"

View file

@ -4,8 +4,7 @@ public class UserAgentDocument : MongoBase
{
public string UserId { get; set; }
public string AgentId { get; set; }
public bool Editable { get; set; }
public IEnumerable<string> Actions { get; set; } = [];
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
}

View file

@ -20,6 +20,7 @@ public class UserDocument : MongoBase
public bool Verified { get; set; }
public string? AffiliateId { get; set; }
public bool IsDisabled { get; set; }
public IEnumerable<string> Permissions { get; set; } = [];
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
@ -43,6 +44,7 @@ public class UserDocument : MongoBase
IsDisabled = IsDisabled,
VerificationCode = VerificationCode,
Verified = Verified,
Permissions = Permissions,
};
}
}

View file

@ -37,7 +37,7 @@ public class MongoStoragePlugin : IBotSharpPlugin
var section = menu.First(x => x.Label == "Apps");
menu.Add(new PluginMenuDef("MongoDB", icon: "bx bx-data", link: "page/mongodb", weight: section.Weight + 10)
{
Roles = new List<string> { UserRole.Admin }
Roles = new List<string> { UserRole.Root, UserRole.Admin }
});
return true;
}

View file

@ -318,19 +318,36 @@ public partial class MongoRepository
return agentDocs.Select(x => TransformAgentDocument(x)).ToList();
}
public List<Agent> GetAgentsByUser(string userId)
public List<UserAgent> GetUserAgents(string userId)
{
var agentIds = (from ua in _dc.UserAgents.AsQueryable()
join u in _dc.Users.AsQueryable() on ua.UserId equals u.Id
where ua.UserId == userId || u.ExternalId == userId
select ua.AgentId).ToList();
var found = (from ua in _dc.UserAgents.AsQueryable()
join u in _dc.Users.AsQueryable() on ua.UserId equals u.Id
where ua.UserId == userId || u.ExternalId == userId
select ua).ToList();
var filter = new AgentFilter
if (found.IsNullOrEmpty()) return [];
var agentIds = found.Select(x => x.AgentId).Distinct().ToList();
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
var res = found.Select(x => new UserAgent
{
AgentIds = agentIds
};
var agents = GetAgents(filter);
return agents;
Id = x.Id,
UserId = x.UserId,
AgentId = x.AgentId,
Actions = x.Actions,
CreatedTime = x.CreatedTime,
UpdatedTime = x.UpdatedTime
}).ToList();
foreach (var item in res)
{
var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
if (agent == null) continue;
item.Agent = agent;
}
return res;
}
public List<string> GetAgentResponses(string agentId, string prefix, string intent)
@ -415,9 +432,9 @@ public partial class MongoRepository
var userAgentDocs = userAgents.Select(x => new UserAgentDocument
{
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
AgentId = x.AgentId,
UserId = !string.IsNullOrEmpty(x.UserId) ? x.UserId : string.Empty,
Editable = x.Editable,
AgentId = x.AgentId,
Actions = x.Actions,
CreatedTime = x.CreatedTime,
UpdatedTime = x.UpdatedTime
}).ToList();
@ -446,11 +463,11 @@ public partial class MongoRepository
if (string.IsNullOrEmpty(agentId)) return false;
var agentFilter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
var agentUserFilter = Builders<UserAgentDocument>.Filter.Eq(x => x.AgentId, agentId);
var userAgentFilter = Builders<UserAgentDocument>.Filter.Eq(x => x.AgentId, agentId);
var agentTaskFilter = Builders<AgentTaskDocument>.Filter.Eq(x => x.AgentId, agentId);
_dc.Agents.DeleteOne(agentFilter);
_dc.UserAgents.DeleteMany(agentUserFilter);
_dc.UserAgents.DeleteMany(userAgentFilter);
_dc.AgentTasks.DeleteMany(agentTaskFilter);
return true;
}

View file

@ -1,157 +0,0 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Users.Models;
namespace BotSharp.Plugin.MongoStorage.Repository;
public partial class MongoRepository
{
public void Add<TTableInterface>(object entity)
{
if (entity is Agent agent)
{
_agents.Add(agent);
_changedTableNames.Add(nameof(Agent));
}
else if (entity is User user)
{
_users.Add(user);
_changedTableNames.Add(nameof(User));
}
else if (entity is UserAgent userAgent)
{
_userAgents.Add(userAgent);
_changedTableNames.Add(nameof(UserAgent));
}
}
public int Transaction<TTableInterface>(Action action)
{
_changedTableNames.Clear();
action();
foreach (var table in _changedTableNames)
{
if (table == nameof(Agent))
{
var agents = _agents.Select(x => new AgentDocument
{
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
Name = x.Name,
IconUrl = x.IconUrl,
Description = x.Description,
Instruction = x.Instruction,
ChannelInstructions = x.ChannelInstructions?
.Select(i => ChannelInstructionMongoElement.ToMongoElement(i))?
.ToList() ?? new List<ChannelInstructionMongoElement>(),
Templates = x.Templates?
.Select(t => AgentTemplateMongoElement.ToMongoElement(t))?
.ToList() ?? new List<AgentTemplateMongoElement>(),
Functions = x.Functions?
.Select(f => FunctionDefMongoElement.ToMongoElement(f))?
.ToList() ?? new List<FunctionDefMongoElement>(),
Responses = x.Responses?
.Select(r => AgentResponseMongoElement.ToMongoElement(r))?
.ToList() ?? new List<AgentResponseMongoElement>(),
Samples = x.Samples ?? new List<string>(),
Utilities = x.Utilities ?? new List<string>(),
IsPublic = x.IsPublic,
Type = x.Type,
InheritAgentId = x.InheritAgentId,
Disabled = x.Disabled,
Profiles = x.Profiles,
RoutingRules = x.RoutingRules?
.Select(r => RoutingRuleMongoElement.ToMongoElement(r))?
.ToList() ?? new List<RoutingRuleMongoElement>(),
LlmConfig = AgentLlmConfigMongoElement.ToMongoElement(x.LlmConfig),
CreatedTime = x.CreatedDateTime,
UpdatedTime = x.UpdatedDateTime
}).ToList();
foreach (var agent in agents)
{
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agent.Id);
var update = Builders<AgentDocument>.Update
.Set(x => x.Name, agent.Name)
.Set(x => x.Description, agent.Description)
.Set(x => x.Instruction, agent.Instruction)
.Set(x => x.ChannelInstructions, agent.ChannelInstructions)
.Set(x => x.Templates, agent.Templates)
.Set(x => x.Functions, agent.Functions)
.Set(x => x.Responses, agent.Responses)
.Set(x => x.Samples, agent.Samples)
.Set(x => x.Utilities, agent.Utilities)
.Set(x => x.IsPublic, agent.IsPublic)
.Set(x => x.Type, agent.Type)
.Set(x => x.InheritAgentId, agent.InheritAgentId)
.Set(x => x.Disabled, agent.Disabled)
.Set(x => x.Profiles, agent.Profiles)
.Set(x => x.RoutingRules, agent.RoutingRules)
.Set(x => x.LlmConfig, agent.LlmConfig)
.Set(x => x.CreatedTime, agent.CreatedTime)
.Set(x => x.UpdatedTime, agent.UpdatedTime);
_dc.Agents.UpdateOne(filter, update, _options);
}
}
else if (table == nameof(User))
{
var users = _users.Select(x => new UserDocument
{
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
UserName = x.UserName,
FirstName = x.FirstName,
LastName = x.LastName,
Salt = x.Salt,
Password = x.Password,
Email = x.Email,
ExternalId = x.ExternalId,
Role = x.Role,
CreatedTime = x.CreatedTime,
UpdatedTime = x.UpdatedTime
}).ToList();
foreach (var user in users)
{
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, user.Id);
var update = Builders<UserDocument>.Update
.Set(x => x.UserName, user.UserName)
.Set(x => x.FirstName, user.FirstName)
.Set(x => x.LastName, user.LastName)
.Set(x => x.Email, user.Email)
.Set(x => x.Salt, user.Salt)
.Set(x => x.Password, user.Password)
.Set(x => x.ExternalId, user.ExternalId)
.Set(x => x.Role, user.Role)
.Set(x => x.CreatedTime, user.CreatedTime)
.Set(x => x.UpdatedTime, user.UpdatedTime);
_dc.Users.UpdateOne(filter, update, _options);
}
}
else if (table == nameof(UserAgent))
{
var userAgents = _userAgents.Select(x => new UserAgentDocument
{
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
AgentId = x.AgentId,
UserId = !string.IsNullOrEmpty(x.UserId) ? x.UserId : string.Empty,
Editable = x.Editable,
CreatedTime = x.CreatedTime,
UpdatedTime = x.UpdatedTime
}).ToList();
foreach (var userAgent in userAgents)
{
var filter = Builders<UserAgentDocument>.Filter.Eq(x => x.Id, userAgent.Id);
var update = Builders<UserAgentDocument>.Update
.Set(x => x.AgentId, userAgent.AgentId)
.Set(x => x.UserId, userAgent.UserId)
.Set(x => x.Editable, userAgent.Editable)
.Set(x => x.CreatedTime, userAgent.CreatedTime)
.Set(x => x.UpdatedTime, userAgent.UpdatedTime);
_dc.UserAgents.UpdateOne(filter, update, _options);
}
}
}
return _changedTableNames.Count;
}
}

View file

@ -1,5 +1,9 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
using System.Globalization;
namespace BotSharp.Plugin.MongoStorage.Repository;
@ -146,4 +150,85 @@ public partial class MongoRepository
UpdateUserIsDisable(userId, isDisable);
}
}
public PagedItems<User> GetUsers(UserFilter filter)
{
var userBuilder = Builders<UserDocument>.Filter;
var userFilters = new List<FilterDefinition<UserDocument>>() { userBuilder.Empty };
// Apply filters
if (!filter.UserIds.IsNullOrEmpty())
{
userFilters.Add(userBuilder.In(x => x.Id, filter.UserIds));
}
if (!filter.UserNames.IsNullOrEmpty())
{
userFilters.Add(userBuilder.In(x => x.UserName, filter.UserNames));
}
if (!filter.ExternalIds.IsNullOrEmpty())
{
userFilters.Add(userBuilder.In(x => x.ExternalId, filter.ExternalIds));
}
if (!filter.Roles.IsNullOrEmpty())
{
userFilters.Add(userBuilder.In(x => x.Role, filter.Roles));
}
if (!filter.Sources.IsNullOrEmpty())
{
userFilters.Add(userBuilder.In(x => x.Source, filter.Sources));
}
// Filter def and sort
var filterDef = userBuilder.And(userFilters);
var sortDef = Builders<UserDocument>.Sort.Descending(x => x.CreatedTime);
// Search
var userDocs = _dc.Users.Find(filterDef).Sort(sortDef).Skip(filter.Offset).Limit(filter.Size).ToList();
var count = _dc.Users.CountDocuments(filterDef);
var users = userDocs.Select(x => x.ToUser()).ToList();
var userIds = users.Select(x => x.Id).ToList();
var userAgents = _dc.UserAgents.AsQueryable().Where(x => userIds.Contains(x.UserId)).Select(x => new UserAgent
{
Id = x.Id,
UserId = x.UserId,
AgentId = x.AgentId,
Actions = x.Actions ?? Enumerable.Empty<string>(),
CreatedTime = x.CreatedTime,
UpdatedTime = x.UpdatedTime
}).ToList();
var agentIds = userAgents.Select(x => x.AgentId).Distinct().ToList();
if (!agentIds.IsNullOrEmpty())
{
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
foreach (var item in userAgents)
{
var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
if (agent == null) continue;
item.Agent = agent;
}
foreach (var user in users)
{
var found = userAgents.Where(x => x.UserId == user.Id).ToList();
if (found.IsNullOrEmpty()) continue;
user.AgentActions = found.Select(x => new UserAgentAction
{
Id = x.Id,
AgentId = x.AgentId,
Agent = x.Agent,
Actions = x.Actions
});
}
}
return new PagedItems<User>
{
Items = users,
Count = (int)count
};
}
}

View file

@ -1,6 +1,3 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Users.Models;
using Microsoft.Extensions.Logging;
namespace BotSharp.Plugin.MongoStorage.Repository;
@ -25,10 +22,4 @@ public partial class MongoRepository : IBotSharpRepository
IsUpsert = true,
};
}
private List<Agent> _agents = new List<Agent>();
private List<User> _users = new List<User>();
private List<UserAgent> _userAgents = new List<UserAgent>();
private List<Conversation> _conversations = new List<Conversation>();
List<string> _changedTableNames = new List<string>();
}