bump: 迁移到.net 8

This commit is contained in:
2023-11-19 12:55:02 +08:00
parent 0f8154e968
commit 8fa0f5bc0f
19 changed files with 44 additions and 53 deletions

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<base href="/"/>
<link href="css/site.css" rel="stylesheet"/>
<link href="_content/AntDesign/css/ant-design-blazor.css" rel="stylesheet" />
<link href="Frontend.styles.css" rel="stylesheet">
<HeadOutlet @rendermode="@InteractiveServer" />
</head>
<body>
<Routes @rendermode="@InteractiveServer" />
<text>
An error has occurred. This application may not response until reloaded.
</text>
<script src="_content/AntDesign/js/ant-design-blazor.js"></script>
<script src="_content/BlazorMonaco/jsInterop.js"></script>
<script src="_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
<script src="_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
<script src="_framework/blazor.web.js"></script>
<script src="js/helper.js"></script>
</body>
</html>

View File

@@ -0,0 +1,132 @@
@using Frontend.Models
@using Katheryne.Abstractions
@inject IJSRuntime JsRuntime
@inject IMessageService MessageService
<div class="content-zone">
<div class="chat-zone" id="chat-scroll-zone">
<AntList TItem="ChatMessage" DataSource="@Messages" Split="@false">
<ListItem>
@if (context.Left)
{
<div>
<MessageBubble Message="context"/>
</div>
}
else
{
<div style="margin-left: auto">
<MessageBubble Message="context"/>
</div>
}
</ListItem>
</AntList>
<div style="height: 200px; width: 100%; flex: none">
</div>
</div>
<div class="control-zone">
<div style="padding: 10px 82px 20px">
<div class="input-zone">
<GridRow>
<GridCol Span="22">
<Input TValue="@string" @bind-Value="@MessageSending"
Placeholder="输入以对话" Bordered="@false" OnPressEnter="@SendMessageClicked"/>
</GridCol>
<GridCol Span="2">
<Button Type="@ButtonType.Primary" @onclick="@SendMessageClicked">
发送
</Button>
</GridCol>
</GridRow>
</div>
</div>
</div>
<Modal Title="设置您的昵称"
Visible="@_setUsernameVisible"
OnOk="@SetUsernameOkClicked"
OnCancel="@SetUsernameCancelClicked">
<div>
<Input TValue="@string" Placeholder="用户名"
@bind-Value="@_username"
OnPressEnter="@SetUsernameOkClicked"/>
</div>
</Modal>
</div>
@code
{
private string MessageSending { get; set; } = string.Empty;
private string _username = string.Empty;
private bool _setUsernameVisible = false;
[Parameter]
public List<ChatMessage> Messages { get; set; } = null!;
[Parameter]
public IChatRobot Robot { get; set; } = null!;
protected override void OnInitialized()
{
if (string.IsNullOrEmpty(_username))
{
_setUsernameVisible = true;
}
base.OnInitialized();
}
protected override void OnAfterRender(bool firstRender)
{
if (!firstRender)
{
JsRuntime.InvokeVoidAsync("scrollToSection");
}
base.OnAfterRender(firstRender);
}
private void SendMessageClicked()
{
if (string.IsNullOrWhiteSpace(MessageSending))
{
return;
}
Messages.Add(new ChatMessage
{
Left = false,
Sender = string.IsNullOrEmpty(_username) ? "default" : _username,
Text = MessageSending
});
foreach (string answer in Robot.ChatNext(MessageSending))
{
Messages.Add(new ChatMessage
{
Left = true,
Sender = Robot.RobotName,
Text = answer
});
}
MessageSending = string.Empty;
}
private void SetUsernameOkClicked()
{
if (string.IsNullOrEmpty(_username))
{
MessageService.Warning("昵称不能为空");
return;
}
_setUsernameVisible = false;
}
private void SetUsernameCancelClicked()
{
MessageService.Warning("呃呃呃呃,没设置用户名就想走?");
}
}

View File

