Compare commits

...

51 Commits
0.1.1 ... 0.1.3

Author SHA1 Message Date
zyxucp
b25e429687 fix 修改maxtoken 2024-03-01 12:15:21 +08:00
zyxucp
a3f87bf123 fix 修复维度和maxtoken问题 2024-03-01 12:10:11 +08:00
zyxucp
2cab253c4a fix 修复openai stream接口格式不正确问题 2024-03-01 12:09:27 +08:00
zyxucp
ed119f02e2 fix 修改message实体 2024-02-29 18:28:32 +08:00
zyxucp
f178fc16e9 style 样式调整 2024-02-29 18:23:50 +08:00
zyxucp
e4b2071689 fix SK还原到1.4 2024-02-29 17:49:43 +08:00
zyxucp
146df7ed1d fix 修改SK版本 2024-02-29 17:42:41 +08:00
zyxucp
7e8db0a3f8 add 修改气泡样式 2024-02-29 17:40:43 +08:00
zyxucp
e098922219 fix 修改聊天窗为气泡样式 2024-02-29 17:30:50 +08:00
zyxucp
16ca024c22 Merge branch 'main' into feature_plugin 2024-02-29 13:55:15 +08:00
zyxucp
a886e9dfb3 fix 修复兼容域名的情况 端口为0 2024-02-29 13:54:33 +08:00
zyxucp
5d6114c9ec update 升级SK 至1.5.0 和升级KM 2024-02-29 12:52:44 +08:00
zyxucp
e3b2e1f434 fix 修复没登陆时没有权限的问题 2024-02-29 12:41:50 +08:00
zyxucp
6145347d9b add 增加权限管理 2024-02-29 12:35:03 +08:00
zyxucp
49e694cbdc add 增加非管理员不允许操作系统设置 2024-02-29 12:00:22 +08:00
zyxucp
f860229993 fix 调整Util目录 2024-02-29 10:38:36 +08:00
zyxucp
f5d93baa17 fix 使用默认切片 2024-02-28 18:48:40 +08:00
zyxucp
76f58c43b0 Update README.md 2024-02-28 17:55:48 +08:00
zyxucp
db29fcc867 add 用户个人设置 2024-02-28 17:17:37 +08:00
zyxucp
2f361c23c7 add 修改登陆人名称 2024-02-28 14:50:04 +08:00
zyxucp
4e7ac6eb4b add 增加多用户登陆验证 2024-02-28 14:31:03 +08:00
zyxucp
58bca689c8 add 增加用户新增功能 2024-02-28 14:15:11 +08:00
zyxucp
e1b2aee33c add 新增用户 2024-02-28 13:58:41 +08:00
zyxucp
7a6e8525c5 add 增加用户新增页面 2024-02-28 12:59:12 +08:00
zyxucp
b8db68bed1 add 增加用户列表 2024-02-28 12:38:55 +08:00
zyxucp
1072f47467 fix 修改切片分段 2024-02-28 12:03:31 +08:00
zyxucp
cfb1d858c6 fix 修改文案 2024-02-27 16:00:56 +08:00
zyxucp
8e0e75aee6 fix 修复切片显示bug 2024-02-27 13:14:42 +08:00
zyxucp
1479c942e2 fix 修改维度 2024-02-27 12:57:14 +08:00
zyxucp
63b02b889c fix 修复Embedding格式错误 2024-02-27 12:26:21 +08:00
zyxucp
5a1de0e2cf add 增加Embedding 2024-02-27 11:43:00 +08:00
zyxucp
ad2a402521 add 修改代理增加端口 2024-02-27 09:56:49 +08:00
zyxucp
a14af93afc add 增加LLamaSharp本地模型对接 2024-02-27 01:14:12 +08:00
zyxucp
90630bca1d add 样式修改 2024-02-25 11:24:46 +08:00
zyxucp
bf1db38e2e fix 修改知识库分配查询逻辑 2024-02-22 22:42:08 +08:00
zyxucp
88e23768c6 Merge pull request #5 from LoganChi/patch-1
Update 404.razor
2024-02-22 20:02:07 +08:00
zyxucp
ea8366ca92 Merge pull request #4 from LoganChi/patch-2
Create 404.razor.css
2024-02-22 20:01:55 +08:00
zyxucp
8e1f07415f Merge pull request #3 from LoganChi/patch-3
Update App.razor
2024-02-22 20:00:48 +08:00
LoganChi
0b62e773eb Update App.razor 2024-02-22 15:36:26 +08:00
LoganChi
1c4b1e2d2f Create 404.razor.css
an intersting 404 page
2024-02-22 15:33:46 +08:00
LoganChi
0e53dd3854 Update 404.razor 2024-02-22 15:30:54 +08:00
zyxucp
c2fa13b6ab Merge branch 'main' of https://github.com/xuzeyu91/AntSK 2024-02-22 12:41:21 +08:00
zyxucp
837c9a1444 add 增加配置文件示例 2024-02-22 11:31:55 +08:00
zyxucp
08ec4f6b7e Update README.md 2024-02-21 18:22:08 +08:00
zyxucp
f5a8076da3 fix 修改返回json请求头 2024-02-20 19:17:40 +08:00
zyxucp
cf8c935694 fix 修改API流式输出 2024-02-20 19:15:50 +08:00
zyxucp
67fd7d952a add 增加接口流式输出 2024-02-20 18:45:41 +08:00
zyxucp
b7a3ad6fe7 update 更新SK与KM版本 2024-02-20 15:55:05 +08:00
zyxucp
0dabd2ee58 fix 修复openai地址为IP端口时正则匹配不到的问题 2024-02-20 11:24:31 +08:00
zyxucp
e841fc6282 fix 处理openai地址是IP端口的情况 2024-02-20 11:15:02 +08:00
zyxucp
ab45a46fc0 fix 修复正则匹配不到IP和端口的问题 2024-02-20 11:10:34 +08:00
51 changed files with 1848 additions and 244 deletions

View File

@@ -8,16 +8,22 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="8.1.0" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="LLamaSharp.kernel-memory" Version="0.10.0" />
<PackageReference Include="LLamaSharp.semantic-kernel" Version="0.10.0" />
<PackageReference Include="MarkdownSharp" Version="2.0.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.137" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.143" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.3.0" />
<PackageReference Include="Microsoft.SemanticKernel.Core" Version="1.3.0" />
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Core" Version="1.3.0-alpha" />
<PackageReference Include="Microsoft.KernelMemory.Core" Version="0.26.240121.1" />
<PackageReference Include="Microsoft.KernelMemory.MemoryDb.Postgres" Version="0.26.240121.1" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.4.0" />
<PackageReference Include="Microsoft.SemanticKernel.Core" Version="1.4.0" />
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Core" Version="1.4.0-alpha" />
<PackageReference Include="Microsoft.KernelMemory.Core" Version="0.30.240227.1" />
<PackageReference Include="Microsoft.KernelMemory.MemoryDb.Postgres" Version="0.30.240227.1" />
<PackageReference Include="LLamaSharp" Version="0.10.0" />
<PackageReference Include="LLamaSharp.Backend.Cpu" Version="0.10.0" />
</ItemGroup>
</Project>

View File

@@ -57,6 +57,11 @@
<param name="result"></param>
<returns></returns>
</member>
<member name="P:AntSK.Domain.Model.MessageInfo.IsSend">
<summary>
发送是true 接收是false
</summary>
</member>
<member name="P:AntSK.Domain.Model.PageList`1.PageIndex">
<summary>
当前页从1开始
@@ -404,6 +409,31 @@
sqlserver连接
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Users.No">
<summary>
工号,用于登陆
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Users.Password">
<summary>
密码
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Users.Name">
<summary>
名称
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Users.Describe">
<summary>
备注
</summary>
</member>
<member name="P:AntSK.Domain.Repositories.Users.MenuRole">
<summary>
菜单权限
</summary>
</member>
<member name="M:AntSK.Domain.Utils.ConvertUtils.IsNull(System.Object)">
<summary>
判断是否为空为空返回true

View File

@@ -19,23 +19,19 @@ namespace AntSK.Domain.Domain.Service
{
foreach (var memoryDb in memoryDbs)
{
var list = memoryDb.GetListAsync(memoryIndex.Name, null, 100, true);
await foreach (var item in list)
var items = await memoryDb.GetListAsync(memoryIndex.Name, new List<MemoryFilter>() { new MemoryFilter().ByDocument(fileid) }, 100, true).ToListAsync();
foreach (var item in items)
{
if (item.Id.Contains(fileid))
KMFile file = new KMFile()
{
KMFile file = new KMFile()
{
Text = item.Payload.FirstOrDefault(p => p.Key == "text").Value.ConvertToString(),
Url= item.Payload.FirstOrDefault(p => p.Key == "url").Value.ConvertToString(),
LastUpdate= item.Payload.FirstOrDefault(p => p.Key == "last_update").Value.ConvertToString(),
Schema = item.Payload.FirstOrDefault(p => p.Key == "schema").Value.ConvertToString(),
File = item.Payload.FirstOrDefault(p => p.Key == "file").Value.ConvertToString(),
};
docTextList.Add(file);
}
Text = item.Payload.FirstOrDefault(p => p.Key == "text").Value.ConvertToString(),
Url = item.Payload.FirstOrDefault(p => p.Key == "url").Value.ConvertToString(),
LastUpdate = item.Payload.FirstOrDefault(p => p.Key == "last_update").Value.ConvertToString(),
Schema = item.Payload.FirstOrDefault(p => p.Key == "schema").Value.ConvertToString(),
File = item.Payload.FirstOrDefault(p => p.Key == "file").Value.ConvertToString(),
};
docTextList.Add(file);
}
}
}

View File

