commit
0e2a457d86
|
|
@ -5,7 +5,7 @@ public interface IKnowledgeHook
|
|||
Task<List<KnowledgeChunk>> CollectChunkedKnowledge()
|
||||
=> Task.FromResult(new List<KnowledgeChunk>());
|
||||
|
||||
Task<List<string>> GetRelevantKnowledges()
|
||||
Task<List<string>> GetRelevantKnowledges(string text)
|
||||
=> Task.FromResult(new List<string>());
|
||||
|
||||
Task<List<string>> GetGlobalKnowledges()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ public interface IKnowledgeService
|
|||
Task<bool> DeleteVectorCollectionAllData(string collectionName);
|
||||
Task<bool> CreateVectorCollectionData(string collectionName, VectorCreateModel create);
|
||||
Task<bool> UpdateVectorCollectionData(string collectionName, VectorUpdateModel update);
|
||||
Task<bool> UpsertVectorCollectionData(string collectionName, VectorUpdateModel update);
|
||||
#endregion
|
||||
|
||||
#region Graph
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ public class GenericTemplateMessage<T> : IRichMessage, ITemplateMessage
|
|||
[JsonPropertyName("rich_type")]
|
||||
public string RichType => RichTypeEnum.GenericTemplate;
|
||||
|
||||
/// <summary>
|
||||
/// Use model refined content if leaving blank
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
[Translate]
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ public interface IPlanningHook
|
|||
{
|
||||
Task<string> GetSummaryAdditionalRequirements(string planner)
|
||||
=> Task.FromResult(string.Empty);
|
||||
|
||||
Task OnPlanningCompleted(string planner, RoleDialogModel msg)
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ public interface IBotSharpRepository
|
|||
#region User
|
||||
User? GetUserByEmail(string email) => throw new NotImplementedException();
|
||||
User? GetUserByPhone(string phone) => throw new NotImplementedException();
|
||||
User? GetUserById(string id) => throw new NotImplementedException();
|
||||
User? GetAffiliateUserByPhone(string phone) => throw new NotImplementedException();
|
||||
User? GetUserById(string id) => throw new NotImplementedException();
|
||||
List<User> GetUserByIds(List<string> ids) => throw new NotImplementedException();
|
||||
User? GetUserByAffiliateId(string affiliateId) => throw new NotImplementedException();
|
||||
User? GetUserByUserName(string userName) => throw new NotImplementedException();
|
||||
void CreateUser(User user) => throw new NotImplementedException();
|
||||
void UpdateUserVerified(string userId) => throw new NotImplementedException();
|
||||
|
|
@ -29,6 +32,7 @@ public interface IBotSharpRepository
|
|||
void UpdateUserPassword(string userId, string password) => throw new NotImplementedException();
|
||||
void UpdateUserEmail(string userId, string email)=> throw new NotImplementedException();
|
||||
void UpdateUserPhone(string userId, string Iphone) => throw new NotImplementedException();
|
||||
void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException();
|
||||
#endregion
|
||||
|
||||
#region Agent
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ public class UserRole
|
|||
public const string CSR = "csr";
|
||||
|
||||
/// <summary>
|
||||
/// Client
|
||||
/// Authorized user
|
||||
/// </summary>
|
||||
public const string Client = "client";
|
||||
public const string User = "user";
|
||||
|
||||
/// <summary>
|
||||
/// Back office operations
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.Abstraction.Users.Enums
|
||||
{
|
||||
public static class UserSource
|
||||
{
|
||||
public const string Internal = "internal";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace BotSharp.Abstraction.Users.Enums;
|
||||
|
||||
public class UserType
|
||||
{
|
||||
public const string Internal = "internal";
|
||||
public const string Client = "client";
|
||||
public const string Affiliate = "affiliate";
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ public interface IUserService
|
|||
Task<User> GetUser(string id);
|
||||
Task<User> CreateUser(User user);
|
||||
Task<Token> ActiveUser(UserActivationModel model);
|
||||
Task<Token?> GetAffiliateToken(string authorization);
|
||||
Task<Token?> GetToken(string authorization);
|
||||
Task<User> GetMyProfile();
|
||||
Task<bool> VerifyUserNameExisting(string userName);
|
||||
|
|
@ -16,4 +17,6 @@ public interface IUserService
|
|||
Task<bool> ResetUserPassword(User user);
|
||||
Task<bool> ModifyUserEmail(string email);
|
||||
Task<bool> ModifyUserPhone(string phone);
|
||||
Task<bool> UpdatePassword(string newPassword, string verificationCode);
|
||||
Task<DateTime> GetUserTokenExpires();
|
||||
}
|
||||
|
|
@ -12,11 +12,17 @@ public class User
|
|||
public string? Phone { get; set; }
|
||||
public string Salt { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public string Source { get; set; } = "internal";
|
||||
public string Source { get; set; } = UserSource.Internal;
|
||||
public string? ExternalId { get; set; }
|
||||
public string Role { get; set; } = UserRole.Client;
|
||||
/// <summary>
|
||||
/// internal, client, affiliate
|
||||
/// </summary>
|
||||
public string Type { get; set; } = UserType.Client;
|
||||
public string Role { get; set; } = UserRole.User;
|
||||
public string? VerificationCode { get; set; }
|
||||
public bool Verified { get; set; }
|
||||
public string? AffiliateId { get; set; }
|
||||
public bool IsDisabled { get; set; }
|
||||
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
|
||||
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ public class AccountSetting
|
|||
/// Whether to enable verification code to verify the authenticity of new users
|
||||
/// </summary>
|
||||
public bool NewUserVerification { get; set; }
|
||||
public string[] AllowMultipleDeviceLoginUserIds { get; set; } = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,5 +5,7 @@ public class VectorCollectionData
|
|||
public string Id { get; set; }
|
||||
public Dictionary<string, string> Data { get; set; } = new();
|
||||
public double? Score { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public float[]? Vector { get; set; }
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Knowledges.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.VectorStorage.Models;
|
||||
|
||||
public class VectorSearchResult : VectorCollectionData
|
||||
|
|
|
|||
|
|
@ -123,6 +123,12 @@ public partial class ConversationService
|
|||
Message = new TextMessage(response.SecondaryContent ?? response.Content)
|
||||
};
|
||||
|
||||
// Use model refined response
|
||||
if (string.IsNullOrEmpty(response.RichContent.Message.Text))
|
||||
{
|
||||
response.RichContent.Message.Text = response.Content;
|
||||
}
|
||||
|
||||
// Patch return function name
|
||||
if (response.PostbackFunctionName != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
|
||||
_historyStates = _db.GetConversationStates(conversationId);
|
||||
var dialogs = _db.GetConversationDialogs(conversationId);
|
||||
var userDialogs = dialogs.Where(x => x.MetaData?.Role == AgentRole.User || x.MetaData?.Role == UserRole.Client)
|
||||
var userDialogs = dialogs.Where(x => x.MetaData?.Role == AgentRole.User || x.MetaData?.Role == UserRole.User)
|
||||
.GroupBy(x => x.MetaData?.MessageId)
|
||||
.Select(g => g.First())
|
||||
.OrderBy(x => x.MetaData?.CreateTime)
|
||||
|
|
|
|||
|
|
@ -16,11 +16,26 @@ public partial class FileRepository
|
|||
return Users.FirstOrDefault(x => x.Phone == phone);
|
||||
}
|
||||
|
||||
public User? GetAffiliateUserByPhone(string phone)
|
||||
{
|
||||
return Users.FirstOrDefault(x => x.Phone == phone && x.Type == UserType.Affiliate);
|
||||
}
|
||||
|
||||
public User? GetUserById(string id = null)
|
||||
{
|
||||
return Users.FirstOrDefault(x => x.Id == id || (x.ExternalId != null && x.ExternalId == id));
|
||||
}
|
||||
|
||||
public List<User> GetUserByIds(List<string> ids)
|
||||
{
|
||||
return Users.Where(x => ids.Contains(x.Id) || (x.ExternalId != null && ids.Contains(x.ExternalId)))?.ToList() ?? new List<User>();
|
||||
}
|
||||
|
||||
public User? GetUserByAffiliateId(string affiliateId)
|
||||
{
|
||||
return Users.FirstOrDefault(x => x.AffiliateId == affiliateId);
|
||||
}
|
||||
|
||||
public User? GetUserByUserName(string userName = null)
|
||||
{
|
||||
return Users.FirstOrDefault(x => x.UserName == userName.ToLower());
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Users.Enums;
|
||||
using BotSharp.Abstraction.Infrastructures;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
using BotSharp.Abstraction.Users.Settings;
|
||||
using BotSharp.OpenAPI.ViewModels.Users;
|
||||
|
|
@ -7,6 +9,7 @@ using NanoidDotNet;
|
|||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Net;
|
||||
|
||||
namespace BotSharp.Core.Users.Services;
|
||||
|
||||
|
|
@ -33,7 +36,7 @@ public class UserService : IUserService
|
|||
if (string.IsNullOrEmpty(user.UserName))
|
||||
{
|
||||
// generate unique name
|
||||
var name = user.Email.Split("@").First() + "-" + Nanoid.Generate("0123456789botsharp", 6);
|
||||
var name = Nanoid.Generate("0123456789botsharp", 10);
|
||||
user.UserName = name;
|
||||
}
|
||||
else
|
||||
|
|
@ -56,7 +59,7 @@ public class UserService : IUserService
|
|||
|
||||
record = user;
|
||||
record.Email = user.Email?.ToLower();
|
||||
if (user.Phone != null)
|
||||
if (!string.IsNullOrWhiteSpace(user.Phone))
|
||||
{
|
||||
record.Phone = "+" + Regex.Match(user.Phone, @"\d+").Value;
|
||||
}
|
||||
|
|
@ -83,12 +86,62 @@ public class UserService : IUserService
|
|||
return record;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdatePassword(string password, string verificationCode)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.GetUserByUserName(_user.UserName);
|
||||
|
||||
if (record == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (record.VerificationCode != verificationCode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var newPassword = Utilities.HashTextMd5($"{password}{record.Salt}");
|
||||
|
||||
db.UpdateUserPassword(record.Id, newPassword);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<Token> GetAffiliateToken(string authorization)
|
||||
{
|
||||
var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization));
|
||||
var (id, password) = base64.SplitAsTuple(":");
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.GetUserByPhone(id);
|
||||
|
||||
var isCanLoginAffiliateRoleType = record != null && !record.IsDisabled && record.Type != UserType.Client;
|
||||
if (!isCanLoginAffiliateRoleType)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (Utilities.HashTextMd5($"{password}{record.Salt}") != record.Password)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var accessToken = GenerateJwtToken(record);
|
||||
var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
|
||||
var token = new Token
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
ExpireTime = jwt.Payload.Exp.Value,
|
||||
TokenType = "Bearer",
|
||||
Scope = "api"
|
||||
};
|
||||
return token;
|
||||
}
|
||||
|
||||
public async Task<Token?> GetToken(string authorization)
|
||||
{
|
||||
var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization));
|
||||
var (id, password) = base64.SplitAsTuple(":");
|
||||
|
||||
var hooks = _services.GetServices<IAuthenticationHook>();
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id);
|
||||
if (record == null)
|
||||
|
|
@ -96,6 +149,12 @@ public class UserService : IUserService
|
|||
record = db.GetUserByUserName(id);
|
||||
}
|
||||
|
||||
if (record != null && record.Type == UserType.Affiliate)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var hooks = _services.GetServices<IAuthenticationHook>();
|
||||
//verify password is correct or not.
|
||||
if (record != null && !hooks.Any())
|
||||
{
|
||||
|
|
@ -108,7 +167,7 @@ public class UserService : IUserService
|
|||
|
||||
User? user = record;
|
||||
var isAuthenticatedByHook = false;
|
||||
if (record == null || record.Source != "internal")
|
||||
if (record == null || record.Source != UserSource.Internal)
|
||||
{
|
||||
// check 3rd party user
|
||||
foreach (var hook in hooks)
|
||||
|
|
@ -119,7 +178,7 @@ public class UserService : IUserService
|
|||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(user.Source) || user.Source == "internal")
|
||||
if (string.IsNullOrEmpty(user.Source) || user.Source == UserSource.Internal)
|
||||
{
|
||||
_logger.LogError($"Please set source name in the Authenticate hook.");
|
||||
return null;
|
||||
|
|
@ -137,6 +196,8 @@ public class UserService : IUserService
|
|||
Source = user.Source,
|
||||
ExternalId = user.ExternalId,
|
||||
Password = user.Password,
|
||||
Type = user.Type,
|
||||
Role = user.Role
|
||||
};
|
||||
await CreateUser(record);
|
||||
}
|
||||
|
|
@ -190,6 +251,8 @@ public class UserService : IUserService
|
|||
new Claim(JwtRegisteredClaimNames.FamilyName, user?.LastName ?? string.Empty),
|
||||
new Claim("source", user.Source),
|
||||
new Claim("external_id", user.ExternalId ?? string.Empty),
|
||||
new Claim("type", user.Type ?? UserType.Client),
|
||||
new Claim("role", user.Role ?? UserRole.User),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||
new Claim("phone", user.Phone ?? string.Empty)
|
||||
};
|
||||
|
|
@ -205,10 +268,11 @@ public class UserService : IUserService
|
|||
var audience = config["Jwt:Audience"];
|
||||
var expireInMinutes = int.Parse(config["Jwt:ExpireInMinutes"] ?? "120");
|
||||
var key = Encoding.ASCII.GetBytes(config["Jwt:Key"]);
|
||||
var expires = DateTime.UtcNow.AddMinutes(expireInMinutes);
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Expires = DateTime.UtcNow.AddMinutes(expireInMinutes),
|
||||
Expires = expires,
|
||||
Issuer = issuer,
|
||||
Audience = audience,
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
|
||||
|
|
@ -216,9 +280,27 @@ public class UserService : IUserService
|
|||
};
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
SaveUserTokenExpiresCache(user.Id, expires).GetAwaiter().GetResult();
|
||||
return tokenHandler.WriteToken(token);
|
||||
}
|
||||
|
||||
private async Task SaveUserTokenExpiresCache(string userId, DateTime expires)
|
||||
{
|
||||
var _cacheService = _services.GetRequiredService<ICacheService>();
|
||||
await _cacheService.SetAsync<DateTime>(GetUserTokenExpiresCacheKey(userId), expires, null);
|
||||
}
|
||||
|
||||
private string GetUserTokenExpiresCacheKey(string userId)
|
||||
{
|
||||
return $"user_{userId}_token_expires";
|
||||
}
|
||||
|
||||
public async Task<DateTime> GetUserTokenExpires()
|
||||
{
|
||||
var _cacheService = _services.GetRequiredService<ICacheService>();
|
||||
return await _cacheService.GetAsync<DateTime>(GetUserTokenExpiresCacheKey(_user.Id));
|
||||
}
|
||||
|
||||
[MemoryCache(10 * 60, perInstanceCache: true)]
|
||||
public async Task<User> GetMyProfile()
|
||||
{
|
||||
|
|
@ -323,24 +405,32 @@ public class UserService : IUserService
|
|||
|
||||
public async Task<bool> SendVerificationCodeResetPassword(User user)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(user.Email) && !string.IsNullOrEmpty(user.Phone))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
|
||||
User? record = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(user.Email))
|
||||
if (!string.IsNullOrWhiteSpace(_user.Id))
|
||||
{
|
||||
record = db.GetUserByEmail(user.Email);
|
||||
record = db.GetUserById(_user.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(user.Email) && !string.IsNullOrEmpty(user.Phone))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(user.Email))
|
||||
{
|
||||
record = db.GetUserByEmail(user.Email);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(user.Phone))
|
||||
{
|
||||
record = db.GetUserByPhone(user.Phone);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(user.Phone))
|
||||
{
|
||||
record = db.GetUserByPhone(user.Phone);
|
||||
}
|
||||
if (record == null)
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@
|
|||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="packages\**" />
|
||||
<EmbeddedResource Remove="packages\**" />
|
||||
<None Remove="packages\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\arts\Icon.png">
|
||||
<Pack>True</Pack>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Microsoft.Net.Http.Headers;
|
|||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.IdentityModel.JsonWebTokens;
|
||||
using BotSharp.OpenAPI.BackgroundServices;
|
||||
using BotSharp.OpenAPI.Filters;
|
||||
|
||||
namespace BotSharp.OpenAPI;
|
||||
|
||||
|
|
@ -32,6 +33,15 @@ public static class BotSharpOpenApiExtensions
|
|||
services.AddScoped<IUserIdentity, UserIdentity>();
|
||||
services.AddHostedService<ConversationTimeoutService>();
|
||||
|
||||
var enableSingleLogin = bool.Parse(config["Jwt:EnableSingleLogin"] ?? "false");
|
||||
if (enableSingleLogin)
|
||||
{
|
||||
services.AddMvc(options =>
|
||||
{
|
||||
options.Filters.Add<UserSingleLoginFilter>();
|
||||
});
|
||||
}
|
||||
|
||||
// Add bearer authentication
|
||||
var schema = "MIXED_SCHEME";
|
||||
var builder = services.AddAuthentication(options =>
|
||||
|
|
|
|||
|
|
@ -121,6 +121,16 @@ public class UserController : ControllerBase
|
|||
return await _userService.ResetUserPassword(user.ToUser());
|
||||
}
|
||||
|
||||
[HttpPost("/user/updatepassword")]
|
||||
public async Task<bool> UpdatePassword([FromBody] User user)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(user.Password) || string.IsNullOrWhiteSpace(user.VerificationCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return await _userService.UpdatePassword(user.Password, user.VerificationCode);
|
||||
}
|
||||
|
||||
[HttpPost("/user/email/modify")]
|
||||
public async Task<bool> ModifyUserEmail([FromQuery] string email)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
using BotSharp.Abstraction.Users.Settings;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
|
||||
namespace BotSharp.OpenAPI.Filters
|
||||
{
|
||||
public class UserSingleLoginFilter : IAuthorizationFilter
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public UserSingleLoginFilter(IUserService userService, IServiceProvider services)
|
||||
{
|
||||
_userService = userService;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
{
|
||||
var bearerToken = GetBearerToken(context);
|
||||
if (!string.IsNullOrWhiteSpace(bearerToken))
|
||||
{
|
||||
var config = _services.GetRequiredService<AccountSetting>();
|
||||
var token = GetJwtToken(bearerToken);
|
||||
|
||||
if (config.AllowMultipleDeviceLoginUserIds.Contains(token.Claims.First(x => x.Type == "nameid").Value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var validTo = token.ValidTo.ToLongTimeString();
|
||||
var currentExpires = GetUserExpires().ToLongTimeString();
|
||||
|
||||
if (validTo != currentExpires)
|
||||
{
|
||||
Serilog.Log.Warning($"Token expired. Token expires at {validTo}, current expires at {currentExpires}");
|
||||
context.Result = new UnauthorizedResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetBearerToken(AuthorizationFilterContext context)
|
||||
{
|
||||
if (context.HttpContext.Request.Headers.TryGetValue(HeaderNames.Authorization, out var bearerToken)
|
||||
&& !string.IsNullOrWhiteSpace(bearerToken.ToString()))
|
||||
{
|
||||
var tokenType = bearerToken.ToString().Split(" ").First();
|
||||
if (tokenType == JwtBearerDefaults.AuthenticationScheme)
|
||||
{
|
||||
return bearerToken.ToString().Split(" ").Last();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private JwtSecurityToken GetJwtToken(string jwtToken)
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var token = handler.ReadJwtToken(jwtToken);
|
||||
return token;
|
||||
}
|
||||
|
||||
private DateTime GetUserExpires()
|
||||
{
|
||||
return _userService.GetUserTokenExpires().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,8 @@ public class UserCreationModel
|
|||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public string Role { get; set; } = UserRole.Client;
|
||||
public string Type { get; set; } = UserType.Client;
|
||||
public string Role { get; set; } = UserRole.User;
|
||||
|
||||
public User ToUser()
|
||||
{
|
||||
|
|
@ -22,7 +23,8 @@ public class UserCreationModel
|
|||
Email = Email,
|
||||
Phone = Phone,
|
||||
Password = Password,
|
||||
Role = Role
|
||||
Role = Role,
|
||||
Type = Type
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ public class UserViewModel
|
|||
public string? LastName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string Role { get; set; } = UserRole.Client;
|
||||
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; }
|
||||
|
|
@ -34,6 +35,7 @@ public class UserViewModel
|
|||
{
|
||||
FirstName = "Unknown",
|
||||
LastName = "Anonymous",
|
||||
Type = UserType.Client,
|
||||
Role = AgentRole.User
|
||||
};
|
||||
}
|
||||
|
|
@ -46,6 +48,7 @@ public class UserViewModel
|
|||
LastName = user.LastName,
|
||||
Email = user.Email,
|
||||
Phone = user.Phone,
|
||||
Type = user.Type,
|
||||
Role = user.Role,
|
||||
Source = user.Source,
|
||||
ExternalId = user.ExternalId,
|
||||
|
|
|
|||
|
|
@ -164,6 +164,42 @@ public partial class KnowledgeService
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpsertVectorCollectionData(string collectionName, VectorUpdateModel update)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(collectionName) || string.IsNullOrWhiteSpace(update.Text) || !Guid.TryParse(update.Id, out var guid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var db = GetVectorDb();
|
||||
var found = await db.GetCollectionData(collectionName, new List<Guid> { guid },
|
||||
withVector: true,
|
||||
withPayload: true);
|
||||
if (!found.IsNullOrEmpty())
|
||||
{
|
||||
if (found.First().Data["text"] == update.Text)
|
||||
{
|
||||
// Only update payload
|
||||
return await db.Upsert(collectionName, guid, found.First().Vector, update.Text, update.Payload);
|
||||
}
|
||||
}
|
||||
|
||||
var textEmbedding = GetTextEmbedding(collectionName);
|
||||
var vector = await textEmbedding.GetVectorAsync(update.Text);
|
||||
var payload = update.Payload ?? new();
|
||||
payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(update.DataSource) ? update.DataSource : VectorDataSource.Api;
|
||||
|
||||
return await db.Upsert(collectionName, guid, vector, update.Text, payload);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when updating vector collection data. {ex.Message}\r\n{ex.InnerException}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteVectorCollectionData(string collectionName, string id)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Users.Enums;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
|
||||
namespace BotSharp.Plugin.MongoStorage.Collections;
|
||||
|
|
@ -11,11 +12,14 @@ public class UserDocument : MongoBase
|
|||
public string? Phone { get; set; }
|
||||
public string Salt { get; set; } = null!;
|
||||
public string Password { get; set; } = null!;
|
||||
public string Source { get; set; } = "internal";
|
||||
public string Source { get; set; } = UserSource.Internal;
|
||||
public string? ExternalId { get; set; }
|
||||
public string Type { get; set; } = UserType.Client;
|
||||
public string Role { get; set; } = null!;
|
||||
public string? VerificationCode { get; set; }
|
||||
public bool Verified { get; set; }
|
||||
public string? AffiliateId { get; set; }
|
||||
public bool IsDisabled { get; set; }
|
||||
public DateTime CreatedTime { get; set; }
|
||||
public DateTime UpdatedTime { get; set; }
|
||||
|
||||
|
|
@ -33,7 +37,10 @@ public class UserDocument : MongoBase
|
|||
Salt = Salt,
|
||||
Source = Source,
|
||||
ExternalId = ExternalId,
|
||||
Type = Type,
|
||||
Role = Role,
|
||||
AffiliateId = AffiliateId,
|
||||
IsDisabled = IsDisabled,
|
||||
VerificationCode = VerificationCode,
|
||||
Verified = Verified,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Users.Enums;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
|
||||
namespace BotSharp.Plugin.MongoStorage.Repository;
|
||||
|
|
@ -16,6 +17,12 @@ public partial class MongoRepository
|
|||
return user != null ? user.ToUser() : null;
|
||||
}
|
||||
|
||||
public User? GetAffiliateUserByPhone(string phone)
|
||||
{
|
||||
var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.Phone == phone && x.Type == UserType.Affiliate);
|
||||
return user != null ? user.ToUser() : null;
|
||||
}
|
||||
|
||||
public User? GetUserById(string id)
|
||||
{
|
||||
var user = _dc.Users.AsQueryable()
|
||||
|
|
@ -23,6 +30,20 @@ public partial class MongoRepository
|
|||
return user != null ? user.ToUser() : null;
|
||||
}
|
||||
|
||||
public List<User> GetUserByIds(List<string> ids)
|
||||
{
|
||||
var users = _dc.Users.AsQueryable()
|
||||
.Where(x => ids.Contains(x.Id) || (x.ExternalId != null && ids.Contains(x.ExternalId))).ToList();
|
||||
return users?.Any() == true ? users.Select(x => x.ToUser()).ToList() : new List<User>();
|
||||
}
|
||||
|
||||
public User? GetUserByAffiliateId(string affiliateId)
|
||||
{
|
||||
var user = _dc.Users.AsQueryable()
|
||||
.FirstOrDefault(x => x.AffiliateId == affiliateId);
|
||||
return user != null ? user.ToUser() : null;
|
||||
}
|
||||
|
||||
public User? GetUserByUserName(string userName)
|
||||
{
|
||||
var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.UserName == userName.ToLower());
|
||||
|
|
@ -46,8 +67,11 @@ public partial class MongoRepository
|
|||
Source = user.Source,
|
||||
ExternalId = user.ExternalId,
|
||||
Role = user.Role,
|
||||
Type = user.Type,
|
||||
VerificationCode = user.VerificationCode,
|
||||
Verified = user.Verified,
|
||||
AffiliateId = user.AffiliateId,
|
||||
IsDisabled = user.IsDisabled,
|
||||
CreatedTime = DateTime.UtcNow,
|
||||
UpdatedTime = DateTime.UtcNow
|
||||
};
|
||||
|
|
@ -94,4 +118,12 @@ public partial class MongoRepository
|
|||
.Set(x => x.UpdatedTime, DateTime.UtcNow);
|
||||
_dc.Users.UpdateOne(filter, update);
|
||||
}
|
||||
|
||||
public void UpdateUserIsDisable(string userId, bool isDisable)
|
||||
{
|
||||
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, userId);
|
||||
var update = Builders<UserDocument>.Update.Set(x => x.IsDisabled, isDisable)
|
||||
.Set(x => x.UpdatedTime, DateTime.UtcNow);
|
||||
_dc.Users.UpdateOne(filter, update);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
var collectionName = knowledgeSettings.Default.CollectionName ?? KnowledgeCollectionName.BotSharp;
|
||||
|
||||
// Get knowledge from vectordb
|
||||
var hooks = _services.GetServices<IKnowledgeHook>();
|
||||
var knowledges = new List<string>();
|
||||
foreach (var question in task.Questions)
|
||||
{
|
||||
|
|
@ -34,8 +35,13 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
{
|
||||
Confidence = 0.2f
|
||||
});
|
||||
|
||||
knowledges.Add(string.Join("\r\n\r\n=====\r\n", list.Select(x => x.ToQuestionAnswer())));
|
||||
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
var k = await hook.GetRelevantKnowledges(question);
|
||||
knowledges.AddRange(k);
|
||||
}
|
||||
}
|
||||
|
||||
// Get first stage planning prompt
|
||||
|
|
@ -92,7 +98,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
var wholeDialogs = conv.GetDialogHistory();
|
||||
|
||||
// Append text
|
||||
wholeDialogs.Last().Content += "\n\nYou must analyze the table description to infer the table relations.";
|
||||
wholeDialogs.Last().Content += "\n\nYou must analyze the table description to infer the table relations. Only output the JSON result.";
|
||||
|
||||
var completion = CompletionProvider.GetChatCompletion(_services,
|
||||
provider: plannerAgent.LlmConfig.Provider,
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ public class SecondaryStagePlanFn : IFunctionCallback
|
|||
var template = agent.Templates.FirstOrDefault(x => x.Name == "two_stage.2nd.plan")?.Content ?? string.Empty;
|
||||
var responseFormat = JsonSerializer.Serialize(new SecondStagePlan
|
||||
{
|
||||
Tool = "tool name if task solution provided",
|
||||
Parameters = [ JsonDocument.Parse("{}") ],
|
||||
Results = [ string.Empty ]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ public class SecondStagePlan
|
|||
[JsonPropertyName("description")]
|
||||
public string Description { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("tool_name")]
|
||||
public string Tool { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("input_args")]
|
||||
public JsonDocument[] Parameters { get; set; } = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -96,31 +96,6 @@ public partial class TwoStageTaskPlanner : IRoutingPlaner
|
|||
return true;
|
||||
}
|
||||
|
||||
private async Task<string> GetFirstStagePlanPrompt(Agent router)
|
||||
{
|
||||
var template = router.Templates.First(x => x.Name == "two_stage.1st.plan").Content;
|
||||
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
|
||||
{
|
||||
Parameters = new JsonDocument[] { JsonDocument.Parse("{}") },
|
||||
Results = new string[] { "" }
|
||||
});
|
||||
|
||||
var relevantKnowledges = new List<string>();
|
||||
var hooks = _services.GetServices<IKnowledgeHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
var k = await hook.GetRelevantKnowledges();
|
||||
relevantKnowledges.AddRange(k);
|
||||
}
|
||||
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
{
|
||||
{ "response_format", responseFormat },
|
||||
{ "relevant_knowledges", relevantKnowledges.ToArray() }
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<string> GetNextStepPrompt(Agent router)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
|
|
@ -134,17 +109,4 @@ public partial class TwoStageTaskPlanner : IRoutingPlaner
|
|||
{ StateConst.EXPECTED_GOAL_AGENT, states.GetState(StateConst.EXPECTED_GOAL_AGENT) }
|
||||
});
|
||||
}
|
||||
|
||||
private string GetSecondStageTaskPrompt(Agent router, SecondStagePlan plan)
|
||||
{
|
||||
var template = router.Templates.First(x => x.Name == "planner_prompt.two_stage.2nd.task").Content;
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
{
|
||||
{ "task_description", plan.Description },
|
||||
{ "related_tables", plan.Tables },
|
||||
{ "input_arguments", JsonSerializer.Serialize(plan.Parameters) },
|
||||
{ "output_results", JsonSerializer.Serialize(plan.Results) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
Use the TwoStagePlanner approach to plan the overall implementation steps, follow the below steps strictly.
|
||||
1. call plan_primary_stage to generate the primary plan.
|
||||
1. Call plan_primary_stage to generate the primary plan.
|
||||
2. If need_additional_information is true, call plan_secondary_stage for the specific primary stage.
|
||||
3. You must call plan_summary for you final planned output.
|
||||
3. You must call plan_summary to generate final planned steps.
|
||||
4. If you can't generate the final accurate planning steps due to missing some specific informations, please ask user for more information.
|
||||
|
||||
*** IMPORTANT ***
|
||||
Don't run the planning process repeatedly if you have already got the result of user's request.
|
||||
|
|
|
|||
|
|
@ -10,4 +10,3 @@ Expected user goal agent is {{ expected_user_goal_agent }}.
|
|||
{%- else -%}
|
||||
User goal agent is inferred based on user initial request.
|
||||
{%- endif %}
|
||||
Always route to planner first.
|
||||
Loading…
Reference in a new issue