实现微信小程序与服务端流式数据交互:打造实时打字效果

https://file-one.7k7s.com//uploads/20240604/89f56a7378e381410f4dfcfab3948775.jpg
陈杰 案例分析 发布于1年前 更新于1年前 1.3K+

小程序端

1.首先我们定义wxml文件的内容
<view class="container">
  <label for="textInput">Prompt:</label>
  <textarea id="textInput" placeholder="您有什么问题" bindinput="bindTextInput" class="prompt-text"></textarea>
  <van-button type="primary" bindtap="runPrompt" class="prompt-button">执行prompt</van-button>
  <div class="answer">{{answer}}</div>
</view>
2.定义简单的wxss样式
page{
  background-color: #f4f6f8;
  height: 100%;
}
.prompt-button{
  margin-top: 1rem;
}
.prompt-text{
  margin-top: 1rem;
  border: 1px solid #fcf;
  padding: 1rem;
}
.answer {
  margin-top: 1rem;
  padding:1rem ;
  white-space: pre-wrap; /* 保留换行符和空白字符 */
  overflow: hidden;
  background-color: #fff;
  border-radius: 6px;
  width: 80%;
}

3.页面

** 接下来要实现的功能是 **

  • 用户输入问题
  • 点击prompt按钮提交
  • 文本以打字机效果出现在白色区域
4.js文件

主要设置 enableChunked: true,

Page({
  data: {
    answer: '',
    textInput: '写一篇五十字的作文',
    polling: true,
    serverUrl: 'http://127.0.0.1:7661/api/chat/stream', // 替换为你的服务端地址
    typingBuffer: [],
    typingInterval: null,
    isTyping: false // 当前是否在执行打字效果
  },

  runPrompt: function () {
    const inputValue = this.data.textInput;
    this.setData({
      answer: '',
      polling: true,
      typingBuffer: [],
      isTyping: false
    });
    this.sendPollingRequest({ 'prompt': inputValue });
  },

  sendPollingRequest: function (data) {
    const that = this;
    if (!this.data.polling) return;

    const requestTask = wx.request({
      url: this.data.serverUrl,
      method: 'POST',
      enableChunked: true,// 一定设置
      header: {
        'content-type': 'application/json'
      },
      data: JSON.stringify(data),
      success: function (res) {
        console.log("Request success:", res);
      },
      fail: function (err) {
        console.error("Request error:", err);
        that.setData({ polling: false });
      }
    });


    requestTask.onChunkReceived(res => {
      const textDecoder = new TextDecoder('utf-8');
      const chunkText = textDecoder.decode(new Uint8Array(res.data));
	  
      console.log('chunkText:',chunkText);
    });
  },

  bindTextInput: function (e) {
    this.setData({
      textInput: e.detail.value
    });
  }
});

截止此处 就可以看到服务端返回的数据

  • 此处的res如果不做转化 是无法直接显示文文字的 我们使用TextDecoder 进行转化
  • 我们可以就此处对chunkText进行后续处理 打字机效果 或者直接展示于页面也好
  requestTask.onChunkReceived(res => {
      const textDecoder = new TextDecoder('utf-8');
      const chunkText = textDecoder.decode(new Uint8Array(res.data));
	  
      console.log('chunkText:',chunkText);
    });

因为服务端返回数据是类似下边的字符串,所以要进行截取转化最后打印机效果展现

{"content":"当然可以。","type":"text"}{"content":"以下是一篇约150字的文章:\n\n随着数字化时代的到来,人工智能在企业营销领域扮演着越来越重要的角色。","type":"text"}

每个服务端返回的数据不一样,我们在拿到数据以后自行处理

以下是完整的js代码

