前端使用vue
1.逐字輸出 閃動css樣式
<span id="response_row" class="result-streaming">{{ item.assistantContent }}</span>
.result-streaming:after {
-webkit-animation: blink 1s steps(5, start) infinite;
animation: blink 1s steps(5, start) infinite;
content: "▋";
margin-left: 0.25rem;
vertical-align: baseline;
}
2.使用fetch/eventSource/fetchEventSource進(jìn)行sse流式處理
先貼最后成功使用的
使用fetchEventSource方法
參考代碼:https://blog.csdn.net/cuiyuchen111/article/details/129468291
參考/下載文檔:https://www.npmjs.com/package/@microsoft/fetch-event-source?activeTab=readme
以下為后端接口要求
前端代碼
<p v-if="item.requestFlag" class="content robot_content"><span id="response_row" class="result-streaming">{{ item.assistantContent }}</span></p>
<p class="content robot_content"><span v-html="item.assistantContent"></span></p>
async getResponseFromAPI() {
const that = this;
this.sendLoading = true;
// 用戶提問時間
let userTime = that.getNowTime();
const form = JSON.parse(JSON.stringify(this.form));
console.log(form, "請求里的form");
//物理添加 頁面
that.conversations.push({
userContentId: "",
userContent: form.prompt,
userContentDatetime: userTime,
assistantContentId: "",
assistantContent: "",
assistantContentDatetime: userTime,
requestFlag: true,
});
// 對話請求
const abortController = new AbortController();
let formData = new FormData();
formData.append("chatid", this.currentChatId);
formData.append("clientid", form.clientid);
formData.append("prompt", form.prompt);
const url = "xxxxx";
const headers = new Headers();
const body = formData;
const eventSource = fetchEventSource(url, {
method: "POST",
headers,
body,
signal: abortController.signal,
onmessage(e) {
that.handleScrollBottom();
that.form.prompt = "";
const response_row = document.getElementById("response_row");
console.log(e.data);
let res = JSON.parse(e.data);
let index = that.conversations.length - 1;
if (res.message == "[DONE]") {
res.data = JSON.parse(res.data);
console.log(res.data);
let obj = {
userContentId: res.data.userContentId,
userContent: res.data.userContent,
userContentDatetime: userTime,
assistantContentId: res.data.assistantContentId,
assistantContent: res.data.assistantContent,
assistantContentDatetime: that.getNowTime(),
requestFlag: false,
};
console.log(obj);
that.$set(that.conversations, index, obj);
that.sendLoading = false;
abortController.abort();
eventSource.close();
console.log("我是結(jié)束??!");
} else {
var content = res.data;
response_row.innerText += content;
// console.log(content)
// if (content.includes("[ENTRY]")) {
// content = content.replaceAll("[ENTRY]", "\n");
// }
}
},
onclose() {
console.log("close");
that.sendLoading = false;
abortController.abort();
eventSource.close();
},
onerror(error) {
let index = that.conversations.length - 1;
that.conversations.splice(index, 1);
that.sendLoading = false;
console.log("error", error);
abortController.abort();
eventSource.close();
},
});
}
遇到的問題:
1.只調(diào)用一次事件 但fetch請求發(fā)送了兩次或多次且終止失敗
//按照fetchEventSource文檔內(nèi)的寫法 請求暫停無效
const ctrl = new AbortController();
fetchEventSource('/api/sse', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
foo: 'bar'
}),
signal: ctrl.signal,
});
//而后看到一種說法,fetchEventSource是針對EventSource API的,而不是xhr或fetch API
//因此定義EventSource存儲接口所返回的數(shù)據(jù) 使用EventSource的暫停方法 =》 fetchEventSource暫停成功
const eventSource = fetchEventSource(url, {
method: "POST",
headers,
body,
signal: abortController.signal,
onmessage(e) {
eventSource.close();
},
onclose() {
eventSource.close();
},
onerror(error) {
eventSource.close();
},
});
以下為fetch/eventSource使用過程中遇到的問題
1.使用fetch方式進(jìn)行sse流式處理
優(yōu)點:可以使用post請求
缺點:獲取到的數(shù)據(jù)處理困難 獲取事件返回格式或有錯誤
參考代碼:https://blog.csdn.net/betterAndBetter_/article/details/129900233
?????http://681314.com/A/YaHyYpjoPF
async function send() {
const input = document.getElementById("input").value;
const output = document.getElementById("output");
output.innerText = "";
const url = "/api/stream";
const data = { "Prompt": input };
//直接獲取 Fetch 的response, 無法使用 await的話, Promise的方式也是可以的。
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json"
}
})
//獲取UTF8的解碼
const encode = new TextDecoder("utf-8");
//獲取body的reader
const reader = response.body.getReader();
// 循環(huán)讀取reponse中的內(nèi)容
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// 解碼內(nèi)容
const text = encode.decode(value);
// 當(dāng)獲取錯誤token時,輸出錯誤信息
if (text === "<ERR>") {
output.innerText = "Error";
break;
} else {
// 獲取正常信息時,逐字追加輸出
output.innerText += text;
}
}
}
【記得補截圖】
2.使用eventSource進(jìn)行sse流式處理
優(yōu)點:獲取到的數(shù)據(jù)格式規(guī)范 易處理
缺點:無法使用post請求
參考b站視頻:https://www.bilibili.com/video/BV1QA411C7mN/?spm_id_from=333.880.my_history.page.click&vd_source=384646ea9baa6985ceb5331bff5b87b0文章來源:http://www.zghlxwxcb.cn/news/detail-467166.html
var rsource = (this.rsource = new EventSource(
`/api/chat/repeat/${this.cid}`
));
rsource.addEventListener("open", function () {
console.log("connect");
});
//如果服務(wù)器響應(yīng)報文中沒有指明事件,默認(rèn)觸發(fā)message事件
rsource.addEventListener("message", function (e) {
console.log(`resp:(${e.data})`);
var rconv = that.conversation[that.conversation.length - 1];
if (e.data == "[DONE]") {
rsource.close();
rconv["loading"] = false;
that.convLoading = false;
that.refrechConversation();
that.rsource = undefined;
return;
}
var content = e.data;
if (content.includes("[ENTRY]")) {
content = content.replaceAll("[ENTRY]", "\n");
}
// 滾動到最下面
that.handleScrollBottom();
var idx = rconv.idx;
rconv["speeches"][idx] += content;
that.refrechConversation();
});
//發(fā)生錯誤,則會觸發(fā)error事件
rsource.addEventListener("error", function (e) {
console.log("error:" + e.data);
rsource.close();
that.rsource = undefined;
});
由于eventSource獲取到的數(shù)據(jù)比fetch流暢許多,所以研究過eventSource能否使用post請求,使用過以下代碼,但失敗了
3.fetch和eventSource同時使用
優(yōu)點:可以很順利的請求并且獲取到數(shù)據(jù)
缺點:fetch支持post eventSource不支持post 對接口請求方式有要求 幾乎不太能兼容文章來源地址http://www.zghlxwxcb.cn/news/detail-467166.html
// 獲取表單元素
const form = document.querySelector('#my-form');
// 監(jiān)聽表單提交事件
form.addEventListener('submit', (event) => {
event.preventDefault(); // 阻止默認(rèn)提交行為
const formData = new FormData(form); // 創(chuàng)建 FormData 對象
// 發(fā)送 POST 請求并接收 SSE 流式輸出
fetch('/api/submit-form', {
method: 'POST',
body: formData
}).then((response) => {
// 如果請求成功,則創(chuàng)建 EventSource 對象監(jiān)聽 SSE 輸出
if (response.ok) {
const eventSource = new EventSource('/api/stream');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log(data); // 處理接收到的數(shù)據(jù)
};
eventSource.onerror = (error) => { // 監(jiān)聽錯誤事件
console.error(error);
};
}
}).catch((error) => {
console.error(error);
});
});
到了這里,關(guān)于chatgpt 逐字輸出 使用fetch/eventSource/fetchEventSouce進(jìn)行sse流式處理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!