项目完成小结:使用Blazor和gRPC开发大模型客户端

项目,完成,小结,使用,blazor,grpc,开发,模型,客户端 · 浏览次数 : 477

小编点评

内容生成时需要带简单的排版,例如: 1. **将所有代码块排版到同一一行** 2. **使用缩进来缩短代码** 3. **使用分隔符将不同代码块分隔到不同的行** 4. **使用引号来引起来字符串** 5. **使用空格来空格代码** 例如: ```csharp //代码块 if (condition) { //代码 } //代码块 //使用缩进 string code = code.Trim(); //代码块 //使用分隔符 string[] lines = code.Split("\n"); //代码块 //使用引号 string codeWithQuotes = $"string"; //代码块 //使用空格 string codeWithSpaces = "code"; ``` 希望以上内容能帮助您生成内容时带简单的排版。

正文

前言

先介绍下这个项目。

最近我一直在探索大语言模型,根据不同场景训练了好几个模型,为了让用户测试使用,需要开发前端。

这时候,用 Gradio 搭建的前端是不太够的,虽说 GitHub 上也有一堆开源的 ChatGPT 前端,但我看了一圈,并没有找到便于二次开发定制的,再一想,这么简单的功能,自己做就好啦,何必去 GitHub copy 呢?

那么就直接开始吧~

一开始我打算用 React 来做前端,然后使用 websocket 或者 eventsource 来实现聊天的打字机效果,但转念一想,不是有 Blazor Server 吗,这东西本来就和服务器建立了长链接,根本不需要折腾,于是决定试试。

先看效果

PC 端

主页 聊天界面
image image

移动端

主页 聊天界面
image image

项目设计

项目架构图

项目架构图

后端使用 gRPC 与各个模型的服务连接,然后使用 Blazor 来实现聊天功能。

关于 gRPC 的使用,在之前写的这篇博客: Asp-Net-Core学习笔记:gRPC快速入门

关于 Blazor

其实几年前我就有轻度使用了一下 Blazor 这个技术,详见文章: Asp-Net-Core学习笔记:4.Blazor-WebAssembly入门

一开始使用 Blazor ,我是有点嫌弃的,我还是比较倾向于传统的前后端分离,AspNetCore用来做后端,用 React 做前端,生态很丰富,要做啥组件都容易。

Blazor 有几个痛点:

  • 每次对界面的一点小修改都需要重新编译才能看到效果
  • 生态比较贫瘠,好用的组件库还比较少
  • 无论 WebAssembly 还是 server rendered 模式,都无法直接操纵 DOM,需要通过 JSRuntime

所以这个项目一开始,我好几次产生了放弃 blazor ,重新用 React 实现的想法,不过随着熟练度提高,反而渐渐觉得,Blazor 好像也不错,开发小应用的效率挺高的。

这几个痛点虽然影响体验,但也不是不能忍受

  • 每次都要编译,是慢了点,那就改完界面休息一下😃 需要反复修改的样式,直接在浏览器的开发者工具里面调整,调到满意再写到代码里
  • 生态贫瘠问题也不大,Blazor 本质上还是前端,虽然 Blazor 原生组件少,但我可以自己写啊,前端生态无所不有,大多数直接拿来改改就可以用了
  • 无法操作 DOM 是比较麻烦,只能多写点 JavaScript 来调用,Blazor 也并不是替代 JS,现在不会 JS 还是没办法做前端的

所以这个项目就这么磕磕绊绊的,边学 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 好一点。

Blazor实现打字机效果

聊天界面需要实现类似 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# 实在是太好用啦~

DOM操作 (JS交互)

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 版本,竟然体验也还不错。

Blazor 组件封装

初步用起来很简单

比如首页的九宫格按钮,我就封装了一个组件

<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开发大模型客户端

