AI对话页的流式处理架构:基于Web Streams+Fetch API的实践

AI对话页的流式处理架构:基于Web Streams+Fetch API的实践

引言

        当前AI浪潮下,基于各大agent平台,我们可以在几分钟内就搭建出一个具备页面交互的智能体,从问答输出到页面交互,这个过程中的数据流转、UI实现被统一封装以降低模型搭建复杂度。为了探索这个过程的底层实现,我们采用“生产者-消费者模式”的流式处理架构,将网络IO、数据解码、文本解析与UI渲染解耦,实现实时流式响应、UI增量渲染。

使用框架如下:

  • 前端框架:Vue 3 + TypeScript + Vite
  • UI组件库:Ant Design Vue、Ant Design X Vue
  • 流处理:Web Streams API + Fetch API

        从请求发送到UI渲染,流程如下:

流式响应处理

请求管理

  1. 采用 AbortControllerReadableStreamDefaultReader 实现“上游网络请求中止”和“下游字节流读取控制”,共同实现一次会话的可取消、可停止的流式处理。
  2. AbortController:管理当前请求的“上游网络中止”句柄,用于在开始新的提问前中止上一轮未完成的请求,或用户点击取消时终止本次请求。
  3. ReadableStreamDefaultReader:管理“下游传输层字节流”的读取器句柄,用来驱动上游生产者向管道入队字节块,以及在用户点击取消时终止字节读取。
// 流控制相关句柄 let abortController: AbortController | null = null; let currentReader: ReadableStreamDefaultReader<Uint8Array> | null = null;

流处理管道

        建立 Web Streams 流式解析管线:生产者 → 解码 → 按行拆分 → 消费者。整体处理流程如下所示:

生产者流

        负责把上游 reader 的chunk字节块统一按流的背压节奏入队,供下游统一消费,实现“读-推送”连续循环。并将下游或外部触发的取消信号正确传播到上游,终止读取链路。当外部状态指示停止或上游耗尽时,关闭控制器并复位响应状态,保证资源释放与状态一致性。