@@ -0,0 +1,58 @@
.chat-column {
margin-left: auto;
}
.content-zone {
align-items: center;
display: flex;
flex-direction: column;
width: 100%;
min-height: calc(100vh - 64px);
max-height: calc(100vh - 64px);
padding: 10px;
position: relative;
background: linear-gradient(180deg,
#f5f4f6,
#b5bddf);
overflow: hidden;
}
.chat-zone {
min-width: 100%;
display: flex;
flex-direction: column;
position: relative;
height: 100%;
overflow-y: auto;
}
.chat-zone::-webkit-scrollbar {
width: 10px;
height: 1px;
}
.chat-zone::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: #bdbdbd;
}
.chat-zone::-webkit-scrollbar-track {
border-radius: 10px;
background: #fff;
}
.control-zone {
min-width: 100%;
bottom: 0;
position: absolute;
background-color: #b5bddf;
display: block;
box-shadow: 3px 0 0 #b5bddf;
}
.input-zone {
padding: 10px;
border-radius: 15px;
background-color: white;
}

View File

@@ -0,0 +1,84 @@
<Modal Title="DSL语法说明"
Visible="@_helpVisible"
OnOk="@(() => _helpVisible = false)"
OnCancel="@(() => _helpVisible = false)">
<div>
<Title Level="3">语法说明</Title>
<Paragraph>
凯瑟琳DSL使用和<Text Code>YAML</Text>类似的语法来编写逻辑。语法通过规定机器人的多个阶段和在阶段之间的
迁移关系来描述聊天机器人的聊天过程。机器人在启动时处在一个特定的阶段,通过正则表达式匹配用户的输入迁移到下一个阶段
并输出对应的内容。
</Paragraph>
<Paragraph>
文法拥有三个顶级属性:
<ul>
<li>
<Text Code>robotName</Text> 字符串类型,规定了机器人的名称;
</li>
<li>
<Text Code>stages</Text> Stage类型的数组规定了机器人的各个阶段
</li>
<li>
<Text Code>beginStageName</Text> 字符串类型,规定了机器人初始阶段,会自动输出该阶段的输出内容。
</li>
</ul>
</Paragraph>
<Paragraph>
Stage类型拥有三个属性
<ul>
<li>
<Text Code>name</Text> 阶段的名称,是阶段<b>唯一的标识符</b>
</li>
<li>
<Text Code>transformers</Text> Transformer类型的数组指定该阶段的迁移规则
</li>
<li>
<Text Code>answer</Text> 该阶段的输出内容。
</li>
</ul>
</Paragraph>
<Paragraph>
Transformer类型拥有两个属性
<ul>
<li>
<Text Code>pattern</Text> 匹配用户输入的正则表达式;
</li>
<li>
<Text Code>nextStageName</Text> 匹配成功之后需要迁移到的阶段名。
</li>
</ul>
</Paragraph>
<Paragraph>
在编译阶段编译器会执行如下检查:
<ul>
<li>
<Text Code>transformers</Text> 中的 <Text Code>nextStageName</Text>指定的阶段是否定义;
</li>
<li>
<Text Code>beginStageName</Text> 指定的阶段是否定义。
</li>
</ul>
</Paragraph>
</div>
</Modal>
@code {
private bool _helpVisible;
public void Show()
{
_helpVisible = true;
StateHasChanged();
}
}

View File

@@ -0,0 +1,35 @@
@inherits LayoutComponentBase
<div>
<Layout>
<Header class="header">
<Space >
<SpaceItem>
<p class="title-text">
试作自律型可重编程客服机器人
</p>
</SpaceItem>
<SpaceItem>
<p class="subtitle-text">
a.k.a 凯瑟琳
</p>
</SpaceItem>
<SpaceItem>
<div>
<a href="editor/">维护界面</a>
</div>
</SpaceItem>
</Space>
</Header>
<Layout>
<Content>
@Body
</Content>
</Layout>
</Layout>
</div>
<AntContainer/>

View File

@@ -0,0 +1,11 @@
.title-text {
font-size: 2rem;
color: white;
margin: 0;
}
.subtitle-text {
font-size: 0.8rem;
color: grey;
margin: 0;
}

View File