@@ -9,9 +9,13 @@ namespace AntSK.Domain.Model
public class MessageInfo
{
public string ID { get; set; } = "";
public string Questions { get; set; } = "";
public string Answers { get; set; } = "";
public string Context { get; set; } = "";
public string HtmlAnswers { get; set; } = "";
/// <summary>
/// 发送是true 接收是false
/// </summary>
public bool IsSend { get; set; } = false;
public DateTime CreateTime { get; set; }
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AntSK.Domain.Options
{
public class LLamaSharpOption
{
public static string Chat { get; set; }
public static string Embedding { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using AntSK.Domain.Repositories.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AntSK.Domain.Repositories
{
public interface IUsers_Repositories : IRepository<Users>
{
}
}

View File

@@ -0,0 +1,47 @@
using SqlSugar;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AntSK.Domain.Repositories
{
[SugarTable("Users")]
public partial class Users
{
[SugarColumn(IsPrimaryKey = true)]
public string Id { get; set; }
/// <summary>
/// 工号,用于登陆
/// </summary>
[Required]
public string No { get; set; }
/// <summary>
/// 密码
/// </summary>
[Required]
public string Password { get; set; }
/// <summary>
/// 名称
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// 备注
/// </summary>
[Required]
public string Describe { get; set; }
/// <summary>
/// 菜单权限
/// </summary>
[Required]
public string MenuRole { get; set; }
}
}

View File

@@ -0,0 +1,16 @@

using AntSK.Domain.Repositories.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AntSK.Domain.Common.DependencyInjection;
namespace AntSK.Domain.Repositories
{
[ServiceDescription(typeof(IUsers_Repositories), ServiceLifetime.Scoped)]
public class Users_Repositories : Repository<Users>, IUsers_Repositories
{
}
}

View File

@@ -3,7 +3,7 @@ using System.Buffers.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AntSK.Utils
namespace AntSK.Domain.Utils
{
public class LongToDateTimeConverter : JsonConverter<DateTime>
{

View File

@@ -13,13 +13,20 @@ namespace AntSK.Domain.Utils
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
UriBuilder uriBuilder;
Regex regex = new Regex(@"(https?)://([^/]+)/(.*)");
Regex regex = new Regex(@"(https?)://([^/:]+)(:\d+)?/(.*)");
Match match = regex.Match(OpenAIOption.EndPoint);
string xieyi = match.Groups[1].Value;
string host = match.Groups[2].Value;
string route = match.Groups[3].Value;
if (match.Success)
{
string xieyi = match.Groups[1].Value;
string host = match.Groups[2].Value;
string port = match.Groups[3].Value; // 可选的端口号
string route = match.Groups[4].Value;
// 如果port不为空它将包含冒号所以你可能需要去除它
port = string.IsNullOrEmpty(port) ? port : port.Substring(1);
// 拼接host和端口号
var hostnew = string.IsNullOrEmpty(port) ? host : $"{host}:{port}";
switch (request.RequestUri.LocalPath)
{
case "/v1/chat/completions":
@@ -27,10 +34,15 @@ namespace AntSK.Domain.Utils
uriBuilder = new UriBuilder(request.RequestUri)
{
// 这里是你要修改的 URL
Scheme = $"{xieyi}://{host}/",
Host = host,
Scheme = $"{xieyi}://{hostnew}/",
Host = host,
Path = route + "v1/chat/completions",
};
if (port.ConvertToInt32() != 0)
{
uriBuilder.Port = port.ConvertToInt32();
}
request.RequestUri = uriBuilder.Uri;
break;
@@ -42,10 +54,15 @@ namespace AntSK.Domain.Utils
Host = host,
Path = route + "v1/embeddings",
};
if (port.ConvertToInt32() != 0)
{
uriBuilder.Port = port.ConvertToInt32();
}
request.RequestUri = uriBuilder.Uri;
break;
}
}
// 接着,调用基类的 SendAsync 方法将你的修改后的请求发出去
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

View File

@@ -0,0 +1,19 @@
using BCrypt.Net;
namespace AntSK.Domain.Utils
{
public class PasswordUtil
{
public static string HashPassword(string password)
{
// 默认的工作因子是10可以根据你的需求调整以增加散列的复杂度
return BCrypt.Net.BCrypt.HashPassword(password);
}
// 验证密码是否匹配散列值
public static bool VerifyPassword(string password, string hashedPassword)
{
return BCrypt.Net.BCrypt.Verify(password, hashedPassword);
}
}
}

View File

@@ -20,9 +20,5 @@
<ItemGroup>
<ProjectReference Include="..\AntSK.Domain\AntSK.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Pages\Setting\" />
</ItemGroup>
</Project>

View File

@@ -16,6 +16,19 @@
</summary>
<returns></returns>
</member>
<member name="M:AntSK.Controllers.LLamaSharpController.chat(AntSK.Models.OpenAIModel)">
<summary>
本地会话接口
</summary>
<returns></returns>
</member>
<member name="M:AntSK.Controllers.LLamaSharpController.embedding(AntSK.Models.OpenAIEmbeddingModel)">
<summary>
本地嵌入接口
</summary>
<param name="model"></param>
<returns></returns>
</member>
<member name="T:AntSK.Controllers.OpenController">
<summary>
对外接口
@@ -89,6 +102,16 @@
<param name="fileid"></param>
<returns></returns>
</member>
<member name="T:AntSK.Services.LLamaSharp.LLamaChatService">
<summary>
</summary>
</member>
<member name="T:AntSK.Services.LLamaSharp.LLamaEmbeddingService">
<summary>
本地Embedding
</summary>
</member>
<member name="M:AntSK.Services.OpenApi.OpenApiService.SendKms(System.String,AntSK.Domain.Repositories.Apps)">
<summary>
发送知识库问答

View File

@@ -9,7 +9,7 @@
</Found>
<NotFound>
<LayoutView Layout="@typeof(BasicLayout)">
<p>Sorry, there's nothing at this address.</p>
<AntSK.Pages.Exception._404/>
</LayoutView>
</NotFound>
</Router>
@@ -29,4 +29,4 @@
NavigationManager.NavigateTo($"user/login?returnUrl={Uri.EscapeDataString(returnUrl)}", true);
}
};
}
}

View File

@@ -1,11 +1,13 @@
@namespace AntSK.Components
@using Microsoft.AspNetCore.Authorization
@inherits AntDomComponentBase
<Space Class="@ClassMapper.Class" Size="@("24")">
<Space Class="@ClassMapper.Class" Size="@("26")">
<SpaceItem>
<AvatarDropdown Name="@_currentUser.Name"
<AvatarDropdown Name="@context.Identity.Name"
Avatar="@_currentUser.Avatar"
MenuItems="@AvatarMenuItems"
OnItemSelected="HandleSelectUser" />
</SpaceItem>
</Space>

View File

@@ -6,6 +6,10 @@ using System.Linq;
using System.Threading.Tasks;
using AntSK.Models;
using AntSK.Services;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
using AntSK.Services.Auth;
using AntSK.Domain.Options;
namespace AntSK.Components
{
@@ -38,7 +42,6 @@ namespace AntSK.Components
public AvatarMenuItem[] AvatarMenuItems { get; set; } = new AvatarMenuItem[]
{
new() { Key = "center", IconType = "user", Option = "个人中心"},
new() { Key = "setting", IconType = "setting", Option = "个人设置"},
new() { IsDivider = true },
new() { Key = "logout", IconType = "logout", Option = "退出登录"}
@@ -50,6 +53,11 @@ namespace AntSK.Components
[Inject] protected IProjectService ProjectService { get; set; }
[Inject] protected MessageService MessageService { get; set; }
[Inject] public AuthenticationStateProvider AuthenticationStateProvider { get; set; }
[Inject] protected MessageService? Message { get; set; }
private ClaimsPrincipal context => ((AntSKAuthProvider)AuthenticationStateProvider).GetCurrentUser();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
@@ -73,11 +81,15 @@ namespace AntSK.Components
{
switch (item.Key)
{
case "center":
NavigationManager.NavigateTo("/account/center");
break;
case "setting":
NavigationManager.NavigateTo("/account/settings");
if (context.Identity.Name != LoginOption.User)
{
NavigationManager.NavigateTo("/setting/user/info/" + context.Identity.Name);
}
else
{
_ = Message.Info("管理员无需设置", 2);
}
break;
case "logout":
NavigationManager.NavigateTo("/user/login");

View File

@@ -0,0 +1,44 @@
using AntSK.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using AntSK.Domain.Utils;
using AntSK.Services.LLamaSharp;
namespace AntSK.Controllers
{
[ApiController]
public class LLamaSharpController(ILLamaSharpService _lLamaSharpService) : ControllerBase
{
/// <summary>
/// 本地会话接口
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("llama/v1/chat/completions")]
public async Task chat(OpenAIModel model)
{
if (model.stream)
{
await _lLamaSharpService.ChatStream(model, HttpContext);
}
else
{
await _lLamaSharpService.Chat(model, HttpContext);
}
}
/// <summary>
/// 本地嵌入接口
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
[Route("llama/v1/embeddings")]
public async Task embedding(OpenAIEmbeddingModel model)
{
await _lLamaSharpService.Embedding(model,HttpContext);
}
}
}

View File

@@ -22,11 +22,10 @@ namespace AntSK.Controllers
/// <returns></returns>
[HttpPost]
[Route("api/v1/chat/completions")]
public async Task<IActionResult> chat(OpenAIModel model)
public async Task chat(OpenAIModel model)
{
string sk = HttpContext.Request.Headers["Authorization"].ConvertToString();
var result=await _openApiService.Chat(model,sk);
return Ok(result);
await _openApiService.Chat(model,sk, HttpContext);
}
}
}

View File

@@ -1,4 +1,9 @@
@namespace AntSK
@using System.Security.Claims
@using AntSK.Services.Auth
@using Microsoft.AspNetCore.Components.Authorization
@using AntSK.Domain.Options
@using AntSK.Domain.Repositories
@inherits LayoutComponentBase
<AntDesign.ProLayout.BasicLayout
@@ -21,11 +26,28 @@
private MenuDataItem[] _menuData = { };
[Inject] public HttpClient HttpClient { get; set; }
[Inject] public AuthenticationStateProvider AuthenticationStateProvider { get; set; }
[Inject] protected IUsers_Repositories _users_Repositories { get; set; }
private ClaimsPrincipal context => ((AntSKAuthProvider)AuthenticationStateProvider).GetCurrentUser();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
_menuData = await HttpClient.GetFromJsonAsync<MenuDataItem[]>("data/menu.json");
//菜单权限控制
var menuList = await HttpClient.GetFromJsonAsync<MenuDataItem[]>("data/menu.json");
if ((bool)context?.Identity.IsAuthenticated)
{
if (context.Identity.Name == LoginOption.User)
{
_menuData = menuList;
}
else
{
var userMenuList = _users_Repositories.GetFirst(p => p.No == context.Identity.Name).MenuRole.Split(",").ToList();
//非管理员用户不允许操作系统设置
_menuData = menuList.Where(p => p.Key != "setting" && userMenuList.Contains(p.Key)).ToArray();
}
}
}

View File

@@ -9,13 +9,20 @@ namespace AntSK.Models
{
public class OpenAIModel
{
public bool stream { get; set; } = false;
public List<OpenAIMessage> messages { get; set; }
}
public class OpenAIMessage
{
public string role { get; set; }
public string role { get; set; }
public string content { get; set; }
}
public class OpenAIEmbeddingModel
{
public List<string> input { get; set; }
}
}

View File

@@ -1,16 +1,18 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace AntSK.Models.OpenAPI
{
public class OpenAIResult
{
public string id { get; set; } = Guid.NewGuid().ToString();
[JsonPropertyName("object")]
[JsonProperty("object")]
public string obj { get; set; } = "chat.completion";
public List<ChoicesModel> choices { get; set; }
public long created { get; set; }
}
public class ChoicesModel
{
public string finish_reason { get; set; } = "stop";
@@ -19,5 +21,47 @@ namespace AntSK.Models.OpenAPI
public OpenAIMessage message { get; set; }
}
public class OpenAIEmbeddingResult
{
[JsonProperty("object")]
public string obj { get; set; } = "list";
public string model { get; set; } = "ada";
public UsageModel usage { get; set; } = new UsageModel();
public List<DataModel> data { get; set; } = new List<DataModel>() { new DataModel() };
}
public class UsageModel
{
public long prompt_tokens { get; set; } = 0;
public long total_tokens { get; set; } = 0;
}
public class DataModel
{
[JsonProperty("object")]
public string obj { get; set; } = "embedding";
public int index { get; set; } = 0;
public List<float> embedding { get; set; }
}
public class OpenAIStreamResult
{
public string id { get; set; } = Guid.NewGuid().ToString();
[JsonProperty("object")]
public string obj { get; set; } = "chat.completion";
public List<StreamChoicesModel> choices { get; set; }
public long created { get; set; }
}
public class StreamChoicesModel
{
public int index { get; set; } = 0;
public OpenAIMessage delta { get; set; }
}
}

View File

@@ -0,0 +1,32 @@
using AntDesign;
namespace AntSK.Models
{
public class LayoutModel
{
public static FormItemLayout _formItemLayout = new FormItemLayout
{
LabelCol = new ColLayoutParam
{
Xs = new EmbeddedProperty { Span = 24 },
Sm = new EmbeddedProperty { Span = 7 },
},
WrapperCol = new ColLayoutParam
{
Xs = new EmbeddedProperty { Span = 24 },
Sm = new EmbeddedProperty { Span = 12 },
Md = new EmbeddedProperty { Span = 10 },
}
};
public static FormItemLayout _submitFormLayout = new FormItemLayout
{
WrapperCol = new ColLayoutParam
{
Xs = new EmbeddedProperty { Span = 24, Offset = 0 },
Sm = new EmbeddedProperty { Span = 10, Offset = 7 },
}
};
}
}

View File

@@ -1,6 +1,6 @@
using System;
using System.Text.Json.Serialization;
using AntSK.Utils;
using AntSK.Domain.Utils;
namespace AntSK.Models
{

View File

@@ -12,31 +12,31 @@
<Form Model="@_appModel"
Style="margin-top: 8px;"
OnFinish="HandleSubmit">
<FormItem Label="知识库名称" LabelCol="_formItemLayout.LabelCol" WrapperCol="_formItemLayout.WrapperCol">
<FormItem Label="知识库名称" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入知识库名称" @bind-Value="@context.Name" />
</FormItem>
<FormItem Label="图标" LabelCol="_formItemLayout.LabelCol" WrapperCol="_formItemLayout.WrapperCol">
<FormItem Label="图标" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入图标" @bind-Value="@context.Icon" />
<a href="https://antblazor.com/zh-CN/components/icon" target="_blank">图标库</a>
</FormItem>
<FormItem Label="类型" LabelCol="_formItemLayout.LabelCol" WrapperCol="_formItemLayout.WrapperCol">
<FormItem Label="类型" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<RadioGroup @bind-Value="context.Type">
<Radio RadioButton Value="@("chat")">简单对话</Radio>
<Radio RadioButton Value="@("kms")" >知识库</Radio>
</RadioGroup>
</FormItem>
<FormItem Label="描述" LabelCol="_formItemLayout.LabelCol" WrapperCol="_formItemLayout.WrapperCol">
<FormItem Label="描述" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入描述" @bind-Value="@context.Describe" />
</FormItem>
@if (@context.Type == "chat")
{
<FormItem Label="提示词" LabelCol="_formItemLayout.LabelCol" WrapperCol="_formItemLayout.WrapperCol">
<FormItem Label="提示词" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<TextArea MinRows="4" Placeholder="请输入提示词,用户输入使用{{$input}} 来做占位符" @bind-Value="@context.Prompt" />
</FormItem>
}
@if (@context.Type == "kms")
{
<FormItem Label="知识库" LabelCol="_formItemLayout.LabelCol" WrapperCol="_formItemLayout.WrapperCol">
<FormItem Label="知识库" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Select Mode="multiple"
@bind-Values="kmsIds"
Placeholder="选择知识库"
@@ -53,7 +53,7 @@
</Select>
</FormItem>
}
<FormItem Label=" " Style="margin-top:32px" WrapperCol="_submitFormLayout.WrapperCol">
<FormItem Label=" " Style="margin-top:32px" WrapperCol="LayoutModel._submitFormLayout.WrapperCol">
<Button Type="primary" HtmlType="submit">
保存
</Button>
@@ -61,13 +61,6 @@
返回
</Button>
</FormItem>
@if (!string.IsNullOrEmpty(_errorMsg))
{
<Alert Type="@AlertType.Error"
Message="错误"
Description="@_errorMsg"
ShowIcon="true" />
}
</Form>
</Card>
</ChildContent>

View File

@@ -18,6 +18,8 @@ namespace AntSK.Pages.AppPage
protected IKmss_Repositories _kmss_Repositories { get; set; }
[Inject]
protected NavigationManager NavigationManager { get; set; }
[Inject]
protected MessageService? Message { get; set; }
private Apps _appModel = new Apps() ;
@@ -26,31 +28,6 @@ namespace AntSK.Pages.AppPage
private List<Kmss> _kmsList = new List<Kmss>();
private string _errorMsg { get; set; }
private readonly FormItemLayout _formItemLayout = new FormItemLayout
{
LabelCol = new ColLayoutParam
{
Xs = new EmbeddedProperty { Span = 24 },
Sm = new EmbeddedProperty { Span = 7 },
},
WrapperCol = new ColLayoutParam
{
Xs = new EmbeddedProperty { Span = 24 },
Sm = new EmbeddedProperty { Span = 12 },
Md = new EmbeddedProperty { Span = 10 },
}
};
private readonly FormItemLayout _submitFormLayout = new FormItemLayout
{
WrapperCol = new ColLayoutParam
{
Xs = new EmbeddedProperty { Span = 24, Offset = 0 },
Sm = new EmbeddedProperty { Span = 10, Offset = 7 },
}
};
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
@@ -72,7 +49,7 @@ namespace AntSK.Pages.AppPage
_appModel.SecretKey="sk-"+ Guid.NewGuid().ToString();
if (_apps_Repositories.IsAny(p => p.Name == _appModel.Name))
{
_errorMsg = "名称已存在!";
_ = Message.Error("名称已存在!", 2);
return;
}

View File

@@ -10,7 +10,7 @@
<GridRow Gutter="(16, 16)">
<GridCol Span="12">
<Spin Size="large" Tip="请稍等..." Spinning="@(_loading)">
<Card Style="height:800px;overflow: auto;">
<Card Style="height:700px;overflow: auto;">
<TitleTemplate>
<Icon Type="setting" /> 选择应用
<Select DataSource="@_list"
@@ -23,28 +23,36 @@
</TitleTemplate>
<Body>
<div id="scrollDiv" style="height: 530px; overflow-y: auto; overflow-x: hidden;">
<GridRow Gutter="(8, 8)">
<Virtualize Items="@(MessageList.OrderByDescending(o => o.CreateTime).ToList())" Context="item">
<GridCol Span="24">
<Card Size="small">
<TitleTemplate>
<Text Strong><Icon Type="bulb" /> @(item.Questions)</Text>
</TitleTemplate>
<Extra>
<Space>
<SpaceItem>
<a style="color: gray;" @onclick="@(() => OnCopyAsync(item))"><Icon Type="copy" /></a>
</SpaceItem>
<SpaceItem>
<a style="color: gray;" @onclick="@(() => OnClearAsync(item.ID))"><Icon Type="rest" /></a>
</SpaceItem>
</Space>
</Extra>
<Body>
@((MarkupString)(item.HtmlAnswers))
</Body>
</Card>
</GridCol>
<GridRow Gutter="(8, 8)" Style="margin:0">
<Virtualize Items="@(MessageList.OrderBy(o => o.CreateTime).ToList())" Context="item">
@if (item.IsSend)
{
<GridRow Style="width:100%">
<GridCol Span="23">
<div class="chat-bubble sent">
@(item.Context)
@* <span class="timestamp">@item.CreateTime</span> *@
</div>
</GridCol>
<GridCol Span="1">
<Image Width="100%" Style="margin-top:10px;" Src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg" />
</GridCol>
</GridRow>
}
else
{
<GridRow Style="width:100%">
<GridCol Span="1">
<Image Width="100%" Style="margin-top:10px;" Src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg" />
</GridCol>
<GridCol Span="23">
<div class="chat-bubble received">
@((MarkupString)(item.HtmlAnswers))
@* <span class="timestamp">@item.CreateTime</span> *@
</div>
</GridCol>
</GridRow>
}
</Virtualize>
</GridRow>
</div>
@@ -59,7 +67,7 @@
</Spin>
</GridCol>
<GridCol Span="12">
<Card Style="height: 800px;overflow: auto;">
<Card Style="height: 700px;overflow: auto;">
<TitleTemplate>
<Icon Type="search" /> 调试结果
</TitleTemplate>
@@ -67,8 +75,8 @@
</Extra>
<Body>
<AntList Bordered DataSource="@RelevantSources">
<ChildContent Context="item">
<AntList Bordered DataSource="@RelevantSources" Style="padding:10px;">
<ChildContent Context="item" >
<span> <b>@item.SourceName </b> 相似度:<Text Mark> @item.Relevance</Text></span>
<Body>
@((MarkupString)(@item.Text))
@@ -80,7 +88,65 @@
</GridCol>
</GridRow>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 10px;
justify-content: center;
align-items: flex-start;
height: 100vh;
}
.chat-container {
width: 350px;
border: 1px solid #ccc;
border-radius: 5px;
overflow: hidden;
display: flex;
flex-direction: column;
background-color: #fff;
padding-bottom: 15px;
}
.chat-bubble {
padding: 10px;
margin: 10px;
margin-bottom: 0;
border-radius: 5px;
max-width: 70%;
position: relative;
}
.received {
background-color: #f0f0f0;
align-self: flex-start;
float: left;
}
.sent {
background-color: #daf8cb;
align-self: flex-end;
float: right;
}
.timestamp {
display: block;
font-size: 0.75em;
margin-top: 5px;
}
.received .timestamp {
text-align: right;
margin-right: 10px;
}
.sent .timestamp {
text-align: left;
margin-left: 10px;
}
</style>
@code {
}

View File

@@ -64,6 +64,15 @@ namespace AntSK.Pages.ChatPage
return;
}
MessageList.Add(new MessageInfo()
{
ID = Guid.NewGuid().ToString(),
Context = _messageInput,
CreateTime = DateTime.Now,
IsSend = true
});
Sendding = true;
await SendAsync(_messageInput);
_messageInput = "";
@@ -74,7 +83,7 @@ namespace AntSK.Pages.ChatPage
{
await Task.Run(() =>
{
_messageInput = item.Questions;
_messageInput = item.Context;
});
}
@@ -140,8 +149,7 @@ namespace AntSK.Pages.ChatPage
var info1 = new MessageInfo()
{
ID = Guid.NewGuid().ToString(),
Questions = questions,
Answers = answers,
Context = answers,
HtmlAnswers = htmlAnswers,
CreateTime = DateTime.Now,
};
@@ -193,9 +201,8 @@ namespace AntSK.Pages.ChatPage
{
info = new MessageInfo();
info.ID = Guid.NewGuid().ToString();
info.Questions = questions;
info.Answers = content.Content!;
info.HtmlAnswers = content.Content!;
info.Context = content?.Content?.ConvertToString();
info.HtmlAnswers = content?.Content?.ConvertToString();
info.CreateTime = DateTime.Now;
MessageList.Add(info);
@@ -208,7 +215,11 @@ namespace AntSK.Pages.ChatPage
await InvokeAsync(StateHasChanged);
}
//全部处理完后再处理一次Markdown
info!.HtmlAnswers = markdown.Transform(info.HtmlAnswers);
if (info.IsNotNull())
{
info!.HtmlAnswers = markdown.Transform(info.HtmlAnswers);
}
await InvokeAsync(StateHasChanged);
}
@@ -219,18 +230,31 @@ namespace AntSK.Pages.ChatPage
/// <returns></returns>
private async Task<string> HistorySummarize(string questions)
{
StringBuilder history = new StringBuilder();
foreach (var item in MessageList)
if (MessageList.Count > 1)
{
history.Append($"user:{item.Questions}{Environment.NewLine}");
history.Append($"assistant:{item.Answers}{Environment.NewLine}");
}
StringBuilder history = new StringBuilder();
foreach (var item in MessageList)
{
if (item.IsSend)
{
history.Append($"user:{item.Context}{Environment.NewLine}");
}
else
{
history.Append($"assistant:{item.Context}{Environment.NewLine}");
}
}
KernelFunction sunFun = _kernel.Plugins.GetFunction("ConversationSummaryPlugin", "SummarizeConversation");
var summary = await _kernel.InvokeAsync(sunFun, new() { ["input"] = $"内容是:{history.ToString()} {Environment.NewLine} 请注意用中文总结" });
string his = summary.GetValue<string>();
var msg = $"历史对话:{his}{Environment.NewLine} 用户问题:{Environment.NewLine}{questions}"; ;
return msg;
KernelFunction sunFun = _kernel.Plugins.GetFunction("ConversationSummaryPlugin", "SummarizeConversation");
var summary = await _kernel.InvokeAsync(sunFun, new() { ["input"] = $"内容是:{history.ToString()} {Environment.NewLine} 请注意用中文总结" });
string his = summary.GetValue<string>();
var msg = $"历史对话:{his}{Environment.NewLine} 用户问题:{Environment.NewLine}{questions}"; ;
return msg;
}
else
{
return questions;
}
}
}

