mirror of
https://github.com/AIDotNet/AntSK.git
synced 2026-02-17 14:06:11 +08:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e032e3733 | ||
|
|
abed362ff2 | ||
|
|
1a3881e7a4 | ||
|
|
ea8dd21478 | ||
|
|
5ec918a2f9 | ||
|
|
08d3a0aa3d | ||
|
|
4044355ae7 | ||
|
|
f03649146f | ||
|
|
15fd59571f | ||
|
|
f8399887ce | ||
|
|
97548b0d2b | ||
|
|
d6b2c3a08b | ||
|
|
3342378feb | ||
|
|
16303d7d92 | ||
|
|
49b67ce3eb | ||
|
|
b8f8688676 | ||
|
|
60eec4ad03 | ||
|
|
8f6341dd6a | ||
|
|
cb50062f3d | ||
|
|
293c94fbf2 | ||
|
|
fe8c026d13 | ||
|
|
b3c435be01 | ||
|
|
36f7ba7931 | ||
|
|
9461ab0aa5 | ||
|
|
6d3f16450b | ||
|
|
a4d0eaad77 | ||
|
|
ecbb36bcc6 | ||
|
|
80a5688f46 | ||
|
|
65cda7dba5 | ||
|
|
bfd1bd7ff1 | ||
|
|
cf0d7acf8b | ||
|
|
34874aa7b8 | ||
|
|
723e3388a7 | ||
|
|
be56859a6d | ||
|
|
901e81c61b | ||
|
|
8a19f61689 | ||
|
|
e6456a2c86 | ||
|
|
0ecf060b2b | ||
|
|
a0bc481ac5 | ||
|
|
9e873f1e2d | ||
|
|
b3eccaba60 | ||
|
|
3a19015b1f | ||
|
|
ea3a94f594 | ||
|
|
8b5b261661 | ||
|
|
bc3f4dcb92 | ||
|
|
01deb3084a | ||
|
|
fb8cc18f99 | ||
|
|
a219bb7eaa | ||
|
|
4ac7c79192 | ||
|
|
a24c5548e1 | ||
|
|
8c4030ae43 | ||
|
|
c912ceeee0 | ||
|
|
58c5edbc5e | ||
|
|
3d86fd1125 | ||
|
|
f8c6a05380 | ||
|
|
10b4706094 | ||
|
|
3f7e2d29e1 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -338,4 +338,5 @@ ASALocalRun/
|
||||
/src/AntSK/appsettings.Development.json
|
||||
/src/AntSK.db
|
||||
/src/AntSK/llama_models
|
||||
/src/AntSK/AntSK.xml
|
||||
/src/AntSK/AntSK.xml
|
||||
/src/.codebuddy/db/vectra
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build stage
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Copy csproj and restore as distinct layers
|
||||
@@ -13,7 +13,7 @@ RUN dotnet build "AntSK.csproj" -c Release -o /app/build
|
||||
RUN dotnet publish "AntSK.csproj" -c Release -o /app/publish
|
||||
|
||||
# Runtime stage
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||
WORKDIR /service
|
||||
EXPOSE 5000
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
WORKDIR /src
|
||||
COPY ["src/AntSK/AntSK.csproj", "AntSK/"]
|
||||
RUN dotnet restore "AntSK/AntSK.csproj"
|
||||
@@ -7,7 +7,7 @@ WORKDIR "/src/AntSK"
|
||||
RUN dotnet build "AntSK.csproj" -c Release -o /app/build
|
||||
RUN dotnet publish "AntSK.csproj" -c Release -o /app/publish
|
||||
|
||||
FROM registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk-base:v1.0.0 AS final
|
||||
FROM registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk-base:9.0.0 AS final
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
|
||||
8
LICENSE
8
LICENSE
@@ -8,9 +8,9 @@
|
||||
|
||||
**一. 商用许可**
|
||||
|
||||
1. **免费商用**:用户在不修改代码的情况下,可以免费用于商业目的。
|
||||
1. **免费商用**:用户在不修改logo和名称的情况下,可以免费用于商业目的。
|
||||
2. **商业授权**:如果您满足以下任意条件之一,需取得商业授权:
|
||||
1. 对本软件进行二次修改、开发(包括但不限于修改应用名称、logo、代码以及功能)。
|
||||
1. 修改应用名称、logo、版权信息等。
|
||||
2. 为企业客户提供多租户服务,且该服务支持 10 人或以上的使用。
|
||||
3. 预装或集成到硬件设备或产品中进行捆绑销售。
|
||||
4. 政府或教育机构的大规模采购项目,特别是涉及安全、数据隐私等敏感需求时。
|
||||
@@ -49,9 +49,9 @@ This software is licensed under the **Apache License 2.0**. In addition to the t
|
||||
|
||||
**I. Commercial Use License**
|
||||
|
||||
1. **Free Commercial Use**: Users can use the software for commercial purposes without modifying the code.
|
||||
1. **Free Commercial Use**: Users can use it for commercial purposes for free without modifying the logo and name.
|
||||
2. **Commercial License Required**: A commercial license is required if any of the following conditions are met:
|
||||
1. You modify, develop, or alter the software, including but not limited to changes to the application name, logo, code, or functionality.
|
||||
1. Modify the application name logo、 Copyright information, etc.
|
||||
2. You provide multi-tenant services to enterprise customers with 10 or more users.
|
||||
3. You pre-install or integrate the software into hardware devices or products and bundle it for sale.
|
||||
4. You are engaging in large-scale procurement for government or educational institutions, especially involving security, data privacy, or other sensitive requirements.
|
||||
|
||||
236
README.en.md
Normal file
236
README.en.md
Normal file
@@ -0,0 +1,236 @@
|
||||
[简体中文](./README.md) | English
|
||||
# AntSK
|
||||
## AI Knowledge Base/Intelligent Agent built on .Net9 + AntBlazor+SemanticKernel
|
||||
|
||||
## ⭐Core Features
|
||||
|
||||
- **Semantic Kernel**: Utilizes advanced natural language processing technology to accurately understand, process, and respond to complex semantic queries, providing users with precise information retrieval and recommendation services.
|
||||
|
||||
- **Kernel Memory**: Capable of continuous learning and storing knowledge points, AntSK has long-term memory function, accumulates experience, and provides a more personalized interaction experience.
|
||||
|
||||
- **Knowledge Base**: Import knowledge base through documents (Word, PDF, Excel, Txt, Markdown, Json, PPT) and perform knowledge base Q&A.
|
||||
|
||||
- **GPT Generation**: This platform supports creating personalized GPT models, enabling users to build their own GPT models.
|
||||
|
||||
- **API Interface Publishing**: Exposes internal functions in the form of APIs, enabling developers to integrate AntSK into other applications and enhance application intelligence.
|
||||
|
||||
- **API Plugin System**: Open API plugin system that allows third-party developers or service providers to easily integrate their services into AntSK, continuously enhancing application functionality.
|
||||
|
||||
- **.Net Plugin System**: Open dll plugin system that allows third-party developers or service providers to easily integrate their business functions by generating dll in standard format code, continuously enhancing application functionality.
|
||||
|
||||
- **Online Search**: AntSK, real-time access to the latest information, ensuring users receive the most timely and relevant data.
|
||||
|
||||
- **Model Management**: Adapts and manages integration of different models from different manufacturers, models offline running supported by **llamafactory** and **ollama**.
|
||||
|
||||
- **Domestic Innovation**: AntSK supports domestic models and databases and can run under domestic innovation conditions.
|
||||
|
||||
- **Model Fine-Tuning**: Planned based on llamafactory for model fine-tuning.
|
||||
|
||||
## ⛪Application Scenarios
|
||||
|
||||
AntSK is suitable for various business scenarios, such as:
|
||||
- Enterprise knowledge management system
|
||||
- Automatic customer service and chatbots
|
||||
- Enterprise search engine
|
||||
- Personalized recommendation system
|
||||
- Intelligent writing assistance
|
||||
- Education and online learning platforms
|
||||
- Other interesting AI Apps
|
||||
|
||||
## ✏️Function Examples
|
||||
### Online Demo
|
||||
[document](http://antsk.cn/)
|
||||
|
||||
[demo](https://demo.antsk.cn/)
|
||||
and
|
||||
[demo1](https://antsk.ai-dotnet.com/)
|
||||
|
||||
```
|
||||
Default account: test
|
||||
|
||||
Default password: test
|
||||
|
||||
Due to the low configuration of the cloud server, the local model cannot be run, so the system settings permissions have been closed. You can simply view the interface. If you want to use the local model, please download and use it on your own.
|
||||
```
|
||||
|
||||
### Other Function Examples
|
||||
[Video Demonstration](https://www.bilibili.com/video/BV1zH4y1h7Y9/)
|
||||
|
||||
## ❓How to get started?
|
||||
|
||||
Here I am using Postgres as the data and vector storage because Semantic Kernel and Kernel Memory support it, but you can also use other options.
|
||||
|
||||
The model by default supports the local model of openai, azure openai, and llama. If you need to use other models, you can integrate them using one-api.
|
||||
|
||||
The Login configuration in the configuration file is the default login account and password.
|
||||
|
||||
The following configuration file needs to be configured
|
||||
|
||||
## 1️⃣Using docker-compose
|
||||
|
||||
Provided the pg version **appsettings.json** and simplified version (Sqlite+disk) **docker-compose.simple.yml**
|
||||
|
||||
Download **docker-compose.yml** from the project root directory and place the configuration file **appsettings.json** in the same directory.
|
||||
|
||||
The pg image has already been prepared. You can modify the default username and password in docker-compose.yml, and then the database connection in your **appsettings.json** needs to be consistent.
|
||||
|
||||
Then you can execute the following command in the directory to start AntSK
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 2️⃣How to mount local models and model download directory in docker
|
||||
```
|
||||
# Non-host version, do not use local proxy
|
||||
version: '3.8'
|
||||
services:
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/AIDotNet/antsk:v0.6.3
|
||||
ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
- antsk
|
||||
depends_on:
|
||||
- antskpg
|
||||
restart: always
|
||||
environment:
|
||||
- ASPNETCORE_URLS=http://*:5000
|
||||
volumes:
|
||||
- ./appsettings.json:/app/appsettings.json # Local configuration file needs to be placed in the same directory
|
||||
- D://model:/app/model
|
||||
networks:
|
||||
antsk:
|
||||
external: true
|
||||
```
|
||||
Taking this as an example, it means mounting the local D://model folder of Windows into the container /app/model. If so, the model address in your appsettings.json should be configured as
|
||||
|
||||
[LiteDockerCompose](https://github.com/AIDotNet/AntSK/blob/main/docker-compose.simple.yml)
|
||||
|
||||
The compact version is deployed with sqlite-disk by one click
|
||||
|
||||
[FullDockerCompose](https://github.com/AIDotNet/AntSK/blob/main/docker-compose.yml)
|
||||
|
||||
The full version uses pg+aspire
|
||||
|
||||
|
||||
## 3️⃣Some meanings of configuration file
|
||||
```
|
||||
{
|
||||
"DBConnection": {
|
||||
"DbType": "Sqlite",
|
||||
"ConnectionStrings": "Data Source=AntSK.db;"
|
||||
},
|
||||
"KernelMemory": {
|
||||
"VectorDb": "Disk",
|
||||
"ConnectionString": "Host=;Port=;Database=antsk;Username=;Password=",
|
||||
"TableNamePrefix": "km-"
|
||||
},
|
||||
"FileDir": {
|
||||
"DirectoryPath": "D:\\git\\AntBlazor\\model"
|
||||
},
|
||||
"Login": {
|
||||
"User": "admin",
|
||||
"Password": "admin"
|
||||
},
|
||||
"BackgroundTaskBroker": {
|
||||
"ImportKMSTask": {
|
||||
"WorkerCount": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
// Supports various databases, you can check SqlSugar, MySql, SqlServer, Sqlite, Oracle, PostgreSQL, Dm, Kdbndp, Oscar, MySqlConnector, Access, OpenGauss, QuestDB, HG, ClickHouse, GBase, Odbc, OceanBaseForOracle, TDengine, GaussDB, OceanBase, Tidb, Vastbase, PolarDB, Custom
|
||||
DBConnection.DbType
|
||||
|
||||
// Connection string, need to use the corresponding string according to the different DB types
|
||||
DBConnection.ConnectionStrings
|
||||
|
||||
//The type of vector storage, supporting Postgres, Disk, Memory, Qdrant, Redis, AzureAISearch
|
||||
//Postgres and Redis require ConnectionString configuration
|
||||
//The ConnectionString of Qdrant and AzureAISearch uses Endpoint | APIKey
|
||||
KernelMemory.VectorDb
|
||||
|
||||
//Local model path, used for quick selection of models under llama, as well as saving downloaded models.
|
||||
FileDir.DirectoryPath
|
||||
|
||||
//Default admin account password
|
||||
Login
|
||||
|
||||
//Import asynchronous processing thread count. A higher count can be used for online API, but for local models, 1 is recommended to avoid memory overflow issues.
|
||||
BackgroundTaskBroker.ImportKMSTask.WorkerCount
|
||||
|
||||
```
|
||||
|
||||
## ⚠️Fixing Style Issues:
|
||||
Run the following in AntSK/src/AntSK:
|
||||
```
|
||||
dotnet clean
|
||||
dotnet build
|
||||
dotnet publish "AntSK.csproj"
|
||||
```
|
||||
Then navigate to AntSK/src/AntSK/bin/Release/net8.0/publish and run:
|
||||
```
|
||||
dotnet AntSK.dll
|
||||
```
|
||||
The styles should now be applied after starting.
|
||||
|
||||
I'm using CodeFirst mode for the database, so as long as the database connection is properly configured, the table structure will be created automatically.
|
||||
|
||||
## ✔️Using llamafactory
|
||||
```
|
||||
1. First, ensure that Python and pip are installed in your environment. This step is not necessary if using an image, such as version v0.2.3.2, which already includes the complete Python environment.
|
||||
2. Go to the model add page and select llamafactory.
|
||||
3. Click "Initialize" to check whether the 'pip install' environment setup is complete.
|
||||
4. Choose a model that you like.
|
||||
5. Click "Start" to begin downloading the model from the tower. This may involve a somewhat lengthy wait.
|
||||
6. After the model has finished downloading, enter http://localhost:8000/ in the request address. The default port is 8000.
|
||||
7. Click "Save" and start chatting.
|
||||
8. Many people ask about the difference between LLamaSharp and llamafactory. In fact, LLamaSharp is a .NET implementation of llama.cpp, but only supports local gguf models, while llamafactory supports a wider variety of models and uses Python implementation. The main difference lies here. Additionally, llamafactory has the ability to fine-tune models, which is an area we will focus on integrating in the future.
|
||||
```
|
||||
|
||||
## 💕 Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
|
||||
<a href="https://github.com/AIDotNet/AntSK/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=AIDotNet/AntSK&max=1000&columns=15&anon=1" />
|
||||
</a>
|
||||
|
||||
## 🚨 Use Protocol
|
||||
|
||||
This warehouse follows the [AntSK License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file) open source protocol.
|
||||
|
||||
This project follows the Apache 2.0 agreement, in addition to the following additional terms
|
||||
|
||||
1. **Free Commercial Use**: Users can use the software for commercial purposes without modifying the code.
|
||||
2. **Commercial License Required**: A commercial license is required if any of the following conditions are met:
|
||||
1. You modify, develop, or alter the software, including but not limited to changes to the application name, logo, code, or functionality.
|
||||
2. You provide multi-tenant services to enterprise customers with 10 or more users.
|
||||
3. You pre-install or integrate the software into hardware devices or products and bundle it for sale.
|
||||
4. You are engaging in large-scale procurement for government or educational institutions, especially involving security, data privacy, or other sensitive requirements.
|
||||
|
||||
3. If you need authorization, you can contact WeChat: **13469996907**
|
||||
|
||||
If you plan to use AntSK in commercial projects, you need to ensure that you follow the following steps:
|
||||
|
||||
1. Copyright statement containing AntSK license. [AntSK License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file).
|
||||
|
||||
2. If you modify the software source code, you need to clearly indicate these modifications in the source code.
|
||||
|
||||
3. Meet the above requirements
|
||||
|
||||
## 💕 Special thanks
|
||||
Helping enterprise AI application development, we recommend [AntBlazor](https://antblazor.com)
|
||||
|
||||
## ☎️Contact Me
|
||||
If you have any questions or suggestions, please contact me through my official WeChat account. We also have a discussion group where you can send a message to join, and then I will add you to the group.
|
||||
|
||||
Additionally, you can also contact me via email: antskpro@qq.com
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
We appreciate your interest in **AntSK** and look forward to collaborating with you to create an intelligent future!
|
||||
594
README.md
594
README.md
@@ -1,92 +1,392 @@
|
||||
[简体中文](./README.zh.md) | English
|
||||
中文|[English](./README.en.md)
|
||||
# AntSK
|
||||
## AI Knowledge Base/Intelligent Agent built on .Net8+AntBlazor+SemanticKernel
|
||||
## 使用.Net9 + Blazor+SemanticKernel 打造的AI知识库/智能体
|
||||
|
||||
## ⭐Core Features
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
- **Semantic Kernel**: Utilizes advanced natural language processing technology to accurately understand, process, and respond to complex semantic queries, providing users with precise information retrieval and recommendation services.
|
||||
AntSK 是一个基于 .NET 9 和 Blazor 技术栈构建的企业级AI知识库和智能体平台,集成了 Semantic Kernel 和 Kernel Memory,提供完整的AI应用开发解决方案。
|
||||
|
||||
- **Kernel Memory**: Capable of continuous learning and storing knowledge points, AntSK has long-term memory function, accumulates experience, and provides a more personalized interaction experience.
|
||||
## 📋 目录
|
||||
|
||||
- **Knowledge Base**: Import knowledge base through documents (Word, PDF, Excel, Txt, Markdown, Json, PPT) and perform knowledge base Q&A.
|
||||
- [⭐ 核心功能](#核心功能)
|
||||
- [🏗️ 技术架构](#技术架构)
|
||||
- [🔄 系统工作流程](#系统工作流程)
|
||||
- [🛠️ 技术栈](#技术栈)
|
||||
- [📁 项目结构](#项目结构)
|
||||
- [🚀 特色功能](#特色功能)
|
||||
- [⛪ 应用场景](#应用场景)
|
||||
- [✏️ 功能示例](#功能示例)
|
||||
- [❓ 如何开始](#如何开始)
|
||||
- [🔧 开发指南](#开发指南)
|
||||
- [📊 性能优化建议](#性能优化建议)
|
||||
- [💕 贡献者](#贡献者)
|
||||
- [🚨 使用协议](#使用协议)
|
||||
- [☎️ 联系我](#联系我)
|
||||
|
||||
- **GPT Generation**: This platform supports creating personalized GPT models, enabling users to build their own GPT models.
|
||||
## ⭐核心功能
|
||||
|
||||
- **API Interface Publishing**: Exposes internal functions in the form of APIs, enabling developers to integrate AntSK into other applications and enhance application intelligence.
|
||||
- **语义内核 (Semantic Kernel)**:采用领先的自然语言处理技术,准确理解、处理和响应复杂的语义查询,为用户提供精确的信息检索和推荐服务。
|
||||
|
||||
- **API Plugin System**: Open API plugin system that allows third-party developers or service providers to easily integrate their services into AntSK, continuously enhancing application functionality.
|
||||
- **内存内核 (Kernel Memory)**:具备持续学习和存储知识点的能力,AntSK 拥有长期记忆功能,累积经验,提供更个性化的交互体验。
|
||||
|
||||
- **.Net Plugin System**: Open dll plugin system that allows third-party developers or service providers to easily integrate their business functions by generating dll in standard format code, continuously enhancing application functionality.
|
||||
- **知识库**:通过文档(Word、PDF、Excel、Txt、Markdown、Json、PPT)等形式导入知识库,可以进行知识库问答,支持本地bge-embedding 向量模型 ,以及bge-rerank 重排模型。
|
||||
|
||||
- **Online Search**: AntSK, real-time access to the latest information, ensuring users receive the most timely and relevant data.
|
||||
- **文生图**:集成**StableDiffusion** 本地模型,可以进行文生图。
|
||||
|
||||
- **Model Management**: Adapts and manages integration of different models from different manufacturers, models offline running supported by **llamafactory** and **ollama**.
|
||||
- **GPTs 生成**:此平台支持创建个性化的GPT模型,尝试构建您自己的GPT模型。
|
||||
|
||||
- **Domestic Innovation**: AntSK supports domestic models and databases and can run under domestic innovation conditions.
|
||||
- **API接口发布**:将内部功能以API的形式对外提供,便于开发者将AntSK 集成进其他应用,增强应用智慧。
|
||||
|
||||
- **Model Fine-Tuning**: Planned based on llamafactory for model fine-tuning.
|
||||
- **API插件系统**:开放式API插件系统,允许第三方开发者或服务商轻松将其服务集成到AntSK,不断增强应用功能。
|
||||
|
||||
## ⛪Application Scenarios
|
||||
- **.Net插件系统**:开放式dll插件系统,允许第三方开发者或服务商轻松将其业务功能通过标准格式的代码生成dll后集成到AntSK,不断增强应用功能。
|
||||
|
||||
AntSK is suitable for various business scenarios, such as:
|
||||
- Enterprise knowledge management system
|
||||
- Automatic customer service and chatbots
|
||||
- Enterprise search engine
|
||||
- Personalized recommendation system
|
||||
- Intelligent writing assistance
|
||||
- Education and online learning platforms
|
||||
- Other interesting AI Apps
|
||||
- **联网搜索**:AntSK,实时获取最新信息,确保用户接受到的资料总是最及时、最相关的。
|
||||
|
||||
## ✏️Function Examples
|
||||
### Online Demo
|
||||
[document](http://antsk.cn/)
|
||||
- **模型管理**:适配和管理集成不同厂商的不同模型。并且支持**llama.cpp**所支持的gguf类型,以及**llamafactory** 和 **ollama** 所支持的模型离线运行
|
||||
|
||||
[demo](https://demo.antsk.cn/)
|
||||
and
|
||||
[demo1](https://antsk.ai-dotnet.com/)
|
||||
- **国产信创**:AntSK支持国产模型,和国产数据库,可以在信创条件下运行
|
||||
|
||||
```
|
||||
Default account: test
|
||||
- **模型微调**:规划中,基于llamafactory进行模型微调
|
||||
|
||||
Default password: test
|
||||
## 🏗️ 技术架构
|
||||
|
||||
Due to the low configuration of the cloud server, the local model cannot be run, so the system settings permissions have been closed. You can simply view the interface. If you want to use the local model, please download and use it on your own.
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "用户界面层"
|
||||
UI[Blazor前端界面]
|
||||
API[Web API接口]
|
||||
end
|
||||
|
||||
subgraph "应用服务层"
|
||||
Chat[聊天服务]
|
||||
KMS[知识库服务]
|
||||
Plugin[插件服务]
|
||||
Model[模型管理服务]
|
||||
Auth[认证服务]
|
||||
end
|
||||
|
||||
subgraph "领域核心层"
|
||||
SK[Semantic Kernel]
|
||||
KM[Kernel Memory]
|
||||
Embedding[向量嵌入]
|
||||
Function[函数调用]
|
||||
end
|
||||
|
||||
subgraph "基础设施层"
|
||||
DB[(数据库)]
|
||||
Vector[(向量数据库)]
|
||||
File[文件存储]
|
||||
OCR[OCR服务]
|
||||
SD[StableDiffusion]
|
||||
end
|
||||
|
||||
subgraph "AI模型层"
|
||||
OpenAI[OpenAI]
|
||||
Local[本地模型]
|
||||
LlamaFactory[LlamaFactory]
|
||||
Ollama[Ollama]
|
||||
Spark[讯飞星火]
|
||||
end
|
||||
|
||||
subgraph "插件系统"
|
||||
NetPlugin[.NET插件]
|
||||
APIPlugin[API插件]
|
||||
FuncPlugin[函数插件]
|
||||
end
|
||||
|
||||
UI --> Chat
|
||||
UI --> KMS
|
||||
UI --> Plugin
|
||||
UI --> Model
|
||||
API --> Auth
|
||||
|
||||
Chat --> SK
|
||||
KMS --> KM
|
||||
Plugin --> Function
|
||||
|
||||
SK --> OpenAI
|
||||
SK --> Local
|
||||
SK --> LlamaFactory
|
||||
SK --> Ollama
|
||||
SK --> Spark
|
||||
|
||||
KM --> Vector
|
||||
KM --> Embedding
|
||||
|
||||
Chat --> NetPlugin
|
||||
Chat --> APIPlugin
|
||||
Chat --> FuncPlugin
|
||||
|
||||
KMS --> DB
|
||||
KMS --> File
|
||||
Model --> DB
|
||||
|
||||
OCR --> SD
|
||||
|
||||
style SK fill:#e1f5fe
|
||||
style KM fill:#e8f5e8
|
||||
style UI fill:#fff3e0
|
||||
style API fill:#fff3e0
|
||||
```
|
||||
|
||||
### Other Function Examples
|
||||
[Video Demonstration](https://www.bilibili.com/video/BV1zH4y1h7Y9/)
|
||||
## 🔄 系统工作流程
|
||||
|
||||
## ❓How to get started?
|
||||
```mermaid
|
||||
graph TD
|
||||
A[用户输入] --> B{输入类型}
|
||||
|
||||
B -->|文档上传| C[文档解析]
|
||||
B -->|聊天对话| D[对话处理]
|
||||
B -->|API调用| E[API处理]
|
||||
|
||||
C --> F[文档分块]
|
||||
F --> G[向量化处理]
|
||||
G --> H[存储到知识库]
|
||||
|
||||
D --> I{是否需要知识库}
|
||||
I -->|是| J[知识库检索]
|
||||
I -->|否| K[直接调用LLM]
|
||||
|
||||
J --> L[向量搜索]
|
||||
L --> M[相关性排序]
|
||||
M --> N[构建Prompt]
|
||||
|
||||
K --> O[LLM推理]
|
||||
N --> O
|
||||
|
||||
O --> P{是否需要插件}
|
||||
P -->|是| Q[插件调用]
|
||||
P -->|否| R[生成回复]
|
||||
|
||||
Q --> S[执行函数]
|
||||
S --> T[合并结果]
|
||||
T --> R
|
||||
|
||||
E --> U[权限验证]
|
||||
U --> V[业务逻辑]
|
||||
V --> W[返回结果]
|
||||
|
||||
R --> X[用户界面展示]
|
||||
W --> X
|
||||
|
||||
style A fill:#e1f5fe
|
||||
style O fill:#e8f5e8
|
||||
style H fill:#fff3e0
|
||||
style X fill:#f3e5f5
|
||||
```
|
||||
|
||||
Here I am using Postgres as the data and vector storage because Semantic Kernel and Kernel Memory support it, but you can also use other options.
|
||||
## 🛠️ 技术栈
|
||||
|
||||
The model by default supports the local model of openai, azure openai, and llama. If you need to use other models, you can integrate them using one-api.
|
||||
### 后端技术
|
||||
- **.NET 9**: 最新的 .NET 框架,提供高性能和现代化开发体验
|
||||
- **Blazor Server**: 基于服务器端渲染的现代Web UI框架
|
||||
- **Semantic Kernel**: 微软开源的AI编排框架
|
||||
- **Kernel Memory**: 知识库和向量存储管理
|
||||
- **SqlSugar**: 高性能 ORM 框架,支持多种数据库
|
||||
- **AutoMapper**: 对象映射框架
|
||||
|
||||
The Login configuration in the configuration file is the default login account and password.
|
||||
### AI & ML 技术
|
||||
- **OpenAI GPT**: 支持 GPT-3.5/GPT-4 系列模型
|
||||
- **Azure OpenAI**: 企业级 OpenAI 服务
|
||||
- **讯飞星火**: 科大讯飞大语言模型
|
||||
- **阿里云积**: 阿里云大语言模型
|
||||
- **LlamaFactory**: 本地模型微调和推理
|
||||
- **Ollama**: 本地模型运行环境
|
||||
- **Stable Diffusion**: 文生图模型
|
||||
- **BGE Embedding**: 中文向量嵌入模型
|
||||
- **BGE Rerank**: 重排序模型
|
||||
|
||||
The following configuration file needs to be configured
|
||||
### 存储技术
|
||||
- **PostgreSQL**: 主数据库存储
|
||||
- **SQLite**: 轻量级数据库支持
|
||||
- **Qdrant**: 向量数据库
|
||||
- **Redis**: 缓存和向量存储
|
||||
- **Disk/Memory**: 本地存储方案
|
||||
|
||||
## 1️⃣Using docker-compose
|
||||
### 前端技术
|
||||
- **Ant Design Blazor**: 企业级UI组件库
|
||||
- **Chart.js**: 数据可视化
|
||||
- **Prism.js**: 代码高亮
|
||||
|
||||
Provided the pg version **appsettings.json** and simplified version (Sqlite+disk) **docker-compose.simple.yml**
|
||||
## 📁 项目结构
|
||||
|
||||
Download **docker-compose.yml** from the project root directory and place the configuration file **appsettings.json** in the same directory.
|
||||
```
|
||||
AntSK/
|
||||
├── src/
|
||||
│ ├── AntSK/ # 主应用(Blazor Server)
|
||||
│ │ ├── Components/ # 自定义组件
|
||||
│ │ ├── Controllers/ # Web API控制器
|
||||
│ │ ├── Pages/ # Blazor页面
|
||||
│ │ │ ├── ChatPage/ # 聊天相关页面
|
||||
│ │ │ ├── KmsPage/ # 知识库管理页面
|
||||
│ │ │ ├── Plugin/ # 插件管理页面
|
||||
│ │ │ ├── Setting/ # 系统设置页面
|
||||
│ │ │ └── User/ # 用户管理页面
|
||||
│ │ ├── Services/ # 应用服务
|
||||
│ │ └── wwwroot/ # 静态资源
|
||||
│ ├── AntSK.Domain/ # 领域层
|
||||
│ │ ├── Domain/ # 领域模型和接口
|
||||
│ │ ├── Repositories/ # 数据仓储
|
||||
│ │ ├── Services/ # 领域服务
|
||||
│ │ └── Common/ # 通用组件
|
||||
│ ├── AntSK.LLM/ # LLM集成层
|
||||
│ │ ├── SparkDesk/ # 讯飞星火集成
|
||||
│ │ ├── StableDiffusion/ # SD文生图集成
|
||||
│ │ └── Mock/ # 模拟服务
|
||||
│ ├── AntSK.LLamaFactory/ # LlamaFactory集成
|
||||
│ ├── AntSK.OCR/ # OCR服务
|
||||
│ ├── AntSK.BackgroundTask/ # 后台任务处理
|
||||
│ └── AntSK.ServiceDefaults/ # 服务默认配置
|
||||
├── docs/ # 文档
|
||||
└── docker-compose.yml # Docker部署文件
|
||||
```
|
||||
|
||||
The pg image has already been prepared. You can modify the default username and password in docker-compose.yml, and then the database connection in your **appsettings.json** needs to be consistent.
|
||||
### 核心模块说明
|
||||
|
||||
Then you can execute the following command in the directory to start AntSK
|
||||
| 模块 | 功能描述 |
|
||||
|------|---------|
|
||||
| **AntSK** | 主应用程序,包含Blazor UI和Web API |
|
||||
| **AntSK.Domain** | 领域层,包含业务逻辑、数据模型和仓储接口 |
|
||||
| **AntSK.LLM** | 大语言模型集成层,支持多种AI模型 |
|
||||
| **AntSK.LLamaFactory** | LlamaFactory集成,支持本地模型微调和推理 |
|
||||
| **AntSK.OCR** | 光学字符识别服务 |
|
||||
| **AntSK.BackgroundTask** | 后台任务处理,如知识库导入 |
|
||||
|
||||
## ⛪应用场景
|
||||
|
||||
AntSK 适用于多种业务场景,例如:
|
||||
- 企业级知识管理系统
|
||||
- 自动客服与聊天机器人
|
||||
- 企业级搜索引擎
|
||||
- 个性化推荐系统
|
||||
- 智能辅助写作
|
||||
- 教育与在线学习平台
|
||||
- 其他有意思的AI App
|
||||
|
||||
## ✏️功能示例
|
||||
### 在线演示
|
||||
|
||||
[体验地址1](https://demo.antsk.cn/)
|
||||
|
||||
和
|
||||
|
||||
[体验地址2](https://antsk.ai-dotnet.com/)
|
||||
```
|
||||
默认账号:test
|
||||
|
||||
默认密码:test
|
||||
|
||||
由于云服务器配置较低,无法运行本地模型,所以把系统设置权限关闭了,大家看看界面即可,要使用本地模型,请下载自行使用
|
||||
|
||||
请勿在演示站点上传敏感信息
|
||||
```
|
||||
|
||||
### 其他功能示例
|
||||
[视频示例](https://www.bilibili.com/video/BV1zH4y1h7Y9/)
|
||||
|
||||
[在线文档:http://antsk.cn](http://antsk.cn)
|
||||
|
||||
## 🚀 特色功能
|
||||
|
||||
### 🤖 多模型支持
|
||||
- **云端模型**: OpenAI GPT、Azure OpenAI、讯飞星火、阿里云积灵等
|
||||
- **本地模型**: 支持 Ollama 和Llamafactory运行离线模型
|
||||
- **LlamaFactory**: 支持主流开源模型的微调和推理
|
||||
- **Ollama**: 本地模型管理和运行
|
||||
- **一键切换**: 支持在不同模型间无缝切换
|
||||
|
||||
### 📚 智能知识库
|
||||
- **多格式支持**: Word、PDF、Excel、TXT、Markdown、JSON、PPT
|
||||
- **向量化存储**: BGE-embedding 中文优化向量模型
|
||||
- **智能检索**: BGE-rerank 重排序提升检索精度
|
||||
- **实时同步**: 知识库内容实时更新和同步
|
||||
|
||||
### 🔌 开放插件系统
|
||||
- **.NET 插件**: 支持 DLL 格式的原生插件
|
||||
- **API 插件**: 通过 HTTP API 集成外部服务
|
||||
- **函数插件**: 基于 Semantic Kernel 的函数调用
|
||||
- **热插拔**: 插件动态加载,无需重启系统
|
||||
|
||||
### 🎨 文生图能力
|
||||
- **Stable Diffusion**: 集成本地 SD 模型
|
||||
- **多种后端**: 支持 CPU、CUDA、ROCm 等不同计算后端
|
||||
- **参数调节**: 丰富的生成参数配置
|
||||
- **批量生成**: 支持批量图片生成
|
||||
|
||||
### 🔍 OCR 文字识别
|
||||
- **图片转文字**: 支持多种图片格式的文字提取
|
||||
- **多语言支持**: 中英文等多语言识别
|
||||
- **高精度**: 优化的 OCR 引擎,识别准确率高
|
||||
|
||||
## ❓如何开始?
|
||||
|
||||
### 🛠️ 环境要求
|
||||
- **.NET 9 SDK**: [下载地址](https://dotnet.microsoft.com/zh-cn/download/dotnet/9.0)
|
||||
- **Docker** (可选): 用于容器化部署
|
||||
- **Python 3.8+** (可选): 使用 LlamaFactory 时需要
|
||||
|
||||
### 💾 数据库支持
|
||||
AntSK 支持多种数据库,通过 SqlSugar ORM 实现:
|
||||
- **PostgreSQL** (推荐): 同时支持关系型数据和向量存储
|
||||
- **SQLite**: 轻量级,适合开发和测试
|
||||
- **MySQL**: 广泛使用的开源数据库
|
||||
- **SQL Server**: 微软企业级数据库
|
||||
- **Oracle**: 企业级数据库解决方案
|
||||
|
||||
### 🔧 向量数据库选择
|
||||
- **PostgreSQL**: 使用 pgvector 扩展
|
||||
- **Qdrant**: 专业向量数据库
|
||||
- **Redis**: 内存向量存储
|
||||
- **Disk**: 本地文件存储
|
||||
- **Memory**: 内存存储 (不持久化)
|
||||
|
||||
模型默认支持openai、azure openai、讯飞星火、阿里云积、 和llama支持的gguf本地模型 以及llamafactory的本地模型,如果需要使用其他模型,可以使用one-api进行集成。
|
||||
|
||||
配置文件中的Login配置是默认的登录账号和密码
|
||||
|
||||
需要配置如下的配置文件
|
||||
|
||||
## 为了方便体验,我已经把打包好的程序放进了网盘,你只需要安装.net9环境即可运行。
|
||||
[.net9环境 ](https://dotnet.microsoft.com/zh-cn/download/dotnet/9.0)
|
||||
|
||||
[我用夸克网盘分享了「AntSK」](https://pan.quark.cn/s/63ea02e1683e)
|
||||
|
||||
下载文件后启动 AntSK.exe 然后会自动打开浏览器
|
||||
```
|
||||
账号: admin
|
||||
密码: admin
|
||||
```
|
||||
|
||||
|
||||
[源码深度解读](https://deepwiki.com/AIDotNet/AntSK)
|
||||
|
||||
|
||||
## 1️⃣使用docker-compose
|
||||
|
||||
提供了pg版本 **appsettings.json** 和 简化版本(**Sqlite+disk**) **docker-compose.simple.yml**
|
||||
|
||||
从项目根目录下载**docker-compose.yml**,然后把配置文件**appsettings.json**和它放在统一目录,
|
||||
|
||||
这里已经把pg的镜像做好了。在docker-compose.yml中可以修改默认账号密码,然后你的**appsettings.json**的数据库连接需要保持一致。
|
||||
|
||||
然后你可以进入到目录后执行
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
来启动AntSK
|
||||
|
||||
## 2️⃣How to mount local models and model download directory in docker
|
||||
## 2️⃣如何在docker中挂载本地模型,和模型下载的目录
|
||||
```
|
||||
# Non-host version, do not use local proxy
|
||||
# 非 host 版本, 不使用本机代理
|
||||
version: '3.8'
|
||||
services:
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/AIDotNet/antsk:v0.6.0
|
||||
image: registry.cn-hangzhou.aliyuncs.com/AIDotNet/antsk:v0.6.5
|
||||
ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
@@ -97,32 +397,32 @@ services:
|
||||
environment:
|
||||
- ASPNETCORE_URLS=http://*:5000
|
||||
volumes:
|
||||
- ./appsettings.json:/app/appsettings.json # Local configuration file needs to be placed in the same directory
|
||||
- ./appsettings.json:/app/appsettings.json # 本地配置文件 需要放在同级目录
|
||||
- D://model:/app/model
|
||||
- D://model:/root/.cache/modelscope/hub/AI-ModelScope #使用Llamafactory时需要挂载 否则初始化的环境重启后会丢失
|
||||
networks:
|
||||
antsk:
|
||||
external: true
|
||||
```
|
||||
Taking this as an example, it means mounting the local D://model folder of Windows into the container /app/model. If so, the model address in your appsettings.json should be configured as
|
||||
以这个为示例,意思是把windows本地D://model的文件夹挂载进 容器内/app/model 如果是这样你的appsettings.json中的模型地址应该配置为
|
||||
|
||||
[LiteDockerCompose](https://github.com/AIDotNet/AntSK/blob/main/docker-compose.simple.yml)
|
||||
|
||||
The compact version is deployed with sqlite-disk by one click
|
||||
精简版使用sqlite+disk向量模式,简化部署配置
|
||||
|
||||
[FullDockerCompose](https://github.com/AIDotNet/AntSK/blob/main/docker-compose.yml)
|
||||
|
||||
The full version uses pg+aspire
|
||||
完整版使用pg+aspire 功能更完整,配置文件需要参考如下配置含义进行配置
|
||||
|
||||
|
||||
## 3️⃣Some meanings of configuration file
|
||||
## 3️⃣配置文件的一些含义
|
||||
```
|
||||
{
|
||||
"DBConnection": {
|
||||
"DbType": "Sqlite",
|
||||
"DbType": "Sqlite",
|
||||
"ConnectionStrings": "Data Source=AntSK.db;"
|
||||
},
|
||||
"KernelMemory": {
|
||||
"VectorDb": "Disk",
|
||||
"VectorDb": "Disk",
|
||||
"ConnectionString": "Host=;Port=;Database=antsk;Username=;Password=",
|
||||
"TableNamePrefix": "km-"
|
||||
},
|
||||
@@ -141,96 +441,166 @@ The full version uses pg+aspire
|
||||
}
|
||||
```
|
||||
```
|
||||
// Supports various databases, you can check SqlSugar, MySql, SqlServer, Sqlite, Oracle, PostgreSQL, Dm, Kdbndp, Oscar, MySqlConnector, Access, OpenGauss, QuestDB, HG, ClickHouse, GBase, Odbc, OceanBaseForOracle, TDengine, GaussDB, OceanBase, Tidb, Vastbase, PolarDB, Custom
|
||||
//支持多种数据库,具体可以查看SqlSugar,MySql,SqlServer,Sqlite,Oracle,PostgreSQL,Dm,Kdbndp,Oscar,MySqlConnector,Access,OpenGauss,QuestDB,HG,ClickHouse,GBase,Odbc,OceanBaseForOracle,TDengine,GaussDB,OceanBase,Tidb,Vastbase,PolarDB,Custom
|
||||
DBConnection.DbType
|
||||
|
||||
// Connection string, need to use the corresponding string according to the different DB types
|
||||
//连接字符串,需要根据不同DB类型,用对应的字符串
|
||||
DBConnection.ConnectionStrings
|
||||
|
||||
//The type of vector storage, supporting Postgres, Disk, Memory, Qdrant, Redis, AzureAISearch
|
||||
//Postgres and Redis require ConnectionString configuration
|
||||
//The ConnectionString of Qdrant and AzureAISearch uses Endpoint | APIKey
|
||||
//向量存储的类型,支持 Postgres、Disk、Memory、Qdrant、Redis、AzureAISearch
|
||||
//Postgres、Redis需要配置 ConnectionString
|
||||
//Qdrant 和AzureAISearch 的 ConnectionString 使用 Endpoint|APIKey
|
||||
KernelMemory.VectorDb
|
||||
|
||||
//Local model path, used for quick selection of models under llama, as well as saving downloaded models.
|
||||
//本地模型路径,用于在选择llama时可以快速选择目录下的模型,以及保存下载的模型
|
||||
FileDir.DirectoryPath
|
||||
|
||||
//Default admin account password
|
||||
//默认管理员账号密码
|
||||
Login
|
||||
|
||||
//Import asynchronous processing thread count. A higher count can be used for online API, but for local models, 1 is recommended to avoid memory overflow issues.
|
||||
//导入异步处理的线程数,使用在线API可以高一点,本地模型建议1 否则容易内存溢出崩掉
|
||||
BackgroundTaskBroker.ImportKMSTask.WorkerCount
|
||||
|
||||
```
|
||||
|
||||
## ⚠️Fixing Style Issues:
|
||||
Run the following in AntSK/src/AntSK:
|
||||
## ⚠️找不到样式问题解决:
|
||||
AntSK/src/AntSK下执行:
|
||||
```
|
||||
dotnet clean
|
||||
dotnet build
|
||||
dotnet publish "AntSK.csproj"
|
||||
```
|
||||
Then navigate to AntSK/src/AntSK/bin/Release/net8.0/publish and run:
|
||||
再去AntSK/src/AntSK/bin/Release/net8.0/publish下
|
||||
```
|
||||
dotnet AntSK.dll
|
||||
```
|
||||
The styles should now be applied after starting.
|
||||
然后启动就有样式了
|
||||
|
||||
I'm using CodeFirst mode for the database, so as long as the database connection is properly configured, the table structure will be created automatically.
|
||||
DB我使用的是CodeFirst模式,只要配置好数据库链接,表结构是自动创建的
|
||||
|
||||
## ✔️Using llamafactory
|
||||
```
|
||||
1. First, ensure that Python and pip are installed in your environment. This step is not necessary if using an image, such as version v0.2.3.2, which already includes the complete Python environment.
|
||||
2. Go to the model add page and select llamafactory.
|
||||
3. Click "Initialize" to check whether the 'pip install' environment setup is complete.
|
||||
4. Choose a model that you like.
|
||||
5. Click "Start" to begin downloading the model from the tower. This may involve a somewhat lengthy wait.
|
||||
6. After the model has finished downloading, enter http://localhost:8000/ in the request address. The default port is 8000.
|
||||
7. Click "Save" and start chatting.
|
||||
8. Many people ask about the difference between LLamaSharp and llamafactory. In fact, LLamaSharp is a .NET implementation of llama.cpp, but only supports local gguf models, while llamafactory supports a wider variety of models and uses Python implementation. The main difference lies here. Additionally, llamafactory has the ability to fine-tune models, which is an area we will focus on integrating in the future.
|
||||
## 🔧 开发指南
|
||||
|
||||
### 本地开发环境搭建
|
||||
|
||||
1. **克隆项目**
|
||||
```bash
|
||||
git clone https://github.com/AIDotNet/AntSK.git
|
||||
cd AntSK
|
||||
```
|
||||
|
||||
## 💕 Contributors
|
||||
2. **安装依赖**
|
||||
```bash
|
||||
# 确保已安装 .NET 9 SDK
|
||||
dotnet restore
|
||||
```
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
3. **配置数据库**
|
||||
- 修改 `src/AntSK/appsettings.json` 中的数据库连接字符串
|
||||
- 首次运行会自动创建数据库表结构 (CodeFirst 模式)
|
||||
|
||||
4. **启动项目**
|
||||
```bash
|
||||
cd src/AntSK
|
||||
dotnet run
|
||||
```
|
||||
访问 `https://localhost:5001` 或 `http://localhost:5000`
|
||||
|
||||
### 插件开发
|
||||
|
||||
#### .NET 插件开发
|
||||
```csharp
|
||||
[AntSKFunction("插件描述")]
|
||||
public class MyPlugin
|
||||
{
|
||||
[AntSKFunction("函数描述")]
|
||||
public async Task<string> MyFunction(string input)
|
||||
{
|
||||
// 您的业务逻辑
|
||||
return "处理结果";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### API 插件开发
|
||||
创建符合 OpenAPI 规范的 HTTP 接口,AntSK 会自动解析并集成。
|
||||
|
||||
### 自定义模型集成
|
||||
|
||||
1. **实现 IChatCompletion 接口**
|
||||
```csharp
|
||||
public class CustomChatCompletion : IChatCompletion
|
||||
{
|
||||
public async Task<IReadOnlyList<ChatMessage>> GetChatMessageContentsAsync(
|
||||
ChatHistory chatHistory,
|
||||
PromptExecutionSettings? executionSettings = null,
|
||||
Kernel? kernel = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 实现您的模型调用逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **注册服务**
|
||||
```csharp
|
||||
services.AddSingleton<IChatCompletion, CustomChatCompletion>();
|
||||
```
|
||||
|
||||
## ✔️使用llamafactory
|
||||
```
|
||||
1、首先需要确保你的环境已经安装了python和pip,如果使用镜像,例如p0.2.4版本已经包含了 python全套环境则无需此步骤
|
||||
2、进入模型添加页面选择llamafactory
|
||||
3、点击初始化,可以检查pip install 环境是否完成
|
||||
4、选择一个喜欢的模型
|
||||
5、点击启动,这会开始从魔塔下载模型,你可能需要有一个较为漫长的等待
|
||||
6、等待模型下载完毕后,在请求地址输入 http://localhost:8000/ 这里默认是使用8000端口
|
||||
7、点击保存,然后就可以开始聊天了
|
||||
8、很多人会问 LLamaSharp与llamafactory有什么区别?其实这两者LLamaSharp是llama.cpp的 dotnet实现,但是只支持本地gguf模型, 而llamafactory 支持的模型种类更多,但使用的是python的实现,其主要差异在这里,另外llamafactory具有模型微调的能力,这也是我们下一步需要重点集成的部分。
|
||||
```
|
||||
|
||||
## 📊 性能优化建议
|
||||
|
||||
### 硬件配置推荐
|
||||
|
||||
| 用途 | CPU | 内存 | 存储 | GPU |
|
||||
|------|-----|------|------|-----|
|
||||
| 开发测试 | 4核+ | 8GB+ | SSD 50GB+ | 可选 |
|
||||
| 小型部署 | 8核+ | 16GB+ | SSD 100GB+ | 可选 |
|
||||
| 生产环境 | 16核+ | 32GB+ | SSD 500GB+ | RTX 3080+ |
|
||||
| 大规模部署 | 32核+ | 64GB+ | SSD 1TB+ | RTX 4090+ |
|
||||
|
||||
### 性能调优
|
||||
- **数据库连接池**: 根据并发量调整连接池大小
|
||||
- **向量维度**: 根据精度需求选择合适的向量维度
|
||||
- **缓存策略**: 合理使用 Redis 缓存热点数据
|
||||
- **模型选择**: 根据场景选择合适的模型大小
|
||||
|
||||
## 💕 贡献者
|
||||
|
||||
这个项目的存在要感谢所有的贡献者。
|
||||
|
||||
<a href="https://github.com/AIDotNet/AntSK/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=AIDotNet/AntSK&max=1000&columns=15&anon=1" />
|
||||
<img src="https://contrib.rocks/image?repo=AIDotNet/AntSK&max=1000&columns=15&anon=1" />
|
||||
</a>
|
||||
|
||||
## 🚨 使用协议
|
||||
|
||||
## 🚨 Use Protocol
|
||||
本仓库遵循 [AntSK License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file) 开源协议。
|
||||
|
||||
This warehouse follows the [AntSK License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file) open source protocol.
|
||||
除以下附加条款外,该项目遵循Apache 2.0协议
|
||||
|
||||
This project follows the Apache 2.0 agreement, in addition to the following additional terms
|
||||
|
||||
1. **Free Commercial Use**: Users can use the software for commercial purposes without modifying the code.
|
||||
2. **Commercial License Required**: A commercial license is required if any of the following conditions are met:
|
||||
1. You modify, develop, or alter the software, including but not limited to changes to the application name, logo, code, or functionality.
|
||||
2. You provide multi-tenant services to enterprise customers with 10 or more users.
|
||||
3. You pre-install or integrate the software into hardware devices or products and bundle it for sale.
|
||||
4. You are engaging in large-scale procurement for government or educational institutions, especially involving security, data privacy, or other sensitive requirements.
|
||||
1. **免费商用**:用户在不修改应用名称、logo、版权信息的情况下,可以免费用于商业目的。
|
||||
2. **商业授权**:如果您满足以下任意条件之一,需取得商业授权:
|
||||
1. 修改应用名称、logo、版权信息等。
|
||||
2. 为企业客户提供多租户服务,且该服务支持 10 人或以上的使用。
|
||||
3. 预装或集成到硬件设备或产品中进行捆绑销售。
|
||||
4. 政府或教育机构的大规模采购项目,特别是涉及安全、数据隐私等敏感需求时。
|
||||
|
||||
3. If you need authorization, you can contact WeChat: **xuzeyu91**
|
||||
3. 如果您需要授权,可以联系微信:**13469996907**
|
||||
|
||||
If you plan to use AntSK in commercial projects, you need to ensure that you follow the following steps:
|
||||
如果您打算在商业项目中使用AntSK,您需要确保遵守以下步骤:
|
||||
|
||||
1. Copyright statement containing AntSK license. [AntSK License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file).
|
||||
|
||||
2. If you modify the software source code, you need to clearly indicate these modifications in the source code.
|
||||
|
||||
3. Meet the above requirements
|
||||
1. 包含AntSK许可证的版权声明。 [AntSK License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file) 。
|
||||
|
||||
## 💕 Special thanks
|
||||
Helping enterprise AI application development, we recommend [AntBlazor](https://antblazor.com)
|
||||
2. 如果您修改了软件源代码,您需要在源代码中明确标明这些修改。
|
||||
|
||||
## ☎️Contact Me
|
||||
If you have any questions or suggestions, please contact me through my official WeChat account. We also have a discussion group where you can send a message to join, and then I will add you to the group.
|
||||
3. 满足以上要求
|
||||
|
||||
Additionally, you can also contact me via email: antskpro@qq.com
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
We appreciate your interest in **AntSK** and look forward to collaborating with you to create an intelligent future!
|
||||
|
||||
254
README.zh.md
254
README.zh.md
@@ -1,254 +0,0 @@
|
||||
中文|[English](./README.md)
|
||||
# AntSK
|
||||
## 使用.Net8+Blazor+SemanticKernel 打造的AI知识库/智能体
|
||||
|
||||
## ⭐核心功能
|
||||
|
||||
- **语义内核 (Semantic Kernel)**:采用领先的自然语言处理技术,准确理解、处理和响应复杂的语义查询,为用户提供精确的信息检索和推荐服务。
|
||||
|
||||
- **内存内核 (Kernel Memory)**:具备持续学习和存储知识点的能力,AntSK 拥有长期记忆功能,累积经验,提供更个性化的交互体验。
|
||||
|
||||
- **知识库**:通过文档(Word、PDF、Excel、Txt、Markdown、Json、PPT)等形式导入知识库,可以进行知识库问答,支持本地bge-embedding 向量模型 ,以及bge-rerank 重排模型。
|
||||
|
||||
- **文生图**:集成**StableDiffusion** 本地模型,可以进行文生图。
|
||||
|
||||
- **GPTs 生成**:此平台支持创建个性化的GPT模型,尝试构建您自己的GPT模型。
|
||||
|
||||
- **API接口发布**:将内部功能以API的形式对外提供,便于开发者将AntSK 集成进其他应用,增强应用智慧。
|
||||
|
||||
- **API插件系统**:开放式API插件系统,允许第三方开发者或服务商轻松将其服务集成到AntSK,不断增强应用功能。
|
||||
|
||||
- **.Net插件系统**:开放式dll插件系统,允许第三方开发者或服务商轻松将其业务功能通过标准格式的代码生成dll后集成到AntSK,不断增强应用功能。
|
||||
|
||||
- **联网搜索**:AntSK,实时获取最新信息,确保用户接受到的资料总是最及时、最相关的。
|
||||
|
||||
- **模型管理**:适配和管理集成不同厂商的不同模型。并且支持**llama.cpp**所支持的gguf类型,以及**llamafactory** 和 **ollama** 所支持的模型离线运行
|
||||
|
||||
- **国产信创**:AntSK支持国产模型,和国产数据库,可以在信创条件下运行
|
||||
|
||||
- **模型微调**:规划中,基于llamafactory进行模型微调
|
||||
|
||||
|
||||
## ⛪应用场景
|
||||
|
||||
AntSK 适用于多种业务场景,例如:
|
||||
- 企业级知识管理系统
|
||||
- 自动客服与聊天机器人
|
||||
- 企业级搜索引擎
|
||||
- 个性化推荐系统
|
||||
- 智能辅助写作
|
||||
- 教育与在线学习平台
|
||||
- 其他有意思的AI App
|
||||
|
||||
## ✏️功能示例
|
||||
### 在线演示
|
||||
|
||||
[体验地址1](https://demo.antsk.cn/)
|
||||
|
||||
和
|
||||
|
||||
[体验地址2](https://antsk.ai-dotnet.com/)
|
||||
```
|
||||
默认账号:test
|
||||
|
||||
默认密码:test
|
||||
|
||||
由于云服务器配置较低,无法运行本地模型,所以把系统设置权限关闭了,大家看看界面即可,要使用本地模型,请下载自行使用
|
||||
|
||||
请勿在演示站点上传敏感信息
|
||||
```
|
||||
|
||||
### 其他功能示例
|
||||
[视频示例](https://www.bilibili.com/video/BV1zH4y1h7Y9/)
|
||||
|
||||
[在线文档:http://antsk.cn](http://antsk.cn)
|
||||
|
||||
## ❓如何开始?
|
||||
|
||||
在这里我使用的是Postgres 作为数据存储和向量存储,因为Semantic Kernel和Kernel Memory都支持他,当然你也可以换成其他的。
|
||||
|
||||
模型默认支持openai、azure openai、讯飞星火、阿里云积、 和llama支持的gguf本地模型 以及llamafactory的本地模型,如果需要使用其他模型,可以使用one-api进行集成。
|
||||
|
||||
配置文件中的Login配置是默认的登录账号和密码
|
||||
|
||||
需要配置如下的配置文件
|
||||
|
||||
## 为了方便体验,我已经把打包好的程序放进了网盘,你只需要安装.net8环境即可运行。
|
||||
[.net8环境 ](https://dotnet.microsoft.com/zh-cn/download/dotnet/8.0)
|
||||
|
||||
[我用夸克网盘分享了「AntSK」](https://pan.quark.cn/s/9462c849cbad)
|
||||
|
||||
|
||||
## 1️⃣使用docker-compose
|
||||
|
||||
提供了pg版本 **appsettings.json** 和 简化版本(**Sqlite+disk**) **docker-compose.simple.yml**
|
||||
|
||||
从项目根目录下载**docker-compose.yml**,然后把配置文件**appsettings.json**和它放在统一目录,
|
||||
|
||||
这里已经把pg的镜像做好了。在docker-compose.yml中可以修改默认账号密码,然后你的**appsettings.json**的数据库连接需要保持一致。
|
||||
|
||||
然后你可以进入到目录后执行
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
来启动AntSK
|
||||
|
||||
## 2️⃣如何在docker中挂载本地模型,和模型下载的目录
|
||||
```
|
||||
# 非 host 版本, 不使用本机代理
|
||||
version: '3.8'
|
||||
services:
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/AIDotNet/antsk:v0.6.0
|
||||
ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
- antsk
|
||||
depends_on:
|
||||
- antskpg
|
||||
restart: always
|
||||
environment:
|
||||
- ASPNETCORE_URLS=http://*:5000
|
||||
volumes:
|
||||
- ./appsettings.json:/app/appsettings.json # 本地配置文件 需要放在同级目录
|
||||
- D://model:/app/model
|
||||
- D://model:/root/.cache/modelscope/hub/AI-ModelScope #使用Llamafactory时需要挂载 否则初始化的环境重启后会丢失
|
||||
networks:
|
||||
antsk:
|
||||
```
|
||||
以这个为示例,意思是把windows本地D://model的文件夹挂载进 容器内/app/model 如果是这样你的appsettings.json中的模型地址应该配置为
|
||||
|
||||
[LiteDockerCompose](https://github.com/AIDotNet/AntSK/blob/main/docker-compose.simple.yml)
|
||||
|
||||
精简版使用sqlite+disk向量模式,简化部署配置
|
||||
|
||||
[FullDockerCompose](https://github.com/AIDotNet/AntSK/blob/main/docker-compose.yml)
|
||||
|
||||
完整版使用pg+aspire 功能更完整,配置文件需要参考如下配置含义进行配置
|
||||
|
||||
|
||||
## 3️⃣配置文件的一些含义
|
||||
```
|
||||
{
|
||||
"DBConnection": {
|
||||
"DbType": "Sqlite",
|
||||
"ConnectionStrings": "Data Source=AntSK.db;"
|
||||
},
|
||||
"KernelMemory": {
|
||||
"VectorDb": "Disk",
|
||||
"ConnectionString": "Host=;Port=;Database=antsk;Username=;Password=",
|
||||
"TableNamePrefix": "km-"
|
||||
},
|
||||
"FileDir": {
|
||||
"DirectoryPath": "D:\\git\\AntBlazor\\model"
|
||||
},
|
||||
"Login": {
|
||||
"User": "admin",
|
||||
"Password": "xuzeyu"
|
||||
},
|
||||
"BackgroundTaskBroker": {
|
||||
"ImportKMSTask": {
|
||||
"WorkerCount": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
//支持多种数据库,具体可以查看SqlSugar,MySql,SqlServer,Sqlite,Oracle,PostgreSQL,Dm,Kdbndp,Oscar,MySqlConnector,Access,OpenGauss,QuestDB,HG,ClickHouse,GBase,Odbc,OceanBaseForOracle,TDengine,GaussDB,OceanBase,Tidb,Vastbase,PolarDB,Custom
|
||||
DBConnection.DbType
|
||||
//连接字符串,需要根据不同DB类型,用对应的字符串
|
||||
DBConnection.ConnectionStrings
|
||||
|
||||
//向量存储的类型,支持 Postgres、Disk、Memory、Qdrant、Redis、AzureAISearch
|
||||
//Postgres、Redis需要配置 ConnectionString
|
||||
//Qdrant 和AzureAISearch 的 ConnectionString 使用 Endpoint|APIKey
|
||||
KernelMemory.VectorDb
|
||||
|
||||
//本地模型路径,用于在选择llama时可以快速选择目录下的模型,以及保存下载的模型
|
||||
FileDir.DirectoryPath
|
||||
|
||||
//默认管理员账号密码
|
||||
Login
|
||||
//导入异步处理的线程数,使用在线API可以高一点,本地模型建议1 否则容易内存溢出崩掉
|
||||
BackgroundTaskBroker.ImportKMSTask.WorkerCount
|
||||
```
|
||||
|
||||
## ⚠️找不到样式问题解决:
|
||||
AntSK/src/AntSK下执行:
|
||||
```
|
||||
dotnet clean
|
||||
dotnet build
|
||||
dotnet publish "AntSK.csproj"
|
||||
```
|
||||
再去AntSK/src/AntSK/bin/Release/net8.0/publish下
|
||||
```
|
||||
dotnet AntSK.dll
|
||||
```
|
||||
然后启动就有样式了
|
||||
|
||||
DB我使用的是CodeFirst模式,只要配置好数据库链接,表结构是自动创建的
|
||||
|
||||
## ✔️使用llamafactory
|
||||
```
|
||||
1、首先需要确保你的环境已经安装了python和pip,如果使用镜像,例如p0.2.4版本已经包含了 python全套环境则无需此步骤
|
||||
2、进入模型添加页面选择llamafactory
|
||||
3、点击初始化,可以检查pip install 环境是否完成
|
||||
4、选择一个喜欢的模型
|
||||
5、点击启动,这会开始从魔塔下载模型,你可能需要有一个较为漫长的等待
|
||||
6、等待模型下载完毕后,在请求地址输入 http://localhost:8000/ 这里默认是使用8000端口
|
||||
7、点击保存,然后就可以开始聊天了
|
||||
8、很多人会问 LLamaSharp与llamafactory有什么区别?其实这两者LLamaSharp是llama.cpp的 dotnet实现,但是只支持本地gguf模型, 而llamafactory 支持的模型种类更多,但使用的是python的实现,其主要差异在这里,另外llamafactory具有模型微调的能力,这也是我们下一步需要重点集成的部分。
|
||||
```
|
||||
|
||||
## 💕 贡献者
|
||||
|
||||
这个项目的存在要感谢所有的贡献者。
|
||||
|
||||
<a href="https://github.com/AIDotNet/AntSK/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=AIDotNet/AntSK&max=1000&columns=15&anon=1" />
|
||||
</a>
|
||||
|
||||
## 🚨 使用协议
|
||||
|
||||
本仓库遵循 [AntSK License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file) 开源协议。
|
||||
|
||||
除以下附加条款外,该项目遵循Apache 2.0协议
|
||||
|
||||
1. **免费商用**:用户在不修改代码的情况下,可以免费用于商业目的。
|
||||
2. **商业授权**:如果您满足以下任意条件之一,需取得商业授权:
|
||||
1. 对本软件进行二次修改、开发(包括但不限于修改应用名称、logo、代码以及功能)。
|
||||
2. 为企业客户提供多租户服务,且该服务支持 10 人或以上的使用。
|
||||
3. 预装或集成到硬件设备或产品中进行捆绑销售。
|
||||
4. 政府或教育机构的大规模采购项目,特别是涉及安全、数据隐私等敏感需求时。
|
||||
|
||||
3. 如果您需要授权,可以联系微信:xuzeyu91
|
||||
|
||||
如果您打算在商业项目中使用AntSK,您需要确保遵守以下步骤:
|
||||
|
||||
1. 包含AntSK许可证的版权声明。 [AntSK License](https://github.com/AIDotNet/AntSK?tab=Apache-2.0-1-ov-file) 。
|
||||
|
||||
2. 如果您修改了软件源代码,您需要在源代码中明确标明这些修改。
|
||||
|
||||
3. 满足以上要求
|
||||
|
||||
## 💕 特别感谢
|
||||
助力企业级AI应用开发,推荐使用 [AntBlazor](https://antblazor.com)
|
||||
|
||||
|
||||
## ☎️联系我
|
||||
如有任何问题或建议,请通过以下方式关注我的公众号《许泽宇的技术分享》,发消息与我联系,我们也有AIDotnet交流群,可以发送进群等消息,然后我会拉你进交流群
|
||||
|
||||
另外您也可以通过邮箱与我联系:antskpro@qq.com
|
||||
|
||||

|
||||
|
||||
## 🌟 Star History
|
||||
<a href="https://github.com/AIDotNet/AntSK/stargazers" target="_blank" style="display: block" align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=AIDotNet/AntSK&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=AIDotNet/AntSK&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=AIDotNet/AntSK&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
@@ -3,9 +3,9 @@ version: '3.8'
|
||||
services:
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:v0.6.2
|
||||
image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:v0.6.4
|
||||
# 如果需要pytorch环境需要使用下面这个镜像,镜像比较大
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:p0.6.2
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:p0.6.4
|
||||
ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
|
||||
@@ -32,9 +32,9 @@ services:
|
||||
- ./pg/data:/var/lib/postgresql/data
|
||||
antsk:
|
||||
container_name: antsk
|
||||
image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:v0.6.2
|
||||
image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:v0.6.4
|
||||
# 如果需要pytorch环境需要使用下面这个镜像,镜像比较大
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:p0.6.2
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/xuzeyu91/antsk:p0.6.4
|
||||
ports:
|
||||
- 5000:5000
|
||||
networks:
|
||||
|
||||
140
docs/RBAC_README.md
Normal file
140
docs/RBAC_README.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# 角色基础授权系统 (Role-Based Access Control)
|
||||
|
||||
## 概述
|
||||
|
||||
本系统实现了完整的角色基础授权功能,支持将权限绑定到角色,角色再绑定给用户,提供灵活的权限管理能力。
|
||||
|
||||
## 数据库结构
|
||||
|
||||
### 核心表
|
||||
|
||||
1. **Roles (角色表)**
|
||||
- Id: 角色ID (主键)
|
||||
- Name: 角色名称
|
||||
- Code: 角色编码 (用于授权验证)
|
||||
- Description: 角色描述
|
||||
- IsEnabled: 是否启用
|
||||
- CreateTime: 创建时间
|
||||
|
||||
2. **Permissions (权限表)**
|
||||
- Id: 权限ID (主键)
|
||||
- Name: 权限名称
|
||||
- Code: 权限编码 (用于授权验证)
|
||||
- Type: 权限类型 (Menu-菜单权限, Operation-操作权限)
|
||||
- Description: 权限描述
|
||||
- CreateTime: 创建时间
|
||||
|
||||
3. **RolePermissions (角色权限关联表)**
|
||||
- Id: 关联ID (主键)
|
||||
- RoleId: 角色ID
|
||||
- PermissionId: 权限ID
|
||||
- CreateTime: 创建时间
|
||||
|
||||
4. **UserRoles (用户角色关联表)**
|
||||
- Id: 关联ID (主键)
|
||||
- UserId: 用户ID
|
||||
- RoleId: 角色ID
|
||||
- CreateTime: 创建时间
|
||||
|
||||
## 默认角色和权限
|
||||
|
||||
系统首次运行时会自动初始化以下角色和权限:
|
||||
|
||||
### 默认角色
|
||||
|
||||
1. **AntSKAdmin (管理员)**
|
||||
- 拥有系统所有权限
|
||||
- 可以管理用户、角色、权限等
|
||||
|
||||
2. **AntSKUser (普通用户)**
|
||||
- 拥有基本功能权限
|
||||
- 包括:聊天、应用、知识库
|
||||
|
||||
### 默认权限
|
||||
|
||||
系统包含以下菜单权限:
|
||||
- chat: 聊天
|
||||
- app: 应用
|
||||
- kms: 知识库
|
||||
- plugins.apilist: API管理
|
||||
- plugins.funlist: 函数管理
|
||||
- modelmanager.modellist: 模型管理
|
||||
- setting.user: 用户管理
|
||||
- setting.role: 角色管理
|
||||
- setting.chathistory: 聊天记录
|
||||
- setting.delkms: 删除向量表
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 1. 角色管理
|
||||
|
||||
访问 `/setting/rolelist` 可以进行角色管理:
|
||||
- 查看所有角色
|
||||
- 创建新角色
|
||||
- 编辑角色信息
|
||||
- 为角色分配权限
|
||||
- 删除角色
|
||||
|
||||
### 2. 用户管理
|
||||
|
||||
访问 `/setting/userlist` 可以进行用户管理:
|
||||
- 创建用户时可以分配角色
|
||||
- 编辑用户时可以修改角色分配
|
||||
- 用户可以拥有多个角色
|
||||
|
||||
### 3. 授权验证
|
||||
|
||||
系统支持两种授权方式:
|
||||
|
||||
**方式一:基于角色的授权**
|
||||
```csharp
|
||||
@attribute [Authorize(Roles = "AntSKAdmin")]
|
||||
```
|
||||
|
||||
**方式二:基于多角色的授权**
|
||||
```csharp
|
||||
@attribute [Authorize(Roles = "AntSKAdmin,AntSKUser")]
|
||||
```
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 认证流程
|
||||
|
||||
1. 用户登录时,系统从数据库加载用户的角色和权限
|
||||
2. 将所有角色添加到用户的Claims中
|
||||
3. 将权限列表存储到UserSession中供后续使用
|
||||
4. 支持多个角色同时验证
|
||||
|
||||
### 向后兼容
|
||||
|
||||
- 保留了原有的MenuRole字段,支持逐步迁移
|
||||
- 硬编码的管理员账号继续使用AntSKAdmin角色
|
||||
- 新用户如果没有分配角色,默认使用AntSKUser角色
|
||||
|
||||
## 安全性
|
||||
|
||||
1. **角色验证**: 只有AntSKAdmin角色可以访问角色和用户管理页面
|
||||
2. **级联删除**: 删除角色时会自动删除相关的角色权限和用户角色关联
|
||||
3. **启用状态**: 角色支持启用/禁用状态,禁用的角色不会加载权限
|
||||
4. **多层级保护**: 数据库层、业务层、UI层都有权限验证
|
||||
|
||||
## 扩展建议
|
||||
|
||||
1. **操作权限**: 可以在Permissions表中添加Type为"Operation"的权限,用于控制具体操作
|
||||
2. **权限缓存**: 可以考虑添加权限缓存机制,提高性能
|
||||
3. **审计日志**: 可以添加角色和权限变更的审计日志
|
||||
4. **批量操作**: 可以添加批量分配角色、批量分配权限的功能
|
||||
|
||||
## 常见问题
|
||||
|
||||
**Q: 如何添加新的权限?**
|
||||
A: 可以通过代码在InitRolesAndPermissions方法中添加,或者后续可以开发权限管理UI。
|
||||
|
||||
**Q: 用户可以有多个角色吗?**
|
||||
A: 可以。系统支持一个用户拥有多个角色,最终拥有所有角色的权限并集。
|
||||
|
||||
**Q: 如何处理权限冲突?**
|
||||
A: 系统采用"允许优先"策略,只要用户的任一角色拥有某项权限,用户就拥有该权限。
|
||||
|
||||
**Q: 删除角色会影响已登录的用户吗?**
|
||||
A: 会的。用户下次登录时会重新加载角色和权限,已删除的角色将不再生效。
|
||||
@@ -3,7 +3,7 @@ sidebar_position: 1
|
||||
---
|
||||
|
||||
# AntSK功能介绍
|
||||
## 基于.Net8+AntBlazor+SemanticKernel 打造的AI知识库/智能体
|
||||
## 基于.Net9+AntBlazor+SemanticKernel 打造的AI知识库/智能体
|
||||
|
||||
## 核心功能
|
||||
|
||||
|
||||
BIN
images/gzh.jpg
BIN
images/gzh.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 172 KiB |
@@ -1,27 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<DocumentationFile>AntSK.Domain.xml</DocumentationFile>
|
||||
<NoWarn>CA1050,CA1707,CA2007,VSTHRD111,CS1591,RCS1110,CA5394,SKEXP0001,SKEXP0002,SKEXP0003,SKEXP0004,SKEXP0010,SKEXP0011,,SKEXP0012,SKEXP0020,SKEXP0021,SKEXP0022,SKEXP0023,SKEXP0024,SKEXP0025,SKEXP0026,SKEXP0027,SKEXP0028,SKEXP0029,SKEXP0030,SKEXP0031,SKEXP0032,SKEXP0040,SKEXP0041,SKEXP0042,SKEXP0050,SKEXP0051,SKEXP0052,SKEXP0053,SKEXP0054,SKEXP0055,SKEXP0060,SKEXP0061,SKEXP0101,SKEXP0102,KMEXP00</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AntDesign.Charts" Version="0.5.6" />
|
||||
<PackageReference Include="AntDesign.ProLayout" Version="0.20.3" />
|
||||
<PackageReference Include="AntDesign.Charts" Version="0.6.0" />
|
||||
<PackageReference Include="AntDesign.ProLayout" Version="1.0.1" />
|
||||
<PackageReference Include="BlazorComponents.Terminal" Version="0.6.0" />
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
|
||||
<PackageReference Include="pythonnet" Version="3.0.4" />
|
||||
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
|
||||
<PackageReference Include="pythonnet" Version="3.0.5" />
|
||||
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.3.1" />
|
||||
|
||||
<PackageReference Include="AutoMapper" Version="8.1.0" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Markdig" Version="0.37.0" />
|
||||
<PackageReference Include="Markdig" Version="0.41.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftVersion)" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.169" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.187" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||
<PackageReference Include="RestSharp" Version="$(RestSharpVersion)" />
|
||||
<PackageReference Include="NPOI" Version="2.7.1" />
|
||||
|
||||
@@ -836,6 +836,126 @@
|
||||
部署名,azure需要使用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:AntSK.Domain.Repositories.Permissions">
|
||||
<summary>
|
||||
权限表
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Permissions.Id">
|
||||
<summary>
|
||||
权限ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Permissions.Name">
|
||||
<summary>
|
||||
权限名称
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Permissions.Code">
|
||||
<summary>
|
||||
权限编码
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Permissions.Type">
|
||||
<summary>
|
||||
权限类型(Menu-菜单权限, Operation-操作权限)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Permissions.Description">
|
||||
<summary>
|
||||
权限描述
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Permissions.CreateTime">
|
||||
<summary>
|
||||
创建时间
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:AntSK.Domain.Repositories.RolePermissions">
|
||||
<summary>
|
||||
角色权限关联表
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.RolePermissions.Id">
|
||||
<summary>
|
||||
关联ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.RolePermissions.RoleId">
|
||||
<summary>
|
||||
角色ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.RolePermissions.PermissionId">
|
||||
<summary>
|
||||
权限ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.RolePermissions.CreateTime">
|
||||
<summary>
|
||||
创建时间
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:AntSK.Domain.Repositories.Roles">
|
||||
<summary>
|
||||
角色表
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Roles.Id">
|
||||
<summary>
|
||||
角色ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Roles.Name">
|
||||
<summary>
|
||||
角色名称
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Roles.Code">
|
||||
<summary>
|
||||
角色编码
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Roles.Description">
|
||||
<summary>
|
||||
角色描述
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Roles.IsEnabled">
|
||||
<summary>
|
||||
是否启用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Roles.CreateTime">
|
||||
<summary>
|
||||
创建时间
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:AntSK.Domain.Repositories.UserRoles">
|
||||
<summary>
|
||||
用户角色关联表
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.UserRoles.Id">
|
||||
<summary>
|
||||
关联ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.UserRoles.UserId">
|
||||
<summary>
|
||||
用户ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.UserRoles.RoleId">
|
||||
<summary>
|
||||
角色ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.UserRoles.CreateTime">
|
||||
<summary>
|
||||
创建时间
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:AntSK.Domain.Repositories.Users.No">
|
||||
<summary>
|
||||
工号,用于登陆
|
||||
|
||||
@@ -81,10 +81,98 @@ namespace AntSK.Domain.Common.DependencyInjection
|
||||
llamafactoryStart.Value = "false";
|
||||
_dic_Repository.Insert(llamafactoryStart);
|
||||
}
|
||||
|
||||
// 初始化角色和权限
|
||||
InitRolesAndPermissions(scope.ServiceProvider);
|
||||
|
||||
_logger.LogInformation("初始化数据库初始数据完成");
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
private static void InitRolesAndPermissions(IServiceProvider serviceProvider)
|
||||
{
|
||||
var _roles_Repository = serviceProvider.GetRequiredService<IRoles_Repositories>();
|
||||
var _permissions_Repository = serviceProvider.GetRequiredService<IPermissions_Repositories>();
|
||||
var _rolePermissions_Repository = serviceProvider.GetRequiredService<IRolePermissions_Repositories>();
|
||||
|
||||
// 检查是否已经初始化
|
||||
if (_roles_Repository.IsAny(r => r.Code == "AntSKAdmin"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建管理员角色
|
||||
var adminRole = new Roles
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Name = "管理员",
|
||||
Code = "AntSKAdmin",
|
||||
Description = "系统管理员,拥有所有权限",
|
||||
IsEnabled = true,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
_roles_Repository.Insert(adminRole);
|
||||
|
||||
// 创建普通用户角色
|
||||
var userRole = new Roles
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Name = "普通用户",
|
||||
Code = "AntSKUser",
|
||||
Description = "普通用户,拥有基本功能权限",
|
||||
IsEnabled = true,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
_roles_Repository.Insert(userRole);
|
||||
|
||||
// 创建菜单权限
|
||||
var menuPermissions = new List<Permissions>
|
||||
{
|
||||
new Permissions { Id = Guid.NewGuid().ToString(), Name = "聊天", Code = "chat", Type = "Menu", Description = "聊天功能权限" },
|
||||
new Permissions { Id = Guid.NewGuid().ToString(), Name = "应用", Code = "app", Type = "Menu", Description = "应用管理权限" },
|
||||
new Permissions { Id = Guid.NewGuid().ToString(), Name = "知识库", Code = "kms", Type = "Menu", Description = "知识库管理权限" },
|
||||
new Permissions { Id = Guid.NewGuid().ToString(), Name = "API管理", Code = "plugins.apilist", Type = "Menu", Description = "API管理权限" },
|
||||
new Permissions { Id = Guid.NewGuid().ToString(), Name = "函数管理", Code = "plugins.funlist", Type = "Menu", Description = "函数管理权限" },
|
||||
new Permissions { Id = Guid.NewGuid().ToString(), Name = "模型管理", Code = "modelmanager.modellist", Type = "Menu", Description = "模型管理权限" },
|
||||
new Permissions { Id = Guid.NewGuid().ToString(), Name = "用户管理", Code = "setting.user", Type = "Menu", Description = "用户管理权限" },
|
||||
new Permissions { Id = Guid.NewGuid().ToString(), Name = "角色管理", Code = "setting.role", Type = "Menu", Description = "角色管理权限" },
|
||||
new Permissions { Id = Guid.NewGuid().ToString(), Name = "聊天记录", Code = "setting.chathistory", Type = "Menu", Description = "聊天记录权限" },
|
||||
new Permissions { Id = Guid.NewGuid().ToString(), Name = "删除向量表", Code = "setting.delkms", Type = "Menu", Description = "删除向量表权限" }
|
||||
};
|
||||
|
||||
foreach (var permission in menuPermissions)
|
||||
{
|
||||
_permissions_Repository.Insert(permission);
|
||||
}
|
||||
|
||||
// 为管理员角色分配所有权限
|
||||
foreach (var permission in menuPermissions)
|
||||
{
|
||||
_rolePermissions_Repository.Insert(new RolePermissions
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
RoleId = adminRole.Id,
|
||||
PermissionId = permission.Id,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
// 为普通用户角色分配基本权限(聊天、应用、知识库)
|
||||
var basicPermissions = menuPermissions.Where(p => p.Code == "chat" || p.Code == "app" || p.Code == "kms").ToList();
|
||||
foreach (var permission in basicPermissions)
|
||||
{
|
||||
_rolePermissions_Repository.Insert(new RolePermissions
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
RoleId = userRole.Id,
|
||||
PermissionId = permission.Id,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
_logger.LogInformation("初始化角色和权限完成");
|
||||
}
|
||||
/// <summary>
|
||||
/// 加载数据库的插件
|
||||
/// </summary>
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace AntSK.Domain.Domain.Interface
|
||||
IAsyncEnumerable<string> SendChatByAppAsync(Apps app, ChatHistory history);
|
||||
|
||||
IAsyncEnumerable<StreamingKernelContent> SendKmsByAppAsync(Apps app, string questions, ChatHistory history, string filePath, List<RelevantSource> relevantSources = null);
|
||||
Task<string> SendImgByAppAsync(Apps app, string questions);
|
||||
Task<ChatHistory> GetChatHistory(List<Chats> MessageList, ChatHistory history);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,5 @@ namespace AntSK.Domain.Domain.Model.Enum
|
||||
{
|
||||
chat = 1,
|
||||
kms = 2,
|
||||
img=3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,21 @@ namespace AntSK.Domain.Domain.Model.Fun
|
||||
{
|
||||
public class FunDto
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Description { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public FunType FunType { get; set; }
|
||||
|
||||
// 函数参数信息(用于前端展示)
|
||||
public List<FunParameterDto> Parameters { get; set; } = new();
|
||||
}
|
||||
|
||||
public class FunParameterDto
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public enum FunType
|
||||
|
||||
@@ -229,112 +229,6 @@ namespace AntSK.Domain.Domain.Service
|
||||
}
|
||||
|
||||
|
||||
public async Task<string> SendImgByAppAsync(Apps app, string questions)
|
||||
{
|
||||
var imageModel = _aIModels_Repositories.GetFirst(p => p.Id == app.ImageModelID);
|
||||
KernelArguments args = new() {
|
||||
{ "input", questions }
|
||||
};
|
||||
var _kernel = _kernelService.GetKernelByApp(app);
|
||||
var temperature = app.Temperature / 100; //存的是0~100需要缩小
|
||||
OpenAIPromptExecutionSettings settings = new() { Temperature = temperature };
|
||||
var func = _kernel.CreateFunctionFromPrompt("Translate this into English:{{$input}}", settings);
|
||||
var chatResult = await _kernel.InvokeAsync(function: func, arguments: args);
|
||||
if (chatResult.IsNotNull())
|
||||
{
|
||||
//Can Load stable-diffusion library in diffenert environment
|
||||
|
||||
//SDHelper.LoadLibrary()
|
||||
string versionString = string.Empty;
|
||||
string extensionString = string.Empty;
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
extensionString = ".dll";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
extensionString = ".so";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("OS Platform no support");
|
||||
}
|
||||
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo("nvcc", "--version");
|
||||
startInfo.RedirectStandardOutput = true;
|
||||
startInfo.UseShellExecute = false;
|
||||
startInfo.CreateNoWindow = true;
|
||||
using (Process process = Process.Start(startInfo))
|
||||
{
|
||||
if (process != null)
|
||||
{
|
||||
string result = process.StandardOutput.ReadToEnd();
|
||||
Regex regex = new Regex(@"release (\d+).[\d]");
|
||||
Match match = regex.Match(result);
|
||||
if (match.Success)
|
||||
{
|
||||
switch (match.Groups[1].Value.ToString())
|
||||
{
|
||||
case "11":
|
||||
versionString = "Cuda11";
|
||||
break;
|
||||
case "12":
|
||||
versionString = "Cuda12";
|
||||
break;
|
||||
default:
|
||||
versionString = "CPU";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("nvcc get an error");
|
||||
}
|
||||
}
|
||||
|
||||
string libraryPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "StableDiffusion", "Backend", versionString, "stable-diffusion" + extensionString);
|
||||
NativeLibrary.TryLoad(libraryPath, out _);
|
||||
string prompt = chatResult.GetValue<string>();
|
||||
if (!SDHelper.IsInitialized)
|
||||
{
|
||||
Structs.ModelParams modelParams = new Structs.ModelParams
|
||||
{
|
||||
ModelPath = imageModel.ModelName,
|
||||
RngType = Structs.RngType.CUDA_RNG,
|
||||
//VaePath = vaePath,
|
||||
//KeepVaeOnCpu = keepVaeOnCpu,
|
||||
//set false can get a better image, otherwise can use lower vram
|
||||
VaeTiling = false,
|
||||
//LoraModelDir = loraModelDir,
|
||||
};
|
||||
bool result = SDHelper.Initialize(modelParams);
|
||||
}
|
||||
|
||||
Structs.TextToImageParams textToImageParams = new Structs.TextToImageParams
|
||||
{
|
||||
Prompt = prompt,
|
||||
NegativePrompt = "bad quality, wrong image, worst quality",
|
||||
SampleMethod = (Structs.SampleMethod)Enum.Parse(typeof(Structs.SampleMethod), "EULER_A"),
|
||||
//the base image size in SD1.5 is 512x512
|
||||
Width = 512,
|
||||
Height = 512,
|
||||
NormalizeInput = true,
|
||||
ClipSkip = -1,
|
||||
CfgScale = 7,
|
||||
SampleSteps = 20,
|
||||
Seed = -1,
|
||||
};
|
||||
Bitmap[] outputImages = SDHelper.TextToImage(textToImageParams);
|
||||
var base64 = ImageUtils.BitmapToBase64(outputImages[0]);
|
||||
return base64;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ChatHistory> GetChatHistory(List<Chats> MessageList, ChatHistory history)
|
||||
{
|
||||
foreach (var item in MessageList)
|
||||
|
||||
@@ -102,21 +102,21 @@ namespace AntSK.Domain.Domain.Service
|
||||
|
||||
var settings = chatModel.ModelKey.Split("|");
|
||||
|
||||
Sdcb.SparkDesk.ModelVersion modelVersion = Sdcb.SparkDesk.ModelVersion.V3_5;
|
||||
Sdcb.SparkDesk.ModelVersion modelVersion = Sdcb.SparkDesk.ModelVersion.Lite;
|
||||
|
||||
switch (chatModel.ModelName)
|
||||
{
|
||||
case "V3_5":
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.V3_5;
|
||||
case "Max":
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.Max;
|
||||
break;
|
||||
case "V3":
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.V3;
|
||||
case "Pro":
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.Pro;
|
||||
break;
|
||||
case "V2":
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.V2;
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.V2_0;
|
||||
break;
|
||||
case "V1_5":
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.V1_5;
|
||||
case "Lite":
|
||||
modelVersion = Sdcb.SparkDesk.ModelVersion.Lite;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
public interface IPermissions_Repositories : IRepository<Permissions>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using SqlSugar;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// 权限表
|
||||
/// </summary>
|
||||
[SugarTable("Permissions")]
|
||||
public partial class Permissions
|
||||
{
|
||||
/// <summary>
|
||||
/// 权限ID
|
||||
/// </summary>
|
||||
[SugarColumn(IsPrimaryKey = true)]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 权限名称
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 权限编码
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 权限类型(Menu-菜单权限, Operation-操作权限)
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 权限描述
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreateTime { get; set; } = DateTime.Now;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using AntSK.Domain.Common.DependencyInjection;
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
[ServiceDescription(typeof(IPermissions_Repositories), ServiceLifetime.Scoped)]
|
||||
public class Permissions_Repositories : Repository<Permissions>, IPermissions_Repositories
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
public interface IRoles_Repositories : IRepository<Roles>
|
||||
{
|
||||
}
|
||||
}
|
||||
45
src/AntSK.Domain/Repositories/Setting/Role/Roles.cs
Normal file
45
src/AntSK.Domain/Repositories/Setting/Role/Roles.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using SqlSugar;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色表
|
||||
/// </summary>
|
||||
[SugarTable("Roles")]
|
||||
public partial class Roles
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色ID
|
||||
/// </summary>
|
||||
[SugarColumn(IsPrimaryKey = true)]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色名称
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色编码
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色描述
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreateTime { get; set; } = DateTime.Now;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using AntSK.Domain.Common.DependencyInjection;
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
[ServiceDescription(typeof(IRoles_Repositories), ServiceLifetime.Scoped)]
|
||||
public class Roles_Repositories : Repository<Roles>, IRoles_Repositories
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
public interface IRolePermissions_Repositories : IRepository<RolePermissions>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色权限关联表
|
||||
/// </summary>
|
||||
[SugarTable("RolePermissions")]
|
||||
public partial class RolePermissions
|
||||
{
|
||||
/// <summary>
|
||||
/// 关联ID
|
||||
/// </summary>
|
||||
[SugarColumn(IsPrimaryKey = true)]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色ID
|
||||
/// </summary>
|
||||
public string RoleId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 权限ID
|
||||
/// </summary>
|
||||
public string PermissionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreateTime { get; set; } = DateTime.Now;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using AntSK.Domain.Common.DependencyInjection;
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
[ServiceDescription(typeof(IRolePermissions_Repositories), ServiceLifetime.Scoped)]
|
||||
public class RolePermissions_Repositories : Repository<RolePermissions>, IRolePermissions_Repositories
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
public interface IUserRoles_Repositories : IRepository<UserRoles>
|
||||
{
|
||||
}
|
||||
}
|
||||
32
src/AntSK.Domain/Repositories/Setting/UserRole/UserRoles.cs
Normal file
32
src/AntSK.Domain/Repositories/Setting/UserRole/UserRoles.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户角色关联表
|
||||
/// </summary>
|
||||
[SugarTable("UserRoles")]
|
||||
public partial class UserRoles
|
||||
{
|
||||
/// <summary>
|
||||
/// 关联ID
|
||||
/// </summary>
|
||||
[SugarColumn(IsPrimaryKey = true)]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// </summary>
|
||||
public string UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色ID
|
||||
/// </summary>
|
||||
public string RoleId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreateTime { get; set; } = DateTime.Now;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using AntSK.Domain.Common.DependencyInjection;
|
||||
using AntSK.Domain.Repositories.Base;
|
||||
|
||||
namespace AntSK.Domain.Repositories
|
||||
{
|
||||
[ServiceDescription(typeof(IUserRoles_Repositories), ServiceLifetime.Scoped)]
|
||||
public class UserRoles_Repositories : Repository<UserRoles>, IUserRoles_Repositories
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
@@ -11,9 +11,9 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftVersion)" />
|
||||
<PackageReference Include="RestSharp" Version="$(RestSharpVersion)" />
|
||||
<PackageReference Include="Cnblogs.KernelMemory.AI.DashScope" Version="0.3.0" />
|
||||
<PackageReference Include="Cnblogs.SemanticKernel.Connectors.DashScope" Version="0.3.2" />
|
||||
<PackageReference Include="Sdcb.SparkDesk" Version="3.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
|
||||
<PackageReference Include="Cnblogs.SemanticKernel.Connectors.DashScope" Version="0.4.0" />
|
||||
<PackageReference Include="Sdcb.SparkDesk" Version="3.1.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="$(NetVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.KernelMemory.Core" Version="$(KMVersion)" />
|
||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.9.0.20240103" />
|
||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.10.0.20241108" />
|
||||
<PackageReference Include="Sdcb.OpenCvSharp4.mini.runtime.debian.12-x64" Version="4.8.0.20231125" />
|
||||
<PackageReference Include="Sdcb.OpenVINO" Version="0.6.6" />
|
||||
<PackageReference Include="Sdcb.OpenVINO" Version="0.6.10" />
|
||||
<PackageReference Include="Sdcb.OpenVINO.PaddleOCR.Models.Online" Version="0.6.2" />
|
||||
<PackageReference Include="Sdcb.OpenVINO.PaddleOCR" Version="0.6.3" />
|
||||
<PackageReference Include="Sdcb.OpenVINO.runtime.ubuntu.22.04-x64" Version="2024.2.0" />
|
||||
<PackageReference Include="Sdcb.OpenVINO.runtime.win-x64" Version="2024.2.0" />
|
||||
<PackageReference Include="Sdcb.OpenVINO.PaddleOCR" Version="0.6.8" />
|
||||
<PackageReference Include="Sdcb.OpenVINO.runtime.ubuntu.22.04-x64" Version="2025.0.0" />
|
||||
<PackageReference Include="Sdcb.OpenVINO.runtime.win-x64" Version="2025.0.0" />
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DocumentationFile>AntSK.xml</DocumentationFile>
|
||||
@@ -19,11 +19,11 @@
|
||||
<Content Include="AntSKlogo.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.6" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Downloader" Version="3.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="$(NetVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="$(NetVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="$(NetVersion)" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="$(NetVersion)" />
|
||||
<PackageReference Include="Downloader" Version="3.3.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,5 +4,7 @@
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public string Role { get; set; }
|
||||
public List<string>? Roles { get; set; }
|
||||
public List<string>? Permissions { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
<RadioGroup @bind-Value="context.Type" OnChange="OnAppTypeChange" TValue="string" ButtonStyle="RadioButtonStyle.Solid">
|
||||
<Radio RadioButton Value="@AppType.chat.ToString()">会话应用</Radio>
|
||||
<Radio RadioButton Value="@AppType.kms.ToString()">知识库</Radio>
|
||||
<Radio RadioButton Value="@AppType.img.ToString()">做图应用</Radio>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem Label="描述" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
@@ -42,25 +41,24 @@
|
||||
</Select>
|
||||
<Button Type="@ButtonType.Link" OnClick="NavigateModelList">去创建</Button>
|
||||
</FormItem>
|
||||
@if (@context.Type != AppType.img.ToString())
|
||||
{
|
||||
<FormItem Label="向量模型" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
<Select DataSource="@_embedingList"
|
||||
@bind-Value="@context.EmbeddingModelID"
|
||||
ValueProperty="c=>c.Id"
|
||||
LabelProperty="c=>'【'+c.AIType.ToString()+'】'+c.ModelDescription">
|
||||
</Select>
|
||||
<Button Type="@ButtonType.Link" OnClick="NavigateModelList">去创建</Button>
|
||||
</FormItem>
|
||||
<FormItem Label="提示词" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
<TextArea MinRows="4" Placeholder="请输入角色信息" @bind-Value="@context.Prompt" />
|
||||
</FormItem>
|
||||
<FormItem Label="温度系数" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
<span>更确定</span>
|
||||
<Slider TValue="double" Style="display: inline-block;width: 300px; " Min="0" Max="100" DefaultValue="70" @bind-Value="@context.Temperature" />
|
||||
<span>更发散</span>
|
||||
</FormItem>
|
||||
}
|
||||
|
||||
<FormItem Label="向量模型" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
<Select DataSource="@_embedingList"
|
||||
@bind-Value="@context.EmbeddingModelID"
|
||||
ValueProperty="c=>c.Id"
|
||||
LabelProperty="c=>'【'+c.AIType.ToString()+'】'+c.ModelDescription">
|
||||
</Select>
|
||||
<Button Type="@ButtonType.Link" OnClick="NavigateModelList">去创建</Button>
|
||||
</FormItem>
|
||||
<FormItem Label="提示词" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
<TextArea MinRows="4" Placeholder="请输入角色信息" @bind-Value="@context.Prompt" />
|
||||
</FormItem>
|
||||
<FormItem Label="温度系数" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
<span>更确定</span>
|
||||
<Slider TValue="double" Style="display: inline-block;width: 300px; " Min="0" Max="100" DefaultValue="70" @bind-Value="@context.Temperature" />
|
||||
<span>更发散</span>
|
||||
</FormItem>
|
||||
|
||||
@if (@context.Type == AppType.chat.ToString())
|
||||
{
|
||||
<FormItem Label="API插件列表" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
|
||||
@@ -59,10 +59,6 @@
|
||||
<Tag Color="@PresetColor.Green.ToString()">知识库</Tag>
|
||||
|
||||
}
|
||||
else if (context.Type == AppType.img.ToString())
|
||||
{
|
||||
<Tag Color="@PresetColor.Red.ToString()">做图应用</Tag>
|
||||
}
|
||||
</DescriptionTemplate>
|
||||
</CardMeta>
|
||||
</Card>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<GridRow Gutter="(16, 16)">
|
||||
<GridCol Span="14">
|
||||
<Card Style="height:75vh;overflow: auto;">
|
||||
<Card Class="chat-card">
|
||||
<TitleTemplate>
|
||||
<Icon Type="setting" /> 选择应用
|
||||
<Select DataSource="@_list"
|
||||
@@ -23,19 +23,21 @@
|
||||
<a href="@( NavigationManager.BaseUri + "openchat/" + AppId)" target="_blank">分享使用</a>
|
||||
</TitleTemplate>
|
||||
<Body>
|
||||
@if (!string.IsNullOrEmpty(AppId))
|
||||
{
|
||||
<Watermark Content="AntSK">
|
||||
<ChatView AppId="@AppId" ShowTitle=false OnRelevantSources="OnRelevantSources"></ChatView>
|
||||
</Watermark>
|
||||
}
|
||||
<div class="chat-card__content">
|
||||
@if (!string.IsNullOrEmpty(AppId))
|
||||
{
|
||||
<Watermark Content="AntSK" Class="chat-card__watermark">
|
||||
<ChatView AppId="@AppId" ShowTitle=false OnRelevantSources="OnRelevantSources"></ChatView>
|
||||
</Watermark>
|
||||
}
|
||||
</div>
|
||||
</Body>
|
||||
</Card>
|
||||
</GridCol>
|
||||
<GridCol Span="10">
|
||||
<Card Style="height: 75vh;overflow: auto;">
|
||||
<TitleTemplate>
|
||||
<Icon Type="search" /> 调试结果
|
||||
<Icon Type="search" /> 知识溯源
|
||||
</TitleTemplate>
|
||||
<Extra>
|
||||
|
||||
@@ -62,13 +64,45 @@
|
||||
</GridRow>
|
||||
|
||||
<style>
|
||||
#chat {
|
||||
height: calc(75vh - 120px);
|
||||
.chat-card {
|
||||
height: 75vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chat-card .ant-card-body {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat-card__content {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chat-card__watermark {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.chat-card__watermark > * {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
@@ -26,6 +26,10 @@ namespace AntSK.Pages.ChatPage
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
_list = _apps_Repositories.GetList();
|
||||
if (string.IsNullOrEmpty(AppId)&&_list.Count>0)
|
||||
{
|
||||
AppId = _list.FirstOrDefault().Id;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRelevantSources(List<RelevantSource> relevantSources)
|
||||
|
||||
@@ -11,45 +11,54 @@
|
||||
{
|
||||
<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;">
|
||||
<div id="scrollDiv" class="chat-scroll">
|
||||
<Virtualize Items="@(MessageList.OrderBy(o => o.CreateTime).ToList())" Context="item">
|
||||
@if (item.IsSend)
|
||||
{
|
||||
<GridRow>
|
||||
<GridRow Class="message-row" Gutter="(8,8)">
|
||||
<GridCol Span="23">
|
||||
<div class="chat-bubble sent">
|
||||
<Popover Title="@item.CreateTime.ToString()">
|
||||
<Unbound>
|
||||
<Flex Vertical RefBack="context">
|
||||
@if (item.FileName != null)
|
||||
{
|
||||
<p class="message-file">
|
||||
<Upload DefaultFileList="[new(){ FileName= item.FileName }]" />
|
||||
</p>
|
||||
}
|
||||
<p>@(item.Context)</p>
|
||||
</Flex>
|
||||
</Unbound>
|
||||
</Popover>
|
||||
<div class="message-shell sent">
|
||||
<div class="chat-bubble sent">
|
||||
<Popover Title="@item.CreateTime.ToString()">
|
||||
<Unbound>
|
||||
<Flex Vertical RefBack="context" Class="bubble-body">
|
||||
@if (item.FileName != null)
|
||||
{
|
||||
<p class="message-file">
|
||||
<Upload DefaultFileList="[new(){ FileName= item.FileName }]" />
|
||||
</p>
|
||||
}
|
||||
<p class="bubble-text">@(item.Context)</p>
|
||||
</Flex>
|
||||
</Unbound>
|
||||
</Popover>
|
||||
</div>
|
||||
<div class="message-meta meta-right">
|
||||
<span>@item.CreateTime.ToString("HH:mm")</span>
|
||||
<Icon Class="meta-action" Type="copy" Theme="outline" OnClick="async () =>await OnCopyAsync(item)" />
|
||||
</div>
|
||||
</div>
|
||||
<Icon Style="float:right;margin-top:10px;" Type="copy" Theme="outline" OnClick="async () =>await OnCopyAsync(item)" />
|
||||
</GridCol>
|
||||
<GridCol Span="1">
|
||||
<Image Width="25px" Height="25px" Style="margin-top:10px;" Src="./assets/KDpgvguMpGfqaHPjicRK.svg" />
|
||||
<GridCol Span="1" Class="avatar-col">
|
||||
<Image Class="avatar" Width="32px" Height="32px" Src="./assets/KDpgvguMpGfqaHPjicRK.svg" Preview="false" />
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
}
|
||||
else
|
||||
{
|
||||
<GridRow>
|
||||
<GridCol Span="1">
|
||||
<Image Width="25px" Height="25px" Style="margin-top:10px;" Src="./assets/method-draw-image.svg" />
|
||||
<GridRow Class="message-row" Gutter="(8,8)">
|
||||
<GridCol Span="1" Class="avatar-col">
|
||||
<Image Class="avatar" Width="32px" Height="32px" Src="./assets/method-draw-image.svg" Preview="false" />
|
||||
</GridCol>
|
||||
<GridCol Span="23">
|
||||
<div class="chat-bubble received">
|
||||
@((MarkupString)(item.Context))
|
||||
<div class="message-shell received">
|
||||
<div class="chat-bubble received">
|
||||
@((MarkupString)(item.Context))
|
||||
</div>
|
||||
<div class="message-meta meta-left">
|
||||
<span>@item.CreateTime.ToString("HH:mm")</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
}
|
||||
@@ -62,9 +71,10 @@
|
||||
<Upload DefaultFileList="fileList" OnRemove="HandleFileRemove" />
|
||||
</Flex>
|
||||
}
|
||||
<Flex Justify="end">
|
||||
|
||||
<AntDesign.Input @bind-Value="@(_messageInput)" DebounceMilliseconds="@(-1)" Placeholder="输入消息回车发送" OnPressEnter="@(async () => await OnSendAsync())" Disabled="@Sendding"></AntDesign.Input>
|
||||
<Flex Class="input-bar" Justify="end" Align="center">
|
||||
<div class="input-bar__field">
|
||||
<AntDesign.Input @bind-Value="@(_messageInput)" DebounceMilliseconds="@(-1)" Placeholder="输入消息回车发送" OnPressEnter="@(async () => await OnSendAsync())" Disabled="@Sendding"></AntDesign.Input>
|
||||
</div>
|
||||
@if (app.EmbeddingModelID != null)
|
||||
{
|
||||
<Upload Action="@("api/File/UploadFile")"
|
||||
@@ -77,59 +87,376 @@
|
||||
</Upload>
|
||||
}
|
||||
<Button Icon="clear" Type="@(ButtonType.Link)" OnClick="@(async () => await OnClearAsync())" Disabled="@Sendding"></Button>
|
||||
<Button Icon="send" Type="@(ButtonType.Link)" OnClick="@(async () => await OnSendAsync())" Disabled="@Sendding"></Button>
|
||||
<Button Icon="send" Type="@(ButtonType.Primary)" OnClick="@(async () => await OnSendAsync())" Disabled="@Sendding"></Button>
|
||||
</Flex>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
height: 100vh;
|
||||
:root {
|
||||
--surface-base: #ffffff;
|
||||
--surface-elevated: #f5f7fb;
|
||||
--border-subtle: rgba(15, 23, 42, 0.07);
|
||||
--shadow-soft: 0 16px 28px rgba(15, 23, 42, 0.08);
|
||||
--primary-color: var(--ant-primary-color, #1677ff);
|
||||
--primary-soft: rgba(22, 119, 255, 0.12);
|
||||
--bubble-recv: #ffffff;
|
||||
--bubble-border-sent: rgba(22, 119, 255, 0.25);
|
||||
--text-primary: #1f2329;
|
||||
--text-secondary: #7a808a;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
width: 350px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
@@supports (color: color-mix(in srgb, white 50%, black 50%)) {
|
||||
:root {
|
||||
--bubble-sent: color-mix(in srgb, var(--primary-color) 28%, white);
|
||||
}
|
||||
}
|
||||
|
||||
#chat {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
padding-bottom: 15px;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-height: min(65vh, 100%);
|
||||
max-height: 100%;
|
||||
background: var(--surface-base);
|
||||
border-radius: 16px 16px 16px 16px;
|
||||
padding: 12px 12px 0 12px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
#chat::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 32px;
|
||||
right: 32px;
|
||||
bottom: -18px;
|
||||
height: 40px;
|
||||
background: rgba(15, 23, 42, 0.22);
|
||||
filter: blur(24px);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.chat-scroll {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 12px 16px 20px;
|
||||
background: var(--surface-base);
|
||||
border-radius: 16px;
|
||||
box-sizing: border-box;
|
||||
scroll-behavior: smooth;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
}
|
||||
|
||||
.chat-scroll::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.chat-scroll::-webkit-scrollbar-thumb {
|
||||
background: rgba(22, 119, 255, 0.24);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.chat-scroll::-webkit-scrollbar-track {
|
||||
background: rgba(15, 23, 42, 0.05);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.message-row {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.message-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.message-shell.sent {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-shell.received {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.chat-bubble {
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
margin-bottom: 0;
|
||||
border-radius: 5px;
|
||||
max-width: 70%;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
max-width: 78%;
|
||||
position: relative;
|
||||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
|
||||
border: none;
|
||||
word-break: break-word;
|
||||
line-height: 1.65;
|
||||
animation: pop .2s ease-out;
|
||||
transition: transform .2s ease, box-shadow .2s ease;
|
||||
}
|
||||
|
||||
.received {
|
||||
background-color: #f0f0f0;
|
||||
align-self: flex-start;
|
||||
float: left;
|
||||
.chat-bubble:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.sent {
|
||||
background-color: #daf8cb;
|
||||
align-self: flex-end;
|
||||
float: right;
|
||||
.chat-bubble .bubble-text {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
height: 90% !important;
|
||||
.chat-bubble .bubble-body {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chat-bubble.received {
|
||||
background: var(--bubble-recv);
|
||||
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.06), inset 0 0 0 1px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.chat-bubble.sent {
|
||||
background: var(--bubble-sent);
|
||||
color: #0c1a33;
|
||||
box-shadow: 0 12px 26px rgba(22, 119, 255, 0.18);
|
||||
}
|
||||
|
||||
.chat-bubble.sent .bubble-text {
|
||||
color: #102347;
|
||||
}
|
||||
|
||||
/* bubble tails */
|
||||
.chat-bubble.received::after,
|
||||
.chat-bubble.sent::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
bottom: auto;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
transform: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none !important;
|
||||
filter: none !important;
|
||||
}
|
||||
|
||||
.chat-bubble.received::after {
|
||||
left: -14px;
|
||||
background: var(--bubble-recv);
|
||||
clip-path: polygon(100% 0, 0 0, 100% 100%);
|
||||
}
|
||||
|
||||
.chat-bubble.sent::after {
|
||||
right: -14px;
|
||||
background: var(--bubble-sent);
|
||||
clip-path: polygon(0 0, 100% 0, 0 100%);
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.meta-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.meta-left {
|
||||
justify-content: flex-start;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.meta-action {
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: color .2s ease;
|
||||
}
|
||||
|
||||
.meta-action:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.avatar-col {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.12) inset, 0 6px 16px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.input-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
border-top: 1px solid rgba(15, 23, 42, 0.06);
|
||||
background: var(--surface-base);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -12px 24px rgba(15, 23, 42, 0.06);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.input-bar .ant-input {
|
||||
border-radius: 999px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
.input-bar__field {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-elevated);
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.input-bar__field .ant-input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* markdown & code inside bubbles */
|
||||
.chat-bubble pre {
|
||||
background: #0b1021;
|
||||
color: #e6e6e6;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
overflow: auto;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.chat-bubble code {
|
||||
background: rgba(27, 31, 35, 0.08);
|
||||
padding: .15rem .35rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.chat-bubble pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.chat-bubble table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.chat-bubble table td,
|
||||
.chat-bubble table th {
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.think {
|
||||
color: gray;
|
||||
color: rgba(22, 119, 255, 0.9);
|
||||
font-style: italic;
|
||||
text-align: left !important;
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
margin-left: 8px;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid rgba(22, 119, 255, 0.4);
|
||||
}
|
||||
|
||||
.message-file {
|
||||
margin: 0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.message-file .ant-upload-list-item {
|
||||
background: rgba(22, 119, 255, 0.08);
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@@media (max-width: 992px) {
|
||||
#chat {
|
||||
border-radius: 12px;
|
||||
padding: 10px 10px 0;
|
||||
}
|
||||
|
||||
.chat-bubble {
|
||||
max-width: 88%;
|
||||
}
|
||||
|
||||
.input-bar {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--surface-base: #111827;
|
||||
--surface-elevated: #1f2937;
|
||||
--border-subtle: rgba(148, 163, 184, 0.16);
|
||||
--shadow-soft: 0 18px 34px rgba(0, 0, 0, 0.45);
|
||||
--text-primary: #f1f5f9;
|
||||
--text-secondary: #94a3b8;
|
||||
--bubble-recv: #1f2937;
|
||||
}
|
||||
|
||||
#chat {
|
||||
background: var(--surface-elevated);
|
||||
box-shadow: 0 28px 48px -24px rgba(0, 0, 0, 0.68);
|
||||
}
|
||||
|
||||
#chat::after {
|
||||
background: rgba(15, 23, 42, 0.55);
|
||||
}
|
||||
|
||||
.chat-scroll {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.chat-bubble {
|
||||
border: 1px solid rgba(148, 163, 184, 0.16);
|
||||
}
|
||||
|
||||
.chat-bubble.received::after {
|
||||
background: var(--bubble-recv);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.message-file .ant-upload-list-item {
|
||||
background: rgba(22, 119, 255, 0.16);
|
||||
}
|
||||
|
||||
.input-bar {
|
||||
background: var(--surface-elevated);
|
||||
}
|
||||
}
|
||||
|
||||
@@keyframes pop {
|
||||
from { transform: translateY(6px); opacity: .55; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -264,48 +264,17 @@ namespace AntSK.Pages.ChatPage.Components
|
||||
await SendKms(questions, history, app, filePath);
|
||||
|
||||
}
|
||||
else if (app.Type == AppType.img.ToString())
|
||||
|
||||
|
||||
await SaveMsg(MessageList);
|
||||
if (OnRelevantSources.IsNotNull())
|
||||
{
|
||||
await SendImg(questions, app);
|
||||
await OnRelevantSources.InvokeAsync(_relevantSources);
|
||||
}
|
||||
|
||||
//缓存消息记录
|
||||
if (app.Type != AppType.img.ToString())
|
||||
{
|
||||
await SaveMsg(MessageList);
|
||||
if (OnRelevantSources.IsNotNull())
|
||||
{
|
||||
await OnRelevantSources.InvokeAsync(_relevantSources);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送图片对话
|
||||
/// </summary>
|
||||
/// <param name="questions"></param>
|
||||
/// <param name="app"></param>
|
||||
/// <returns></returns>
|
||||
private async Task SendImg(string questions,Apps app)
|
||||
{
|
||||
Chats info = new Chats();
|
||||
info.Id = Guid.NewGuid().ToString();
|
||||
info.UserName=_userName;
|
||||
info.AppId=AppId;
|
||||
info.CreateTime = DateTime.Now;
|
||||
var base64= await _chatService.SendImgByAppAsync(app, questions);
|
||||
if (string.IsNullOrEmpty(base64))
|
||||
{
|
||||
info.Context = "生成失败";
|
||||
}
|
||||
else
|
||||
{
|
||||
info.Context = $"<img src=\"data:image/jpeg;base64,{base64}\" alt=\"Base64 Image\" />";
|
||||
}
|
||||
MessageList.Add(info);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送知识库问答
|
||||
/// </summary>
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<Text>● 未获取商业授权,禁止商业使用</Text>
|
||||
<br>
|
||||
<Text>● 如需商业授权,请联系微信:</Text>
|
||||
<b style="color:#7F7FFF">xuzeyu91</b>
|
||||
<b style="color:#1890ff">13469996907</b>
|
||||
<br>
|
||||
</Body>
|
||||
</Card>
|
||||
@@ -93,7 +93,7 @@
|
||||
</Text>
|
||||
<br>
|
||||
<Text>● 装机直发、开箱即用</Text>
|
||||
<a href="https://antsk.cn/archives/wei-ming-ming-wen-zhang" target="_blank">更多详情</a>
|
||||
<a href="https://antsk.cn/solutions.html" target="_blank">更多详情</a>
|
||||
<br>
|
||||
<Text>
|
||||
● 保姆式指导
|
||||
|
||||
@@ -2,42 +2,165 @@
|
||||
/* stylelint-disable no-duplicate-selectors */
|
||||
/* stylelint-disable */
|
||||
/* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */
|
||||
|
||||
/* 全局样式优化 */
|
||||
:root {
|
||||
--primary-color: #1890ff;
|
||||
--primary-hover: #40a9ff;
|
||||
--primary-active: #096dd9;
|
||||
--primary-light: rgba(24, 144, 255, 0.1);
|
||||
--text-color: rgba(0, 0, 0, 0.85);
|
||||
--text-color-secondary: rgba(0, 0, 0, 0.45);
|
||||
--border-radius: 4px;
|
||||
--box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
--transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
/* 图标组样式优化 */
|
||||
.iconGroup span.anticon {
|
||||
margin-left: 16px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
color: var(--text-color-secondary);
|
||||
cursor: pointer;
|
||||
transition: color 0.32s;
|
||||
transition: all var(--transition-duration) ease;
|
||||
}
|
||||
|
||||
.iconGroup span.anticon:hover {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
color: var(--text-color);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* 统计卡片样式增强 */
|
||||
.ant-card {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
transition: all var(--transition-duration) ease;
|
||||
}
|
||||
|
||||
.ant-card:hover {
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
/* ChartCard 组件样式优化 */
|
||||
.chartCard {
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
transition: all var(--transition-duration) ease;
|
||||
}
|
||||
|
||||
.chartCard .chartTop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.chartCard .metaWrap {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chartCard .meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.chartCard .action {
|
||||
color: var(--primary-color);
|
||||
transition: all var(--transition-duration) ease;
|
||||
}
|
||||
|
||||
.chartCard .total {
|
||||
margin-top: 4px;
|
||||
color: var(--text-color);
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.chartCard .content {
|
||||
position: relative;
|
||||
margin-bottom: 12px;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chartCard .contentFixed {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chartCard .footer {
|
||||
margin-top: 8px;
|
||||
padding-top: 9px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 功能特性卡片样式优化 */
|
||||
.ant-space-item .ant-card {
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
}
|
||||
|
||||
.ant-space-item .ant-card:hover {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.ant-space-item .ant-card .ant-card-head {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
background: linear-gradient(90deg, var(--primary-light) 0%, rgba(255, 255, 255, 0) 100%);
|
||||
}
|
||||
|
||||
.ant-space-item .ant-card .ant-card-head-title {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* 排名列表样式优化 */
|
||||
.rankingList {
|
||||
margin: 25px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.rankingList li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
zoom: 1;
|
||||
padding: 8px;
|
||||
border-radius: var(--border-radius);
|
||||
transition: background-color var(--transition-duration) ease;
|
||||
}
|
||||
|
||||
.rankingList li:hover {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.rankingList li::before,
|
||||
.rankingList li::after {
|
||||
display: table;
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
.rankingList li::after {
|
||||
clear: both;
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.rankingList li span {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
color: var(--text-color);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.rankingList li .rankingItemNumber {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
@@ -49,12 +172,15 @@
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
background-color: #fafafa;
|
||||
border-radius: 20px;
|
||||
border-radius: 50%;
|
||||
transition: all var(--transition-duration) ease;
|
||||
}
|
||||
|
||||
.rankingList li .rankingItemNumber.active {
|
||||
color: #fff;
|
||||
background-color: #314659;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.rankingList li .rankingItemTitle {
|
||||
flex: 1;
|
||||
margin-right: 8px;
|
||||
@@ -62,79 +188,41 @@
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.salesExtra {
|
||||
display: inline-block;
|
||||
margin-right: 24px;
|
||||
}
|
||||
.salesExtra a {
|
||||
margin-left: 24px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
.salesExtra a:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
.salesExtra a.currentDate {
|
||||
color: #1890ff;
|
||||
}
|
||||
.salesCard .salesBar {
|
||||
padding: 0 0 32px 32px;
|
||||
}
|
||||
.salesCard .salesRank {
|
||||
padding: 0 32px 32px 72px;
|
||||
}
|
||||
.salesCard :global .ant-tabs-bar,
|
||||
.salesCard :global .ant-tabs-nav-wrap {
|
||||
padding-left: 16px;
|
||||
}
|
||||
.salesCard :global .ant-tabs-bar .ant-tabs-nav .ant-tabs-tab,
|
||||
.salesCard :global .ant-tabs-nav-wrap .ant-tabs-nav .ant-tabs-tab {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.salesCard :global .ant-tabs-extra-content {
|
||||
padding-right: 24px;
|
||||
line-height: 55px;
|
||||
}
|
||||
.salesCard :global .ant-card-head {
|
||||
|
||||
/* 标题样式优化 */
|
||||
.ant-divider .ant-typography {
|
||||
position: relative;
|
||||
color: var(--text-color);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
.salesCard :global .ant-card-head-title {
|
||||
align-items: normal;
|
||||
}
|
||||
.salesCardExtra {
|
||||
height: inherit;
|
||||
}
|
||||
.salesTypeRadio {
|
||||
|
||||
.ant-divider .ant-typography::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 54px;
|
||||
bottom: 12px;
|
||||
bottom: -8px;
|
||||
left: 50%;
|
||||
width: 40px;
|
||||
height: 3px;
|
||||
background: var(--primary-color);
|
||||
transform: translateX(-50%);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.offlineCard :global .ant-tabs-ink-bar {
|
||||
bottom: auto;
|
||||
|
||||
/* 标签样式优化 */
|
||||
.ant-tag {
|
||||
margin: 4px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
transition: all var(--transition-duration) ease;
|
||||
}
|
||||
.offlineCard :global .ant-tabs-bar {
|
||||
border-bottom: none;
|
||||
}
|
||||
.offlineCard :global .ant-tabs-nav-container-scrolling {
|
||||
padding-right: 40px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
.offlineCard :global .ant-tabs-tab-prev-icon::before {
|
||||
position: relative;
|
||||
left: 6px;
|
||||
}
|
||||
.offlineCard :global .ant-tabs-tab-next-icon::before {
|
||||
position: relative;
|
||||
right: 6px;
|
||||
}
|
||||
.offlineCard :global .ant-tabs-tab-active h4 {
|
||||
color: #1890ff;
|
||||
}
|
||||
.trendText {
|
||||
margin-left: 8px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
|
||||
.ant-tag:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media screen and (max-width: 992px) {
|
||||
.salesExtra {
|
||||
display: none;
|
||||
@@ -142,7 +230,11 @@
|
||||
.rankingList li span:first-child {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.ant-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.rankingTitle {
|
||||
margin-top: 16px;
|
||||
@@ -150,7 +242,11 @@
|
||||
.salesCard .salesBar {
|
||||
padding: 16px;
|
||||
}
|
||||
.ant-space {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 576px) {
|
||||
.salesExtraWrap {
|
||||
display: none;
|
||||
@@ -158,4 +254,7 @@
|
||||
.salesCard :global .ant-tabs-content {
|
||||
padding-top: 30px;
|
||||
}
|
||||
.chartCard .total {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
@using AntSK.Domain.Domain.Model.Enum
|
||||
@using AntSK.Domain.Domain.Model.Fun
|
||||
@page "/plugins/funlist"
|
||||
@inject NavigationManager NavigationManager
|
||||
@using AntSK.Services.Auth
|
||||
@inherits AuthComponentBase
|
||||
|
||||
@@ -26,16 +25,20 @@
|
||||
<ListItem NoFlex>
|
||||
@if (string.IsNullOrEmpty(context.Name))
|
||||
{
|
||||
<Button Type="dashed" class="newButton" @onclick="AddFun">
|
||||
<Icon Type="plus" Theme="outline" /> 创建函数
|
||||
</Button>
|
||||
<Button Type="dashed" class="newButton" @onclick="ClearFun">
|
||||
<Icon Type="clear" Theme="outline" /> 清空导入函数
|
||||
</Button>
|
||||
<Card Hoverable Bordered Class="card" Style="height:160px; display:flex; align-items:center; justify-content:center;">
|
||||
<div style="display:flex; gap:12px; flex-wrap:wrap; justify-content:center;">
|
||||
<Button Type="dashed" class="newButton" @onclick="AddFun">
|
||||
<Icon Type="plus" Theme="outline" /> 创建函数
|
||||
</Button>
|
||||
<Button Type="dashed" class="newButton" @onclick="ClearFun">
|
||||
<Icon Type="clear" Theme="outline" /> 清空导入函数
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Card Hoverable Bordered Class="card" Style="max-height:247px;">
|
||||
<Card Hoverable Bordered Class="card" Style="height:160px; overflow:hidden;">
|
||||
<CardMeta>
|
||||
<AvatarTemplate>
|
||||
|
||||
@@ -44,10 +47,29 @@
|
||||
<a>@context.Name</a>
|
||||
</TitleTemplate>
|
||||
<DescriptionTemplate>
|
||||
<Paragraph class="item" Ellipsis>
|
||||
<Paragraph class="item" Ellipsis Style="margin-bottom:8px;">
|
||||
<!--todo: Ellipsis not working-->
|
||||
@context.Description
|
||||
</Paragraph>
|
||||
@if (context.Parameters?.Count > 0)
|
||||
{
|
||||
<div style="margin-top:8px; max-height:140px; overflow:auto; padding-right:4px;">
|
||||
<span style="font-weight:600;">参数:</span>
|
||||
<ul style="margin: 6px 0 0 18px; padding:0;">
|
||||
@foreach (var p in context.Parameters)
|
||||
{
|
||||
<li style="list-style: disc;">
|
||||
<span style="color:#3b3b3b">@p.Name</span>
|
||||
<span style="color:#999"> (@p.Type)</span>
|
||||
@if (!string.IsNullOrWhiteSpace(p.Description))
|
||||
{
|
||||
<span style="color:#666"> - @p.Description</span>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</DescriptionTemplate>
|
||||
</CardMeta>
|
||||
</Card>
|
||||
|
||||
@@ -17,18 +17,18 @@ namespace AntSK.Pages.FunPage
|
||||
{
|
||||
private FunDto[] _data = { };
|
||||
|
||||
[Inject]
|
||||
FunctionService _functionService { get; set; }
|
||||
[Inject]
|
||||
IServiceProvider _serviceProvider { get; set; }
|
||||
[Inject]
|
||||
IConfirmService _confirmService { get; set; }
|
||||
[Inject]
|
||||
IFuns_Repositories _funs_Repositories { get; set; }
|
||||
[Inject]
|
||||
FunctionService _functionService { get; set; } = default!;
|
||||
[Inject]
|
||||
IServiceProvider _serviceProvider { get; set; } = default!;
|
||||
[Inject]
|
||||
IConfirmService _confirmService { get; set; } = default!;
|
||||
[Inject]
|
||||
IFuns_Repositories _funs_Repositories { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected MessageService? _message { get; set; }
|
||||
[Inject] protected ILogger<FunDto> _logger { get; set; }
|
||||
[Inject] protected ILogger<FunDto> _logger { get; set; } = default!;
|
||||
|
||||
bool _fileVisible = false;
|
||||
bool _fileConfirmLoading = false;
|
||||
@@ -56,7 +56,19 @@ namespace AntSK.Pages.FunPage
|
||||
foreach (var func in funList)
|
||||
{
|
||||
var methodInfo = _functionService.MethodInfos[func.Key];
|
||||
list.Add(new FunDto() { Name = func.Key, Description = methodInfo.Description });
|
||||
var paramDtos = methodInfo.Parameters?.Select(p => new FunParameterDto
|
||||
{
|
||||
Name = p.ParameterName ?? string.Empty,
|
||||
Type = (p.ParameterType?.IsClass ?? false) ? "object" : (p.ParameterType?.Name ?? "string"),
|
||||
Description = p.Description ?? string.Empty
|
||||
}).ToList() ?? new List<FunParameterDto>();
|
||||
|
||||
list.Add(new FunDto()
|
||||
{
|
||||
Name = func.Key,
|
||||
Description = methodInfo.Description,
|
||||
Parameters = paramDtos
|
||||
});
|
||||
}
|
||||
_data = list.ToArray();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
@@ -72,9 +84,7 @@ namespace AntSK.Pages.FunPage
|
||||
await InitData(searchKey);
|
||||
}
|
||||
|
||||
private async Task AddFun() {
|
||||
_fileVisible = true;
|
||||
}
|
||||
private Task AddFun() { _fileVisible = true; return Task.CompletedTask; }
|
||||
private async Task ClearFun()
|
||||
{
|
||||
var content = "清空自定义函数将会删除全部导入函数,并且需要程序重启后下次生效,如不是DLL冲突等原因不建议清空,是否要清空?";
|
||||
@@ -97,7 +107,10 @@ namespace AntSK.Pages.FunPage
|
||||
_funs_Repositories.Insert(new Funs() { Id = Guid.NewGuid().ToString(), Path = file.FilePath });
|
||||
_functionService.FuncLoad(file.FilePath);
|
||||
}
|
||||
_message.Info("上传成功");
|
||||
if (_message is not null)
|
||||
{
|
||||
await _message.Info("上传成功");
|
||||
}
|
||||
await InitData("");
|
||||
_fileVisible = false;
|
||||
}
|
||||
@@ -119,12 +132,12 @@ namespace AntSK.Pages.FunPage
|
||||
{
|
||||
if (file.Ext != ".dll")
|
||||
{
|
||||
_message.Error("请上传dll文件!");
|
||||
_message?.Error("请上传dll文件!");
|
||||
}
|
||||
var IsLt500K = file.Size < 1024 * 1024 * 100;
|
||||
if (!IsLt500K)
|
||||
{
|
||||
_message.Error("文件需不大于100MB!");
|
||||
_message?.Error("文件需不大于100MB!");
|
||||
}
|
||||
|
||||
return IsLt500K;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@namespace AntSK.Pages.Setting.AIModel
|
||||
@namespace AntSK.Pages.Setting.ChatHistory
|
||||
@page "/setting/chathistory"
|
||||
@using AntSK.Services.Auth
|
||||
@inherits AuthComponentBase
|
||||
@@ -9,53 +9,359 @@
|
||||
@using AntSK.Domain.Common.Map
|
||||
@attribute [Authorize(Roles = "AntSKAdmin")]
|
||||
|
||||
<Table @ref="table"
|
||||
TItem="ChatsDto"
|
||||
DataSource="@chatDtoList"
|
||||
Total="_total"
|
||||
@bind-PageIndex="_pageIndex"
|
||||
@bind-PageSize="_pageSize"
|
||||
OnChange="OnChange"
|
||||
Size="TableSize.Small"
|
||||
RowKey="x=>x.Id">
|
||||
<TitleTemplate>
|
||||
<GridRow>
|
||||
<GridCol Span="4">
|
||||
<Title Level="3">聊天记录</Title>
|
||||
</GridCol>
|
||||
<GridCol Span="8" Offset="12">
|
||||
<Search Placeholder="搜索" @bind-Value="searchString" OnSearch="Search" />
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
</TitleTemplate>
|
||||
<ColumnDefinitions>
|
||||
<PropertyColumn Property="c=>c.Id" />
|
||||
<PropertyColumn Title="用户名" Property="c=>c.UserName" />
|
||||
<PropertyColumn Title="应用名称" Property="c=>c.AppName" />
|
||||
<PropertyColumn Title="发送/接收" Property="c=>c.SendReveice" />
|
||||
<ActionColumn Title="消息内容" Width="30%">
|
||||
<Space>
|
||||
<SpaceItem>
|
||||
@((MarkupString)(context.Context))
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</ActionColumn>
|
||||
<PropertyColumn Title="时间" Property="c=>c.CreateTime" Format="yyyy-MM-dd HH:mm:ss" />
|
||||
</ColumnDefinitions>
|
||||
</Table>
|
||||
<div class="chat-history-container">
|
||||
<Card>
|
||||
<TitleTemplate>
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<Icon Type="message" Theme="outline" />
|
||||
<Title Level="2" style="margin-bottom: 0; margin-left: 8px;">聊天记录</Title>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<Search
|
||||
Placeholder="搜索用户名或消息内容"
|
||||
@bind-Value="searchString"
|
||||
OnSearch="Search"
|
||||
EnterButton="true"
|
||||
Size="@InputSize.Large"
|
||||
style="width: 300px;" />
|
||||
<span class="total-text">共 @_total 条</span>
|
||||
</div>
|
||||
</div>
|
||||
</TitleTemplate>
|
||||
<Body>
|
||||
<Table @ref="table"
|
||||
TItem="ChatsDto"
|
||||
DataSource="@chatDtoList"
|
||||
Total="_total"
|
||||
@bind-PageIndex="_pageIndex"
|
||||
@bind-PageSize="_pageSize"
|
||||
OnChange="OnChange"
|
||||
Size="TableSize.Middle"
|
||||
RowKey="x=>x.Id"
|
||||
Bordered="true"
|
||||
Loading="_loading">
|
||||
<ColumnDefinitions>
|
||||
<PropertyColumn Property="c=>c.Id" Width="80px">
|
||||
<TitleTemplate>
|
||||
<strong>ID</strong>
|
||||
</TitleTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Title="用户" Property="c=>c.UserName" Width="160px">
|
||||
<TitleTemplate>
|
||||
<strong><Icon Type="user" /> 用户</strong>
|
||||
</TitleTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Title="应用" Property="c=>c.AppName" Width="150px">
|
||||
<TitleTemplate>
|
||||
<strong><Icon Type="appstore" /> 应用</strong>
|
||||
</TitleTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Title="类型" Property="c=>c.SendReveice" Width="120px">
|
||||
<TitleTemplate>
|
||||
<strong><Icon Type="swap" /> 类型</strong>
|
||||
</TitleTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Title="消息内容" Property="c=>c.Context" Width="40%">
|
||||
<TitleTemplate>
|
||||
<strong><Icon Type="message" /> 消息内容</strong>
|
||||
</TitleTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<PropertyColumn Title="时间" Property="c=>c.CreateTime" Format="yyyy-MM-dd HH:mm:ss" Width="200px">
|
||||
<TitleTemplate>
|
||||
<strong><Icon Type="clock-circle" /> 时间</strong>
|
||||
</TitleTemplate>
|
||||
</PropertyColumn>
|
||||
</ColumnDefinitions>
|
||||
</Table>
|
||||
</Body>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.chat-history-container {
|
||||
padding: 24px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.ant-card {
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
padding: 12px 0 16px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-left .anticon {
|
||||
font-size: 26px;
|
||||
color: #1677ff;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.header-left .ant-typography {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.total-text {
|
||||
margin-left: 12px;
|
||||
color: rgba(0,0,0,.45);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ant-input-search-large {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(22, 119, 255, 0.12);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper-focused {
|
||||
box-shadow: 0 0 0 3px rgba(22, 119, 255, 0.12);
|
||||
border-color: #1677ff !important;
|
||||
}
|
||||
|
||||
.ant-table {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.ant-table-thead > tr > th {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
color: rgba(0,0,0,.85);
|
||||
letter-spacing: .2px;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr > td {
|
||||
padding: 12px 16px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
max-width: 600px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.message-content .ant-typography {
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
background: linear-gradient(180deg, #fafafa 0%, #fff 100%);
|
||||
padding: 12px 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #f0f0f0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bubble::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -6px;
|
||||
top: 14px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid transparent;
|
||||
border-right: 6px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.bubble::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -5px;
|
||||
top: 14px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid transparent;
|
||||
border-right: 6px solid #fff;
|
||||
}
|
||||
|
||||
.time-cell {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #fafafa;
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.time-cell .date {
|
||||
font-size: 13px;
|
||||
color: #595959;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.time-cell .time {
|
||||
font-size: 11px;
|
||||
color: #8c8c8c;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
font-size: 12px;
|
||||
color: #595959;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.ant-tag {
|
||||
margin: 0;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.type-tag {
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,.06);
|
||||
}
|
||||
|
||||
.app-tag {
|
||||
background: #f0f5ff;
|
||||
color: #2f54eb;
|
||||
border-color: #adc6ff;
|
||||
}
|
||||
|
||||
.user-cell {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.avatar-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: #e6f4ff;
|
||||
color: #1677ff;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@@media (max-width: 1200px) {
|
||||
.message-content {
|
||||
max-width: 420px;
|
||||
}
|
||||
}
|
||||
|
||||
@@media (max-width: 768px) {
|
||||
.chat-history-container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.header-left .ant-typography {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-right .ant-input-search {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.user-name { max-width: 160px; }
|
||||
}
|
||||
|
||||
@@media (max-width: 480px) {
|
||||
.message-content {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.time-cell {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.time-cell .date {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.time-cell .time {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.user-name { max-width: 120px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@using System.Text.Json;
|
||||
@code {
|
||||
[Inject] IChats_Repositories _chats_Repositories { get; set; }
|
||||
[Inject] IChats_Repositories _chats_Repositories { get; set; } = default!;
|
||||
|
||||
ChatsDto[] chatDtoList;
|
||||
|
||||
ITable table;
|
||||
ChatsDto[] chatDtoList = Array.Empty<ChatsDto>();
|
||||
ITable? table;
|
||||
bool _loading = false;
|
||||
|
||||
int _pageIndex = 1;
|
||||
int _pageSize = 10;
|
||||
int _total = 0;
|
||||
string searchString;
|
||||
string searchString = string.Empty;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -64,58 +370,78 @@
|
||||
|
||||
public async Task OnChange(QueryModel<ChatsDto> queryModel)
|
||||
{
|
||||
_loading = true;
|
||||
StateHasChanged();
|
||||
await InitData();
|
||||
_loading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task InitData()
|
||||
private Task InitData()
|
||||
{
|
||||
if (string.IsNullOrEmpty(searchString))
|
||||
try
|
||||
{
|
||||
_total = _chats_Repositories.Count(p => true);
|
||||
var chatList = _chats_Repositories.GetDB().Queryable<Chats, Apps>((c, a) => new object[] {
|
||||
SqlSugar.JoinType.Left,c.AppId==a.Id
|
||||
}).Select((c, a) => new ChatsDto
|
||||
{
|
||||
Id = c.Id,
|
||||
UserName = c.UserName,
|
||||
AppId = c.AppId,
|
||||
IsSend = c.IsSend,
|
||||
SendReveice = c.IsSend ? "发送" : "接收",
|
||||
Context = c.Context,
|
||||
CreateTime = c.CreateTime,
|
||||
AppName = a.Name
|
||||
}).ToPageList(_pageIndex, _pageSize);
|
||||
chatDtoList = chatList.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
_total = _chats_Repositories.Count(p => p.UserName.Contains(searchString) || p.Context.Contains(searchString));
|
||||
var chatList = _chats_Repositories.GetDB().Queryable<Chats, Apps>((c, a) => new object[] {
|
||||
SqlSugar.JoinType.Left,c.AppId==a.Id
|
||||
}).Select((c, a) => new ChatsDto
|
||||
if (string.IsNullOrEmpty(searchString))
|
||||
{
|
||||
Id = c.Id,
|
||||
UserName = c.UserName,
|
||||
AppId = c.AppId,
|
||||
IsSend = c.IsSend,
|
||||
SendReveice = c.IsSend ? "发送" : "接收",
|
||||
Context = c.Context,
|
||||
CreateTime = c.CreateTime,
|
||||
AppName = a.Name
|
||||
}).Where(c => c.UserName.Contains(searchString) || c.Context.Contains(searchString)).ToPageList(_pageIndex, _pageSize);
|
||||
chatDtoList = chatList.ToArray();
|
||||
_total = _chats_Repositories.Count(p => true);
|
||||
var chatList = _chats_Repositories.GetDB().Queryable<Chats, Apps>((c, a) => new object[] {
|
||||
SqlSugar.JoinType.Left, c.AppId == a.Id
|
||||
}).Select((c, a) => new ChatsDto
|
||||
{
|
||||
Id = c.Id,
|
||||
UserName = c.UserName,
|
||||
AppId = c.AppId,
|
||||
IsSend = c.IsSend,
|
||||
SendReveice = c.IsSend ? "发送" : "接收",
|
||||
Context = c.Context,
|
||||
CreateTime = c.CreateTime,
|
||||
AppName = a.Name ?? "未知应用"
|
||||
}).OrderByDescending(c => c.CreateTime).ToPageList(_pageIndex, _pageSize);
|
||||
|
||||
chatDtoList = chatList.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
_total = _chats_Repositories.Count(p => p.UserName.Contains(searchString) || p.Context.Contains(searchString));
|
||||
var chatList = _chats_Repositories.GetDB().Queryable<Chats, Apps>((c, a) => new object[] {
|
||||
SqlSugar.JoinType.Left, c.AppId == a.Id
|
||||
}).Select((c, a) => new ChatsDto
|
||||
{
|
||||
Id = c.Id,
|
||||
UserName = c.UserName,
|
||||
AppId = c.AppId,
|
||||
IsSend = c.IsSend,
|
||||
SendReveice = c.IsSend ? "发送" : "接收",
|
||||
Context = c.Context,
|
||||
CreateTime = c.CreateTime,
|
||||
AppName = a.Name ?? "未知应用"
|
||||
}).Where(c => c.UserName.Contains(searchString) || c.Context.Contains(searchString))
|
||||
.OrderByDescending(c => c.CreateTime).ToPageList(_pageIndex, _pageSize);
|
||||
|
||||
chatDtoList = chatList.ToArray();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 处理异常,可以添加日志记录
|
||||
chatDtoList = Array.Empty<ChatsDto>();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Search(string searchKey)
|
||||
{
|
||||
_pageIndex = 1; // 重置到第一页
|
||||
_loading = true;
|
||||
StateHasChanged();
|
||||
await InitData();
|
||||
_loading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public class ChatsDto : Chats
|
||||
{
|
||||
public string AppName { get; set; }
|
||||
public string SendReveice { get; set; }
|
||||
public string AppName { get; set; } = string.Empty;
|
||||
public string SendReveice { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
61
src/AntSK/Pages/Setting/Role/AddRole.razor
Normal file
61
src/AntSK/Pages/Setting/Role/AddRole.razor
Normal file
@@ -0,0 +1,61 @@
|
||||
@namespace AntSK.Pages.Setting.Role
|
||||
@using AntSK.Domain.Repositories
|
||||
@using AntSK.Models
|
||||
@page "/setting/role/add"
|
||||
@page "/setting/role/add/{RoleId}"
|
||||
@using AntSK.Services.Auth
|
||||
@inherits AuthComponentBase
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@attribute [Authorize(Roles = "AntSKAdmin")]
|
||||
|
||||
<PageContainer Title="新增角色">
|
||||
<ChildContent>
|
||||
<Card>
|
||||
<Form Model="@_roleModel"
|
||||
Style="margin-top: 8px;"
|
||||
OnFinish="HandleSubmit">
|
||||
<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 Placeholder="请输入角色编码" @bind-Value="@context.Code" />
|
||||
</FormItem>
|
||||
<FormItem Label="角色描述" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
<Input Placeholder="请输入角色描述" @bind-Value="@context.Description" />
|
||||
</FormItem>
|
||||
<FormItem Label="是否启用" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
<Switch @bind-Checked="@context.IsEnabled" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem Label="权限分配" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
<Select Mode="multiple"
|
||||
@bind-Values="_permissionIds"
|
||||
Placeholder="选择权限"
|
||||
TItemValue="string"
|
||||
TItem="string"
|
||||
Size="@AntSizeLDSType.Default">
|
||||
<SelectOptions>
|
||||
@foreach (var permission in _allPermissions)
|
||||
{
|
||||
<SelectOption TItem="string" TItemValue="string" Value="@permission.Id" Label="@permission.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 {
|
||||
|
||||
}
|
||||
103
src/AntSK/Pages/Setting/Role/AddRole.razor.cs
Normal file
103
src/AntSK/Pages/Setting/Role/AddRole.razor.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using AntDesign;
|
||||
using AntSK.Domain.Repositories;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AntSK.Pages.Setting.Role
|
||||
{
|
||||
public partial class AddRole
|
||||
{
|
||||
[Parameter]
|
||||
public string RoleId { get; set; }
|
||||
[Inject] protected IRoles_Repositories _roles_Repositories { get; set; }
|
||||
[Inject] protected IPermissions_Repositories _permissions_Repositories { get; set; }
|
||||
[Inject] protected IRolePermissions_Repositories _rolePermissions_Repositories { get; set; }
|
||||
[Inject] protected MessageService? Message { get; set; }
|
||||
|
||||
private Roles _roleModel = new Roles();
|
||||
private List<Permissions> _allPermissions = new List<Permissions>();
|
||||
private IEnumerable<string> _permissionIds;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// 加载所有权限
|
||||
_allPermissions = _permissions_Repositories.GetList();
|
||||
|
||||
if (!string.IsNullOrEmpty(RoleId))
|
||||
{
|
||||
_roleModel = _roles_Repositories.GetFirst(p => p.Id == RoleId);
|
||||
|
||||
// 加载角色已有的权限
|
||||
var rolePermissions = _rolePermissions_Repositories.GetList(p => p.RoleId == RoleId);
|
||||
_permissionIds = rolePermissions.Select(rp => rp.PermissionId);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSubmit()
|
||||
{
|
||||
if (string.IsNullOrEmpty(RoleId))
|
||||
{
|
||||
//新增
|
||||
_roleModel.Id = Guid.NewGuid().ToString();
|
||||
_roleModel.CreateTime = DateTime.Now;
|
||||
|
||||
if (_roles_Repositories.IsAny(p => p.Code == _roleModel.Code))
|
||||
{
|
||||
_ = Message.Error("角色编码已存在!", 2);
|
||||
return;
|
||||
}
|
||||
_roles_Repositories.Insert(_roleModel);
|
||||
|
||||
// 添加角色权限关联
|
||||
if (_permissionIds != null)
|
||||
{
|
||||
foreach (var permissionId in _permissionIds)
|
||||
{
|
||||
_rolePermissions_Repositories.Insert(new RolePermissions
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
RoleId = _roleModel.Id,
|
||||
PermissionId = permissionId,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//修改
|
||||
_roles_Repositories.Update(_roleModel);
|
||||
|
||||
// 先删除旧的角色权限关联
|
||||
var oldRolePermissions = _rolePermissions_Repositories.GetList(p => p.RoleId == RoleId);
|
||||
foreach (var rp in oldRolePermissions)
|
||||
{
|
||||
_rolePermissions_Repositories.Delete(rp.Id);
|
||||
}
|
||||
|
||||
// 添加新的角色权限关联
|
||||
if (_permissionIds != null)
|
||||
{
|
||||
foreach (var permissionId in _permissionIds)
|
||||
{
|
||||
_rolePermissions_Repositories.Insert(new RolePermissions
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
RoleId = _roleModel.Id,
|
||||
PermissionId = permissionId,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Back();
|
||||
}
|
||||
|
||||
private void Back()
|
||||
{
|
||||
NavigationManager.NavigateTo("/setting/rolelist");
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/AntSK/Pages/Setting/Role/RoleList.razor
Normal file
72
src/AntSK/Pages/Setting/Role/RoleList.razor
Normal file
@@ -0,0 +1,72 @@
|
||||
@namespace AntSK.Pages.Setting.Role
|
||||
@using AntSK.Domain.Repositories
|
||||
@page "/setting/rolelist"
|
||||
@inject NavigationManager NavigationManager
|
||||
@using AntSK.Services.Auth
|
||||
@inherits AuthComponentBase
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@attribute [Authorize(Roles = "AntSKAdmin")]
|
||||
|
||||
<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" OnSearch="OnSearch" />
|
||||
</div>
|
||||
</Extra>
|
||||
<ChildContent>
|
||||
<Button Type="dashed"
|
||||
Style="width: 100%; margin-bottom: 8px;"
|
||||
OnClick="AddRole">
|
||||
<Icon Type="plus" Theme="outline" />
|
||||
新增角色
|
||||
</Button>
|
||||
|
||||
<AntList TItem="Roles"
|
||||
DataSource="_data"
|
||||
ItemLayout="ListItemLayout.Horizontal">
|
||||
<ListItem Actions="new[] {
|
||||
edit(()=> Edit(context.Id)),
|
||||
delete(async ()=>await Delete(context.Id))
|
||||
}" Style="width:100%">
|
||||
<div class="listContent" style="width:100%">
|
||||
<div class="listContentItem" style="width:20%">
|
||||
<b>角色名称</b>
|
||||
<p>@context.Name</p>
|
||||
</div>
|
||||
<div class="listContentItem" style="width:20%">
|
||||
<b>角色编码</b>
|
||||
<p>@context.Code</p>
|
||||
</div>
|
||||
<div class="listContentItem" style="width:30%">
|
||||
<b>描述</b>
|
||||
<p>@context.Description</p>
|
||||
</div>
|
||||
<div class="listContentItem" style="width:15%">
|
||||
<b>状态</b>
|
||||
<p>@(context.IsEnabled ? "启用" : "禁用")</p>
|
||||
</div>
|
||||
</div>
|
||||
</ListItem>
|
||||
</AntList>
|
||||
</ChildContent>
|
||||
</Card>
|
||||
</div>
|
||||
</ChildContent>
|
||||
</PageContainer>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
RenderFragment edit(Action clickAction) =>@<a key="edit" @onclick="@clickAction">修改</a>;
|
||||
RenderFragment delete(Action clickAction) =>@<a key="delete" @onclick="@clickAction">删除</a>;
|
||||
|
||||
}
|
||||
84
src/AntSK/Pages/Setting/Role/RoleList.razor.cs
Normal file
84
src/AntSK/Pages/Setting/Role/RoleList.razor.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using AntDesign;
|
||||
using AntSK.Domain.Repositories;
|
||||
using AntSK.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AntSK.Pages.Setting.Role
|
||||
{
|
||||
public partial class RoleList
|
||||
{
|
||||
private readonly BasicListFormModel _model = new BasicListFormModel();
|
||||
|
||||
private List<Roles> _data;
|
||||
|
||||
private string _searchKeyword;
|
||||
|
||||
[Inject]
|
||||
protected IRoles_Repositories _roles_Repositories { get; set; }
|
||||
[Inject]
|
||||
protected IRolePermissions_Repositories _rolePermissions_Repositories { get; set; }
|
||||
[Inject]
|
||||
protected IUserRoles_Repositories _userRoles_Repositories { get; set; }
|
||||
[Inject]
|
||||
IConfirmService _confirmService { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
await InitData();
|
||||
}
|
||||
private async Task InitData(string searchKey = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(searchKey))
|
||||
{
|
||||
_data = _roles_Repositories.GetList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_data = _roles_Repositories.GetList(p => p.Name.Contains(searchKey) || p.Code.Contains(searchKey) || (p.Description != null && p.Description.Contains(searchKey)));
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
public async Task OnSearch()
|
||||
{
|
||||
await InitData(_searchKeyword);
|
||||
}
|
||||
|
||||
public async Task AddRole()
|
||||
{
|
||||
NavigationManager.NavigateTo("/setting/role/add");
|
||||
}
|
||||
|
||||
public void Edit(string roleid)
|
||||
{
|
||||
NavigationManager.NavigateTo("/setting/role/add/" + roleid);
|
||||
}
|
||||
|
||||
public async Task Delete(string roleid)
|
||||
{
|
||||
var content = "是否确认删除此角色";
|
||||
var title = "删除";
|
||||
var result = await _confirmService.Show(content, title, ConfirmButtons.YesNo);
|
||||
if (result == ConfirmResult.Yes)
|
||||
{
|
||||
// 删除角色权限关联
|
||||
var rolePerms = _rolePermissions_Repositories.GetList(p => p.RoleId == roleid);
|
||||
foreach (var rp in rolePerms)
|
||||
{
|
||||
await _rolePermissions_Repositories.DeleteAsync(rp.Id);
|
||||
}
|
||||
|
||||
// 删除用户角色关联
|
||||
var userRoles = _userRoles_Repositories.GetList(p => p.RoleId == roleid);
|
||||
foreach (var ur in userRoles)
|
||||
{
|
||||
await _userRoles_Repositories.DeleteAsync(ur.Id);
|
||||
}
|
||||
|
||||
// 删除角色
|
||||
await _roles_Repositories.DeleteAsync(roleid);
|
||||
await InitData("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,22 @@
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem Label="角色分配" LabelCol="LayoutModel._formItemLayout.LabelCol" WrapperCol="LayoutModel._formItemLayout.WrapperCol">
|
||||
<Select Mode="multiple"
|
||||
@bind-Values="_roleIds"
|
||||
Placeholder="选择角色"
|
||||
TItemValue="string"
|
||||
TItem="string"
|
||||
Size="@AntSizeLDSType.Default">
|
||||
<SelectOptions>
|
||||
@foreach (var role in _allRoles)
|
||||
{
|
||||
<SelectOption TItem="string" TItemValue="string" Value="@role.Id" Label="@role.Name" />
|
||||
}
|
||||
</SelectOptions>
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem Label=" " Style="margin-top:32px" WrapperCol="LayoutModel._submitFormLayout.WrapperCol">
|
||||
<Button Type="primary" OnClick="HandleSubmit">
|
||||
保存
|
||||
|
||||
@@ -12,21 +12,34 @@ namespace AntSK.Pages.Setting.User
|
||||
[Parameter]
|
||||
public string UserId { get; set; }
|
||||
[Inject] protected IUsers_Repositories _users_Repositories { get; set; }
|
||||
[Inject] protected IRoles_Repositories _roles_Repositories { get; set; }
|
||||
[Inject] protected IUserRoles_Repositories _userRoles_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;
|
||||
IEnumerable<string> _roleIds;
|
||||
|
||||
private List<MenuDataItem> menuList = new List<MenuDataItem>();
|
||||
private List<Roles> _allRoles = new List<Roles>();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// 加载所有角色
|
||||
_allRoles = _roles_Repositories.GetList(r => r.IsEnabled);
|
||||
|
||||
if (!string.IsNullOrEmpty(UserId))
|
||||
{
|
||||
_userModel = _users_Repositories.GetFirst(p => p.Id == UserId);
|
||||
_password = _userModel.Password;
|
||||
|
||||
// 加载用户已有的角色
|
||||
var userRoles = _userRoles_Repositories.GetList(p => p.UserId == UserId);
|
||||
_roleIds = userRoles.Select(ur => ur.RoleId);
|
||||
}
|
||||
menuList = (await HttpClient.GetFromJsonAsync<MenuDataItem[]>("data/menu.json")).ToList().Where(p => p.Key != "setting").ToList();
|
||||
_menuKeys = _userModel.MenuRole?.Split(",");
|
||||
@@ -52,6 +65,21 @@ namespace AntSK.Pages.Setting.User
|
||||
}
|
||||
_userModel.Password = PasswordUtil.HashPassword(_userModel.Password);
|
||||
_users_Repositories.Insert(_userModel);
|
||||
|
||||
// 添加用户角色关联
|
||||
if (_roleIds != null)
|
||||
{
|
||||
foreach (var roleId in _roleIds)
|
||||
{
|
||||
_userRoles_Repositories.Insert(new UserRoles
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
UserId = _userModel.Id,
|
||||
RoleId = roleId,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -61,6 +89,28 @@ namespace AntSK.Pages.Setting.User
|
||||
_userModel.Password = PasswordUtil.HashPassword(_userModel.Password);
|
||||
}
|
||||
_users_Repositories.Update(_userModel);
|
||||
|
||||
// 先删除旧的用户角色关联
|
||||
var oldUserRoles = _userRoles_Repositories.GetList(p => p.UserId == UserId);
|
||||
foreach (var ur in oldUserRoles)
|
||||
{
|
||||
_userRoles_Repositories.Delete(ur.Id);
|
||||
}
|
||||
|
||||
// 添加新的用户角色关联
|
||||
if (_roleIds != null)
|
||||
{
|
||||
foreach (var roleId in _roleIds)
|
||||
{
|
||||
_userRoles_Repositories.Insert(new UserRoles
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
UserId = _userModel.Id,
|
||||
RoleId = roleId,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Back();
|
||||
|
||||
@@ -123,5 +123,15 @@ app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
|
||||
|
||||
//延迟3秒后打开浏览器 localhost:5000
|
||||
//Task.Run(async () =>
|
||||
//{
|
||||
// await Task.Delay(3000);
|
||||
// System.Diagnostics.Process.Start("explorer.exe", "http://localhost:5000");
|
||||
//});
|
||||
app.Run();
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# AntSK
|
||||
@@ -10,6 +10,10 @@ namespace AntSK.Services.Auth
|
||||
{
|
||||
public class AntSKAuthProvider(
|
||||
IUsers_Repositories _users_Repositories,
|
||||
IUserRoles_Repositories _userRoles_Repositories,
|
||||
IRoles_Repositories _roles_Repositories,
|
||||
IRolePermissions_Repositories _rolePermissions_Repositories,
|
||||
IPermissions_Repositories _permissions_Repositories,
|
||||
ProtectedSessionStorage _protectedSessionStore
|
||||
) : AuthenticationStateProvider
|
||||
{
|
||||
@@ -29,13 +33,18 @@ namespace AntSK.Services.Auth
|
||||
new Claim(ClaimTypes.Role, AdminRole)
|
||||
};
|
||||
identity = new ClaimsIdentity(claims, AdminRole);
|
||||
await _protectedSessionStore.SetAsync("UserSession", new UserSession() { UserName = username, Role = AdminRole });
|
||||
await _protectedSessionStore.SetAsync("UserSession", new UserSession()
|
||||
{
|
||||
UserName = username,
|
||||
Role = AdminRole,
|
||||
Roles = new List<string> { AdminRole },
|
||||
Permissions = new List<string>()
|
||||
});
|
||||
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
string UserRole = "AntSKUser";
|
||||
if (user.IsNull())
|
||||
{
|
||||
return false;
|
||||
@@ -44,13 +53,42 @@ namespace AntSK.Services.Auth
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取用户的角色和权限
|
||||
var userRoles = _userRoles_Repositories.GetList(p => p.UserId == user.Id);
|
||||
var roleIds = userRoles.Select(ur => ur.RoleId).ToList();
|
||||
var roles = _roles_Repositories.GetList(r => roleIds.Contains(r.Id) && r.IsEnabled);
|
||||
|
||||
// 获取角色的权限
|
||||
var rolePermissions = _rolePermissions_Repositories.GetList(rp => roleIds.Contains(rp.RoleId));
|
||||
var permissionIds = rolePermissions.Select(rp => rp.PermissionId).Distinct().ToList();
|
||||
var permissions = _permissions_Repositories.GetList(p => permissionIds.Contains(p.Id));
|
||||
|
||||
// 如果没有角色,使用默认角色
|
||||
string defaultRole = "AntSKUser";
|
||||
var roleList = roles.Any() ? roles.Select(r => r.Code).ToList() : new List<string> { defaultRole };
|
||||
var permissionList = permissions.Select(p => p.Code).ToList();
|
||||
|
||||
// 用户认证成功,创建用户的ClaimsIdentity
|
||||
var claims = new[] {
|
||||
new Claim(ClaimTypes.Name, username),
|
||||
new Claim(ClaimTypes.Role, UserRole)
|
||||
};
|
||||
identity = new ClaimsIdentity(claims, UserRole);
|
||||
await _protectedSessionStore.SetAsync("UserSession", new UserSession() { UserName = username, Role = UserRole });
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Name, username)
|
||||
};
|
||||
|
||||
// 添加所有角色到Claims
|
||||
foreach (var role in roleList)
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, role));
|
||||
}
|
||||
|
||||
identity = new ClaimsIdentity(claims, roleList.FirstOrDefault() ?? defaultRole);
|
||||
await _protectedSessionStore.SetAsync("UserSession", new UserSession()
|
||||
{
|
||||
UserName = username,
|
||||
Role = roleList.FirstOrDefault() ?? defaultRole,
|
||||
Roles = roleList,
|
||||
Permissions = permissionList
|
||||
});
|
||||
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
|
||||
return true;
|
||||
}
|
||||
@@ -68,9 +106,25 @@ namespace AntSK.Services.Auth
|
||||
var userSession = userSessionStorageResult.Success ? userSessionStorageResult.Value : null;
|
||||
if (userSession.IsNotNull())
|
||||
{
|
||||
var claims = new[] {
|
||||
new Claim(ClaimTypes.Name, userSession.UserName),
|
||||
new Claim( ClaimTypes.Role, userSession.Role) };
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Name, userSession.UserName)
|
||||
};
|
||||
|
||||
// 添加所有角色到Claims
|
||||
if (userSession.Roles != null && userSession.Roles.Any())
|
||||
{
|
||||
foreach (var role in userSession.Roles)
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, role));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 向后兼容,使用单个Role
|
||||
claims.Add(new Claim(ClaimTypes.Role, userSession.Role));
|
||||
}
|
||||
|
||||
identity = new ClaimsIdentity(claims, userSession.Role);
|
||||
}
|
||||
var user = new ClaimsPrincipal(identity);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"urls": "http://*:5000",
|
||||
"ProSettings": {
|
||||
"NavTheme": "light",
|
||||
"Layout": "side",
|
||||
"Layout": "top",
|
||||
"ContentWidth": "Fluid",
|
||||
"FixedHeader": false,
|
||||
"FixSiderbar": true,
|
||||
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"Login": {
|
||||
"User": "admin",
|
||||
"Password": "xuzeyu"
|
||||
"Password": "admin"
|
||||
},
|
||||
"BackgroundTaskBroker": {
|
||||
"ImportKMSTask": {
|
||||
|
||||
@@ -59,6 +59,11 @@
|
||||
"name": "用户管理",
|
||||
"key": "setting.user"
|
||||
},
|
||||
{
|
||||
"path": "/setting/rolelist",
|
||||
"name": "角色管理",
|
||||
"key": "setting.role"
|
||||
},
|
||||
{
|
||||
"path": "/setting/chathistory",
|
||||
"name": "聊天记录",
|
||||
@@ -71,18 +76,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "https://antsk.cn",
|
||||
"name": "官方文档",
|
||||
"key": "antskdoc",
|
||||
"icon": "question-circle"
|
||||
},
|
||||
{
|
||||
"path": "https://api.antsk.cn/",
|
||||
"name": "GPT代理接口",
|
||||
"key": "antskapi",
|
||||
"icon": "bulb"
|
||||
},
|
||||
{
|
||||
"path": "https://antsk.cn",
|
||||
"name": "官网",
|
||||
"key": "antskdoc",
|
||||
"icon": "question-circle"
|
||||
},
|
||||
{
|
||||
"path": "https://www.bilibili.com/video/BV1vK421b7NF",
|
||||
"name": "教程视频",
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
<NewtonsoftVersion>13.0.3</NewtonsoftVersion>
|
||||
<RestSharpVersion>112.1.0</RestSharpVersion>
|
||||
<SKVersion>1.17.2</SKVersion>
|
||||
<NetVersion>9.0.0</NetVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<AssemblyName>AntSK.BackgroundTask</AssemblyName>
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<LangVersion>Preview</LangVersion>
|
||||
|
||||
Reference in New Issue
Block a user