当前位置: 首页 > news >正文

给web增加简单的ai对话功能

自从 ChatGPT 火了之后,越来越多人开始对 AI 感兴趣,AI 的使用也越来越普遍了。现在你随便点开个知名网站或者 APP,基本都能看到 AI 的影子,而且这些 AI 大多都是用 “问答” 的形式跟人互动,说白了就是 “聊天”。

当然啦,现在的 AI 也不是完美的,比如有时候会瞎编东西(就是大家说的 “AI 幻觉”),但给 APP 加个 AI 功能,确实能让它变好玩、互动感更强。那咱们自己的 APP,怎么快速加上 AI 功能呢?其实不用自己从头搞,直接用现成平台提供的模型和 API 就行,今天就来聊聊怎么用阿里云百炼。

它是阿里云出的大模型服务平台,把复杂的技术都打包好了,就算你没什么 AI 基础,也能很快把 AI 功能集成到自己的 APP 里。

以下内容可配合视频一起食用

前端基础项目搭建

首先我们先搭建一个基础的前端项目,我这里用的是vue框架,UI组件使用了ant-design-vue和ant-design-x-vue,其中ant-design-x-vue增加了对ai交互的支持,可以让开发效率更高。

接下来我们就来尝试「直接」使用通义千问API来实现ai对话功能。

前置准备

在编写代码之前,我们先完成一些准备工作,注册登录,以及获取API key。

构造请求并显示返回结果

然后我们就可以开始构造请求进行API的调用了。