View File

@@ -8,27 +8,36 @@
<div id="chat" style="display:flex; flex-direction:column; height:100%; overflow-x:hidden;">
<PageHeader Class="site-page-header" Title="@app.Name" Subtitle="@app.Describe" />
<div id="scrollDiv" style="flex:1; width:100%; overflow-y:auto; overflow-x:hidden;padding:10px;">
<Virtualize Items="@(MessageList.OrderByDescending(o => o.CreateTime).ToList())" Context="item">
<GridCol Span="24">
<Card>
<TitleTemplate>
<Text Strong><Icon Type="bulb" />@(item.Questions)</Text>
</TitleTemplate>
<Extra>
<Space>
<SpaceItem>
<a style="color: gray;" @onclick="@(() => OnCopyAsync(item))"><Icon Type="copy" /></a>
</SpaceItem>
<SpaceItem>
<a style="color: gray;" @onclick="@(() => OnClearAsync(item.ID))"><Icon Type="rest" /></a>
</SpaceItem>
</Space>
</Extra>
<Body>
@((MarkupString)(item.HtmlAnswers))
</Body>
</Card>
</GridCol>
<Virtualize Items="@(MessageList.OrderBy(o => o.CreateTime).ToList())" Context="item">
@if (item.IsSend)
{
<GridRow>
<GridCol Span="23">
<div class="chat-bubble sent">
@(item.Context)
@* <span class="timestamp">@item.CreateTime</span> *@
</div>
</GridCol>
<GridCol Span="1">
<Image Width="100%" Style="margin-top:10px;" Src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg" />
</GridCol>
</GridRow>
}
else
{
<GridRow>
<GridCol Span="1">
<Image Width="100%" Style="margin-top:10px;" Src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg" />
</GridCol>
<GridCol Span="23">
<div class="chat-bubble received">
@((MarkupString)(item.HtmlAnswers))
@* <span class="timestamp">@item.CreateTime</span> *@
</div>
</GridCol>
</GridRow>
}
</Virtualize>
</div>
<div style="flex-shrink:0;margin:10px;">
@@ -40,6 +49,66 @@
</div>
</div>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 10px;
justify-content: center;
align-items: flex-start;
height: 100vh;
}
.chat-container {
width: 350px;
border: 1px solid #ccc;
border-radius: 5px;
overflow: hidden;
display: flex;
flex-direction: column;
background-color: #fff;
padding-bottom: 15px;
}
.chat-bubble {
padding: 10px;
margin: 10px;
margin-bottom: 0;
border-radius: 5px;
max-width: 70%;
position: relative;
}
.received {
background-color: #f0f0f0;
align-self: flex-start;
float: left;
}
.sent {
background-color: #daf8cb;
align-self: flex-end;
float: right;
}
.timestamp {
display: block;
font-size: 0.75em;
margin-top: 5px;
}
.received .timestamp {
text-align: right;
margin-right: 10px;
}
.sent .timestamp {
text-align: left;
margin-left: 10px;
}
</style>
@code {
}