## 前言 先介绍下这个项目。 最近我一直在探索大语言模型,根据不同场景训练了好几个模型,为了让用户测试使用,需要开发前端。 这时候,用 Gradio 搭建的前端是不太够的,虽说 GitHub 上也有一堆开源的 ChatGPT 前端,但我看了一圈,并没有找到便于二次开发定制的,再一想,这么简单的功能

C#集成ViewFaceCore人脸检测识别库

前言 人脸检测与识别现在已经很成熟了,C# 上有 ViewFaceCore 这个很方便的库,但这种涉及到 native 调用的库,一般会有一些坑,本文记录一下开发和部署的过程。 本文的项目是 AIHub ,关于本项目的开发过程,可以参考之前的文章:项目完成小结:使用Blazor和gRPC开发大模型客

项目完成小结 - Django-React-Docker-Swag部署配置

前言 最近有个项目到一段落,做个小结记录。 内容可能会多次补充,在博客上实时更新哈~ 如果是在公众号阅读这篇文章,可以点击「查看原文」访问最新版本~ 这个项目是前后端分离,后端为了快,依然用我的DjangoStarter框架。前端一开始是小程序,后面突然换成公众号H5的形式,还好我用了Taro,大差

我的第一个项目(十四) :完成数据保存功能(前端,增查改接口)

好家伙,天天拖,终于写完了 代码已开源(Gitee) PH-planewar: 个人开发的全栈小游戏 前端:vue2 + element-ui 后端: Springboot + mybatis-plus 数据库: mysql 目前实现功能: 1.注册登陆 2.游戏数据保存 3.游戏运行 (gitee

我的第一个项目(十五) :完成数据保存功能(后端,改update)

好家伙, 代码已开源(Gitee) PH-planewar: 个人开发的全栈小游戏 前端:vue2 + element-ui 后端: Springboot + mybatis-plus 数据库: mysql 目前实现功能: 1.注册登陆 2.游戏数据保存 3.游戏运行 (gitee.com) 后端这

从零做软件开发项目系列之三——系统设计

前言 在与客户充分接触后取得需求调研结果,然后分析调研内容,撰写完成项目的需求规格说明书。这是一个正式的文件,需要供需双方签字确认。说明书中会明确需求方的要求和开发方实现的内容,依据需求规格说明书,开发方就要开展系统设计工作。 进行系统设计工作,粗略的可以分成两个阶段,概要设计(总体设计)阶段和详细

Go-Zero从0到1实现微服务项目开发(二)

继续更新GoZero微服务实战系列文章:上一篇被GoZero作者万总点赞了,本文将继续使用 Go-zero 提供的工具和组件,从零开始逐步构建一个基本的微服务项目。手把手带你完成:项目初始化+需求分析+表结构设计+api+rpc+goctl+apifox调试+细节处理。带你实现一个完整微服务的开发。

创建nodejs项目并接入mysql,完成用户相关的增删改查的详细操作

本文为博主原创,转载请注明出处: 1.使用npm进行初始化 在本地创建项目的文件夹名称,如 node_test,并在该文件夹下进行黑窗口执行初始化命令 2. 安装 expres包和myslq依赖包 npm i express@4.17.1 mysql2@2.2.5 Express是一个流行的Web应

nodejs使用eggjs创建项目,接入influxdb完成单表增删改查

转载请注明出处: 1.Eggjs 特性: Eggjs 是 Node.js 服务端应用开发框架,它提供了一套约定,使开发者能够快速搭建、开发和部署应用。以下是 Egg.js 的一些特性和作用: 框架内置了基于约定的目录结构、约定的扩展机制和一些常用的插件,可以帮助开发者快速搭建应用。 Egg.js 遵

你想要的【微前端】都在这里了!

某次遇到一个从0到1的大型项目,该项目涉及两个端,除了鉴权和部分业务逻辑不同外,页面UI和其余逻辑几乎一致,遇到这种项目,该如何架构?既能保证项目顺利开发完成,又能保证后期的迭代、维护、可扩展?