实现原理可参考文档:https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream

 const producerStream = new ReadableStream<Uint8Array>({ start(controller) { function pump() { if (!isResponding.value) { controller.close(); return; } currentReader?.read().then(({ done, value }) => { if (done) { isResponding.value = false; controller.close(); return; } controller.enqueue(value); // 推送字节块 pump(); }); } pump(); }, });

转换流

        当上游将生产的字节块入队后,我们构建一条流式处理管道,兼容粘包/半包:先将上游的二进制字节流解码为字符串流,再定义转换流(TransformStream类型)按行拆分并过滤空行,确保下游以“完整且非空的文本行”为单位消费数据。并在流结束的 flush() 钩子中,再次冲刷缓冲区,以处理可能残留的最后一行,避免丢失收尾数据,最后得到的是由一对可读流和可写流组成的TransformStream

const textStream = producerStream.pipeThrough(new TextDecoderStream() as unknown as TransformStream<Uint8Array, string>); let; const lineSplitter = new TransformStream<string, string>({ transform(chunk, controller) { buffer += chunk; const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { const trimmed = line.trim(); if (trimmed) controller.enqueue(trimmed); } }, flush(controller) { const trimmed = buffer.trim(); if (trimmed) controller.enqueue(trimmed); }, });

SSE解析流

        构建SSE 消费者读取器并驱动 UI 增量渲染,读取时若 done = true,表示“传输层的流”已经真正结束(数据源已关闭,后续不会再有字节到来),这时调用 releaseLock 释放读取器的独占锁;若读取到 message === '[DONE]',表示应用层的结束,业务上不再需要继续读取,但此时连接/流不一定已被对端关闭,后续仍可能存在空闲或遗留的字节,主动调用 cancel 终止读取,cancel() 方法返回一个 Promise,这个Promise 在流被取消时兑现,消费者在流中调用该方法发出取消流的信号。

const sseStream = textStream.pipeThrough(lineSplitter); // ReadableStream const downstreamReader = sseStream.getReader(); // ReadableStreamDefaultReader function consume() { downstreamReader.read().then(({ done, value }) => { if (done) { isResponding.value = false; downstreamReader.releaseLock(); return; } const message = value.replace(/^data:\s*/, ''); if (message === '[DONE]') { …… } try { const parsed = JSON.parse(message); const content = parsed?.choices?.[0]?.delta?.content; if (content) { streamReply += content; // 消息增量渲染 if (chatList.value.length < index + 1) { const newReply: ChatItem = { key: index, role: 'assistant', content: streamReply, }; chatList.value.push(newReply); } else { chatList.value[index]!.content = streamReply; } } } catch (e) { console.warn('SSE parse error:', e); } consume(); }); } consume();

        整体数据流转如下所示:

API通信层

发送请求

        以qwen-plus模型为例,其接入方式如下:

async function fetchReply(list: ChatItem[], signal?: AbortSignal) { const messages = list.map(({ role, content }) => ({ role, content })); 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, stream: true, }), signal, }); }

中止请求

  1. 重置 UI 响应状态:将会话状态标记为非响应中,避免界面继续显示“正在响应”等状态提示
  2. 终止流式读取:如果存在当前的流读取器,则主动取消读取,防止继续从流中消费数据,并清理引用
  3. 终止网络请求:如果存在未完成的流,则触发终止,并清理引用
const handleCancel = () => { isResponding.value = false; // 终止读取 if (currentReader) { try { currentReader.cancel('user canceled'); } catch (e) { console.error(e); } currentReader = null; } // 终止请求 if (abortController) { try { abortController.abort('user cancellation'); } catch (e) { console.error(e); } abortController = null; } message.error('已取消发送'); };

基础UI交互组件封装

        UI设计主要基于Ant Design VueAnt Design X Vue组件库,其中 Ant Design X Vue 专注于Vue生态的先进AI组件库,旨在简化对话式AI应用的开发,同时支持tstsx

消息展示

  1. 功能:展示对话历史,支持多角色渲染,支持Markdown渲染。
  2. 处理 markdown 输出渲染:
import { h } from 'vue'; import { type BubbleProps } from 'ant-design-x-vue'; import { Typography } from 'ant-design-vue'; import markdownit from 'markdown-it'; const md = new markdownit({ html: false, breaks: true, linkify: true, typographer: true }); const renderMarkdown: BubbleProps['messageRender'] = (content) => h(Typography, null, { default: () => h('div', { innerHTML: md.render(content) }), });

3. 角色映射配置:

import { UserOutlined } from '@ant-design/icons-vue'; import { h } from 'vue'; const rolesAsObject = { 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' } }, }, system: { placement: 'start', avatar: { icon: h(UserOutlined), style: { background: '#d9d9d9' } }, styles: { maxWidth: '600px', }, messageRender: renderMarkdown, }, } as const;

4.  适配 BubbleList 组件的 roles 类型处理

const bubbleListRoles = rolesAsObject as NonNullable<BubbleListProps['roles']>; const bubbleItems = computed(() => props.chatList.map((m, idx) => { type RoleKey = keyof typeof rolesAsObject; const roleKey = (m.role in rolesAsObject ? m.role : 'assistant') as RoleKey; return { key: m.key ?? idx, role: m.role, // 绑定role,对应rolesAsObject中的配置项 placement: rolesAsObject[roleKey].placement, avatar: rolesAsObject[roleKey].avatar, content: m.content, }; }) );

5. 最后将 bubbleItems 和 bubbleListRoles 传入 BubbleList 组件
 

// ChatBubble组件 <template> <BubbleList :items="bubbleItems" :roles="bubbleListRoles" /> </template> // 使用方法 <ChatBubble :chat-list="chatList" :md-render="false" />

消息发送

  1. 功能:支持消息输入、发送控制、状态展示
<template> <Sender :value="props.inputText" @update:value="onUpdateValue" :loading="props.isResponding" :auto-size="{ minRows: 2, maxRows: 6 }" :onSubmit="handleSubmit" :onCancel="handleCancel" /> </template> <script setup lang="ts"> import { Sender } from 'ant-design-x-vue'; const props = defineProps({ inputText: String, isResponding: Boolean, }); const emit = defineEmits(['submit', 'cancel', 'update:inputText']); const onUpdateValue = (val: string) => { emit('update:inputText', val); }; const handleSubmit = () => { emit('submit', props.inputText); }; const handleCancel = () => { emit('cancel'); }; </script>

        以上是AI对话页中最基础也是必不可少的部分,基于业务背景和用户体验提升,我们还可以添加更多的交互配置,比如还可以使用 vue-clipboard3 库中的 toClipboard 方法实现一键复制功能,等等。

Read more

Nanbeige4.1-3B快速上手:用curl命令直连WebUI API完成批量推理任务

Nanbeige4.1-3B快速上手:用curl命令直连WebUI API完成批量推理任务 1. 引言:为什么需要绕过WebUI直接调用API? 如果你已经通过WebUI体验过Nanbeige4.1-3B的强大能力,可能会遇到这样的场景:需要一次性处理几十甚至上百个文本任务,比如批量生成产品描述、分析大量用户反馈,或者为数据集中的每条记录生成摘要。这时候,如果还在WebUI里一条条手动输入、点击生成,效率就太低了。 有没有更高效的方法?当然有。Nanbeige4.1-3B的WebUI背后,其实是一个标准的HTTP API服务。这意味着,我们可以直接用命令行工具(比如curl)或者写个简单的脚本,直接和这个API“对话”,实现自动化、批量化的文本生成。这就像是从手动拧螺丝升级到了电动螺丝刀,效率提升不是一点半点。 这篇文章,我就带你绕过漂亮的WebUI界面,直连背后的API引擎,用最朴素的curl命令,解锁Nanbeige4.1-3B的批量推理能力。你会发现,原来命令行操作大模型,可以如此简单直接。 2. 准备工作:确认你的WebUI服务正在运行 在开始“飙车”之前,得先

前端 SSG:别让你的网站加载速度慢得像蜗牛

前端 SSG:别让你的网站加载速度慢得像蜗牛 毒舌时刻 这网站加载速度慢得能让我泡杯咖啡回来还没好。 各位前端同行,咱们今天聊聊前端 SSG(静态站点生成)。别告诉我你还在使用纯客户端渲染,那感觉就像在没有预加载的情况下开车——能开,但起步慢得要命。 为什么你需要 SSG 最近看到一个项目,每次加载都要重新获取数据,用户体验差。我就想问:你是在做网站还是在做实时应用? 反面教材 // 反面教材:纯客户端渲染 // App.jsx import React, { useState, useEffect } from 'react'; function App() { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchPosts() { setLoading(

ReactNative for Harmony 项目鸿蒙化三方库集成实战:react-native-webview

ReactNative for Harmony 项目鸿蒙化三方库集成实战:react-native-webview

📋 前言 react-native-webview 是 React Native 社区最流行的 WebView 组件,提供了一套完整的网页加载和交互解决方案。它支持加载网络 URL、本地 HTML 文件、注入 JavaScript、处理网页事件等多种功能,并且完全兼容 Android、iOS 和 HarmonyOS 三端。 🎯 库简介 基本信息 * 库名称: @react-native-ohos/react-native-webview * 版本信息: * 13.10.5: 支持 RN 0.72 版本 * 13.15.1: 支持 RN 0.77 版本 * 官方仓库: https://github.com/react-native-oh-library/

Java Web 开发学习Day2 数据库知识复习与整理(黑马程序员网课知识总结)

引言: web开发调用流程 * SQL(Structured Query Language,简称SQL):结构化查询语言,它是操作关系型数据库的编程语言,定义了一套操作关系型数据库的统一标准。 * 程序员给数据库管理系统(DBMS)发送SQL语句,再由数据库管理系统操作数据库当中的数据。 1. MySQL概述 2. SQL语句(DDL、DML、DQL) 3. 多表设计 4. 多表查询 5. 事务 6.  索引 1.MySQL概述 1.1安装(省略) 1.2连接 MySQL服务器启动完毕后,然后再使用如下指令,来连接MySQL服务器: mysql -u用户名 -p密码 [-h数据库服务器的IP地址 -P端口号] * -h 参数不加,默认连接的是本地 127.0.0.