@@ -0,0 +1,60 @@
@using Frontend.Models
<div>
@if (Message.Left)
{
<Space Align="start">
<SpaceItem>
<Avatar Style="background-color: #5d92e4" Size="2.5rem">
@Message.Sender.First()
</Avatar>
</SpaceItem>
<SpaceItem>
<p style="font-size: 0.8rem; color: rgb(128,128,128)">
@Message.Sender
</p>
<div class="bubble-left">
<p class="message-text">
@Message.Text
</p>
</div>
</SpaceItem>
</Space>
}
else
{
<Space Align="start">
<SpaceItem>
<div>
<p style="font-size: 0.8rem; color: rgb(128, 128, 128); text-align: right;">
@Message.Sender
</p>
<div class="bubble-right">
<p class="message-text">
@Message.Text
</p>
</div>
</div>
</SpaceItem>
<SpaceItem>
<Avatar Style="background-color: #5d92e4" Size="2.5rem">
@Message.Sender.First()
</Avatar>
</SpaceItem>
</Space>
}
</div>
@code {
[Parameter]
public ChatMessage Message { get; set; } = new()
{
Left = true,
Sender = "default",
Text = "default"
};
}

View File

@@ -0,0 +1,24 @@
.bubble-left {
background-color: white;
border-radius: 1px 10px 10px 10px;
padding: 5px;
min-width: 80px;
max-width: 300px;
width: fit-content;
justify-content: center;
}
.bubble-right {
background-color: white;
border-radius: 10px 1px 10px 10px;
padding: 5px;
max-width: 300px;
width: fit-content;
justify-content: center;
}
.message-text {
font-size: 1.1rem;
margin: 3px;
word-break: break-word;
}

View File

@@ -0,0 +1,138 @@
@page "/editor"
@using Katheryne.Exceptions
@using Katheryne.Abstractions
@using Frontend.Services
@inject NavigationManager Navigation
@inject IChatRobotFactory RobotFactory
@inject GrammarStorageService GrammarStorage
<Layout>
<Content>
<div class="editor-zone">
<div class="control-zone">
<Space Size="@("0")">
<SpaceItem>
<Tooltip Placement="@Placement.Bottom"
Title="退出语法编辑器">
<Button Type="@ButtonType.Text" OnClick="@QuitButtonClicked">
退出
</Button>
</Tooltip>
</SpaceItem>
<SpaceItem>
<Tooltip Placement="@Placement.Bottom"
Title="编译文法并保存在浏览器缓存中">
<Button Type="@ButtonType.Text" @onclick="@CompileGrammarClicked">
编译
</Button>
</Tooltip>
</SpaceItem>
<SpaceItem>
<Tooltip Placement="@Placement.Bottom"
Title="清除浏览器缓存中的文法">
<Button Type="@ButtonType.Text" @onclick="@ClearGrammarClicked">
清除
</Button>
</Tooltip>
</SpaceItem>
<SpaceItem>
<Button Type="@ButtonType.Text" @onclick="HelpButtonClicked">
帮助
</Button>
</SpaceItem>
</Space>
</div>
<StandaloneCodeEditor Id="code-editor" @ref="@_editor"
ConstructionOptions="GetEditorConstructionOptions"/>
<div class="logging-zone">
<AntList TItem="@string" DataSource="@_logs" Split="@false" @ref="@_logList">
<ListItem Style="padding: 0 0 0">
<p class="logging-item">@context</p>
</ListItem>
</AntList>
</div>
</div>
</Content>
</Layout>
<GrammarHelp @ref="@_grammarHelp"/>
@code {
private StandaloneCodeEditor _editor = null!;
private AntList<string> _logList = null!;
private GrammarHelp _grammarHelp = null!;
private readonly List<string> _logs = new();
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Log("编辑器加载完成");
if (await GrammarStorage.RestoreGrammar())
{
await _editor.SetValue(RobotFactory.GrammarText);
Log("从浏览器中恢复成功");
}
else
{
Log("尚未设置语法");
}
}
await base.OnAfterRenderAsync(firstRender);
}
private StandaloneEditorConstructionOptions GetEditorConstructionOptions(StandaloneCodeEditor editor)
{
return new StandaloneEditorConstructionOptions
{
Language = "yaml",
Value = !string.IsNullOrEmpty(RobotFactory.GrammarText) ? RobotFactory.GrammarText : string.Empty
};
}
private async Task CompileGrammarClicked()
{
string grammarText = await _editor.GetValue();
try
{
Log("编译文法...");
RobotFactory.SetGrammar(grammarText);
Log("编译成功!");
await GrammarStorage.StoreGrammar();
}
catch (GrammarException e)
{
Log($"编译文法遇到错误:{e.Message}");
}
}
private void QuitButtonClicked()
{
Navigation.NavigateTo("/", replace: true);
}
private async Task ClearGrammarClicked()
{
await GrammarStorage.RemoveGrammar();
Log("清除浏览器中的语法成功");
}
private void HelpButtonClicked()
{
_grammarHelp.Show();
}
private void Log(string message)
{
_logs.Add($"{DateTime.Now:HH:mm:ss} {message}");
StateHasChanged();
}
}

