Merge branch 'SciSharp:master' into master
This commit is contained in:
commit
ba03428c14
|
|
@ -10,7 +10,7 @@ Building solution using dotnet CLI (preferred).
|
|||
|
||||
### Clone the source code and build
|
||||
```powershell
|
||||
PS D:\> git clone https://github.com/Oceania2018/BotSharp
|
||||
PS D:\> git clone https://github.com/SciSharp/BotSharp
|
||||
PS D:\> cd BotSharp
|
||||
PS D:\> dotnet build
|
||||
```
|
||||
|
|
@ -62,28 +62,28 @@ So far, you have set up the Bot's running and development environment, but you c
|
|||
|
||||
**Ignore below section if you're going to just use REST API to interact with your bot.**
|
||||
|
||||
### Launch a chatbot UI (Optional)
|
||||
You can use a third-party open source UI for debugging and development, or you can directly use the REST API to integrate with your system.
|
||||
If you want to use the [Chatbot UI](https://github.com/mckaywrigley/chatbot-ui) as a front end.
|
||||
### Launch a BotSharp UI (Optional)
|
||||
BotSharp has an official front-end project to be used in conjunction with the backend. The main function of this project is to allow developers to visualize various configurations of the backend.
|
||||
```powershell
|
||||
PS D:\> git clone https://github.com/mckaywrigley/chatbot-ui
|
||||
PS D:\> cd chatbot-ui
|
||||
PS D:\> cd npm i
|
||||
PS D:\> cd npm run dev
|
||||
PS D:\> git clone https://github.com/SciSharp/BotSharp-UI
|
||||
PS D:\> cd BotSharp-UI
|
||||
PS D:\> npm install
|
||||
PS D:\> npm run dev
|
||||
```
|
||||
|
||||
Update API url in `.env.local` to your localhost BotSharp backend service.
|
||||
Update API url in `.env` to your localhost BotSharp backend service.
|
||||
```config
|
||||
OPENAI_API_HOST=http://localhost:5500
|
||||
PUBLIC_SERVICE_URL=http://localhost:5500
|
||||
PUBLIC_LIVECHAT_HOST=http://localhost:5015
|
||||
```
|
||||
|
||||
Point your web browser at http://localhost:3000 and enjoy Chatbot with BotSharp.
|
||||
Point your web browser at http://localhost:5015 and enjoy Chatbot with BotSharp.
|
||||
|
||||

|
||||

|
||||
|
||||
## Install in NuGet
|
||||
If you don't want to use the source code to experience this framework, you can also directly install the [NuGet packages](https://www.nuget.org/packages?q=BotSharp) released by BotSharp, and install different function packages according to the needs of your project. Before installing, please read the documentation carefully to understand the functions that different modules can provide.
|
||||
|
||||
```powershell
|
||||
PS D:\> Install-Package BotSharp.Core
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ public class StreamingLogModel
|
|||
public string ConversationId { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
[JsonPropertyName("role")]
|
||||
public string Role { get; set; }
|
||||
|
||||
[JsonPropertyName("content")]
|
||||
public string Content { get; set; }
|
||||
|
|
|
|||
|
|
@ -206,8 +206,13 @@ namespace BotSharp.Core.Repository
|
|||
var records = new List<Conversation>();
|
||||
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
|
||||
var pager = filter?.Pager ?? new Pagination();
|
||||
var totalDirs = Directory.GetDirectories(dir);
|
||||
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
var totalDirs = Directory.GetDirectories(dir);
|
||||
foreach (var d in totalDirs)
|
||||
{
|
||||
var path = Path.Combine(d, CONVERSATION_FILE);
|
||||
|
|
|
|||
|
|
@ -14,17 +14,27 @@ public class UserIdentity : IUserIdentity
|
|||
}
|
||||
|
||||
|
||||
public string Id
|
||||
public string Id
|
||||
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value!;
|
||||
|
||||
public string UserName
|
||||
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value!;
|
||||
|
||||
public string Email
|
||||
public string Email
|
||||
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value!;
|
||||
|
||||
public string FirstName
|
||||
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value!;
|
||||
public string FirstName
|
||||
{
|
||||
get
|
||||
{
|
||||
var givenName = _claims?.FirstOrDefault(x => x.Type == ClaimTypes.GivenName);
|
||||
if (givenName == null)
|
||||
{
|
||||
return UserName;
|
||||
}
|
||||
return givenName.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public string LastName
|
||||
=> _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value!;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public class UserService : IUserService
|
|||
}
|
||||
|
||||
record = user;
|
||||
record.Email = user.Email.ToLower();
|
||||
record.Email = user.Email?.ToLower();
|
||||
record.Salt = Guid.NewGuid().ToString("N");
|
||||
record.Password = Utilities.HashText(user.Password, record.Salt);
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ public class UserService : IUserService
|
|||
Issuer = issuer,
|
||||
Audience = audience,
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
|
||||
SecurityAlgorithms.HmacSha512Signature)
|
||||
SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
|
|
@ -178,7 +178,7 @@ public class UserService : IUserService
|
|||
public async Task<User> GetMyProfile()
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var user = db.GetUserById(_user.Id);
|
||||
var user = db.GetUserByUserName(_user.UserName);
|
||||
return user;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,18 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.25" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
|
||||
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="6.0.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.25" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.26" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
using BotSharp.Abstraction.Messaging.JsonConverters;
|
||||
using BotSharp.Core.Users.Services;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.IdentityModel.Logging;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
|
||||
namespace BotSharp.OpenAPI;
|
||||
|
|
@ -29,11 +31,14 @@ public static class BotSharpOpenApiExtensions
|
|||
services.AddScoped<IUserIdentity, UserIdentity>();
|
||||
|
||||
// Add bearer authentication
|
||||
services.AddAuthentication(options =>
|
||||
var schema = "MIXED_SCHEME";
|
||||
var builder = services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
// custom scheme defined in .AddPolicyScheme() below
|
||||
// inspired from https://weblog.west-wind.com/posts/2022/Mar/29/Combining-Bearer-Token-and-Cookie-Auth-in-ASPNET
|
||||
options.DefaultScheme = schema;
|
||||
options.DefaultChallengeScheme = schema;
|
||||
options.DefaultAuthenticateScheme = schema;
|
||||
}).AddJwtBearer(o =>
|
||||
{
|
||||
o.TokenValidationParameters = new TokenValidationParameters
|
||||
|
|
@ -52,8 +57,39 @@ public static class BotSharpOpenApiExtensions
|
|||
o.TokenValidationParameters.SignatureValidator = (string token, TokenValidationParameters parameters) =>
|
||||
new JwtSecurityToken(token);
|
||||
}
|
||||
}).AddCookie(options =>
|
||||
{
|
||||
}).AddPolicyScheme(schema, "Mixed authentication", options =>
|
||||
{
|
||||
// runs on each request
|
||||
options.ForwardDefaultSelector = context =>
|
||||
{
|
||||
// filter by auth type
|
||||
string authorization = context.Request.Headers[HeaderNames.Authorization];
|
||||
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
|
||||
return JwtBearerDefaults.AuthenticationScheme;
|
||||
else if (context.Request.Cookies.ContainsKey(".AspNetCore.Cookies"))
|
||||
return CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
else if (context.Request.Path.StartsWithSegments("/sso") && context.Request.Method == "GET")
|
||||
return CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
else if (context.Request.Path.ToString().StartsWith("/signin-") && context.Request.Method == "GET")
|
||||
return CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
|
||||
// otherwise always check for cookie auth
|
||||
return JwtBearerDefaults.AuthenticationScheme;
|
||||
};
|
||||
});
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(config["OAuth:GitHub:ClientId"]) && !string.IsNullOrWhiteSpace(config["OAuth:GitHub:ClientSecret"]))
|
||||
{
|
||||
builder = builder.AddGitHub(options =>
|
||||
{
|
||||
options.ClientId = config["OAuth:GitHub:ClientId"];
|
||||
options.ClientSecret = config["OAuth:GitHub:ClientSecret"];
|
||||
options.Scope.Add("user:email");
|
||||
});
|
||||
}
|
||||
|
||||
// Add services to the container.
|
||||
services.AddControllers()
|
||||
.AddJsonOptions(options =>
|
||||
|
|
@ -75,18 +111,18 @@ public static class BotSharpOpenApiExtensions
|
|||
Type = SecuritySchemeType.ApiKey
|
||||
});
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement {
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -123,6 +159,7 @@ public static class BotSharpOpenApiExtensions
|
|||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
IdentityModelEventSource.ShowPII = true;
|
||||
app.UseSwaggerUI();
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
using AspNet.Security.OAuth.GitHub;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BotSharp.OpenAPI.Controllers;
|
||||
|
|
@ -6,9 +9,11 @@ namespace BotSharp.OpenAPI.Controllers;
|
|||
[ApiController]
|
||||
public class UserController : ControllerBase
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IUserService _userService;
|
||||
public UserController(IUserService userService)
|
||||
public UserController(IUserService userService, IServiceProvider services)
|
||||
{
|
||||
_services = services;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
|
|
@ -30,6 +35,25 @@ public class UserController : ControllerBase
|
|||
return Ok(token);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("/sso/{provider}")]
|
||||
public async Task<IActionResult> Authorize([FromRoute] string provider)
|
||||
{
|
||||
return Challenge(new AuthenticationProperties { RedirectUri = $"page/user/me" }, provider);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("/signout")]
|
||||
[HttpPost("/signout")]
|
||||
public IActionResult SignOutCurrentUser()
|
||||
{
|
||||
// Instruct the cookies middleware to delete the local cookie created
|
||||
// when the user agent is redirected from the external identity provider
|
||||
// after a successful authentication flow (e.g Google or Facebook).
|
||||
return SignOut(new AuthenticationProperties { RedirectUri = "/" },
|
||||
CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("/user")]
|
||||
public async Task<UserViewModel> CreateUser(UserCreationModel user)
|
||||
|
|
@ -38,10 +62,28 @@ public class UserController : ControllerBase
|
|||
return UserViewModel.FromUser(createdUser);
|
||||
}
|
||||
|
||||
[HttpGet("/user/my")]
|
||||
[HttpGet("/user/me")]
|
||||
public async Task<UserViewModel> GetMyUserProfile()
|
||||
{
|
||||
var user = await _userService.GetMyProfile();
|
||||
if (user == null)
|
||||
{
|
||||
var identiy = _services.GetRequiredService<IUserIdentity>();
|
||||
var accessor = _services.GetRequiredService<IHttpContextAccessor>();
|
||||
var claims = accessor.HttpContext.User.Claims;
|
||||
if (claims.Any(x => x.Type == GitHubAuthenticationConstants.Claims.Name))
|
||||
{
|
||||
user = await _userService.CreateUser(new User
|
||||
{
|
||||
Email = identiy.Email,
|
||||
UserName = identiy.UserName,
|
||||
FirstName = identiy.FirstName,
|
||||
LastName = identiy.LastName,
|
||||
Source = "GitHub",
|
||||
ExternalId = identiy.Id,
|
||||
});
|
||||
}
|
||||
}
|
||||
return UserViewModel.FromUser(user);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ public class UserViewModel
|
|||
public string Email { get; set; }
|
||||
public string Role { get; set; } = UserRole.Client;
|
||||
[JsonPropertyName("full_name")]
|
||||
public string FullName => $"{FirstName} {LastName}";
|
||||
public string FullName => $"{FirstName} {LastName}".Trim();
|
||||
public string Source { get; set; }
|
||||
[JsonPropertyName("external_id")]
|
||||
public string? ExternalId { get; set; }
|
||||
[JsonPropertyName("create_date")]
|
||||
|
|
@ -43,6 +44,7 @@ public class UserViewModel
|
|||
LastName = user.LastName,
|
||||
Email = user.Email,
|
||||
Role = user.Role,
|
||||
Source = user.Source,
|
||||
ExternalId = user.ExternalId,
|
||||
CreateDate = user.CreatedTime,
|
||||
UpdateDate = user.UpdatedTime
|
||||
|
|
|
|||
|
|
@ -16,10 +16,6 @@
|
|||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.OpenAPI\BotSharp.OpenAPI.csproj" />
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook
|
|||
{
|
||||
var conversationId = _state.GetConversationId();
|
||||
var log = $"MessageId: {message.MessageId} ==>\r\n{message.Role}: {message.Content}";
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, _user.UserName, log));
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, _user.UserName, message.Role, log));
|
||||
}
|
||||
|
||||
public async Task BeforeGenerating(Agent agent, List<RoleDialogModel> conversations)
|
||||
|
|
@ -64,21 +64,22 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook
|
|||
var conversationId = _state.GetConversationId();
|
||||
var agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, tokenStats.Prompt));
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, message.Role, tokenStats.Prompt));
|
||||
|
||||
var log = message.Role == AgentRole.Function ?
|
||||
$"[{agent?.Name}]: {message.FunctionName}({message.FunctionArgs})" :
|
||||
$"[{agent?.Name}]: {message.Content}";
|
||||
log += $"\r\n<== MessageId: {message.MessageId}";
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, log));
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, message.Role, log));
|
||||
}
|
||||
|
||||
private string BuildLog(string conversationId, string? name, string content)
|
||||
private string BuildLog(string conversationId, string? name, string role, string content)
|
||||
{
|
||||
var log = new StreamingLogModel
|
||||
{
|
||||
ConversationId = conversationId,
|
||||
Name = name,
|
||||
Role = role,
|
||||
Content = content,
|
||||
CreateTime = DateTime.UtcNow
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public class WebSocketsMiddleware
|
|||
if (request.Path.StartsWithSegments("/chatHub", StringComparison.OrdinalIgnoreCase) &&
|
||||
request.Query.TryGetValue("access_token", out var accessToken))
|
||||
{
|
||||
request.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||
request.Headers["Authorization"] = $"Bearer {accessToken}";
|
||||
}
|
||||
|
||||
await _next(httpContext);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@
|
|||
"Key": "31ba6052aa6f4569901facc3a41fcb4a"
|
||||
},
|
||||
|
||||
"OAuth": {
|
||||
"GitHub": {
|
||||
"ClientId": "",
|
||||
"ClientSecret": ""
|
||||
}
|
||||
},
|
||||
|
||||
"LlmProviders": [
|
||||
{
|
||||
"Provider": "azure-openai",
|
||||
|
|
|
|||
Loading…
Reference in a new issue