// ...
const handleSubmit = message => {const newMessage: chatItem = {key: chatList.value.length,role: 'user',content: message,};chatList.value.push(newMessage);fetchReply();
}const fetchReply = async () => {return fetch( // +'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', // +{ // +method: 'POST', // +headers: { // +Authorization: `Bearer ${import.meta.env.VITE_ALIYUN_API_APPKEY}`, // +'Content-Type': 'application/json', // +}, // +body: JSON.stringify({ // +model: 'qwen-plus', // +messages: chatList.value // +}) // +} // +) // +
};
// ...

参数model是指定了我们使用的模型,我们还给接口传递了一个messages的消息数组,这是因为通义千问API是无状态的,它不会自动记录历史对话,所以要实现多轮对话,就需要在每次请求中显式地传递完整的上下文信息,很显然这样随着聊天记录增加,请求体会变得越来越大,文档后面呢也提供了一些优化策略,不过这不是我们今天的重点。

现在我们刷新页面试着发送消息去请求一下API,就可以看到接口返回的ai消息了,消息的内容就在choices数组里message的content属性里。

ai_api2

我们把它显示到页面上就可以了。

const handleSubmit = message => {const newMessage: chatItem = {key: chatList.value.length,role: 'user',content: message,};chatList.value.push(newMessage);fetchReply() // M.then (response => response.json()) // +.then(result => { // +const newReply: chatItem = { // +key: chatList.value.length, // +role: 'assistant', // +content: result.choices[0].message.content, // +}; // +chatList.value.push(newReply); // +}); // +
}

ai_chat1

问题1:长时间等待

这时我们再发送消息,让ai输出更详细的自我介绍,可以注意到明显地有一段比较长的等待时间,这是因为这次返回的内容比较多。

ai_chat2

解决:改为流式输出

为了能有更好的用户体验,我们可以将返回的形式改为流式输出,在请求参数里增加stream参数的设置,将它设置为true。

const fetchReply = async () => {return fetch('https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',{method: 'POST',headers: {Authorization: `Bearer ${import.meta.env.VITE_ALIYUN_API_KEY}`,'Content-Type': 'application/json',},body: JSON.stringify({model: 'qwen-plus',messages: chatList.value,stream: true // +})})
};

当我们再次请求时,会发现这个请求返回的内容和普通的请求不一样,network显示的请求里多了一个tab,标题是EventStream。

ai_api

这是因为我们设置了流式输出。流式输出通过持续返回模型生成的文本片段,可以提供给用户更好的应用体验,避免长时间的等待,那我们要怎么处理这类请求返回的流式内容呢?

这一类请求我以前也没有处理过,所以我找了MDN的文档来参考,MDN上有一个关于fetch API response的文档:ReadableStream,对返回的流式内容进行处理,我们把它复制过来。

const handleSubmit = message => {const newMessage: chatItem = {key: chatList.value.length,role: 'user',content: message,};chatList.value.push(newMessage);const index = chatList.value.length; // +let reply = ''; // +fetchReply()// .then (response => response.json())// .then(result => {//   const newReply: chatItem = {//     key: chatList.value.length,//     role: 'assistant',//     content: result.choices[0].message.content,//   };//   chatList.value.push(newReply);// });.then(response => response.body).then(rb => {const reader = rb.getReader();return new ReadableStream({start(controller) {// The following function handles each data chunkfunction push() {// "done" is a Boolean and value a "Uint8Array"reader.read().then(({ done, value }) => {// If there is no more data to readif (done) {console.log("done", done);controller.close();return;}// Get the data and send it to the browser via the controllercontroller.enqueue(value);// Check chunks by logging to the console// console.log(done, value); // Mconst decoder = new TextDecoder(); // +const decodedString = decoder.decode(value); // +console.log(decodedString); // +push();});}push();},});});
}

我们看到这里的注释里写着,value是Uint8Array,也就是无符号整型数组,所以我们需要对他进行解析,这里我用了TextDecoder来解析,最后打印的decodedString就是解析出来的文本内容,我们先去请求一下,看一下这个内容是什么样子的。

ai_chat4

可以看到decodedString打印出来的是一个多行的内容,每一行内容都是以data:开头,消息的最后一行是一个[done]的标识。

那么我们接下来就对decodedString进行处理,将它返回的多行的内容取出来,再进行拼接。

我们刚才看到,消息的最后会返回一个data: [DONE]表示回答结束,这条内容是我们需要过滤掉的。

// ...
// console.log(decodedString);
const lines = decodedString.split('\n').filter(line => line.trim() !== ''); // +
for (const line of lines) { // +const message = line.replace(/^data: /, ''); // +if (message === '[DONE]') { // +console.log("Stream finished"); // +return; // +} else { // +const parsed = JSON.parse(message); // +const content = parsed.choices[0].delta.content; // +if (content) { // +reply += content; // +if (chatList.value.length < index + 1) { // +const newReply: chatItem = { // +key: index, // +role: 'assistant', // +content: reply, // +}; // +chatList.value.push(newReply); // +} else { // +chatList.value[index].content = reply; // +} // +} // +} // +
} // +
push();
// ...

这个时候我们再去重新开启对话,可以看到请求很快就得到了响应,并把内容一点点的显示到对话框里,这时候的用户体验就比一次性获得消息好很多。

问题2:markdown内容的显示

到这里呢,基本功能就完成了,但是页面上还存在一个很明显的问题,我们可以看到,ai返回的消息它其实是一段markdown格式的内容,如果就这样直接显示出来、显然并不太好,那么我们可以加入一个markdown-it的包,增加对markdown内容的显示处理。

解决:使用markdown-it处理

ant-design-x-vue这个UI库这里也提供了支持,可以直接按照文档来进行配置

// ...
import type { BubbleListProps, BubbleProps } from "ant-design-x-vue"; // M
// ...
import { Typography } from 'ant-design-vue'; // +
import markdownit from 'markdown-it'; // +const md = markdownit({ html: true, breaks: true }); // +
const renderMarkdown: BubbleProps['messageRender'] = (content) => // +h(Typography, null, { // +default: () => h('div', { innerHTML: md.render(content) }), // +}); // +const rolesAsObject: BubbleListProps['roles'] = {'assistant': {placement: 'start',avatar: { icon: h(UserOutlined), style: { background: '#fde3cf'} },typing: { step: 5, interval: 20 },styles: {maxWidth: '600px',},messageRender: renderMarkdown, // +},'user': {placement: 'end',avatar: { icon: h(UserOutlined), style: { background: '#87d068' } },},
};

这时我们再去发起一次对话看一下效果。

可以看到返回的markdown格式的内容在页面上的显示已经是经过转换后的了。到这里呢我们就实现了简单的ai对话功能,这也是我第一次接触,所以还没有更深入的研究,感兴趣的同学也可以动手尝试一下呀。

ai_chat

http://www.jsqmd.com/news/22260/

相关文章:

  • 2025 年食堂厨房设备,商用厨房设备,酒店厨房设备厂家推荐:江南星公司的全场景设备与服务解决方案解析
  • 权威调研榜单:气动旋塞阀厂家TOP3榜单好评深度解析
  • 2025年10月离心干燥机厂家全景解析报告,基于专业测评的技术、性能及市场优势深度分析
  • Jetbrains IDEA or Webstorm新版本回退到之前的UI风格
  • session、cookie、token的区别
  • 85-python电网可视化项目-5 - 详解
  • 解码Linux文件IO之开机动画原理与实现
  • P2135 方块消除 题解
  • 2025 年展会展台搭建设计公司最新推荐榜,聚焦服务能力与专业水平深度解析美国‌/法国/德国/俄罗斯/英国/日本/泰国/迪拜/西班牙/丹麦/挪威展会展台搭建设计商推荐指南
  • 2025年10月心式喷雾干燥机厂家全景解析报告,基于专业测评的技术、性能及市场优势深度分析
  • 2025 年液态硅胶设备厂家最新推荐榜,技术实力与市场口碑深度解析
  • 2025 年不锈钢通风柜厂家最新推荐榜:聚焦企业专利技术、品质管控及知名客户合作案例的权威解析
  • 2025 年不锈钢管制造厂家最新推荐榜,深度剖析企业技术实力与市场口碑展现优质品牌不锈钢矩形管/不锈钢管材/不锈钢异行管/不锈钢毛细管公司推荐
  • 2025 年阳台光伏品牌最新推荐榜,技术实力与市场口碑深度解析产品/阳台太阳能光伏/储能/发电/阳台光伏板优质厂家推荐
  • 使用戴尔T3680工作站与DoraCloud搭建多人虚拟工作站
  • 2025 年最新推荐炼铅炉实力厂家排行榜:含废电瓶 / 反射 / 大型等类型设备,权威测评下优质品牌盘点
  • 权威调研榜单:自动控制器厂家TOP3榜单好评深度解析
  • 2025 年异构烷供应厂家推荐:异构十二烷、异构十四烷、异构十六烷、异构二十烷最新推荐榜,聚焦企业技术实力与市场口碑深度解析
  • 2025 年冲压油供应厂家最新推荐榜,聚焦技术实力与市场口碑深度解析锈钢/翅片/高速/挥发性/免清洗冲压油厂家推荐
  • 面试清单:JVM类加载与虚拟机执行核心障碍
  • 2025年热门的用地预审技术服务供应商、市面上用地预审技术服务公司、行业内用地预审技术服务品牌、市场用地预审技术服务方案、2025年用地预审技术服务单位综合评测
  • 湿地铁魂与盐韵共生:盐城城市旅游宣传片的专业化叙事构建 - 指南
  • Microsoft AI Genius | 用智能 Microsoft Copilot 副驾驶 构建高韧性 DevOps 流程
  • 2025 年对焊机源头厂家最新推荐排行榜权威发布,含闪光 / 成型 / 打圈 / T 型 / 双头 T 型对焊机行业口碑榜单
  • 2025年市面上双曲铝单板品牌、行业内双曲铝单板厂家、市场双曲铝单板产品、目前双曲铝单板供应商、口碑好的双曲铝单板公司排行榜
  • 2025市面上双曲铝单板品牌、行业内双曲铝单板厂家、市场双曲铝单板产品、口碑好的双曲铝单板厂家、2025年双曲铝单板供应商权威排名
  • 2025市面上双曲铝单板品牌、行业内双曲铝单板生产厂家、市场双曲铝单板供应厂家、目前双曲铝单板实力厂家、口碑好的双曲铝单板公司排行榜
  • 2025 年调直机厂家最新推荐排行榜权威发布:聚焦伺服 / 高速 / 铁线 / 扁铁机型,揭秘行业优质企业
  • 2025年10月杭州茅台酒回收服务商全景解析报告,基于专业测评的技术、性能及市场优势深度分析
  • 2025年市面上中压电缆品牌、行业内中压电缆公司、口碑好的中压电缆品牌、有实力的中压电缆产品、中压电缆公司推荐榜单深度解析