View File

@@ -16,6 +16,7 @@ using Newtonsoft.Json;
using SqlSugar;
using System;
using System.Text;
using AntSK.Domain.Utils;
namespace AntSK.Pages.ChatPage
{
@@ -56,6 +57,14 @@ namespace AntSK.Pages.ChatPage
return;
}
MessageList.Add(new MessageInfo() {
ID=Guid.NewGuid().ToString(),
Context=_messageInput,
CreateTime=DateTime.Now,
IsSend=true
});
Sendding = true;
await SendAsync(_messageInput);
_messageInput = "";
@@ -66,7 +75,7 @@ namespace AntSK.Pages.ChatPage
{
await Task.Run(() =>
{
_messageInput = item.Questions;
_messageInput = item.Context;
});
}
@@ -132,8 +141,7 @@ namespace AntSK.Pages.ChatPage
var info1 = new MessageInfo()
{
ID = Guid.NewGuid().ToString(),
Questions = questions,
Answers = answers,
Context = answers,
HtmlAnswers = htmlAnswers,
CreateTime = DateTime.Now,
};
@@ -170,9 +178,8 @@ namespace AntSK.Pages.ChatPage
{
info = new MessageInfo();
info.ID = Guid.NewGuid().ToString();
info.Questions = questions;
info.Answers = content.Content!;
info.HtmlAnswers = content.Content!;
info.Context = content?.Content?.ConvertToString();
info.HtmlAnswers = content?.Content?.ConvertToString();
info.CreateTime = DateTime.Now;
MessageList.Add(info);
@@ -196,18 +203,31 @@ namespace AntSK.Pages.ChatPage
/// <returns></returns>
private async Task<string> HistorySummarize(string questions)
{
StringBuilder history = new StringBuilder();
foreach (var item in MessageList)
if (MessageList.Count > 1)
{
history.Append($"user:{item.Questions}{Environment.NewLine}");
history.Append($"assistant:{item.Answers}{Environment.NewLine}");
}
StringBuilder history = new StringBuilder();
foreach (var item in MessageList)
{
if (item.IsSend)
{
history.Append($"user:{item.Context}{Environment.NewLine}");
}
else
{
history.Append($"assistant:{item.Context}{Environment.NewLine}");
}
}
KernelFunction sunFun = _kernel.Plugins.GetFunction("ConversationSummaryPlugin", "SummarizeConversation");
var summary = await _kernel.InvokeAsync(sunFun, new() { ["input"] = $"内容是:{history.ToString()} {Environment.NewLine} 请注意用中文总结" });
string his = summary.GetValue<string>();
var msg = $"历史对话:{his}{Environment.NewLine} 用户问题:{Environment.NewLine}{questions}"; ;
return msg;
KernelFunction sunFun = _kernel.Plugins.GetFunction("ConversationSummaryPlugin", "SummarizeConversation");
var summary = await _kernel.InvokeAsync(sunFun, new() { ["input"] = $"内容是:{history.ToString()} {Environment.NewLine} 请注意用中文总结" });
string his = summary.GetValue<string>();
var msg = $"历史对话:{his}{Environment.NewLine} 用户问题:{Environment.NewLine}{questions}"; ;
return msg;
}
else
{
return questions;
}
}
}
}