View File

@@ -0,0 +1,17 @@
.editor-zone {
height: calc(100vh - 64px);
}
.control-zone {
padding: 2px;
}
.logging-zone {
height: 15%;
overflow-y: auto;
margin-bottom: 5px;
}
.logging-item {
margin: 0 0 0;
}

View File

@@ -0,0 +1,119 @@
@page "/"
@using Frontend.Models
@using Frontend.Services
@using Katheryne
@using Katheryne.Abstractions
@inject IChatRobotFactory ChatRobotFactory
@inject GrammarStorageService GrammarStorage
@inject DefaultChatRobot DefaultRobot
<Layout>
<Sider Width="200">
<div class="chat-control-zone">
<div>
<Button Type="@ButtonType.Primary" Size="large" @onclick="@CreateChatClicked">
新建对话
</Button>
<div class="chat-list">
<AntList TItem="@Chat" DataSource="@_chatDictionary.Values"
Split="@false">
<ListItem OnClick="@(() => ChangeChatClicked(context.Guid))">
<ListItemMeta>
<TitleTemplate>
@if (context.Selected)
{
<div class="selected-chat-name">
<p style="margin: 5px">@context.Title</p>
</div>
}
else
{
<div class="chat-name">
<p style="margin: 5px">@context.Title</p>
</div>
}
</TitleTemplate>
</ListItemMeta>
</ListItem>
</AntList>
</div>
</div>
</div>
</Sider>
<Content>
<ChatZone Messages="@GetChatMessages()" Robot="@GetChatRobot()"/>
</Content>
</Layout>
@code {
private readonly Dictionary<Guid, Chat> _chatDictionary = new();
private Guid _currentGuid;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await GrammarStorage.RestoreGrammar();
Chat chat = GetInitChat();
_chatDictionary.Add(chat.Guid, chat);
_currentGuid = chat.Guid;
StateHasChanged();
}
await base.OnInitializedAsync();
}
private void CreateChatClicked()
{
Chat chat = GetInitChat();
_chatDictionary.Add(chat.Guid, chat);
_chatDictionary[_currentGuid].Selected = false;
_currentGuid = chat.Guid;
_chatDictionary[_currentGuid].Selected = true;
}
private void ChangeChatClicked(Guid guid)
{
_chatDictionary[_currentGuid].Selected = false;
_currentGuid = guid;
_chatDictionary[_currentGuid].Selected = true;
}
private Chat GetInitChat()
{
var chat = new Chat
{
Title = $"对话:{_chatDictionary.Count + 1}",
Robot = ChatRobotFactory.GetRobot()
};
foreach (string answer in chat.Robot.OnChatStart())
{
chat.Messages.Add(new ChatMessage
{
Sender = chat.Robot.RobotName,
Left = true,
Text = answer
});
}
chat.Selected = true;
return chat;
}
private List<ChatMessage> GetChatMessages()
{
return _chatDictionary.TryGetValue(_currentGuid, out Chat? chat) ? chat.Messages : new List<ChatMessage>();
}
private IChatRobot GetChatRobot()
{
return _chatDictionary.TryGetValue(_currentGuid, out Chat? chat) ? chat.Robot : DefaultRobot;
}
}

View File

@@ -0,0 +1,28 @@
.chat-control-zone {
margin-top: 20px;
display: flex;
justify-content: center;
}
.selected-chat-name {
display: flex;
justify-content: center;
margin: 5px 5px 5px;
padding: 5px 0 5px;
border-radius: 5px;
background-color: #bcc9e2;
}
.chat-name {
display: flex;
justify-content: center;
margin: 5px 5px 5px;
padding: 5px 0 5px;
border-radius: 5px;
background-color: #5d92e4;
}
.chat-list {
overflow-y: auto;
margin-top: 40px;
}

View File

@@ -0,0 +1,12 @@
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

View File

@@ -0,0 +1,11 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.JSInterop
@using AntDesign
@using BlazorMonaco
@using BlazorMonaco.Editor
@using BlazorMonaco.Languages
@using Frontend.Components