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

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

小程序端

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

喜欢就支持一下吧!

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

问题在于用事实证明有理,没事实,有理也不值一文

罗曼·罗兰

推荐阅读

前端开发基础:绝对路径与相对路径的概念

本文介绍了前端开发中绝对路径和相对路径的概念,包括定义、示例、优缺点及选择场景,旨在帮助前端开发者有效管理项目文件引用。

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

深入理解 PHP 中的依赖注入与控制反转

本文深入讲解 PHP 中的依赖注入与控制反转,包括技术细节、实战案例,并提供总结与扩展学习建议,帮助开发者提升项目架构能...

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

利用Guzzle进行高效异步请求与Workerman构建实时数据处理

本文展示了如何利用PHP中的Guzzle和Workerman工具,实现高效的异步请求处理和实时数据响应。

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

PHP $_SERVER 超全局变量全面解读:深入挖掘 Web 开发的宝库

深入探索PHP中的$_SERVER超全局变量,包括常用字段解析、安全性考虑及实际应用示例,助力开发者构建更稳定、安全的W...

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

Mysql数据库 Explain执行语句中的type类型

mysql语句编写完成以后,要习惯用explain分析SQL语句根据结果进而进行sql方面的优化处理,此文章介绍expl...

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

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

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

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

PHP 一匿名函数、回调函数和闭包函数的介绍

本文详细介绍了PHP中的匿名函数、回调函数和闭包函数的概念、用法和具体示例。匿名函数是没有名字的函数,可以在任何需要函数...

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

深入浅出:后端开发中的缓存机制

这篇文章深入探讨了后端开发中的缓存机制,包括缓存的定义、分类、常见使用场景、挑战与解决方案,以及如何选择合适的缓存工具,...

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