View File

@@ -1,10 +1,27 @@
@namespace AntSK.Pages.Exception
@page "/exception/404"
@using AntSK.Services.Auth
@inherits AuthComponentBase
<Result Status="404"
Title="404"
SubTitle="Sorry, the page you visited does not exist.">
<Extra>
<Button Type="primary">Back Home</Button>
</Extra>
</Result>
<div class="rail">
@for (var i = 0; i < StampCount; i++)
{
<div class="@((i % 2 == 0) ? "stamp zero" : "stamp four")">
@(i % 2 == 0 ? "4" : "0")
</div>
}
<div class="world">
<div class="forward">
<div class="box">
@for (var i = 0; i < 6; i++)
{
<div class="wall"></div>
}
</div>
</div>
</div>
</div>
@code {
private int StampCount { get; set; } = 20; // 初始的循环次数
}

View File

@@ -0,0 +1,245 @@
body {
background: #fff;
height: 100vh;
overflow: hidden;
display: flex;
font-family: "Anton", sans-serif;
justify-content: center;
align-items: center;
perspective: 1000px;
}
div {
transform-style: preserve-3d;
}
.rail {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
transform: rotateX(-30deg) rotateY(-50deg);
}
.rail .stamp {
position: absolute;
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
background: rgb(20, 20, 20);
color: #fff;
font-size: 7rem;
}
.rail .stamp:nth-child(1) {
animation: stampSlide 40000ms -2300ms linear infinite;
}
.rail .stamp:nth-child(2) {
animation: stampSlide 40000ms -4300ms linear infinite;
}
.rail .stamp:nth-child(3) {
animation: stampSlide 40000ms -6300ms linear infinite;
}
.rail .stamp:nth-child(4) {
animation: stampSlide 40000ms -8300ms linear infinite;
}
.rail .stamp:nth-child(5) {
animation: stampSlide 40000ms -10300ms linear infinite;
}
.rail .stamp:nth-child(6) {
animation: stampSlide 40000ms -12300ms linear infinite;
}
.rail .stamp:nth-child(7) {
animation: stampSlide 40000ms -14300ms linear infinite;
}
.rail .stamp:nth-child(8) {
animation: stampSlide 40000ms -16300ms linear infinite;
}
.rail .stamp:nth-child(9) {
animation: stampSlide 40000ms -18300ms linear infinite;
}
.rail .stamp:nth-child(10) {
animation: stampSlide 40000ms -20300ms linear infinite;
}
.rail .stamp:nth-child(11) {
animation: stampSlide 40000ms -22300ms linear infinite;
}
.rail .stamp:nth-child(12) {
animation: stampSlide 40000ms -24300ms linear infinite;
}
.rail .stamp:nth-child(13) {
animation: stampSlide 40000ms -26300ms linear infinite;
}
.rail .stamp:nth-child(14) {
animation: stampSlide 40000ms -28300ms linear infinite;
}
.rail .stamp:nth-child(15) {
animation: stampSlide 40000ms -30300ms linear infinite;
}
.rail .stamp:nth-child(16) {
animation: stampSlide 40000ms -32300ms linear infinite;
}
.rail .stamp:nth-child(17) {
animation: stampSlide 40000ms -34300ms linear infinite;
}
.rail .stamp:nth-child(18) {
animation: stampSlide 40000ms -36300ms linear infinite;
}
.rail .stamp:nth-child(19) {
animation: stampSlide 40000ms -38300ms linear infinite;
}
.rail .stamp:nth-child(20) {
animation: stampSlide 40000ms -40300ms linear infinite;
}
@keyframes stampSlide {
0% {
transform: rotateX(90deg) rotateZ(-90deg) translateZ(-200px) translateY(130px);
}
100% {
transform: rotateX(90deg) rotateZ(-90deg) translateZ(-200px) translateY(-3870px);
}
}
.world .forward {
position: absolute;
animation: slide 2000ms linear infinite;
}
.world .box {
width: 200px;
height: 200px;
transform-origin: 100% 100%;
animation: roll 2000ms cubic-bezier(1, 0.01, 1, 1) infinite;
}
.world .box .wall {
position: absolute;
width: 200px;
height: 200px;
background: rgba(10, 10, 10, 0.8);
border: 1px solid rgb(250, 250, 250);
box-sizing: border-box;
}
.world .box .wall::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 7rem;
}
.world .box .wall:nth-child(1) {
transform: translateZ(100px);
}
.world .box .wall:nth-child(2) {
transform: rotateX(180deg) translateZ(100px);
}
.world .box .wall:nth-child(3) {
transform: rotateX(90deg) translateZ(100px);
}
.world .box .wall:nth-child(3)::before {
transform: rotateX(180deg) rotateZ(90deg) translateZ(-1px);
animation: zeroFour 4000ms -2000ms linear infinite;
}
.world .box .wall:nth-child(4) {
transform: rotateX(-90deg) translateZ(100px);
}
.world .box .wall:nth-child(4)::before {
transform: rotateX(180deg) rotateZ(-90deg) translateZ(-1px);
animation: zeroFour 4000ms -2000ms linear infinite;
}
.world .box .wall:nth-child(5) {
transform: rotateY(90deg) translateZ(100px);
}
.world .box .wall:nth-child(5)::before {
transform: rotateX(180deg) translateZ(-1px);
animation: zeroFour 4000ms linear infinite;
}
.world .box .wall:nth-child(6) {
transform: rotateY(-90deg) translateZ(100px);
}
.world .box .wall:nth-child(6)::before {
transform: rotateX(180deg) rotateZ(180deg) translateZ(-1px);
animation: zeroFour 4000ms linear infinite;
}
@keyframes zeroFour {
0% {
content: "4";
}
100% {
content: "0";
}
}
@keyframes roll {
0% {
transform: rotateZ(0deg);
}
85% {
transform: rotateZ(90deg);
}
87% {
transform: rotateZ(88deg);
}
90% {
transform: rotateZ(90deg);
}
100% {
transform: rotateZ(90deg);
}
}
@keyframes slide {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-200px);
}
}

View File

@@ -13,29 +13,21 @@
Model="@_kmsModel"
Style="margin-top: 8px;"
OnFinish="HandleSubmit">
<FormItem Label="知识库名称" LabelCol="_formItemLayout.LabelCol" WrapperCol="_formItemLayout.WrapperCol">
<FormItem Label="知识库名称" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入知识库名称" @bind-Value="@context.Name" />
</FormItem>
<FormItem Label="图标" LabelCol="_formItemLayout.LabelCol" WrapperCol="_formItemLayout.WrapperCol">
<FormItem Label="图标" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入图标" @bind-Value="@context.Icon" />
<a href="https://antblazor.com/zh-CN/components/icon" target="_blank">图标库</a>
</FormItem>
<FormItem Label="描述" LabelCol="_formItemLayout.LabelCol" WrapperCol="_formItemLayout.WrapperCol">
<FormItem Label="描述" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入描述" @bind-Value="@context.Describe" />
</FormItem>
<FormItem Label=" " Style="margin-top:32px" WrapperCol="_submitFormLayout.WrapperCol">
<FormItem Label=" " Style="margin-top:32px" WrapperCol="LayoutModel._submitFormLayout.WrapperCol">
<Button Type="primary" HtmlType="submit">
保存
</Button>
</FormItem>
@if ( !string.IsNullOrEmpty(_errorMsg))
{
<Alert Type="@AlertType.Error"
Message="错误"
Description="@_errorMsg"
ShowIcon="true"
/>
}
</Form>
</Card>
</ChildContent>

View File