Page({
  data: {
    answer: '',
    textInput: '写一篇五十字的作文',
    polling: true,
    serverUrl: 'http://127.0.0.1:7661/api/chat/stream', // 替换为你的服务端地址
    typingBuffer: [],
    typingInterval: null,
    isTyping: false // 当前是否在执行打字效果
  },

  runPrompt: function () {
    const inputValue = this.data.textInput;
    this.setData({
      answer: '',
      polling: true,
      typingBuffer: [],
      isTyping: false
    });
    this.sendPollingRequest({ 'prompt': inputValue });
  },

  sendPollingRequest: function (data) {
    const that = this;
    if (!this.data.polling) return;

    const requestTask = wx.request({
      url: this.data.serverUrl,
      method: 'POST',
      enableChunked: true,
      header: {
        'content-type': 'application/json'
      },
      data: JSON.stringify(data),
      success: function (res) {
        console.log("Request success:", res);
      },
      fail: function (err) {
        console.error("Request error:", err);
        that.setData({ polling: false });
      }
    });

    let buffer = ''; // 用于存储不完整的JSON字符串

    requestTask.onChunkReceived(res => {
      const textDecoder = new TextDecoder('utf-8');
      const chunkText = textDecoder.decode(new Uint8Array(res.data));

      console.log('chunkText:',chunkText);
      buffer += chunkText;



      // 使用正则表达式找到所有完整的JSON对象
      const regex = /(\{.*?\})(?=\{|\s*$)/g;
      let match;
      let lastProcessedIndex = 0;

      while ((match = regex.exec(buffer)) !== null) {
        try {
          const jsonStr = match[1];
          const json = JSON.parse(jsonStr);

          if (json.type === 'text') {
            that.data.typingBuffer.push(...json.content.split('')); // 将内容逐字符添加到缓冲区
            // console.log('Updated typingBuffer:', that.data.typingBuffer);
          }

          lastProcessedIndex = regex.lastIndex;
        } catch (e) {
          console.error('JSON解析错误:', e);
        }
      }

      // 清除已经处理的部分
      buffer = buffer.slice(lastProcessedIndex);

      if (!that.data.isTyping) {
        that.typingEffect(); // 触发打字效果
      }
    });
  },

  typingEffect: function () {
    const that = this;

    if (this.data.isTyping) {
      return;
    }

    this.setData({ isTyping: true });

    this.data.typingInterval = setInterval(() => {
      if (that.data.typingBuffer.length > 0) {
        const char = that.data.typingBuffer.shift(); // 从缓冲区取出一个字符
        // console.log('Typing character:', char);
        that.setData({
          answer: that.data.answer + char
        });
      } else {
        console.log('Buffer empty, clearing interval.');
        clearInterval(that.data.typingInterval);
        that.setData({ isTyping: false });
      }
    }, 100); // 调整间隔时间以控制打字速度
  },

  bindTextInput: function (e) {
    this.setData({
      textInput: e.detail.value
    });
  }
});

服务端

服务端就不多做解释了 主要是返回头

     'Content-Type'      => 'text/event-stream',  // 必须
     'Cache-Control'     => 'no-cache',
     'Connection'        => 'keep-alive',
     'Transfer-Encoding' => 'chunked', // 必须

最终效果

此文章仅做学习参考使用,其逻辑自行修改 总结就是wx.request 开启 enableChunked: true, 配置 ,以onChunkReceived 读取数据 并实时显示在页面上

THE END

喜欢就支持一下吧!

版权声明:除却声明转载或特殊注明,否则均为艾林博客原创文章,分享是一种美德,转载请保留原链接,感谢您的支持和理解

读书人上厕所时间长,那不是干肠,是在蹲坑读书;读书人最能忍受老婆的嘟嚷,也不是脾气好,是读书人入了迷两耳如塞。吃饭读书,筷子常会把烟灰缸的烟头送进口里,但不易得脚气病,因为读书时最习惯抠脚丫子。可怜都是蜘蛛般的体形,都是金鱼似的肿眼,没个倾国倾城貌,只有多愁多病身。

贾平凹

推荐阅读

现代接口安全实战:从加密到防滥用的全栈策略

很多人以为接口加了个 API-Key 或 JWT 就算“安全”。其实现代 API 安全从来不靠某一种“工具”,而是靠传输...

https://file-one.7k7s.com//uploads/20240604/89f56a7378e381410f4dfcfab3948775.jpg
陈杰 07月04日

探索实用宝藏网站:提升效率的工具集锦

在这篇文章中,我们将分享一些提升工作效率的宝藏网站,包括AI生成PPT、Markdown在线转HTML、AI在线生成Wo...

https://file-one.7k7s.com//uploads/20240604/89f56a7378e381410f4dfcfab3948775.jpg
陈杰 07月25日

深入探索PHP面向对象编程

探索PHP面向对象编程(OOP)的核心概念,包括类和对象的定义、继承、接口、抽象类、特质、匿名类等,通过具体案例深入理解...

https://file-one.7k7s.com//uploads/20240604/89f56a7378e381410f4dfcfab3948775.jpg
陈杰 03月18日

PHP 运算符大全(完整详细版)

本文详细介绍了 PHP 中的各种运算符,包括算术运算符、赋值运算符、比较运算符、逻辑运算符、位运算符、字符串运算符、数组...

https://file-one.7k7s.com//uploads/20240604/89f56a7378e381410f4dfcfab3948775.jpg
陈杰 06月17日

PHP常用数组函数解析

PHP常用数组函数解析,完整解析核心函数,包含参数类型、模式常量、多维数组处理等高级技巧,提供简单到企业级的实战案例演示...

https://file-one.7k7s.com//uploads/20240604/89f56a7378e381410f4dfcfab3948775.jpg
陈杰 02月26日

查看内存系列命令应用以及介绍【Linux 篇】

在日常运维Linux系统时,物理内存是其中最重要的一方面。Linux 本身提供了少的方法来帮助我们查看相关信息!下面是一...

https://file-one.7k7s.com//uploads/20240604/89f56a7378e381410f4dfcfab3948775.jpg
陈杰 12月08日

深入理解PHP中的面向对象编程(OOP)

本文深入探讨PHP中的面向对象编程概念,包括类、对象、属性、方法、继承、接口、抽象类和特质的使用,以及通过一个简单的博客...

https://file-one.7k7s.com//uploads/20240604/89f56a7378e381410f4dfcfab3948775.jpg
陈杰 03月18日

16个PHP开发者必知必会的魔术方法

本文列举了16个PHP开发者应当掌握的魔术方法,涵盖了它们的定义、使用场景和实现技巧,为PHP开发提供重要参考。

https://file-one.7k7s.com//uploads/20240604/89f56a7378e381410f4dfcfab3948775.jpg
陈杰 03月22日