|
|
@ -14,7 +14,7 @@ |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
<scroll-view scroll-y class="content-container"> |
|
|
|
|
|
|
|
|
<scroll-view scroll-y class="content-container" :scroll-into-view="scrollIntoView"> |
|
|
|
|
|
|
|
|
<view class="content-header"> |
|
|
<view class="content-header"> |
|
|
<view class="content-header-area"> |
|
|
<view class="content-header-area"> |
|
|
@ -47,7 +47,7 @@ |
|
|
<view class="card-text"> |
|
|
<view class="card-text"> |
|
|
|
|
|
|
|
|
<rich-text :nodes="renderMarkdown(answerContent)"></rich-text> |
|
|
<rich-text :nodes="renderMarkdown(answerContent)"></rich-text> |
|
|
|
|
|
|
|
|
|
|
|
<view id="answer-end" style="width:1px;height:1px;"></view> |
|
|
<!-- <text class="card-paragraph"> |
|
|
<!-- <text class="card-paragraph"> |
|
|
{{answerContent}} |
|
|
{{answerContent}} |
|
|
</text> --> |
|
|
</text> --> |
|
|
@ -66,7 +66,9 @@ |
|
|
|
|
|
|
|
|
<script> |
|
|
<script> |
|
|
import { |
|
|
import { |
|
|
getAnswerApi |
|
|
|
|
|
|
|
|
getAnswerIdApi, |
|
|
|
|
|
getAnswerStatusApi, |
|
|
|
|
|
getAnswerContentApi |
|
|
} from "../../api/customerServicePlatform/customerServicePlatform"; |
|
|
} from "../../api/customerServicePlatform/customerServicePlatform"; |
|
|
import marked from "marked"; // 引入 marked 库 |
|
|
import marked from "marked"; // 引入 marked 库 |
|
|
|
|
|
|
|
|
@ -78,12 +80,21 @@ |
|
|
questionTitle: '', |
|
|
questionTitle: '', |
|
|
answerContent: '正在思考...', |
|
|
answerContent: '正在思考...', |
|
|
showLoginRegister: false, |
|
|
showLoginRegister: false, |
|
|
|
|
|
// 轮询句柄 |
|
|
|
|
|
pollInterval: null, |
|
|
|
|
|
pollTimeout: null, |
|
|
|
|
|
scrollIntoView: '' |
|
|
}; |
|
|
}; |
|
|
}, |
|
|
}, |
|
|
mounted() { |
|
|
mounted() { |
|
|
this.iSMT = uni.getSystemInfoSync().statusBarHeight || 0; |
|
|
this.iSMT = uni.getSystemInfoSync().statusBarHeight || 0; |
|
|
this.getAnswerContent() |
|
|
this.getAnswerContent() |
|
|
}, |
|
|
}, |
|
|
|
|
|
onUnload() { |
|
|
|
|
|
clearInterval(this.pollInterval); |
|
|
|
|
|
clearTimeout(this.pollTimeout); |
|
|
|
|
|
clearInterval(this.interval); |
|
|
|
|
|
}, |
|
|
onLoad(options) { |
|
|
onLoad(options) { |
|
|
if (options.question) { |
|
|
if (options.question) { |
|
|
this.questionTitle = decodeURIComponent(options.question); |
|
|
this.questionTitle = decodeURIComponent(options.question); |
|
|
@ -95,6 +106,18 @@ |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
|
|
|
scrollToBottom() { |
|
|
|
|
|
// 使用 $nextTick 保证 rich-text 的渲染/DOM 更新完成 |
|
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
|
// 先设置目标 id,scroll-view 会滚动使其可见 |
|
|
|
|
|
this.scrollIntoView = 'answer-end'; |
|
|
|
|
|
|
|
|
|
|
|
// 清空值以便下次再次触发 |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
this.scrollIntoView = ''; |
|
|
|
|
|
}, 100); |
|
|
|
|
|
}); |
|
|
|
|
|
}, |
|
|
renderMarkdown(content) { |
|
|
renderMarkdown(content) { |
|
|
const renderer = new marked.Renderer(); |
|
|
const renderer = new marked.Renderer(); |
|
|
// renderer.heading = function (text, level) { |
|
|
// renderer.heading = function (text, level) { |
|
|
@ -120,39 +143,102 @@ |
|
|
}, |
|
|
}, |
|
|
async getAnswerContent() { |
|
|
async getAnswerContent() { |
|
|
let conversationId = ''; |
|
|
let conversationId = ''; |
|
|
|
|
|
let chatId = ''; |
|
|
|
|
|
//尝试获取本地缓存 |
|
|
try { |
|
|
try { |
|
|
const cache = uni.getStorageSync('conversationId'); |
|
|
const cache = uni.getStorageSync('conversationId'); |
|
|
|
|
|
const chatIdCache = uni.getStorageSync('chatId'); |
|
|
if (cache) conversationId = cache; |
|
|
if (cache) conversationId = cache; |
|
|
|
|
|
if (chatIdCache) chatId = chatIdCache |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
conversationId = ''; |
|
|
conversationId = ''; |
|
|
} |
|
|
} |
|
|
try { |
|
|
try { |
|
|
const res = await getAnswerApi({ |
|
|
|
|
|
|
|
|
const res = await getAnswerIdApi({ |
|
|
question: this.questionTitle, |
|
|
question: this.questionTitle, |
|
|
conversationId: conversationId, |
|
|
conversationId: conversationId, |
|
|
|
|
|
chatId: chatId |
|
|
}) |
|
|
}) |
|
|
console.log(res) |
|
|
console.log(res) |
|
|
|
|
|
|
|
|
if (res.code == 200) { |
|
|
|
|
|
uni.setStorageSync('conversationId', res.data.conversationId); |
|
|
|
|
|
const answer = res.data.answer |
|
|
|
|
|
// 流式输出逻辑 |
|
|
|
|
|
|
|
|
if (res.code == 200 && res.data.chatId) { |
|
|
|
|
|
const resConversationId = res.data.conversationId; |
|
|
|
|
|
const resChatId = res.data.chatId; |
|
|
|
|
|
uni.setStorageSync('conversationId', resConversationId); |
|
|
|
|
|
uni.setStorageSync('chatId', resChatId); |
|
|
|
|
|
let pollCount = 0; |
|
|
|
|
|
const maxPoll = 10; |
|
|
|
|
|
this.pollInterval && clearInterval(this.pollInterval); |
|
|
|
|
|
this.pollTimeout && clearTimeout(this.pollTimeout); |
|
|
|
|
|
// === 轮询函数 === |
|
|
|
|
|
const checkStatus = async () => { |
|
|
|
|
|
pollCount++; |
|
|
|
|
|
const pollRes = await getAnswerStatusApi({ |
|
|
|
|
|
conversationId: resConversationId, |
|
|
|
|
|
chatId: resChatId |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
console.log("pollRes =>", pollRes); |
|
|
|
|
|
|
|
|
|
|
|
const status = pollRes?.data; |
|
|
|
|
|
//轮询结束,获取回答 |
|
|
|
|
|
if (status === "completed") { |
|
|
|
|
|
clearInterval(this.pollInterval); |
|
|
|
|
|
clearTimeout(this.pollTimeout); |
|
|
|
|
|
//获取最终答案 |
|
|
|
|
|
const answerRes = await getAnswerContentApi({ |
|
|
|
|
|
conversationId: resConversationId, |
|
|
|
|
|
chatId: resChatId |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
console.log("answerRes =>", answerRes); |
|
|
|
|
|
const answer = answerRes?.data; |
|
|
|
|
|
//打印机效果 |
|
|
|
|
|
if (answer) { |
|
|
|
|
|
// 逐字输出 |
|
|
let currentIndex = 0; |
|
|
let currentIndex = 0; |
|
|
const answerLength = answer.length; |
|
|
const answerLength = answer.length; |
|
|
|
|
|
|
|
|
// 每隔一定时间显示一部分内容 |
|
|
|
|
|
|
|
|
this.interval && clearInterval(this.interval); |
|
|
this.interval = setInterval(() => { |
|
|
this.interval = setInterval(() => { |
|
|
this.answerContent = answer.slice(0, currentIndex); |
|
|
this.answerContent = answer.slice(0, currentIndex); |
|
|
currentIndex++; |
|
|
currentIndex++; |
|
|
|
|
|
|
|
|
|
|
|
this.scrollToBottom(); |
|
|
if (currentIndex > answerLength) { |
|
|
if (currentIndex > answerLength) { |
|
|
clearInterval(this.interval); |
|
|
clearInterval(this.interval); |
|
|
|
|
|
this.scrollToBottom(); |
|
|
} |
|
|
} |
|
|
}, Math.floor(Math.random() * (150 - 30 + 1)) + 30); |
|
|
}, Math.floor(Math.random() * (150 - 30 + 1)) + 30); |
|
|
|
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
this.answerContent = "获取回答失败,请重试"; |
|
|
|
|
|
} |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
//超过10秒就回答失败 |
|
|
|
|
|
if (pollCount >= maxPoll) { |
|
|
|
|
|
clearInterval(this.pollInterval); |
|
|
|
|
|
clearTimeout(this.pollTimeout); |
|
|
|
|
|
this.answerContent = "获取回答失败,请重试"; |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 每 2 秒轮询 |
|
|
|
|
|
this.pollInterval = setInterval(checkStatus, 2000); |
|
|
|
|
|
// 超时兜底 10s |
|
|
|
|
|
this.pollTimeout = setTimeout(() => { |
|
|
|
|
|
clearInterval(this.pollInterval); |
|
|
|
|
|
this.answerContent = "获取回答失败,请重试"; |
|
|
|
|
|
}, 20000); |
|
|
} else { |
|
|
} else { |
|
|
this.answerContent = '获取回答失败,请重试'; |
|
|
this.answerContent = '获取回答失败,请重试'; |
|
|
} |
|
|
} |
|
|
} catch { |
|
|
} catch { |
|
|
|
|
|
this.pollInterval && clearInterval(this.pollInterval); |
|
|
|
|
|
this.pollTimeout && clearTimeout(this.pollTimeout); |
|
|
|
|
|
this.interval && clearInterval(this.interval); |
|
|
this.answerContent = '获取回答失败,请重试'; |
|
|
this.answerContent = '获取回答失败,请重试'; |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|