From eff7854005abbb6cc42d7d2cfb300355f7c33f26 Mon Sep 17 00:00:00 2001
From: token <239573049@qq.com>
Date: Sun, 24 Mar 2024 23:29:30 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=A3=9E=E4=B9=A6=E6=9C=BA?=
=?UTF-8?q?=E5=99=A8=E4=BA=BA=E6=8E=A5=E5=85=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ChatApplication/Dto/ChatApplicationDto.cs | 35 +
.../Dto/UpdateChatApplicationInput.cs | 5 +
.../FastWiki.Service.Contracts.csproj | 4 +
.../Feishu/Dto/FeiShuChatResultDto.cs | 15 +
.../Feishu/Dto/FeiShuChatToken.cs | 14 +
.../Feishu/Dto/FeishuChatEvent.cs | 7 +
.../Feishu/Dto/FeishuChatHeader.cs | 11 +
.../Feishu/Dto/FeishuChatId.cs | 8 +
.../Feishu/Dto/FeishuChatInput.cs | 17 +
.../Feishu/Dto/FeishuChatMention.cs | 9 +
.../Feishu/Dto/FeishuChatMessage.cs | 16 +
.../Feishu/Dto/FeishuChatSendMessageInput.cs | 10 +
.../Feishu/Dto/FeishuChatSender.cs | 8 +
.../Feishu/Dto/FeishuChatSenderId.cs | 8 +
.../Feishu/Dto/FeishuChatUserInput.cs | 6 +
.../DataAccess/WikiDbContext.cs | 11 +-
.../Aggregates/ChatApplication.cs | 58 +-
.../20240324144453_AddExtend.Designer.cs | 642 +++
.../Migrations/20240324144453_AddExtend.cs | 71 +
.../Migrations/WikiDbContextModelSnapshot.cs | 50 +-
src/Service/FastWiki.Service/Program.cs | 6 +
.../FastWiki.Service/Service/FeishuService.cs | 497 ++
.../FastWiki.Service/Service/OpenAIService.cs | 5 +-
web/src/models/index.d.ts | 1 +
.../app-detail/feautres/AppDetailInfo.tsx | 76 +-
.../feautres/ReleaseApplication.tsx | 20 +-
web/stats.html | 4842 +++++++++++++++++
27 files changed, 6413 insertions(+), 39 deletions(-)
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeiShuChatResultDto.cs
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeiShuChatToken.cs
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatEvent.cs
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatHeader.cs
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatId.cs
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatInput.cs
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatMention.cs
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatMessage.cs
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSendMessageInput.cs
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSender.cs
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSenderId.cs
create mode 100644 src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatUserInput.cs
create mode 100644 src/Service/FastWiki.Service/Migrations/20240324144453_AddExtend.Designer.cs
create mode 100644 src/Service/FastWiki.Service/Migrations/20240324144453_AddExtend.cs
create mode 100644 src/Service/FastWiki.Service/Service/FeishuService.cs
create mode 100644 web/stats.html
diff --git a/src/Contracts/FastWiki.Service.Contracts/ChatApplication/Dto/ChatApplicationDto.cs b/src/Contracts/FastWiki.Service.Contracts/ChatApplication/Dto/ChatApplicationDto.cs
index 2d1b0b2f..3b7271c4 100644
--- a/src/Contracts/FastWiki.Service.Contracts/ChatApplication/Dto/ChatApplicationDto.cs
+++ b/src/Contracts/FastWiki.Service.Contracts/ChatApplication/Dto/ChatApplicationDto.cs
@@ -72,4 +72,39 @@ public class ChatApplicationDto
/// AI模型类型
///
public string ChatType { get; set; }
+
+ ///
+ /// 扩展字段
+ ///
+ public Dictionary Extend { get; set; } = new();
+
+ public void SetFeishuAppId(string appId)
+ {
+ Extend["FeishuAppId"] = appId;
+ }
+
+ public string? GetFeishuAppId()
+ {
+ return Extend.TryGetValue("FeishuAppId", out var appId) ? appId : null;
+ }
+
+ public void SetFeishuAppSecret(string appSecret)
+ {
+ Extend["FeishuAppSecret"] = appSecret;
+ }
+
+ public string? GetFeishuAppSecret()
+ {
+ return Extend.TryGetValue("FeishuAppSecret", out var appSecret) ? appSecret : null;
+ }
+
+ public string SetBotName(string botName)
+ {
+ return Extend["BotName"] = botName;
+ }
+
+ public string? GetBotName()
+ {
+ return Extend.TryGetValue("BotName", out var botName) ? botName : null;
+ }
}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/ChatApplication/Dto/UpdateChatApplicationInput.cs b/src/Contracts/FastWiki.Service.Contracts/ChatApplication/Dto/UpdateChatApplicationInput.cs
index 4dd77764..1a5bd5f0 100644
--- a/src/Contracts/FastWiki.Service.Contracts/ChatApplication/Dto/UpdateChatApplicationInput.cs
+++ b/src/Contracts/FastWiki.Service.Contracts/ChatApplication/Dto/UpdateChatApplicationInput.cs
@@ -55,4 +55,9 @@ public class UpdateChatApplicationInput
/// 关联的知识库
///
public List WikiIds { get; set; }
+
+ ///
+ /// 扩展字段
+ ///
+ public Dictionary Extend { get; set; } = new();
}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/FastWiki.Service.Contracts.csproj b/src/Contracts/FastWiki.Service.Contracts/FastWiki.Service.Contracts.csproj
index c47b419e..ffc23a71 100644
--- a/src/Contracts/FastWiki.Service.Contracts/FastWiki.Service.Contracts.csproj
+++ b/src/Contracts/FastWiki.Service.Contracts/FastWiki.Service.Contracts.csproj
@@ -10,4 +10,8 @@
+
+
+
+
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeiShuChatResultDto.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeiShuChatResultDto.cs
new file mode 100644
index 00000000..22c773b7
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeiShuChatResultDto.cs
@@ -0,0 +1,15 @@
+namespace FastWiki.Service.Contracts.Model.Dto;
+
+public class FeiShuChatResult : FeiShuChatResultBase
+{
+ public object data { get; set; }
+}
+public class FeiShuChatResult : FeiShuChatResultBase
+{
+ public T data { get; set; }
+}
+public abstract class FeiShuChatResultBase
+{
+ public int code { get; set; }
+ public string msg { get; set; }
+}
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeiShuChatToken.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeiShuChatToken.cs
new file mode 100644
index 00000000..3ce2e538
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeiShuChatToken.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+
+namespace FastWiki.Service.Contracts.Feishu.Dto;
+
+public sealed class FeiShuChatToken
+{
+ [JsonPropertyName("tenant_access_token")]
+ public string TenantAccessToken { get; set; }
+
+ [JsonPropertyName("user_access_token")]
+ public string UserAccessToken { get; set; }
+
+ [JsonPropertyName("expire")] public int Expire { get; set; }
+}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatEvent.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatEvent.cs
new file mode 100644
index 00000000..51621f1c
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatEvent.cs
@@ -0,0 +1,7 @@
+namespace FastWiki.Service.Contracts.Model.Dto;
+
+public sealed class FeishuChatEvent
+{
+ public FeishuChatSender sender { get; set; }
+ public FeishuChatMessage message { get; set; }
+}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatHeader.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatHeader.cs
new file mode 100644
index 00000000..49267515
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatHeader.cs
@@ -0,0 +1,11 @@
+namespace FastWiki.Service.Contracts.Model.Dto;
+
+public class FeishuChatHeader
+{
+ public string event_id { get; set; }
+ public string event_type { get; set; }
+ public string create_time { get; set; }
+ public string token { get; set; }
+ public string app_id { get; set; }
+ public string tenant_key { get; set; }
+}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatId.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatId.cs
new file mode 100644
index 00000000..401a3aa1
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatId.cs
@@ -0,0 +1,8 @@
+namespace FastWiki.Service.Contracts.Model.Dto;
+
+public sealed class FeishuChatId
+{
+ public string union_id { get; set; }
+ public string user_id { get; set; }
+ public string open_id { get; set; }
+}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatInput.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatInput.cs
new file mode 100644
index 00000000..c2cf3497
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatInput.cs
@@ -0,0 +1,17 @@
+using System.Text.Json.Serialization;
+
+namespace FastWiki.Service.Contracts.Model.Dto;
+
+public sealed class FeishuChatInput
+{
+ public string schema { get; set; }
+ public FeishuChatHeader header { get; set; }
+
+ [JsonPropertyName("event")]
+ public FeishuChatEvent _event { get; set; }
+
+ public string? challenge { get; set; }
+ public string? encrypt { get; set; }
+
+ public string? type { get; set; }
+}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatMention.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatMention.cs
new file mode 100644
index 00000000..102a30c5
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatMention.cs
@@ -0,0 +1,9 @@
+namespace FastWiki.Service.Contracts.Model.Dto;
+
+public sealed class FeishuChatMention
+{
+ public string key { get; set; }
+ public FeishuChatId id { get; set; }
+ public string name { get; set; }
+ public string tenant_key { get; set; }
+}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatMessage.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatMessage.cs
new file mode 100644
index 00000000..045a3bb2
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatMessage.cs
@@ -0,0 +1,16 @@
+namespace FastWiki.Service.Contracts.Model.Dto;
+
+public sealed class FeishuChatMessage
+{
+ public string message_id { get; set; }
+ public string root_id { get; set; }
+ public string parent_id { get; set; }
+ public string create_time { get; set; }
+ public string update_time { get; set; }
+ public string chat_id { get; set; }
+ public string chat_type { get; set; }
+ public string message_type { get; set; }
+ public string content { get; set; }
+ public FeishuChatMention[] mentions { get; set; }
+ public string user_agent { get; set; }
+}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSendMessageInput.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSendMessageInput.cs
new file mode 100644
index 00000000..bcdcdf87
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSendMessageInput.cs
@@ -0,0 +1,10 @@
+namespace FastWiki.Service.Contracts.Model.Dto;
+
+public class FeishuChatSendMessageInput(string content, string msg_type, string receive_id)
+{
+ public string content { get; set; } = content;
+
+ public string msg_type { get; set; } = msg_type;
+
+ public string receive_id { get; set; } = receive_id;
+}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSender.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSender.cs
new file mode 100644
index 00000000..bc44a957
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSender.cs
@@ -0,0 +1,8 @@
+namespace FastWiki.Service.Contracts.Model.Dto;
+
+public class FeishuChatSender
+{
+ public FeishuChatSenderId sender_id { get; set; }
+ public string sender_type { get; set; }
+ public string tenant_key { get; set; }
+}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSenderId.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSenderId.cs
new file mode 100644
index 00000000..5feb61f4
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatSenderId.cs
@@ -0,0 +1,8 @@
+namespace FastWiki.Service.Contracts.Model.Dto;
+
+public class FeishuChatSenderId
+{
+ public string union_id { get; set; }
+ public string user_id { get; set; }
+ public string open_id { get; set; }
+}
\ No newline at end of file
diff --git a/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatUserInput.cs b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatUserInput.cs
new file mode 100644
index 00000000..433cddd9
--- /dev/null
+++ b/src/Contracts/FastWiki.Service.Contracts/Feishu/Dto/FeishuChatUserInput.cs
@@ -0,0 +1,6 @@
+namespace FastWiki.Service.Contracts.Model.Dto;
+
+public sealed class FeishuChatUserInput
+{
+ public string text { get; set; }
+}
\ No newline at end of file
diff --git a/src/Service/FastWiki.Service/DataAccess/WikiDbContext.cs b/src/Service/FastWiki.Service/DataAccess/WikiDbContext.cs
index d426578d..1a59049a 100644
--- a/src/Service/FastWiki.Service/DataAccess/WikiDbContext.cs
+++ b/src/Service/FastWiki.Service/DataAccess/WikiDbContext.cs
@@ -96,6 +96,13 @@ private static void ConfigEntities(ModelBuilder modelBuilder)
.HasConversion(
v => JsonSerializer.Serialize(v, new JsonSerializerOptions()),
v => JsonSerializer.Deserialize>(v, new JsonSerializerOptions()));
+
+ entity.Property(x => x.Extend)
+ .HasConversion(
+ v => JsonSerializer.Serialize(v, new JsonSerializerOptions()),
+ v => v.IsNullOrEmpty()
+ ? new Dictionary()
+ : JsonSerializer.Deserialize>(v, new JsonSerializerOptions()));
});
modelBuilder.Entity(entity =>
@@ -169,9 +176,9 @@ private static void ConfigEntities(ModelBuilder modelBuilder)
var user = new User("admin", "admin", "Aa123456",
"https://blog-simple.oss-cn-shenzhen.aliyuncs.com/Avatar.jpg", "239573049@qq.com", "13049809673", false);
-
+
user.SetAdminRole();
-
+
// 默认初始账号
modelBuilder.Entity().HasData(user);
diff --git a/src/Service/FastWiki.Service/Domain/ChatApplications/Aggregates/ChatApplication.cs b/src/Service/FastWiki.Service/Domain/ChatApplications/Aggregates/ChatApplication.cs
index b968e7a1..7bda9746 100644
--- a/src/Service/FastWiki.Service/Domain/ChatApplications/Aggregates/ChatApplication.cs
+++ b/src/Service/FastWiki.Service/Domain/ChatApplications/Aggregates/ChatApplication.cs
@@ -12,34 +12,34 @@ protected ChatApplication()
public ChatApplication(string id) : base(id)
{
Opener =
-"""
-FastWiki本项目是一个高性能、基于最新技术栈的知识库系统,专为大规模信息检索和智能搜索设计。
- 利用微软Semantic Kernel进行深度学习和自然语言处理,结合.NET 8和`MasaBlazor`前端框架,后台采用`MasaFramework`,实现了一个高效、易用、可扩展的智能向量搜索平台。
- 我们的目标是提供一个能够理解和处理复杂查询的智能搜索解决方案,帮助用户快速准确地获取所需信息。采用Apache-2.0,您也可以完全商用不会有任何版权纠纷
-[Github](https://github.com/239573049/fast-wiki)
-[Gitee](https://gitee.com/hejiale010426/fast-wiki)
-[项目文档](https://docs.token-ai.cn/)
+ """
+ FastWiki本项目是一个高性能、基于最新技术栈的知识库系统,专为大规模信息检索和智能搜索设计。
+ 利用微软Semantic Kernel进行深度学习和自然语言处理,结合.NET 8和`MasaBlazor`前端框架,后台采用`MasaFramework`,实现了一个高效、易用、可扩展的智能向量搜索平台。
+ 我们的目标是提供一个能够理解和处理复杂查询的智能搜索解决方案,帮助用户快速准确地获取所需信息。采用Apache-2.0,您也可以完全商用不会有任何版权纠纷
+ [Github](https://github.com/239573049/fast-wiki)
+ [Gitee](https://gitee.com/hejiale010426/fast-wiki)
+ [项目文档](https://docs.token-ai.cn/)
-当前AI提供了Avalonia中文文档知识库功能!
-""";
+ 当前AI提供了Avalonia中文文档知识库功能!
+ """;
Template =
-""""
-使用 标记中的内容作为你的知识:
-
- {{quote}}
-
-
-回答要求:
-- 如果你不清楚答案,你需要澄清。
-- 避免提及你是从 获取的知识。
-- 保持答案与 中描述的一致。
-- 使用 Markdown 语法优化回答格式。
-- 使用与问题相同的语言回答。
-- 如果 Markdown中有图片则正常显示。
-
-问题:"""{{question}}"""
-"""";
+ """"
+ 使用 标记中的内容作为你的知识:
+
+ {{quote}}
+
+
+ 回答要求:
+ - 如果你不清楚答案,你需要澄清。
+ - 避免提及你是从 获取的知识。
+ - 保持答案与 中描述的一致。
+ - 使用 Markdown 语法优化回答格式。
+ - 使用与问题相同的语言回答。
+ - 如果 Markdown中有图片则正常显示。
+
+ 问题:"""{{question}}"""
+ """";
}
public string Name { get; set; }
@@ -80,7 +80,7 @@ 利用微软Semantic Kernel进行深度学习和自然语言处理,结合.NET
/// 关联的知识库
///
public List WikiIds { get; set; } = new();
-
+
///
/// 引用上限
///
@@ -106,4 +106,10 @@ 利用微软Semantic Kernel进行深度学习和自然语言处理,结合.NET
/// AI模型类型
///
public string ChatType { get; set; }
+
+ ///
+ /// 扩展字段
+ ///
+ public Dictionary Extend { get; set; } = new();
+
}
\ No newline at end of file
diff --git a/src/Service/FastWiki.Service/Migrations/20240324144453_AddExtend.Designer.cs b/src/Service/FastWiki.Service/Migrations/20240324144453_AddExtend.Designer.cs
new file mode 100644
index 00000000..66ac0494
--- /dev/null
+++ b/src/Service/FastWiki.Service/Migrations/20240324144453_AddExtend.Designer.cs
@@ -0,0 +1,642 @@
+//
+using System;
+using FastWiki.Service.DataAccess;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace FastWiki.Service.Migrations
+{
+ [DbContext(typeof(WikiDbContext))]
+ [Migration("20240324144453_AddExtend")]
+ partial class AddExtend
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("FastWiki.Service.Domain.ChatApplications.Aggregates.ChatApplication", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ChatModel")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ChatType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Creator")
+ .HasColumnType("uuid");
+
+ b.Property("Extend")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean");
+
+ b.Property("MaxResponseToken")
+ .HasColumnType("integer");
+
+ b.Property("ModificationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Modifier")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("NoReplyFoundTemplate")
+ .HasColumnType("text");
+
+ b.Property("Opener")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Parameter")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Prompt")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ReferenceUpperLimit")
+ .HasColumnType("integer");
+
+ b.Property("Relevancy")
+ .HasColumnType("double precision");
+
+ b.Property("ShowSourceFile")
+ .HasColumnType("boolean");
+
+ b.Property("Temperature")
+ .HasColumnType("double precision");
+
+ b.Property("Template")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("WikiIds")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name");
+
+ b.ToTable("wiki-chat-application", (string)null);
+ });
+
+ modelBuilder.Entity("FastWiki.Service.Domain.ChatApplications.Aggregates.ChatDialog", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ApplicationId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ChatId")
+ .HasColumnType("text");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Creator")
+ .HasColumnType("uuid");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean");
+
+ b.Property("ModificationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Modifier")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Type")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChatId");
+
+ b.ToTable("wiki-chat-dialog", (string)null);
+ });
+
+ modelBuilder.Entity("FastWiki.Service.Domain.ChatApplications.Aggregates.ChatDialogHistory", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ChatDialogId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasMaxLength(-1)
+ .HasColumnType("text");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Creator")
+ .HasColumnType("uuid");
+
+ b.Property("Current")
+ .HasColumnType("boolean");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean");
+
+ b.Property("ModificationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Modifier")
+ .HasColumnType("uuid");
+
+ b.Property("ReferenceFile")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TokenConsumption")
+ .HasColumnType("integer");
+
+ b.Property("Type")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChatDialogId");
+
+ b.HasIndex("Creator");
+
+ b.ToTable("wiki-chat-dialog-history", (string)null);
+ });
+
+ modelBuilder.Entity("FastWiki.Service.Domain.ChatApplications.Aggregates.ChatShare", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("APIKey")
+ .HasColumnType("text");
+
+ b.Property("AvailableQuantity")
+ .HasColumnType("integer");
+
+ b.Property("AvailableToken")
+ .HasColumnType("bigint");
+
+ b.Property("ChatApplicationId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Creator")
+ .HasColumnType("uuid");
+
+ b.Property("Expires")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("ModificationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Modifier")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UsedToken")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChatApplicationId");
+
+ b.ToTable("wiki-chat-share", (string)null);
+ });
+
+ modelBuilder.Entity("FastWiki.Service.Domain.Model.Aggregates.FastModel", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ApiKey")
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Creator")
+ .HasColumnType("uuid");
+
+ b.Property("Description")
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)");
+
+ b.Property("Enable")
+ .HasColumnType("boolean");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean");
+
+ b.Property("Models")
+ .IsRequired()
+ .HasMaxLength(-1)
+ .HasColumnType("text");
+
+ b.Property("ModificationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Modifier")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(30)
+ .HasColumnType("character varying(30)");
+
+ b.Property("Order")
+ .HasColumnType("integer");
+
+ b.Property("TestTime")
+ .HasColumnType("bigint");
+
+ b.Property("Type")
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("Url")
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)");
+
+ b.Property("UsedQuota")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name");
+
+ b.HasIndex("Type");
+
+ b.ToTable("wiki-fast-models", (string)null);
+
+ b.HasData(
+ new
+ {
+ Id = "adf1fe462e7a4f7a93ee6951ca31d63c",
+ ApiKey = "",
+ CreationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7163),
+ Description = "OpenAI",
+ Enable = true,
+ IsDeleted = false,
+ Models = "[\"gpt-3.5-turbo\",\"gpt-3.5-turbo-0125\",\"gpt-3.5-turbo-1106\",\"gpt-3.5-turbo-16k\",\"gpt-3.5-turbo-0613\",\"gpt-3.5-turbo-16k-0613\",\"gpt-4-0125-preview\",\"gpt-4-turbo-preview\",\"gpt-4-1106-preview\",\"gpt-4-vision-preview\",\"gpt-4-1106-vision-preview\",\"gpt-4\",\"gpt-4-0613\",\"gpt-4-32k\",\"gpt-4-32k-0613\"]",
+ ModificationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7165),
+ Name = "OpenAI",
+ Order = 1,
+ Type = "OpenAI",
+ Url = "https://api.openai.com/",
+ UsedQuota = 0L
+ },
+ new
+ {
+ Id = "e124eb014a944dd79f3e4823461086d2",
+ ApiKey = "",
+ CreationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7196),
+ Description = "星火大模型",
+ Enable = true,
+ IsDeleted = false,
+ Models = "[\"SparkDesk-v3.5\",\"SparkDesk-v3.1\",\"SparkDesk-v1.5\",\"SparkDesk-v2.1\"]",
+ ModificationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7197),
+ Name = "SparkDesk",
+ Order = 1,
+ Type = "SparkDesk",
+ Url = "",
+ UsedQuota = 0L
+ });
+ });
+
+ modelBuilder.Entity("FastWiki.Service.Domain.Model.Aggregates.ModelLogger", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ApiKey")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ApplicationId")
+ .HasColumnType("text");
+
+ b.Property("ComplementCount")
+ .HasColumnType("integer");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("FastModelId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Model")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("PromptCount")
+ .HasColumnType("integer");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApiKey");
+
+ b.HasIndex("ApplicationId");
+
+ b.HasIndex("CreationTime");
+
+ b.HasIndex("FastModelId");
+
+ b.HasIndex("Type");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("wiki-model-logger", (string)null);
+ });
+
+ modelBuilder.Entity("FastWiki.Service.Domain.Storage.Aggregates.FileStorage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Creator")
+ .HasColumnType("uuid");
+
+ b.Property("FullName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsCompression")
+ .HasColumnType("boolean");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean");
+
+ b.Property("ModificationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Modifier")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)");
+
+ b.Property("Size")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.ToTable("wiki-file-storages", (string)null);
+ });
+
+ modelBuilder.Entity("FastWiki.Service.Domain.Users.Aggregates.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Account")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Avatar")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Creator")
+ .HasColumnType("uuid");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean");
+
+ b.Property("IsDisable")
+ .HasColumnType("boolean");
+
+ b.Property("ModificationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Modifier")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Role")
+ .HasColumnType("integer");
+
+ b.Property("Salt")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("wiki-users", (string)null);
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("f0b2b97a-1f5f-4741-8110-7dc382b52025"),
+ Account = "admin",
+ Avatar = "https://blog-simple.oss-cn-shenzhen.aliyuncs.com/Avatar.jpg",
+ CreationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(5388),
+ Email = "239573049@qq.com",
+ IsDeleted = false,
+ IsDisable = false,
+ ModificationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(5390),
+ Name = "admin",
+ Password = "2a2e3a0f18f38c8ba99bee8e499ed572",
+ Phone = "13049809673",
+ Role = 2,
+ Salt = "124dc3dc26674fad9ea6adea9eb231b6"
+ });
+ });
+
+ modelBuilder.Entity("FastWiki.Service.Domain.Wikis.Aggregates.Wiki", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Creator")
+ .HasColumnType("uuid");
+
+ b.Property("EmbeddingModel")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Icon")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean");
+
+ b.Property("Model")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ModificationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Modifier")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name");
+
+ b.ToTable("wiki-wikis", (string)null);
+ });
+
+ modelBuilder.Entity("FastWiki.Service.Domain.Wikis.Aggregates.WikiDetail", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Creator")
+ .HasColumnType("bigint");
+
+ b.Property("DataCount")
+ .HasColumnType("integer");
+
+ b.Property("FileId")
+ .HasColumnType("bigint");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("ModificationTime")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("Modifier")
+ .HasColumnType("bigint");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)");
+
+ b.Property("State")
+ .HasColumnType("integer");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("WikiId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.ToTable("wiki-wiki-details", (string)null);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Service/FastWiki.Service/Migrations/20240324144453_AddExtend.cs b/src/Service/FastWiki.Service/Migrations/20240324144453_AddExtend.cs
new file mode 100644
index 00000000..ca4c31a8
--- /dev/null
+++ b/src/Service/FastWiki.Service/Migrations/20240324144453_AddExtend.cs
@@ -0,0 +1,71 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
+
+namespace FastWiki.Service.Migrations
+{
+ ///
+ public partial class AddExtend : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DeleteData(
+ table: "wiki-users",
+ keyColumn: "Id",
+ keyValue: new Guid("289b14a4-a5e4-409f-97ec-049313593610"));
+
+ migrationBuilder.AddColumn(
+ name: "Extend",
+ table: "wiki-chat-application",
+ type: "text",
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.InsertData(
+ table: "wiki-fast-models",
+ columns: new[] { "Id", "ApiKey", "CreationTime", "Creator", "Description", "Enable", "IsDeleted", "Models", "ModificationTime", "Modifier", "Name", "Order", "TestTime", "Type", "Url", "UsedQuota" },
+ values: new object[,]
+ {
+ { "adf1fe462e7a4f7a93ee6951ca31d63c", "", new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7163), null, "OpenAI", true, false, "[\"gpt-3.5-turbo\",\"gpt-3.5-turbo-0125\",\"gpt-3.5-turbo-1106\",\"gpt-3.5-turbo-16k\",\"gpt-3.5-turbo-0613\",\"gpt-3.5-turbo-16k-0613\",\"gpt-4-0125-preview\",\"gpt-4-turbo-preview\",\"gpt-4-1106-preview\",\"gpt-4-vision-preview\",\"gpt-4-1106-vision-preview\",\"gpt-4\",\"gpt-4-0613\",\"gpt-4-32k\",\"gpt-4-32k-0613\"]", new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7165), null, "OpenAI", 1, null, "OpenAI", "https://api.openai.com/", 0L },
+ { "e124eb014a944dd79f3e4823461086d2", "", new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7196), null, "星火大模型", true, false, "[\"SparkDesk-v3.5\",\"SparkDesk-v3.1\",\"SparkDesk-v1.5\",\"SparkDesk-v2.1\"]", new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7197), null, "SparkDesk", 1, null, "SparkDesk", "", 0L }
+ });
+
+ migrationBuilder.InsertData(
+ table: "wiki-users",
+ columns: new[] { "Id", "Account", "Avatar", "CreationTime", "Creator", "Email", "IsDeleted", "IsDisable", "ModificationTime", "Modifier", "Name", "Password", "Phone", "Role", "Salt" },
+ values: new object[] { new Guid("f0b2b97a-1f5f-4741-8110-7dc382b52025"), "admin", "https://blog-simple.oss-cn-shenzhen.aliyuncs.com/Avatar.jpg", new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(5388), null, "239573049@qq.com", false, false, new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(5390), null, "admin", "2a2e3a0f18f38c8ba99bee8e499ed572", "13049809673", 2, "124dc3dc26674fad9ea6adea9eb231b6" });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DeleteData(
+ table: "wiki-fast-models",
+ keyColumn: "Id",
+ keyValue: "adf1fe462e7a4f7a93ee6951ca31d63c");
+
+ migrationBuilder.DeleteData(
+ table: "wiki-fast-models",
+ keyColumn: "Id",
+ keyValue: "e124eb014a944dd79f3e4823461086d2");
+
+ migrationBuilder.DeleteData(
+ table: "wiki-users",
+ keyColumn: "Id",
+ keyValue: new Guid("f0b2b97a-1f5f-4741-8110-7dc382b52025"));
+
+ migrationBuilder.DropColumn(
+ name: "Extend",
+ table: "wiki-chat-application");
+
+ migrationBuilder.InsertData(
+ table: "wiki-users",
+ columns: new[] { "Id", "Account", "Avatar", "CreationTime", "Creator", "Email", "IsDeleted", "IsDisable", "ModificationTime", "Modifier", "Name", "Password", "Phone", "Role", "Salt" },
+ values: new object[] { new Guid("289b14a4-a5e4-409f-97ec-049313593610"), "admin", "https://blog-simple.oss-cn-shenzhen.aliyuncs.com/Avatar.jpg", new DateTime(2024, 3, 21, 14, 11, 35, 212, DateTimeKind.Utc).AddTicks(3429), null, "239573049@qq.com", false, false, new DateTime(2024, 3, 21, 14, 11, 35, 212, DateTimeKind.Utc).AddTicks(3431), null, "admin", "75746e8a1cb14fed92c6688de29bd450", "13049809673", 1, "10604d18cf8e4475a622859a84ed5077" });
+ }
+ }
+}
diff --git a/src/Service/FastWiki.Service/Migrations/WikiDbContextModelSnapshot.cs b/src/Service/FastWiki.Service/Migrations/WikiDbContextModelSnapshot.cs
index fd73e6de..4bedf668 100644
--- a/src/Service/FastWiki.Service/Migrations/WikiDbContextModelSnapshot.cs
+++ b/src/Service/FastWiki.Service/Migrations/WikiDbContextModelSnapshot.cs
@@ -41,6 +41,10 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("Creator")
.HasColumnType("uuid");
+ b.Property("Extend")
+ .IsRequired()
+ .HasColumnType("text");
+
b.Property("IsDeleted")
.HasColumnType("boolean");
@@ -307,6 +311,40 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("Type");
b.ToTable("wiki-fast-models", (string)null);
+
+ b.HasData(
+ new
+ {
+ Id = "adf1fe462e7a4f7a93ee6951ca31d63c",
+ ApiKey = "",
+ CreationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7163),
+ Description = "OpenAI",
+ Enable = true,
+ IsDeleted = false,
+ Models = "[\"gpt-3.5-turbo\",\"gpt-3.5-turbo-0125\",\"gpt-3.5-turbo-1106\",\"gpt-3.5-turbo-16k\",\"gpt-3.5-turbo-0613\",\"gpt-3.5-turbo-16k-0613\",\"gpt-4-0125-preview\",\"gpt-4-turbo-preview\",\"gpt-4-1106-preview\",\"gpt-4-vision-preview\",\"gpt-4-1106-vision-preview\",\"gpt-4\",\"gpt-4-0613\",\"gpt-4-32k\",\"gpt-4-32k-0613\"]",
+ ModificationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7165),
+ Name = "OpenAI",
+ Order = 1,
+ Type = "OpenAI",
+ Url = "https://api.openai.com/",
+ UsedQuota = 0L
+ },
+ new
+ {
+ Id = "e124eb014a944dd79f3e4823461086d2",
+ ApiKey = "",
+ CreationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7196),
+ Description = "星火大模型",
+ Enable = true,
+ IsDeleted = false,
+ Models = "[\"SparkDesk-v3.5\",\"SparkDesk-v3.1\",\"SparkDesk-v1.5\",\"SparkDesk-v2.1\"]",
+ ModificationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(7197),
+ Name = "SparkDesk",
+ Order = 1,
+ Type = "SparkDesk",
+ Url = "",
+ UsedQuota = 0L
+ });
});
modelBuilder.Entity("FastWiki.Service.Domain.Model.Aggregates.ModelLogger", b =>
@@ -481,19 +519,19 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasData(
new
{
- Id = new Guid("289b14a4-a5e4-409f-97ec-049313593610"),
+ Id = new Guid("f0b2b97a-1f5f-4741-8110-7dc382b52025"),
Account = "admin",
Avatar = "https://blog-simple.oss-cn-shenzhen.aliyuncs.com/Avatar.jpg",
- CreationTime = new DateTime(2024, 3, 21, 14, 11, 35, 212, DateTimeKind.Utc).AddTicks(3429),
+ CreationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(5388),
Email = "239573049@qq.com",
IsDeleted = false,
IsDisable = false,
- ModificationTime = new DateTime(2024, 3, 21, 14, 11, 35, 212, DateTimeKind.Utc).AddTicks(3431),
+ ModificationTime = new DateTime(2024, 3, 24, 14, 44, 52, 662, DateTimeKind.Utc).AddTicks(5390),
Name = "admin",
- Password = "75746e8a1cb14fed92c6688de29bd450",
+ Password = "2a2e3a0f18f38c8ba99bee8e499ed572",
Phone = "13049809673",
- Role = 1,
- Salt = "10604d18cf8e4475a622859a84ed5077"
+ Role = 2,
+ Salt = "124dc3dc26674fad9ea6adea9eb231b6"
});
});
diff --git a/src/Service/FastWiki.Service/Program.cs b/src/Service/FastWiki.Service/Program.cs
index aca75092..29d2808b 100644
--- a/src/Service/FastWiki.Service/Program.cs
+++ b/src/Service/FastWiki.Service/Program.cs
@@ -154,6 +154,12 @@
.WithDescription("OpenAI Completions")
.WithOpenApi();
+app.MapPost("/v1/feishu/completions/{id}", FeishuService.Completions)
+ .WithTags("Feishu")
+ .WithGroupName("Feishu")
+ .WithDescription("飞书对话接入处理")
+ .WithOpenApi();
+
if (app.Environment.IsDevelopment())
{
app.UseSwagger()
diff --git a/src/Service/FastWiki.Service/Service/FeishuService.cs b/src/Service/FastWiki.Service/Service/FeishuService.cs
new file mode 100644
index 00000000..bccc87fe
--- /dev/null
+++ b/src/Service/FastWiki.Service/Service/FeishuService.cs
@@ -0,0 +1,497 @@
+using System.Collections.Concurrent;
+using System.Net.Http.Headers;
+using AIDotNet.OpenAI;
+using FastWiki.Service.Application.Model.Commands;
+using FastWiki.Service.Application.Storage.Queries;
+using FastWiki.Service.Contracts.Model.Dto;
+using FastWiki.Service.Domain.Storage.Aggregates;
+using FastWiki.Service.Infrastructure;
+using FastWiki.Service.Infrastructure.Helper;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.SemanticKernel.ChatCompletion;
+using Microsoft.SemanticKernel.Connectors.OpenAI;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using FastWiki.Service.Contracts.Feishu.Dto;
+using TokenApi.Service.Exceptions;
+
+namespace FastWiki.Service.Service;
+
+public class FeishuService
+{
+ private static HttpClient httpClient = new();
+
+ public static JsonSerializerOptions JsonSerializerOptions = new()
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ };
+
+ public static async Task Completions(string id, HttpContext context, [FromBody] FeishuChatInput input)
+ {
+ var memoryCache = context.RequestServices.GetRequiredService();
+
+ if (!input.encrypt.IsNullOrWhiteSpace())
+ {
+ await context.Response.WriteAsJsonAsync(new
+ {
+ code = 1,
+ message = new
+ {
+ zh_CN = "你配置了 Encrypt Key,请关闭该功能。",
+ en_US = "You have open Encrypt Key Feature, please close it.",
+ }
+ });
+ return;
+ }
+
+
+ // 处理飞书开放平台的服务端校验
+ if (input.type == "url_verification")
+ {
+ await context.Response.WriteAsJsonAsync(new
+ {
+ input.challenge,
+ });
+ return;
+ }
+
+ // 处理飞书开放平台的事件回调
+ if (input.header.event_type == "im.message.receive_v1")
+ {
+ var eventId = input.header.event_id; // 事件id
+ var messageId = input._event.message.message_id; // 消息id
+ var chatId = input._event.message.chat_id; // 群聊id
+ var senderId = input._event.sender.sender_id.user_id; // 发送人id
+ var sessionId = input._event.sender.sender_id.open_id; // 发送人openid
+
+ Console.WriteLine(eventId);
+ if (memoryCache.TryGetValue(eventId, out var eventValue))
+ {
+ await context.Response.WriteAsJsonAsync(new
+ {
+ code = 0,
+ });
+
+ return;
+ }
+
+ memoryCache.Set(eventId, true, TimeSpan.FromHours(1));
+
+ var eventBus = context.RequestServices.GetRequiredService();
+ var chatShareInfoQuery = new ChatShareInfoQuery(id);
+
+ await eventBus.PublishAsync(chatShareInfoQuery);
+
+ var getApiKeyChatShareQuery = new GetAPIKeyChatShareQuery(string.Empty);
+ // 如果chatShareId不存在则返回让下面扣款
+ getApiKeyChatShareQuery.Result = chatShareInfoQuery.Result;
+
+ var chatApplicationQuery = new ChatApplicationInfoQuery(chatShareInfoQuery.Result.ChatApplicationId);
+
+ await eventBus.PublishAsync(chatApplicationQuery);
+
+ var chatApplication = chatApplicationQuery?.Result;
+
+ if (chatApplication == null)
+ {
+ await context.Response.WriteAsJsonAsync(new
+ {
+ code = 2,
+ });
+ return;
+ }
+
+ // 私聊直接回复
+ if (input._event.message.chat_type == "p2p")
+ {
+ // 不是文本消息,不处理
+ if (input._event.message.message_type != "text")
+ {
+ await SendMessages(chatApplication, messageId, "暂不支持其他类型的提问");
+ await context.Response.WriteAsJsonAsync(new
+ {
+ code = 0,
+ });
+ return;
+ }
+
+
+ // 是文本消息,直接回复
+ var userInput = JsonSerializer.Deserialize(input._event.message.content);
+
+ var history = new ChatHistory();
+ history.AddUserMessage(userInput.text);
+
+ var text = new StringBuilder();
+
+ await ChatMessageAsync(getApiKeyChatShareQuery, chatApplication, context, history, id,
+ async x => { text.Append(x); });
+
+ await SendMessages(chatApplication, sessionId, text.ToString());
+
+ await context.Response.WriteAsJsonAsync(new
+ {
+ code = 0,
+ });
+ return;
+ }
+
+
+ // 群聊,需要 @ 机器人
+ if (input._event.message.chat_type == "group")
+ {
+ // 这是日常群沟通,不用管
+ if (input._event.message.mentions.Length == 0)
+ {
+ await context.Response.WriteAsJsonAsync(new
+ {
+ code = 0,
+ });
+ return;
+ }
+
+
+ // 没有 mention 机器人,则退出。
+ if (input._event.message.mentions[0].name != chatApplication.GetBotName())
+ {
+ await context.Response.WriteAsJsonAsync(new
+ {
+ code = 0,
+ });
+ return;
+ }
+
+
+ // 是文本消息,直接回复
+ var userInput = JsonSerializer.Deserialize(input._event.message.content);
+
+ var history = new ChatHistory();
+ history.AddUserMessage(userInput.text);
+
+ var text = new StringBuilder();
+
+ await ChatMessageAsync(getApiKeyChatShareQuery, chatApplication, context, history, id,
+ async x => { text.Append(x); });
+
+ await SendMessages(chatApplication, chatId, text.ToString(), "chat_id");
+
+ await context.Response.WriteAsJsonAsync(new
+ {
+ code = 0,
+ });
+ return;
+ }
+ }
+
+ await context.Response.WriteAsJsonAsync(new
+ {
+ code = 2,
+ });
+ }
+
+
+ public static async Task ChatMessageAsync(GetAPIKeyChatShareQuery getAPIKeyChatShareQuery,
+ ChatApplicationDto chatApplication, HttpContext context,
+ ChatHistory history, string chatShareId,
+ Action chatResponse)
+ {
+ var eventBus = context.RequestServices.GetRequiredService();
+
+ // 如果设置了Prompt,则添加
+ if (!chatApplication.Prompt.IsNullOrEmpty())
+ {
+ history.AddSystemMessage(chatApplication.Prompt);
+ }
+
+
+ var content = history.Last();
+ var question = content.Content;
+
+ var prompt = string.Empty;
+
+ var sourceFile = new List();
+ // 如果为空则不使用知识库
+ if (chatApplication.WikiIds.Count != 0)
+ {
+ var memoryServerless =
+ context.RequestServices.GetRequiredService().CreateMemoryServerless();
+
+ var filters = chatApplication.WikiIds
+ .Select(chatApplication => new MemoryFilter().ByTag("wikiId", chatApplication.ToString())).ToList();
+
+ var result = await memoryServerless.SearchAsync(content.Content, "wiki", filters: filters, limit: 3,
+ minRelevance: chatApplication.Relevancy);
+
+ var fileIds = new List();
+
+ result.Results.ForEach(x =>
+ {
+ // 获取fileId
+ var fileId = x.Partitions.Select(x => x.Tags.FirstOrDefault(x => x.Key == "fileId"))
+ .FirstOrDefault(x => !x.Value.IsNullOrEmpty())
+ .Value.FirstOrDefault();
+
+ if (!fileId.IsNullOrWhiteSpace() && long.TryParse(fileId, out var id))
+ {
+ fileIds.Add(id);
+ }
+
+ prompt += string.Join(Environment.NewLine, x.Partitions.Select(x => x.Text));
+ });
+
+ if (result.Results.Count == 0 &&
+ !string.IsNullOrWhiteSpace(chatApplication.NoReplyFoundTemplate))
+ {
+ await context.WriteEndAsync(chatApplication.NoReplyFoundTemplate);
+ return;
+ }
+
+ var tokens = TokenHelper.GetGptEncoding().Encode(prompt);
+
+ // 这里可以有效的防止token数量超出限制,但是也会降低回复的质量
+ prompt = TokenHelper.GetGptEncoding()
+ .Decode(tokens.Take(chatApplication.MaxResponseToken).ToList());
+
+ // 如果prompt不为空,则需要进行模板替换
+ if (!prompt.IsNullOrEmpty())
+ {
+ prompt = chatApplication.Template.Replace("{{quote}}", prompt)
+ .Replace("{{question}}", content.Content);
+ }
+
+ // 在这里需要获取源文件
+ if (fileIds.Count > 0 && chatApplication.ShowSourceFile)
+ {
+ var fileQuery = new StorageInfosQuery(fileIds);
+
+ await eventBus.PublishAsync(fileQuery);
+
+ sourceFile.AddRange(fileQuery.Result);
+ }
+
+ if (!prompt.IsNullOrEmpty())
+ {
+ // 删除最后一个消息
+ history.RemoveAt(history.Count - 1);
+ history.AddUserMessage(prompt);
+ }
+ }
+
+ // 添加用户输入,并且计算请求token数量
+ int requestToken = history.Where(x => !x.Content.IsNullOrEmpty()).Sum(x => TokenHelper.ComputeToken(x.Content));
+
+
+ if (getAPIKeyChatShareQuery?.Result != null)
+ {
+ // 如果token不足则返回,使用token和当前request总和大于可用token,则返回
+ if (getAPIKeyChatShareQuery.Result.AvailableToken != -1 &&
+ (getAPIKeyChatShareQuery.Result.UsedToken + requestToken) >=
+ getAPIKeyChatShareQuery.Result.AvailableToken)
+ {
+ await context.WriteEndAsync("Token不足");
+ return;
+ }
+
+ // 如果没有过期则继续
+ if (getAPIKeyChatShareQuery.Result.Expires != null &&
+ getAPIKeyChatShareQuery.Result.Expires < DateTimeOffset.Now)
+ {
+ await context.WriteEndAsync("Token已过期");
+ return;
+ }
+ }
+
+ if (chatApplication.ChatType.IsNullOrEmpty())
+ {
+ // 防止没有设置对话类型
+ chatApplication.ChatType = OpenAIOptions.ServiceName;
+ }
+
+ var modelService = context.RequestServices.GetRequiredService();
+
+ var (chatStream, fastModelDto) = await modelService.GetChatService(chatApplication.ChatType);
+
+ if (fastModelDto.Enable != true)
+ {
+ await context.WriteEndAsync("模型未启用");
+ return;
+ }
+
+ if (fastModelDto.Models.Any(x => x == chatApplication.ChatModel) == false)
+ {
+ await context.WriteEndAsync($"模型渠道并未找到 {chatApplication.ChatModel} 模型的支持!");
+ return;
+ }
+
+ var setting = new OpenAIPromptExecutionSettings
+ {
+ MaxTokens = chatApplication.MaxResponseToken,
+ Temperature = chatApplication.Temperature,
+ ModelId = chatApplication.ChatModel,
+ ExtensionData = new Dictionary()
+ };
+
+ setting.ExtensionData.TryAdd(AIDotNet.Abstractions.Constant.API_KEY, fastModelDto.ApiKey);
+ setting.ExtensionData.TryAdd(AIDotNet.Abstractions.Constant.API_URL, fastModelDto.Url);
+
+ var responseId = Guid.NewGuid().ToString("N");
+ var requestId = Guid.NewGuid().ToString("N");
+ var output = new StringBuilder();
+ try
+ {
+ await foreach (var item in chatStream.GetStreamingChatMessageContentsAsync(history, setting))
+ {
+ if (item.Content.IsNullOrEmpty())
+ {
+ continue;
+ }
+
+ output.Append(item.Content);
+
+ chatResponse.Invoke(item.Content);
+ }
+ }
+ catch (NotModelException notModelException)
+ {
+ chatResponse.Invoke("未找到模型兼容:" + notModelException.Message);
+ return;
+ }
+ catch (InvalidOperationException invalidOperationException)
+ {
+ chatResponse.Invoke("对话异常:" + invalidOperationException.Message);
+ return;
+ }
+ catch (ArgumentException argumentException)
+ {
+ chatResponse.Invoke(argumentException.Message);
+ return;
+ }
+ catch (Exception e)
+ {
+ chatResponse.Invoke("对话异常:" + e.Message);
+ return;
+ }
+
+ #region 记录对话内容
+
+ var createChatDialogHistoryCommand = new CreateChatDialogHistoryCommand(new CreateChatDialogHistoryInput()
+ {
+ ChatDialogId = string.Empty,
+ Id = requestId,
+ Content = question,
+ ExpendToken = requestToken,
+ Type = ChatDialogHistoryType.Text,
+ Current = true
+ });
+
+ await eventBus.PublishAsync(createChatDialogHistoryCommand);
+
+ var outputContent = output.ToString();
+ var completeToken = TokenHelper.ComputeToken(outputContent);
+
+ var chatDialogHistory = new CreateChatDialogHistoryCommand(new CreateChatDialogHistoryInput()
+ {
+ ChatDialogId = string.Empty,
+ Content = outputContent,
+ Id = responseId,
+ ExpendToken = completeToken,
+ Type = ChatDialogHistoryType.Text,
+ Current = false,
+ ReferenceFile = sourceFile.Select(x => new SourceFileDto()
+ {
+ Name = x.Name,
+ FileId = x.Id.ToString(),
+ FilePath = x.Path
+ }).ToList()
+ });
+
+ await eventBus.PublishAsync(chatDialogHistory);
+
+ #endregion
+
+ var fastModelComputeTokenCommand = new FastModelComputeTokenCommand(chatApplication.ChatType, requestToken,
+ completeToken);
+
+ await eventBus.PublishAsync(fastModelComputeTokenCommand);
+
+ //对于对话扣款
+ if (getAPIKeyChatShareQuery?.Result != null)
+ {
+ var updateChatShareCommand = new DeductTokenCommand(getAPIKeyChatShareQuery.Result.Id,
+ requestToken);
+
+ await eventBus.PublishAsync(updateChatShareCommand);
+ }
+ }
+
+ private static readonly ConcurrentDictionary MemoryCache = new();
+
+ public static async ValueTask SendMessages(ChatApplicationDto chatApplication, string sessionId, string message,
+ string receive_id_type = "open_id")
+ {
+ await SendMessages(chatApplication, new FeishuChatSendMessageInput(JsonSerializer.Serialize(new
+ {
+ text = message,
+ }, JsonSerializerOptions), "text", sessionId), receive_id_type);
+ }
+
+ private static async ValueTask SendMessages(ChatApplicationDto chatApplication, FeishuChatSendMessageInput input,
+ string receive_id_type = "open_id")
+ {
+ var requestMessage = new HttpRequestMessage(HttpMethod.Post,
+ "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=" + receive_id_type);
+
+ requestMessage.Content = new StringContent(JsonSerializer.Serialize(input, JsonSerializerOptions),
+ Encoding.UTF8, "application/json");
+
+ await RefreshTokenAsync(requestMessage, chatApplication);
+
+ var response = await httpClient.SendAsync(requestMessage);
+
+ var result = await response.Content.ReadFromJsonAsync();
+
+ if (result?.code != 0)
+ {
+ throw new UserFriendlyException(result?.msg);
+ }
+ }
+
+ private static async ValueTask RefreshTokenAsync(HttpRequestMessage requestMessage,
+ ChatApplicationDto chatApplication)
+ {
+ if (requestMessage.Headers.Contains("Authorization"))
+ {
+ if (!MemoryCache.TryGetValue(chatApplication.Id, out var _lastTime))
+ {
+ MemoryCache.TryUpdate(chatApplication.Id, DateTime.Now, _lastTime);
+ }
+ else if (_lastTime != null && DateTime.Now - _lastTime < TimeSpan.FromHours(1.5))
+ {
+ // 如果LastTime大于1.5小时,刷新token
+ return;
+ }
+
+ requestMessage.Headers.Remove("Authorization");
+ }
+
+
+ var request = new HttpRequestMessage(HttpMethod.Post,
+ "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal");
+
+ request.Content = new StringContent(JsonSerializer.Serialize(new
+ {
+ app_id = chatApplication.GetFeishuAppId(),
+ app_secret = chatApplication.GetFeishuAppSecret(),
+ }), Encoding.UTF8, "application/json");
+ var response = await httpClient.SendAsync(request);
+ var result = await response.Content.ReadAsStringAsync();
+ var token = JsonSerializer.Deserialize(result);
+ requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.TenantAccessToken);
+
+ MemoryCache.Remove(chatApplication.Id, out _);
+ MemoryCache.TryAdd(chatApplication.Id, DateTime.Now);
+ }
+}
\ No newline at end of file
diff --git a/src/Service/FastWiki.Service/Service/OpenAIService.cs b/src/Service/FastWiki.Service/Service/OpenAIService.cs
index 59412604..6218b240 100644
--- a/src/Service/FastWiki.Service/Service/OpenAIService.cs
+++ b/src/Service/FastWiki.Service/Service/OpenAIService.cs
@@ -1,5 +1,3 @@
-using System.Text;
-using System.Text.Json;
using AIDotNet.OpenAI;
using FastWiki.Service.Application.Model.Commands;
using FastWiki.Service.Application.Storage.Queries;
@@ -9,6 +7,8 @@
using FastWiki.Service.Infrastructure.Helper;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
+using System.Text;
+using System.Text.Json;
using TokenApi.Service.Exceptions;
namespace FastWiki.Service.Service;
@@ -360,7 +360,6 @@ await context.WriteOpenAiResultAsync(item.Content, module.model, requestId,
await eventBus.PublishAsync(updateChatShareCommand);
}
}
-
private static bool IsVision(string model)
{
if (model.Contains("vision") || model.Contains("image"))
diff --git a/web/src/models/index.d.ts b/web/src/models/index.d.ts
index e6a6dd6c..c0d0080c 100644
--- a/web/src/models/index.d.ts
+++ b/web/src/models/index.d.ts
@@ -14,6 +14,7 @@ export interface ChatApplicationDto {
noReplyFoundTemplate: string | null;
showSourceFile: boolean;
relevancy: number;
+ extend: { [key: string]: string; };
}
export interface ChatDialogDto {
diff --git a/web/src/pages/app-detail/feautres/AppDetailInfo.tsx b/web/src/pages/app-detail/feautres/AppDetailInfo.tsx
index 5ea08cba..1274b66a 100644
--- a/web/src/pages/app-detail/feautres/AppDetailInfo.tsx
+++ b/web/src/pages/app-detail/feautres/AppDetailInfo.tsx
@@ -5,6 +5,7 @@ import { ChatApplicationDto } from "../../../models";
import { PutChatApplications } from "../../../services/ChatApplicationService";
import { GetWikisList } from "../../../services/WikiService";
import { ChatModelList } from "../../../services/ModelService";
+import { Input } from "@lobehub/ui";
interface IAppDetailInfoProps {
value: ChatApplicationDto
@@ -80,7 +81,7 @@ const AppDetailInfo = memo(({ value }: IAppDetailInfoProps) => {
return { label: item, value: item }
}))
}
- }, [application,chatModelType]);
+ }, [application, chatModelType]);
function save() {
PutChatApplications(application)
@@ -323,8 +324,81 @@ const AppDetailInfo = memo(({ value }: IAppDetailInfoProps) => {
>
+ }, {
+ key: '2',
+ label: '飞书设置',
+ children: <>
+
+ 飞书机器人名称
+
+ {
+ setApplication({
+ ...application,
+ extend: {
+ ...application.extend,
+ BotName: e.target.value
+ }
+ });
+ }}
+ style={{ width: 380 }}>
+
+
+
+ 飞书AppId
+
+ {
+ setApplication({
+ ...application,
+ extend: {
+ ...application.extend,
+ FeishuAppId: e.target.value
+ }
+ });
+ }}
+ style={{ width: 380 }}>
+
+
+
+ 飞书AppSecret
+
+ {
+ setApplication({
+ ...application,
+ extend: {
+ ...application.extend,
+ FeishuAppSecret: e.target.value
+ }
+ });
+ }}
+ style={{ width: 380 }}>
+
+
+ >
}]}
/>
+
+
{
setApplication({
...application,
diff --git a/web/src/pages/app-detail/feautres/ReleaseApplication.tsx b/web/src/pages/app-detail/feautres/ReleaseApplication.tsx
index 9ce5f1d6..384141d7 100644
--- a/web/src/pages/app-detail/feautres/ReleaseApplication.tsx
+++ b/web/src/pages/app-detail/feautres/ReleaseApplication.tsx
@@ -6,6 +6,7 @@ import styled from 'styled-components';
import CreateApplication from "./CreateApplication";
import { GetChatShareList, RemoveChatShare } from "../../../services/ChatApplicationService";
import { copyToClipboard } from "../../../utils/stringHelper";
+import { config } from "../../../config";
const Title = styled.div`
@@ -80,8 +81,26 @@ export default memo((props: IReleaseApplicationProps) => {
message.success('复制APIKey成功');
}
})
+
items.push({
key: '3',
+ label: '复制飞书对接地址',
+ onClick: async () => {
+ let url = config.FAST_API_URL;
+ if(!url){
+ url = location.origin;
+ }
+ // 删除最后的/
+ if(url.endsWith('/')){
+ url = url.slice(0, url.length - 1);
+ }
+ copyToClipboard(url + "/v1/feishu/completions/" + item.id)
+ message.success('复制成功');
+ }
+ })
+
+ items.push({
+ key: '4',
label: '删除',
onClick: async () => {
await RemoveChatShare(item.id);
@@ -92,7 +111,6 @@ export default memo((props: IReleaseApplicationProps) => {
})
}
})
-
return (
<>
diff --git a/web/stats.html b/web/stats.html
new file mode 100644
index 00000000..b1cf25c9
--- /dev/null
+++ b/web/stats.html
@@ -0,0 +1,4842 @@
+
+
+
+
+
+
+
+ Rollup Visualizer
+
+
+
+
+
+
+
+
+