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

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

小程序端

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

喜欢就支持一下吧!

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

狐狸诡计多端,而刺狷只有一种技能,但这种技能却最顶用

伊拉斯谟

推荐阅读

深度探索 PHP 8 注解:从基础概念到高级应用

本文全面深入地探讨了 PHP 8 注解,从基础概念、原理分析到自带注解详解与高级应用实践,为开发者提供了关于注解的全方位...

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

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

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

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

thinkphp 模型withCount方法如何指定COUNT字段

本文将详细介绍如何在 ThinkPHP 模型中使用 withCount 方法来获取关联模型的计数信息。通过指定 COUN...

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

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

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

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

PHP中[guzzlehttp/guzzle] 的使用方法

如何在PHP中使用GuzzleHttp库进行HTTP请求。我们将详细解释如何使用GuzzleHttp发送GET、POST...

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

Laravel 日志系统全面解析

深入探索Laravel日志系统,了解不同日志级别的使用场景,如何通过日志进行有效的问题定位,以及高级配置和性能优化策略。

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

如何提升网站性能?从后端优化到整体提速的实用技巧

本文分享了如何在后端开发中优化网站性能,从数据库优化、缓存设计到负载均衡,涵盖实践案例与工具推荐,帮助开发者高效提升网站...

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

PHP中【nesbot/carbon的一些常用方法】

PHP中【nesbot/carbon的一些常用方法】,Carbon 是 DateTime 的简单 PHP API 扩展

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