mirror of
http://112.124.100.131/ebiz-ai/ebiz-base-ai.git
synced 2025-12-21 08:46:52 +08:00
feat(AI-new): 优化聊天组件并添加新功能
- 重构 chat-new 组件,优化输入框和发送按钮逻辑 - 新增单次请求管控功能,改进多次请求处理 - 优化消息渲染逻辑,处理未闭合标签问题 - 调整卡片样式,增加新图标 - 更新路由配置,修改页面标题
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
<!-- 输入框 or 按住说话提示 -->
|
||||
<div class="input-wrapper">
|
||||
<input v-if="!isVoiceMode" type="text" v-model="newMessage" placeholder="你有什么想知道的?"
|
||||
@keyup.enter="sendMessage" />
|
||||
@keyup.enter="beforeSend" />
|
||||
<div v-else class="voice-hint-container" :class="{ disabled: messageStatus === 'send' }"
|
||||
@mousedown="startRecording" @selectstart="() => false" @mouseup="stopRecording" @mouseleave="stopRecording"
|
||||
@touchend="stopRecording" @touchstart="startRecording">
|
||||
@@ -25,11 +25,11 @@
|
||||
<span class="bar"></span>
|
||||
<span class="bar"></span>
|
||||
</div>
|
||||
<!-- <div class="hint-text" v-if='!isRecording'>按住说话</div>-->
|
||||
<!--<div class="hint-text" v-if='!isRecording'>按住说话</div>-->
|
||||
</div>
|
||||
<!-- 发送按钮 -->
|
||||
<button @click="sendMessage" :disabled="messageStatus === 'send'"
|
||||
:class="{ disabled: messageStatus === 'send' }" class="active send">
|
||||
<button @click="beforeSend" :disabled="messageStatus === 'send'"
|
||||
:class="{ disabled: messageStatus === 'send' }" class="ml10 active send">
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
@@ -39,24 +39,13 @@
|
||||
<span v-else class="ml15 mr5 input-icon">⌨</span> -->
|
||||
</button>
|
||||
</footer>
|
||||
<!-- <section class="section pb10" > -->
|
||||
<!-- <button @click="deepInternet" :class="{ active: isDeep }">
|
||||
<svg-icon icon-class="think" class-name="chat-icon"></svg-icon>
|
||||
深度思考
|
||||
</button> -->
|
||||
<!-- 发送按钮 -->
|
||||
<!-- <button @click="sendMessage" :disabled="messageStatus === 'send'"
|
||||
:class="{ disabled: messageStatus === 'send' }" class="mr10 active send fs16">
|
||||
发送
|
||||
</button> -->
|
||||
<!-- </section> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SvgIcon from '@/components/svg-icon/index.vue'
|
||||
import { audioToText, gwcsChat, chatProduct } from '@/api/generatedApi'
|
||||
import { audioToText, gwcsChat } from '@/api/generatedApi'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -95,10 +84,16 @@ export default {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
// action: {
|
||||
// type: String,
|
||||
// default: 'normal_chat',
|
||||
// }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
requestSingle: undefined,
|
||||
// 管控单次请求,当 abort 之后, while 后面的会进行重复请求
|
||||
single: false,
|
||||
isThink: false,
|
||||
newMessage: '',
|
||||
isRecording: false,
|
||||
@@ -108,7 +103,10 @@ export default {
|
||||
isVoiceMode: false,
|
||||
answerMap: '',
|
||||
currentMessage: null,
|
||||
|
||||
messageInfo: {
|
||||
is_complete: false.toString(),
|
||||
information: '',
|
||||
},
|
||||
// 打字机相关
|
||||
typingText: '',
|
||||
typingQueue: [],
|
||||
@@ -118,6 +116,14 @@ export default {
|
||||
typingTimeout: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isTyping(value) {
|
||||
// 通过 typing 控制发送的状态
|
||||
// console.log(`typing status change `, value)
|
||||
if (!value) this.$emit('update:messageStatus', 'stop')
|
||||
else this.$emit('update:messageStatus', 'send')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deepInternet() {
|
||||
this.$emit('update:isDeep', !this.isDeep)
|
||||
@@ -152,19 +158,19 @@ export default {
|
||||
console.error(err)
|
||||
}
|
||||
},
|
||||
hasTreasureBox() {
|
||||
chatProduct({ query: this.newMessage })
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
this.messageStatus = 'stop'
|
||||
this.messages.push({ type: 'box', text: this.newMessage, detail: res.content })
|
||||
this.newMessage = ''
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.messageStatus = 'stop'
|
||||
})
|
||||
},
|
||||
// hasTreasureBox() {
|
||||
// chatProduct({ query: this.newMessage })
|
||||
// .then((res) => {
|
||||
// if (res) {
|
||||
// this.messageStatus = 'stop'
|
||||
// this.messages.push({ type: 'box', text: this.newMessage, detail: res.content })
|
||||
// this.newMessage = ''
|
||||
// }
|
||||
// })
|
||||
// .catch(() => {
|
||||
// this.messageStatus = 'stop'
|
||||
// })
|
||||
// },
|
||||
stopRecording() {
|
||||
if (this.mediaRecorder && this.isRecording) {
|
||||
this.mediaRecorder.stop()
|
||||
@@ -207,21 +213,22 @@ export default {
|
||||
axiosGetAiChat() {
|
||||
const abortController = new AbortController()
|
||||
this.requestSingle = abortController
|
||||
let message = {
|
||||
is_complete: false.toString(),
|
||||
information: this.newMessage,
|
||||
}
|
||||
this.messageInfo.information = this.single ? this.messageInfo.information : this.newMessage
|
||||
|
||||
this.currentMessage = JSON.parse(JSON.stringify(message))
|
||||
// 重置 answerMap
|
||||
this.answerMap = ''
|
||||
|
||||
this.currentMessage = JSON.parse(JSON.stringify(this.messageInfo))
|
||||
let params = {
|
||||
appType: "gwcsHelper",
|
||||
conversationId: "",
|
||||
message: JSON.stringify(message),
|
||||
message: JSON.stringify(this.messageInfo),
|
||||
user: "gwcs-test",
|
||||
inputs: {}
|
||||
inputs: {},
|
||||
// action: this.action
|
||||
}
|
||||
this.currentMessage = {
|
||||
...message,
|
||||
...this.messageInfo,
|
||||
type: 'bot',
|
||||
text: '',
|
||||
think: '',
|
||||
@@ -230,28 +237,37 @@ export default {
|
||||
isLike: false,
|
||||
isDisLike: false,
|
||||
}
|
||||
this.messages.push(this.currentMessage)
|
||||
|
||||
if (this.single) {
|
||||
// this.messages[this.messages.length - 1]
|
||||
|
||||
this.messages.splice(this.messages.length - 1, 1, this.currentMessage)
|
||||
} else {
|
||||
this.messages.push(this.currentMessage)
|
||||
}
|
||||
// 如果有自定义参数
|
||||
if (this.chatData) {
|
||||
for (let k in this.chatData) {
|
||||
params[k] = this.chatData[k]
|
||||
}
|
||||
}
|
||||
if (this.$route.query.compareId) {
|
||||
params.compareResult = JSON.parse(sessionStorage.getItem('results'))
|
||||
}
|
||||
// if (this.$route.query.compareId) {
|
||||
// params.compareResult = JSON.parse(sessionStorage.getItem('results'))
|
||||
// }
|
||||
this.newMessage = ''
|
||||
fetch(gwcsChat(), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
signal: abortController.signal,
|
||||
body: JSON.stringify(params),
|
||||
timeout: 60000,
|
||||
})
|
||||
.then(async (res) => {
|
||||
this.newMessage = ''
|
||||
await this.processStreamResponse(res)
|
||||
this.single = false
|
||||
})
|
||||
.catch((err) => {
|
||||
// debugger
|
||||
this.$emit('update:messageStatus', 'stop')
|
||||
})
|
||||
},
|
||||
@@ -264,6 +280,8 @@ export default {
|
||||
const reader = response.body.getReader()
|
||||
let buffer = ''
|
||||
while (true) {
|
||||
// if (this.single) break
|
||||
|
||||
try {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
@@ -286,12 +304,30 @@ export default {
|
||||
if (!cleanLine) return null
|
||||
const data = JSON.parse(cleanLine)
|
||||
// console.log(data)
|
||||
// console.log(data)
|
||||
if (data.answer) {
|
||||
this.answerMap += data.answer
|
||||
const match = /<is_complete>([^<]*)(?:<\/is_complete>)?/.exec(data.answer)
|
||||
if (match && match[1] === 'true') {
|
||||
|
||||
const is_complete = /<is_complete>([^<]*)(?:<\/is_complete>)?/.exec(this.answerMap)
|
||||
const information = /<information>([^<]*)(?:<\/information>)?/.exec(this.answerMap)
|
||||
const text = /<text>([^<]*)(?:<\/text>)?/.exec(this.answerMap)
|
||||
// console.log(`is_complete, information, text`, is_complete, information, text)
|
||||
|
||||
// const isCompleteRes = is_complete.some(item => item === 'true')
|
||||
|
||||
this.messageInfo.information = information ? information[1].trim() : this.newMessage
|
||||
this.messageInfo.is_complete = is_complete ? is_complete[1].trim() : 'false'
|
||||
if (is_complete && is_complete[1] === 'true' && text && text[1].trim() === '') {
|
||||
// alert("message end")
|
||||
|
||||
this.requestSingle.abort()
|
||||
// setTimeout(() => {
|
||||
// debugger
|
||||
// this.$emit('update:messageStatus', 'send')
|
||||
this.single = true
|
||||
// debugger
|
||||
this.axiosGetAiChat()
|
||||
// }, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,9 +360,10 @@ export default {
|
||||
},
|
||||
updateMessageContent(parse) {
|
||||
let { event, answer, isThink } = parse
|
||||
if (event === 'message_end') {
|
||||
this.$emit('update:messageStatus', 'stop')
|
||||
}
|
||||
// 会导致发送按钮提前高亮展示
|
||||
// if (event === 'message_end') {
|
||||
// this.$emit('update:messageStatus', 'stop')
|
||||
// }
|
||||
if (!this.currentMessage || !answer) return
|
||||
if (event !== 'message') return
|
||||
// console.log(parse);
|
||||
@@ -342,7 +379,6 @@ export default {
|
||||
},
|
||||
startTypingAnimation() {
|
||||
this.isTyping = true
|
||||
|
||||
const typeNextChar = () => {
|
||||
if (this.typingQueue.length === 0) {
|
||||
this.isTyping = false
|
||||
@@ -353,8 +389,7 @@ export default {
|
||||
const chunk = this.typingQueue.shift()
|
||||
// console.log(this.messages);
|
||||
|
||||
const chars = /* chunk.answer.split('') */ Array.from(chunk.answer)
|
||||
// console.log(chars);
|
||||
const chars = Array.from(chunk.answer)
|
||||
|
||||
const isThink = chunk.isThink
|
||||
// 内部递归函数,用于逐字输出当前块
|
||||
@@ -365,6 +400,7 @@ export default {
|
||||
return
|
||||
}
|
||||
const char = chars.shift() || ''
|
||||
// if (this.single)
|
||||
this.$set(this.currentMessage, isThink ? 'think' : 'text', this.currentMessage[isThink ? 'think' : 'text'] + char)
|
||||
const delay = this.getTypingDelay(char)
|
||||
setTimeout(outputChar, delay)
|
||||
@@ -381,26 +417,23 @@ export default {
|
||||
}
|
||||
return this.typingSpeed
|
||||
},
|
||||
sendMessage() {
|
||||
beforeSend() {
|
||||
if (this.messageStatus === 'send') return
|
||||
if (this.newMessage.trim() === '') return
|
||||
this.newMessage = this.newMessage.replace(/<[^>]+>/g, '')
|
||||
this.messages.push({ type: 'user', text: this.newMessage })
|
||||
this.$emit('update:messageStatus', 'send')
|
||||
if (this.newMessage.includes('工具箱')) {
|
||||
this.hasTreasureBox()
|
||||
return
|
||||
}
|
||||
this.$emit('update:autoScrollEnabled', true)
|
||||
this.axiosGetAiChat()
|
||||
this.sendMessage()
|
||||
},
|
||||
cellClick(item) {
|
||||
this.newMessage = item.title
|
||||
this.messages.push({ type: 'user', text: this.newMessage })
|
||||
this.sendMessage()
|
||||
},
|
||||
sendMessage() {
|
||||
this.$emit('update:messageStatus', 'send')
|
||||
this.$emit('update:autoScrollEnabled', true)
|
||||
this.axiosGetAiChat()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -517,7 +550,7 @@ $primary-trans-color: rgba(135, 162, 208, 0.5);
|
||||
}
|
||||
|
||||
input {
|
||||
width: 110%;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
background: #fff;
|
||||
|
||||
Reference in New Issue
Block a user