@@ -13,41 +13,17 @@ namespace AntSK.Pages.KmsPage
protected IKmss_Repositories _kmss_Repositories { get; set; }
[Inject]
protected NavigationManager NavigationManager { get; set; }
private string _errorMsg { get; set; }
[Inject]
protected MessageService? Message { get; set; }
private readonly Kmss _kmsModel = new Kmss() ;
private readonly FormItemLayout _formItemLayout = new FormItemLayout
{
LabelCol = new ColLayoutParam
{
Xs = new EmbeddedProperty { Span = 24 },
Sm = new EmbeddedProperty { Span = 7 },
},
WrapperCol = new ColLayoutParam
{
Xs = new EmbeddedProperty { Span = 24 },
Sm = new EmbeddedProperty { Span = 12 },
Md = new EmbeddedProperty { Span = 10 },
}
};
private readonly FormItemLayout _submitFormLayout = new FormItemLayout
{
WrapperCol = new ColLayoutParam
{
Xs = new EmbeddedProperty { Span = 24, Offset = 0 },
Sm = new EmbeddedProperty { Span = 10, Offset = 7 },
}
};
private void HandleSubmit()
{
_kmsModel.Id = Guid.NewGuid().ToString();
if (_kmss_Repositories.IsAny(p => p.Name == _kmsModel.Name))
{
_errorMsg = "名称已存在!";
_ = Message.Error("名称已存在!", 2);
return;
}

View File

@@ -26,7 +26,7 @@
@if (string.IsNullOrEmpty(context.Id))
{
<Button Type="dashed" class="newButton" @onclick="NavigateToAddKms">
<Icon Type="plus" Theme="outline" /> 创建应用
<Icon Type="plus" Theme="outline" /> 创建知识库
</Button>
}
else

View File

@@ -0,0 +1,60 @@
@namespace AntSK.Pages.Setting.User
@using AntSK.Domain.Repositories
@using AntSK.Models
@page "/setting/user/add"
@page "/setting/user/add/{UserId}"
@using AntSK.Services.Auth
@inherits AuthComponentBase
<PageContainer Title="新增用户">
<ChildContent>
<Card>
<Form Model="@_userModel"
Style="margin-top: 8px;"
OnFinish="HandleSubmit">
<FormItem Label="工号" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入用户工号" @bind-Value="@context.No" />
</FormItem>
<FormItem Label="用户名称" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入用户名称" @bind-Value="@context.Name" />
</FormItem>
<FormItem Label="用户密码" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Type="password" Placeholder="请输入用户密码" @bind-Value="@context.Password" />
</FormItem>
<FormItem Label="用户备注" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入用户备注" @bind-Value="@context.Describe" />
</FormItem>
<FormItem Label="菜单权限" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Select Mode="multiple"
@bind-Values="_menuKeys"
Placeholder="选择菜单权限"
TItemValue="string"
TItem="string"
Size="@AntSizeLDSType.Default">
<SelectOptions>
@foreach (var menu in menuList)
{
<SelectOption TItem="string" TItemValue="string" Value="@menu.Key" Label="@menu.Name" />
}
</SelectOptions>
</Select>
</FormItem>
<FormItem Label=" " Style="margin-top:32px" WrapperCol="LayoutModel._submitFormLayout.WrapperCol">
<Button Type="primary" OnClick="HandleSubmit">
保存
</Button>
<Button OnClick="Back">
返回
</Button>
</FormItem>
</Form>
</Card>
</ChildContent>
</PageContainer>
@code {
}

View File

@@ -0,0 +1,76 @@
using AntDesign;
using AntDesign.ProLayout;
using AntSK.Domain.Options;
using AntSK.Domain.Repositories;
using AntSK.Domain.Utils;
using DocumentFormat.OpenXml.InkML;
using DocumentFormat.OpenXml.Wordprocessing;
using Microsoft.AspNetCore.Components;
namespace AntSK.Pages.Setting.User
{
public partial class AddUser
{
[Parameter]
public string UserId { get; set; }
[Inject] protected IUsers_Repositories _users_Repositories { get; set; }
[Inject] protected MessageService? Message { get; set; }
[Inject] public HttpClient HttpClient { get; set; }
private Users _userModel = new Users();
private string _password = "";
IEnumerable<string> _menuKeys;
private List<MenuDataItem> menuList = new List<MenuDataItem>();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (!string.IsNullOrEmpty(UserId))
{
_userModel= _users_Repositories.GetFirst(p => p.Id == UserId);
_password= _userModel.Password;
}
menuList = (await HttpClient.GetFromJsonAsync<MenuDataItem[]>("data/menu.json")).ToList().Where(p=>p.Key!= "setting").ToList();
_menuKeys= _userModel.MenuRole?.Split(",");
}
private void HandleSubmit()
{
_userModel.MenuRole = string.Join(",", _menuKeys);
if (string.IsNullOrEmpty(UserId))
{
//新增
_userModel.Id = Guid.NewGuid().ToString();
if (_userModel.No == LoginOption.User)
{
_ = Message.Error("工号不能为管理员账号!", 2);
return;
}
if (_users_Repositories.IsAny(p => p.No == _userModel.No))
{
_ = Message.Error("工号已存在!", 2);
return;
}
_userModel.Password=PasswordUtil.HashPassword(_userModel.Password);
_users_Repositories.Insert(_userModel);
}
else
{
//修改
if (_userModel.Password!=_password)
{
_userModel.Password = PasswordUtil.HashPassword(_userModel.Password);
}
_users_Repositories.Update(_userModel);
}
Back();
}
private void Back()
{
NavigationManager.NavigateTo("/setting/userlist");
}
}
}

View File

@@ -0,0 +1,44 @@
@namespace AntSK.Pages.Setting.User
@using AntSK.Domain.Repositories
@using AntSK.Models
@page "/setting/user/info/{UserNo}"
@inject IMessageService _message
@using AntSK.Services.Auth
@inherits AuthComponentBase
<PageContainer Title="个人设置">
<ChildContent>
<Card>
<Form Model="@_userModel"
Style="margin-top: 8px;"
OnFinish="HandleSubmit">
<FormItem Label="工号" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入用户工号" @bind-Value="@context.No" Disabled="true" />
</FormItem>
<FormItem Label="用户名称" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入用户名称" @bind-Value="@context.Name" />
</FormItem>
<FormItem Label="用户密码" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Type="password" Placeholder="请输入用户密码" @bind-Value="@context.Password" />
</FormItem>
<FormItem Label="用户备注" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
<Input Placeholder="请输入用户备注" @bind-Value="@context.Describe" />
</FormItem>
<FormItem Label=" " Style="margin-top:32px" WrapperCol="LayoutModel._submitFormLayout.WrapperCol">
<Button Type="primary" HtmlType="submit">
保存
</Button>
<Button OnClick="Back">
返回
</Button>
</FormItem>
</Form>
</Card>
</ChildContent>
</PageContainer>
@code {
}

View File

@@ -0,0 +1,49 @@
using AntDesign;
using AntSK.Domain.Repositories;
using AntSK.Domain.Utils;
using DocumentFormat.OpenXml.Wordprocessing;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace AntSK.Pages.Setting.User
{
public partial class UserInfo
{
[Parameter]
public string UserNo { get; set; }
[Inject] protected IUsers_Repositories _users_Repositories { get; set; }
[Inject] protected MessageService? Message { get; set; }
private Users _userModel = new Users();
private string _password = "";
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (!string.IsNullOrEmpty(UserNo))
{
_userModel= _users_Repositories.GetFirst(p => p.No == UserNo);
_password= _userModel.Password;
}
}
private async Task HandleSubmit()
{
//修改
if (_userModel.Password!=_password)
{
_userModel.Password = PasswordUtil.HashPassword(_userModel.Password);
}
_users_Repositories.Update(_userModel);
_ = Message.Info("保存成功!", 2);
}
private async Task Back()
{
NavigationManager.NavigateTo("/");
}
}
}

View File

@@ -0,0 +1,64 @@
@namespace AntSK.Pages.Setting.User
@using AntSK.Domain.Repositories
@page "/setting/userlist"
@inject NavigationManager NavigationManager
@using AntSK.Services.Auth
@inherits AuthComponentBase
<div>
<PageContainer Title="用户管理">
<ChildContent>
<div class="standardList">
<Card Class="listCard"
Title="用户列表"
Style="margin-top: 24px;"
BodyStyle="padding: 0 32px 40px 32px">
<Extra>
<div class="extraContent">
<Search Class="extraContentSearch" Placeholder="查询" @bind-Value="_searchKeyword" />
</div>
</Extra>
<ChildContent>
<Button Type="dashed"
Style="width: 100%; margin-bottom: 8px;"
OnClick="AddUser">
<Icon Type="plus" Theme="outline" />
新增用户
</Button>
<AntList TItem="Users"
DataSource="_data"
ItemLayout="ListItemLayout.Horizontal">
<ListItem Actions="new[] {
edit(()=> Edit(context.Id)
)}" Style="width:100%">
<div class="listContent" style="width:100%">
<div class="listContentItem" style="width:20%">
<b>工号</b>
<p>@context.No</p>
</div>
<div class="listContentItem" style="width:20%">
<b>姓名</b>
<p>@context.Name</p>
</div>
<div class="listContentItem" style="width:20%">
<b>备注</b>
<p>@context.Describe</p>
</div>
</div>
</ListItem>
</AntList>
</ChildContent>
</Card>
</div>
</ChildContent>
</PageContainer>
</div>
@code
{
RenderFragment edit(Action clickAction) =>@<a key="edit" @onclick="@clickAction">修改</a>;
}

View File

@@ -0,0 +1,44 @@
using AntDesign;
using AntSK.Domain.Repositories;
using AntSK.Models;
using AntSK.Services;
using Microsoft.AspNetCore.Components;
namespace AntSK.Pages.Setting.User
{
public partial class UserList
{
private readonly BasicListFormModel _model = new BasicListFormModel();
private readonly IDictionary<string, ProgressStatus> _pStatus = new Dictionary<string, ProgressStatus>
{
{"active", ProgressStatus.Active},
{"exception", ProgressStatus.Exception},
{"normal", ProgressStatus.Normal},
{"success", ProgressStatus.Success}
};
private List<Users> _data;
private string _searchKeyword;
[Inject]
protected IUsers_Repositories _users_Repositories { get; set; }
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
_data = _users_Repositories.GetList();
}
public void AddUser() {
NavigationManager.NavigateTo("/setting/user/add");
}
public void Edit(string userid)
{
NavigationManager.NavigateTo("/setting/user/add/"+userid);
}
}
}

View File

@@ -0,0 +1,186 @@
/* stylelint-disable at-rule-empty-line-before,at-rule-name-space-after,at-rule-no-unknown */
/* stylelint-disable no-duplicate-selectors */
/* stylelint-disable */
/* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */
.standardList .ant-card-head {
border-bottom: none;
}
.standardList .ant-card-head-title {
padding: 24px 0;
line-height: 32px;
}
.standardList .ant-card-extra {
padding: 24px 0;
}
.standardList .ant-list-pagination {
margin-top: 24px;
text-align: right;
}
.standardList .ant-avatar-lg {
width: 48px;
height: 48px;
line-height: 48px;
}
.standardList .headerInfo {
position: relative;
text-align: center;
}
.standardList .headerInfo > span {
display: inline-block;
margin-bottom: 4px;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
line-height: 22px;
}
.standardList .headerInfo > p {
margin: 0;
color: rgba(0, 0, 0, 0.85);
font-size: 24px;
line-height: 32px;
}
.standardList .headerInfo > em {
position: absolute;
top: 0;
right: 0;
width: 1px;
height: 56px;
background-color: #f0f0f0;
}
.standardList .listContent {
font-size: 0;
}
.standardList .listContent .listContentItem {
display: inline-block;
margin-left: 40px;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
vertical-align: middle;
}
.standardList .listContent .listContentItem > span {
line-height: 20px;
}
.standardList .listContent .listContentItem > p {
margin-top: 4px;
margin-bottom: 0;
line-height: 22px;
}
.standardList .extraContentSearch {
width: 272px;
margin-left: 16px;
}
@media screen and (max-width: 480px) {
.standardList .ant-list-item-content {
display: block;
flex: none;
width: 100%;
}
.standardList .ant-list-item-action {
margin-left: 0;
}
.standardList .listContent {
margin-left: 0;
}
.standardList .listContent > div {
margin-left: 0;
}
.standardList .listCard .ant-card-head-title {
overflow: visible;
}
}
@media screen and (max-width: 576px) {
.standardList .extraContentSearch {
width: 100%;
margin-left: 0;
}
.standardList .headerInfo {
margin-bottom: 16px;
}
.standardList .headerInfo > em {
display: none;
}
}
@media screen and (max-width: 768px) {
.standardList .listContent > div {
display: block;
}
.standardList .listContent > div:last-child {
top: 0;
width: 100%;
}
.listCard .ant-radio-group {
display: block;
margin-bottom: 8px;
}
}
@media screen and (max-width: 992px) and (min-width: 768px) {
.standardList .listContent > div {
display: block;
}
.standardList .listContent > div:last-child {
top: 0;
width: 100%;
}
}
@media screen and (max-width: 1200px) {
.standardList .listContent > div {
margin-left: 24px;
}
.standardList .listContent > div:last-child {
top: 0;
}
}
@media screen and (max-width: 1400px) {
.standardList .listContent {
text-align: right;
}
.standardList .listContent > div:last-child {
top: 0;
}
}
.standardListForm .ant-form-item {
margin-bottom: 12px;
}
.standardListForm .ant-form-item:last-child {
margin-bottom: 32px;
padding-top: 4px;
}
.formResult {
width: 100%;
}
.formResult [class^='title'] {
margin-bottom: 8px;
}

