小程序端
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 读取数据 并实时显示在页面上