- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
先介绍下这个项目.
最近我一直在探索大语言模型,根据不同场景训练了好几个模型,为了让用户测试使用,需要开发前端.
这时候,用 Gradio 搭建的前端是不太够的,虽说 GitHub 上也有一堆开源的 ChatGPT 前端,但我看了一圈,并没有找到便于二次开发定制的,再一想,这么简单的功能,自己做就好啦,何必去 GitHub copy 呢?
那么就直接开始吧~ 。
一开始我打算用 React 来做前端,然后使用 websocket 或者 eventsource 来实现聊天的打字机效果,但转念一想,不是有 Blazor Server 吗,这东西本来就和服务器建立了长链接,根本不需要折腾,于是决定试试.
主页 | 聊天界面 |
---|---|
移动端 。
主页 | 聊天界面 |
---|---|
项目架构图 。
后端使用 gRPC 与各个模型的服务连接,然后使用 Blazor 来实现聊天功能.
关于 gRPC 的使用,在之前写的这篇博客: Asp-Net-Core学习笔记:gRPC快速入门 。
其实几年前我就有轻度使用了一下 Blazor 这个技术,详见文章: Asp-Net-Core学习笔记:4.Blazor-WebAssembly入门 。
一开始使用 Blazor ,我是有点嫌弃的,我还是比较倾向于传统的前后端分离,AspNetCore用来做后端,用 React 做前端,生态很丰富,要做啥组件都容易.
Blazor 有几个痛点:
所以这个项目一开始,我好几次产生了放弃 blazor ,重新用 React 实现的想法,不过随着熟练度提高,反而渐渐觉得,Blazor 好像也不错,开发小应用的效率挺高的.
这几个痛点虽然影响体验,但也不是不能忍受 。
所以这个项目就这么磕磕绊绊的,边学 Blazor 边做,搞定了,效果竟然还可以😂 。
事实证明,Blazor (Server) 用在小项目上还是可以的,Server 模式的长链接可以做太多事了,数据交互这一块可以节省很多精力.
使用内置模板创建 Blazor 的项目,静态文件直接是附带在 wwwroot/lib 目录下 。
这个还是不趁手,我习惯用 npm 管理静态资源,第三方的前端依赖不添加到版本管理中 。
可以参考之前的博客: Asp-Net-Core开发笔记:使用NPM和gulp管理前端静态文件 。
本项目中,我使用了这些依赖 。
"dependencies": {
"@fortawesome/fontawesome-free": "^6.0.0",
"admin-lte": "^3.2.0",
"bootstrap": "^4.6.2",
"open-iconic": "^1.1.1"
}
然后发现 open-iconic 其实不怎么好看,还是 FontAwesome 好一点.
聊天界面需要实现类似 ChatGPT 的打字机效果 。
这就得用到流式输出,看了下 ChatGPT 的实现是 EventSource,不过我们用 Blazor Server 就不用考虑这些麻烦的数据交互问题了.
书接上回的 gRPC 调用,定义为服务端流式输出,然后我又在 Blazor 项目里封装了一个 ChatService 。
用生成器的方式,返回一个 IAsyncEnumerable 对象。 (详见第三个参考资料) 。
public async IAsyncEnumerable<string> StreamingChat(string prompt) {
using var call = ClientRoute(prompt).StreamingChat(GetRequest(prompt));
await foreach (var resp in call.ResponseStream.ReadAllAsync()) {
yield return RenderText(resp.Response);
}
}
在 Blazor 组件里调用的时候,只需要用 await foreach 搭配 StateHasChanged() ,就可以实现打字机效果了 。
await foreach (var resp in ChatService.StreamingChat(input.Content)) {
output.Content = resp;
StateHasChanged();
}
PS:我们 C# 实在是太好用啦~ 。
Blazor 无论是 server 模式,还是 WebAssembly 模式,都不能直接操作 DOM.
所以直接借助 JS,Blazor 提供了不错的 JS 互操作能力 。
我这里用到了,聊天界面自动滚动到页面底部的操作 。
首先写一个 js 函数 。
function scrollToEnd(elem) {
elem.scrollTop = elem.scrollHeight
}
一开始我看官网文档,JS 文件是可以和组件放在一起的 。
比如我的聊天组件,放在项目中的 Pages/Chat.razor 文件 。
然后 css 文件,是在 Pages/Chat.razor.css 。
它会自动把这俩关联起来 。
Pages/_Layout.cshtml 有一个 css 引用 。
<link href="AIHub.Blazor.styles.css" rel="stylesheet"/>
在 build 的时候,编译器会把所有的css都放在这个文件里面 。
但对于 js 并不是这样 。
在 Debug 模式运行,并不会自动复制 JS 文件 。
只有 Release 模式 publish 的时候,才会把 wwwroot 复制到发布目录里 。
然后还需要在 Blazor 组件里面手动引入 这个 JS Module 。
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./Pages/Chat.razor.js");
之后通过这个 module 来执行 JS 调用.
但这样调试的时候是不会复制的,拿不到 JS 很不方便.
所以我最终没有采用这种方式,而是直接把 JS 放到 wwwroot/js 目录下面 。
然后在 Pages/_Layout.cshtml 里面引用 。
<script src="js/common.js"></script>
最后在 Blazor 组件里面使用就很简单了 。
前面说了 JS 互操作 。
现在可以在 Blazor 组件里面调用 JS 的方法来滚动到页面底部了 。
先定义个 ref (感觉和 vue 有点像) 。
private ElementReference _chatMessagesRef;
然后在元素上绑定 。
<div class="chat-messages" @ref="_chatMessagesRef"></div>
最后调用 JS 方法,把这个 ref 作为参数传入 。
await foreach (var resp in ChatService.StreamingChat(input.Content)) {
output.Content = resp;
StateHasChanged();
await Js.InvokeVoidAsync("scrollToEnd", _chatMessagesRef);
}
搞定.
PS:和 Vue 真的太像了.
聊天界面需要同时适配电脑版和手机版 。
参考 Bootstrap 的自适应设计 。
电脑版有侧边栏,高度可以吃满,但宽度得减去左侧栏的宽度 。
.chat-messages {
--large-width: calc(100vw - 250px - 70px);
max-width: var(--large-width);
min-width: var(--large-width);
}
手机版没有侧栏,变成了顶栏,宽度吃满,高度减去顶栏高度 。
@media screen and (max-width: 992px) {
:root {
--mobile-width: calc(100vw - 0);
--mobile-height: calc(100vh - 1.2rem - 50px);
}
.chat-wrapper {
height: var(--mobile-height);
}
.chat-messages {
max-width: var(--mobile-width);
min-width: var(--mobile-width);
}
.chat-controls {
--mobile-width: calc(100vw - 0);
max-width: var(--mobile-width);
min-width: var(--mobile-width);
}
}
Breakpoint 我只用了一个 992px ,相当于 Bootstrap 的 lg 。
这里是 Bootstrap 的宽度定义,可以参考一下.
Breakpoint | Class infix | Dimensions |
---|---|---|
Extra small | None | <576px |
Small | sm |
≥576px |
Medium | md |
≥768px |
Large | lg |
≥992px |
Extra large | xl |
≥1200px |
Extra extra large | xxl |
≥1400px |
PS:最后觉得,移动端还是单独做一个好了,自适应实在是别扭。我突然想到之前用 Flutter 做的 App,打包成 HTML 版本,竟然体验也还不错.
初步用起来很简单 。
比如首页的九宫格按钮,我就封装了一个组件 。
<div class="col-xl-2 col-lg-3 col-md-4 col-6 mt-2 mb-2">
<a class="btn btn-outline-dark btn-block" href="@Href" target="@Target" style="border-color: rgba(52,58,64,.8)">
<div class="mt-2" style="">
<i class="@IconClass" style="font-size: 6em;color: #bdc6d0;"></i>
</div>
<div class="mt-2">@Title</div>
</a>
</div>
@code {
[Parameter]
public string? IconClass { get; set; }
[Parameter]
public string? Href { get; set; }
[Parameter]
public string? Target { get; set; }
[Parameter]
public string? Title { get; set; }
}
使用很简单 。
<DialButton IconClass="oi oi-chat" Title="大语言模型" Href="chat"/>
搞定~ 。
这次只是个小 Demo 项目,试用了一下 Blazor ,从一开始的非常别扭,到越来越顺手 。
感觉 Blazor Server 写小项目还是挺好用的,后面继续完善项目,持续发掘 Blazor 的能力,到时再继续更新博客~ 。
最后此篇关于项目完成小结:使用Blazor和gRPC开发大模型客户端的文章就讲到这里了,如果你想了解更多关于项目完成小结:使用Blazor和gRPC开发大模型客户端的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
可不可以命名为MVVM模型?因为View通过查看模型数据。 View 是否应该只与 ViewModelData 交互?我确实在某处读到正确的 MVVM 模型应该在 ViewModel 而不是 Mode
我正在阅读有关设计模式的文章,虽然作者们都认为观察者模式很酷,但在设计方面,每个人都在谈论 MVC。 我有点困惑,MVC 图不是循环的,代码流具有闭合拓扑不是很自然吗?为什么没有人谈论这种模式: mo
我正在开发一个 Sticky Notes 项目并在 WPF 中做 UI,显然将 MVVM 作为我的架构设计选择。我正在重新考虑我的模型、 View 和 View 模型应该是什么。 我有一个名为 Not
不要混淆:How can I convert List to Hashtable in C#? 我有一个模型列表,我想将它们组织成一个哈希表,以枚举作为键,模型列表(具有枚举的值)作为值。 publi
我只是花了一些时间阅读这些术语(我不经常使用它们,因为我们没有任何 MVC 应用程序,我通常只说“模型”),但我觉得根据上下文,这些意味着不同的东西: 实体 这很简单,它是数据库中的一行: 2) In
我想知道你们中是否有人知道一些很好的教程来解释大型应用程序的 MVVM。我发现关于 MVVM 的每个教程都只是基础知识解释(如何实现模型、 View 模型和 View ),但我对在应用程序页面之间传递
我想realm.delete() 我的 Realm 中除了一个模型之外的所有模型。有什么办法可以不列出所有这些吗? 也许是一种遍历 Realm 中当前存在的所有类型的方法? 最佳答案 您可以从您的 R
我正在尝试使用 alias 指令模拟一个 Eloquent 模型,如下所示: $transporter = \Mockery::mock('alias:' . Transporter::class)
我正在使用 stargazer 创建我的 plm 汇总表。 library(plm) library(pglm) data("Unions", package = "pglm") anb1 <- pl
我读了几篇与 ASP.NET 分层架构相关的文章和问题,但是读得太多后我有点困惑。 UI 层是在 ASP.NET MVC 中开发的,对于数据访问,我在项目中使用 EF。 我想通过一个例子来描述我的问题
我收到此消息错误: Inceptionv3.mlmodel: unable to read document 我下载了最新版本的 xcode。 9.4 版测试版 (9Q1004a) 最佳答案 您没有
(同样,一个 MVC 验证问题。我知道,我知道......) 我想使用 AutoMapper ( http://automapper.codeplex.com/ ) 来验证我的创建 View 中不在我
需要澄清一件事,现在我正在处理一个流程,其中我有两个 View 模型,一个依赖于另一个 View 模型,为了处理这件事,我尝试在我的基本 Activity 中注入(inject)两个 View 模型,
如果 WPF MVVM 应该没有代码,为什么在使用 ICommand 时,是否需要在 Window.xaml.cs 代码中实例化 DataContext 属性?我已经并排观看并关注了 YouTube
当我第一次听说 ASP.NET MVC 时,我认为这意味着应用程序由三个部分组成:模型、 View 和 Controller 。 然后我读到 NerdDinner并学习了存储库和 View 模型的方法
Platform : ubuntu 16.04 Python version: 3.5.2 mmdnn version : 0.2.5 Source framework with version :
我正在学习本教程:https://www.raywenderlich.com/160728/object-oriented-programming-swift ...并尝试对代码进行一些个人调整,看看
我正试图围绕 AngularJS。我很喜欢它,但一个核心概念似乎在逃避我——模型在哪里? 例如,如果我有一个显示多个交易列表的应用程序。一个列表向服务器查询匹配某些条件的分页事务集,另一个列表使用不同
我在为某个应用程序找出最佳方法时遇到了麻烦。我不太习惯取代旧 TLA(三层架构)的新架构,所以这就是我的来源。 在为我的应用程序(POCO 类,对吧??)设计模型和 DAL 时,我有以下疑问: 我的模
我有两个模型:Person 和 Department。每个人可以在一个部门工作。部门可以由多人管理。我不确定如何在 Django 模型中构建这种关系。 这是我不成功的尝试之一 [models.py]:
我是一名优秀的程序员,十分优秀!