View File

@@ -6,6 +6,8 @@ using AntSK.Services;
using AntSK.Domain.Options;
using SqlSugar;
using AntSK.Services.Auth;
using AntSK.Domain.Repositories;
using AntSK.Domain.Utils;
namespace AntSK.Pages.User
{
@@ -15,12 +17,11 @@ namespace AntSK.Pages.User
[Inject] public NavigationManager NavigationManager { get; set; }
[Inject] public IAccountService AccountService { get; set; }
[Inject] public MessageService Message { get; set; }
public async Task HandleSubmit()
{
//判断是否管理员
var loginFailed = await((AntSKAuthProvider)AuthenticationStateProvider).SignIn(_model.UserName, _model.Password);
if (loginFailed)
{

View File

@@ -75,6 +75,7 @@ builder.Services.AddSwaggerGen(c =>
builder.Configuration.GetSection("ConnectionStrings").Get<ConnectionOption>();
builder.Configuration.GetSection("OpenAIOption").Get<OpenAIOption>();
builder.Configuration.GetSection("Login").Get<LoginOption>();
builder.Configuration.GetSection("LLamaSharp").Get<LLamaSharpOption>();
}
InitSK(builder);
var app = builder.Build();
@@ -118,21 +119,24 @@ void InitDB(WebApplication app)
_repository.GetDB().CodeFirst.InitTables(typeof(Apps));
_repository.GetDB().CodeFirst.InitTables(typeof(Kmss));
_repository.GetDB().CodeFirst.InitTables(typeof(KmsDetails));
_repository.GetDB().CodeFirst.InitTables(typeof(Users));
}
}
//初始化SK
void InitSK(WebApplicationBuilder builder)
{
{
var services = builder.Services;
var handler = new OpenAIHttpClientHandler();
var httpClient = new HttpClient(handler);
httpClient.Timeout= TimeSpan.FromMinutes(5);
services.AddScoped<Kernel>((serviceProvider) =>
{
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: OpenAIOption.Model,
apiKey: OpenAIOption.Key,
httpClient: new HttpClient(handler))
httpClient: httpClient)
.Build();
RegisterPluginsWithKernel(kernel);
return kernel;
@@ -140,7 +144,7 @@ void InitSK(WebApplicationBuilder builder)
//Kernel Memory
var searchClientConfig = new SearchClientConfig
{
MaxAskPromptSize = 128000,
MaxAskPromptSize = 2048,
MaxMatchesCount = 3,
AnswerTokens = 1000,
EmptyAnswer = "知识库未搜索到相关内容"
@@ -153,18 +157,25 @@ void InitSK(WebApplicationBuilder builder)
.WithPostgresMemoryDb(postgresConfig)
.WithSimpleFileStorage(new SimpleFileStorageConfig { StorageType = FileSystemTypes.Volatile, Directory = "_files" })
.WithSearchClientConfig(searchClientConfig)
//如果用本地模型需要设置token小一点。
.WithCustomTextPartitioningOptions(new Microsoft.KernelMemory.Configuration.TextPartitioningOptions
{
MaxTokensPerLine = 99,
MaxTokensPerParagraph = 299,
OverlappingTokens = 47
})
.WithOpenAITextGeneration(new OpenAIConfig()
{
APIKey = OpenAIOption.Key,
TextModel = OpenAIOption.Model
}, null, new HttpClient(handler))
}, null, httpClient)
.WithOpenAITextEmbeddingGeneration(new OpenAIConfig()
{
APIKey = OpenAIOption.Key,
EmbeddingModel = OpenAIOption.EmbeddingModel
}, null, false, new HttpClient(handler))
}, null, false, httpClient)
.Build<MemoryServerless>();
return memory;
});

View File

@@ -1,29 +1,52 @@
using AntSK.Domain.Options;
using AntSK.Domain.Repositories;
using AntSK.Domain.Utils;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
using System.Security.Principal;
namespace AntSK.Services.Auth
{
public class AntSKAuthProvider : AuthenticationStateProvider
public class AntSKAuthProvider(IUsers_Repositories _users_Repositories) : AuthenticationStateProvider
{
private ClaimsIdentity identity = new ClaimsIdentity();
public async Task<bool> SignIn(string username, string password)
{
var user = _users_Repositories.GetFirst(p => p.No == username);
if (username == LoginOption.User && password == LoginOption.Password)
{
// 用户认证成功创建用户的ClaimsIdentity
// 管理员认证成功创建用户的ClaimsIdentity
var claims = new[] { new Claim(ClaimTypes.Name, username) };
identity = new ClaimsIdentity(claims, "AntSK");
identity = new ClaimsIdentity(claims, "AntSKAdmin");
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
return true;
}
else
else
{
// 用户认证失败
return false;
if (user.IsNull())
{
return false;
}
if (!PasswordUtil.VerifyPassword(password, user.Password))
{
return false;
}
// 用户认证成功创建用户的ClaimsIdentity
var claims = new[] { new Claim(ClaimTypes.Name, username) };
identity = new ClaimsIdentity(claims, "AntSKUser");
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
return true;
}
}
public ClaimsPrincipal GetCurrentUser()
{
var user = new ClaimsPrincipal(identity);
return user;
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var user = new ClaimsPrincipal(identity);

View File

@@ -0,0 +1,106 @@
using AntSK.Domain.Common.DependencyInjection;
using AntSK.Domain.Options;
using AntSK.Models.OpenAPI;
using AntSK.Models;
using LLama;
using LLama.Common;
using Newtonsoft.Json;
using static Azure.Core.HttpHeader;
namespace AntSK.Services.LLamaSharp
{
public interface ILLamaChatService
{
Task<string> ChatAsync(string input);
IAsyncEnumerable<string> ChatStreamAsync(string input);
}
/// <summary>
///
/// </summary>
[ServiceDescription(typeof(ILLamaChatService), Domain.Common.DependencyInjection.ServiceLifetime.Singleton)]
public class LLamaChatService : IDisposable, ILLamaChatService
{
private readonly ChatSession _session;
private readonly LLamaContext _context;
private readonly ILogger<LLamaChatService> _logger;
private bool _continue = false;
private const string SystemPrompt = "You are a personal assistant who needs to help users in Chinese.";
public LLamaChatService(ILogger<LLamaChatService> logger)
{
var @params = new ModelParams(LLamaSharpOption.Chat)
{
ContextSize = 2048,
};
// todo: share weights from a central service
using var weights = LLamaWeights.LoadFromFile(@params);
_logger = logger;
_context = new LLamaContext(weights, @params);
_session = new ChatSession(new InteractiveExecutor(_context));
_session.History.AddMessage(AuthorRole.System, SystemPrompt);
}
public void Dispose()
{
_context?.Dispose();
}
public async Task<string> ChatAsync(string input)
{
if (!_continue)
{
_logger.LogInformation("Prompt: {text}", SystemPrompt);
_continue = true;
}
_logger.LogInformation("Input: {text}", input);
var outputs = _session.ChatAsync(
new ChatHistory.Message(AuthorRole.User, input),
new InferenceParams()
{
RepeatPenalty = 1.0f,
AntiPrompts = new string[] { "User:" },
});
var result = "";
await foreach (var output in outputs)
{
_logger.LogInformation("Message: {output}", output);
result += output;
}
return result;
}
public async IAsyncEnumerable<string> ChatStreamAsync(string input)
{
if (!_continue)
{
_logger.LogInformation(SystemPrompt);
_continue = true;
}
_logger.LogInformation(input);
var outputs = _session.ChatAsync(
new ChatHistory.Message(AuthorRole.User, input!)
, new InferenceParams()
{
RepeatPenalty = 1.0f,
AntiPrompts = new string[] { "User:" },
});
await foreach (var output in outputs)
{
_logger.LogInformation(output);
yield return output;
}
}
}
}

View File

@@ -0,0 +1,42 @@
using AntSK.Domain.Common.DependencyInjection;
using AntSK.Domain.Options;
using AntSK.Models.OpenAPI;
using AntSK.Models;
using LLama;
using LLama.Common;
using Newtonsoft.Json;
namespace AntSK.Services.LLamaSharp
{
public interface ILLamaEmbeddingService
{
Task<List<float>> Embedding(string text);
}
/// <summary>
/// 本地Embedding
/// </summary>
[ServiceDescription(typeof(ILLamaEmbeddingService), Domain.Common.DependencyInjection.ServiceLifetime.Singleton)]
public class LLamaEmbeddingService : IDisposable, ILLamaEmbeddingService
{
private LLamaEmbedder _embedder;
public LLamaEmbeddingService() {
var @params = new ModelParams(LLamaSharpOption.Embedding) { EmbeddingMode = true };
using var weights = LLamaWeights.LoadFromFile(@params);
_embedder = new LLamaEmbedder(weights, @params);
}
public void Dispose()
{
_embedder?.Dispose();
}
public async Task<List<float>> Embedding(string text)
{
float[] embeddings =await _embedder.GetEmbeddings(text);
//PG只有1536维
return embeddings.ToList();
}
}
}

View File

@@ -0,0 +1,79 @@
using AntDesign;
using AntSK.Domain.Common.DependencyInjection;
using AntSK.Domain.Domain.Service;
using AntSK.Domain.Options;
using AntSK.Domain.Utils;
using AntSK.Models;
using AntSK.Models.OpenAPI;
using AntSK.Services.OpenApi;
using Azure;
using DocumentFormat.OpenXml.EMMA;
using LLama;
using LLama.Common;
using Newtonsoft.Json;
using System.Text;
using System.Threading;
using static Azure.Core.HttpHeader;
using ServiceLifetime = AntSK.Domain.Common.DependencyInjection.ServiceLifetime;
namespace AntSK.Services.LLamaSharp
{
public interface ILLamaSharpService
{
Task Chat(OpenAIModel model, HttpContext HttpContext);
Task ChatStream(OpenAIModel model, HttpContext HttpContext);
Task Embedding(OpenAIEmbeddingModel model, HttpContext HttpContext);
}
[ServiceDescription(typeof(ILLamaSharpService), ServiceLifetime.Scoped)]
public class LLamaSharpService(
ILLamaEmbeddingService _lLamaEmbeddingService,
ILLamaChatService _lLamaChatService
) : ILLamaSharpService
{
public async Task ChatStream(OpenAIModel model, HttpContext HttpContext)
{
HttpContext.Response.Headers.Add("Content-Type", "text/event-stream");
OpenAIStreamResult result = new OpenAIStreamResult();
result.created = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
result.choices = new List<StreamChoicesModel>() { new StreamChoicesModel() { delta = new OpenAIMessage() { role = "assistant" } } };
string questions = model.messages.LastOrDefault().content;
await foreach (var r in _lLamaChatService.ChatStreamAsync(questions))
{
result.choices[0].delta.content = r.ConvertToString();
string message = $"data: {JsonConvert.SerializeObject(result)}\n\n";
await HttpContext.Response.WriteAsync(message, Encoding.UTF8);
await HttpContext.Response.Body.FlushAsync();
}
await HttpContext.Response.WriteAsync("data: [DONE]");
await HttpContext.Response.Body.FlushAsync();
await HttpContext.Response.CompleteAsync();
}
public async Task Chat(OpenAIModel model, HttpContext HttpContext)
{
string questions = model.messages.LastOrDefault().content;
OpenAIResult result = new OpenAIResult();
result.created = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
result.choices = new List<ChoicesModel>() { new ChoicesModel() { message = new OpenAIMessage() { role = "assistant" } } };
result.choices[0].message.content =await _lLamaChatService.ChatAsync(questions); ;
HttpContext.Response.ContentType = "application/json";
await HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(result));
await HttpContext.Response.CompleteAsync();
}
public async Task Embedding(OpenAIEmbeddingModel model, HttpContext HttpContext)
{
var result = new OpenAIEmbeddingResult();
result.data[0].embedding = await _lLamaEmbeddingService.Embedding(model.input[0]);
HttpContext.Response.ContentType = "application/json";
await HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(result));
await HttpContext.Response.CompleteAsync();
}
}
}

View File

@@ -14,12 +14,18 @@ using System;
using ServiceLifetime = AntSK.Domain.Common.DependencyInjection.ServiceLifetime;
using AntDesign.Core.Extensions;
using Azure.AI.OpenAI;
using Azure;
using Azure.Core;
using Microsoft.AspNetCore.Http.HttpResults;
using AntDesign;
using Newtonsoft.Json;
using System.Text.Json;
namespace AntSK.Services.OpenApi
{
public interface IOpenApiService
{
Task<OpenAIResult> Chat(OpenAIModel model, string sk);
Task Chat(OpenAIModel model, string sk, HttpContext HttpContext);
}
[ServiceDescription(typeof(IOpenApiService), ServiceLifetime.Scoped)]
@@ -31,15 +37,12 @@ namespace AntSK.Services.OpenApi
MemoryServerless _memory
) : IOpenApiService
{
public async Task<OpenAIResult> Chat(OpenAIModel model,string sk)
public async Task Chat(OpenAIModel model,string sk, HttpContext HttpContext)
{
OpenAIResult result = new OpenAIResult();
result.created= DateTimeOffset.UtcNow.ToUnixTimeSeconds();
result.choices=new List<ChoicesModel>() { new ChoicesModel() { message=new OpenAIMessage() { role= "assistant" } } };
Apps app = _apps_Repositories.GetFirst(p => p.SecretKey == sk);
if (app.IsNotNull())
{
string msg= await HistorySummarize(model);
@@ -47,17 +50,77 @@ namespace AntSK.Services.OpenApi
{
case "chat":
//普通会话
result.choices[0].message.content= await SendChat( msg, app);
if (model.stream)
{
OpenAIStreamResult result1 = new OpenAIStreamResult();
result1.created = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
result1.choices = new List<StreamChoicesModel>() { new StreamChoicesModel() { delta = new OpenAIMessage() { role = "assistant" } } };
await SendChatStream( HttpContext, result1, app, msg);
HttpContext.Response.ContentType = "application/json";
await HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(result1));
await HttpContext.Response.CompleteAsync();
return;
}
else
{
OpenAIResult result2 = new OpenAIResult();
result2.created = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
result2.choices = new List<ChoicesModel>() { new ChoicesModel() { message = new OpenAIMessage() { role = "assistant" } } };
result2.choices[0].message.content = await SendChat(msg, app);
HttpContext.Response.ContentType = "application/json";
await HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(result2));
await HttpContext.Response.CompleteAsync();
}
break;
case "kms":
//知识库问答
result.choices[0].message.content = await SendKms( msg, app);
OpenAIResult result3 = new OpenAIResult();
result3.created = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
result3.choices = new List<ChoicesModel>() { new ChoicesModel() { message = new OpenAIMessage() { role = "assistant" } } };
result3.choices[0].message.content = await SendKms( msg, app);
HttpContext.Response.ContentType = "application/json";
await HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(result3));
await HttpContext.Response.CompleteAsync();
break;
}
}
return result;
}
private async Task SendChatStream( HttpContext HttpContext, OpenAIStreamResult result, Apps app, string msg)
{
HttpContext.Response.Headers.Add("Content-Type", "text/event-stream");
if (string.IsNullOrEmpty(app.Prompt))
{
//如果模板为空,给默认提示词
app.Prompt = "{{$input}}";
}
var promptTemplateFactory = new KernelPromptTemplateFactory();
var promptTemplate = promptTemplateFactory.Create(new PromptTemplateConfig(app.Prompt));
var renderedPrompt = await promptTemplate.RenderAsync(_kernel);
var func = _kernel.CreateFunctionFromPrompt(app.Prompt, new OpenAIPromptExecutionSettings());
var chatResult = _kernel.InvokeStreamingAsync<StreamingChatMessageContent>(function: func, arguments: new KernelArguments() { ["input"] = msg });
int i = 0;
await foreach (var content in chatResult)
{
result.choices[0].delta.content = content.Content.ConvertToString();
string message = $"data: {JsonConvert.SerializeObject(result)}\n\n";
await HttpContext.Response.WriteAsync(message, Encoding.UTF8);
await HttpContext.Response.Body.FlushAsync();
//模拟延迟。
await Task.Delay(TimeSpan.FromMilliseconds(50));
}
await HttpContext.Response.WriteAsync("data: [DONE]");
await HttpContext.Response.Body.FlushAsync();
await HttpContext.Response.CompleteAsync();
}
/// <summary>
/// 发送知识库问答
/// </summary>
@@ -89,6 +152,8 @@ namespace AntSK.Services.OpenApi
return result;
}
/// <summary>
/// 发送普通对话
/// </summary>

View File

@@ -27,15 +27,19 @@
"Postgres": "Host=;Port=;Database=antsk;Username=;Password="
},
"OpenAIOption": {
"EndPoint": "",
"EndPoint": "https://localhost:5001/llama/",
"Key": "",
"Model": "",
"EmbeddingModel": ""
"Model": "gpt-3.5-turbo",
"EmbeddingModel": "text-embedding-ada-002"
},
"Postgres": {
"ConnectionString": "Host=;Port=;Database=antsk;Username=;Password=",
"TableNamePrefix": "km-"
},
"LLamaSharp": {
"Chat": "D:\\isoftstone\\Code\\AI\\AntBlazor\\model\\tinyllama-1.1b-chat.gguf",
"Embedding": "D:\\isoftstone\\Code\\AI\\AntBlazor\\model\\nomic-embed-text-v1.5.f32.gguf"
},
"Login": {
"User": "admin",
"Password": "xuzeyu"

View File

@@ -21,6 +21,13 @@
"path": "/setting",
"name": "设置",
"key": "setting",
"icon": "setting"
"icon": "setting",
"children": [
{
"path": "/setting/userlist",
"name": "用户管理",
"key": "setting.user"
}
]
}
]

View File

@@ -63,6 +63,8 @@ AntSK 适用于多种业务场景,例如:
模型默认支持openai,如果需要使用azure openai需要调整SK的依赖注入也可以使用one-api进行集成。
Login是默认的登陆账号和密码
需要配置如下的配置文件
### 需要注意的是PostgreSQL需要安装扩展vector
```
"ConnectionStrings": {
"Postgres": "Host=;Port=;Database=antsk;Username=;Password="
@@ -84,6 +86,24 @@ Login是默认的登陆账号和密码
```
我使用的是CodeFirst模式只要配置好数据库链接表结构是自动创建的
如果想使用LLamaSharp运行本地模型还需要设置如下配置
```
"LLamaSharp": {
"Chat": "D:\\Code\\AI\\AntBlazor\\model\\tinyllama-1.1b-chat.gguf",
"Embedding": "D:\\Code\\AI\\AntBlazor\\model\\tinyllama-1.1b-chat.gguf"
},
```
需要配置Chat和Embedding模型的地址然后修改EndPoint为本地
```
"OpenAIOption": {
"EndPoint": "https://ip:port/llama/",
"Key": "",
"Model": "",
"EmbeddingModel": ""
},
```
想了解更多信息或开始使用 **AntSK**,可以关注我的公众号以及加入交流群。