mirror of
http://112.124.100.131/ebiz-ai/ebiz-base-ai.git
synced 2025-12-09 19:06:50 +08:00
feat: 新增AI聊天界面及消息组件,支持语音输入和markdown渲染
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
.card {
|
.card {
|
||||||
|
border-radius: 5px;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
|
padding: 5px 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 5%;
|
gap: 5%;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, .05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card > div {
|
.card > div {
|
||||||
@@ -30,15 +33,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card > section > :nth-child(2) > div {
|
.card > section > :nth-child(2) > div {
|
||||||
|
font-size: 12px;
|
||||||
background-color: #0065ff;
|
background-color: #0065ff;
|
||||||
padding: 1px 4px;
|
padding: 0px 3px;
|
||||||
border-radius: 3px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选择遇到的第一个 div */
|
/* 选择遇到的第一个 div */
|
||||||
.render-container > div:nth-of-type(1) > img {
|
.render-container > div:nth-of-type(1) > img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: red;
|
/* background-color: red; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.render-container > ul {
|
||||||
|
/* background-color: red; */
|
||||||
|
list-style: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.render-container > div {
|
.render-container > div {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
:class="{ disabled: messageStatus === 'send' }" class="ml10 active send">
|
:class="{ disabled: messageStatus === 'send' }" class="ml10 active send">
|
||||||
发送
|
发送
|
||||||
</button>
|
</button>
|
||||||
|
<!-- <button @click="cancelSend">取消</button> -->
|
||||||
</div>
|
</div>
|
||||||
<!-- 语音按钮:按住说话 -->
|
<!-- 语音按钮:按住说话 -->
|
||||||
<button @click="isVoiceMode = !isVoiceMode" class="mic-button ml10 mr10" v-if="false">
|
<button @click="isVoiceMode = !isVoiceMode" class="mic-button ml10 mr10" v-if="false">
|
||||||
@@ -218,7 +219,7 @@ export default {
|
|||||||
|
|
||||||
// 重置 answerMap
|
// 重置 answerMap
|
||||||
this.answerMap = ''
|
this.answerMap = ''
|
||||||
|
this.typingQueue = []
|
||||||
this.currentMessage = JSON.parse(JSON.stringify(this.messageInfo))
|
this.currentMessage = JSON.parse(JSON.stringify(this.messageInfo))
|
||||||
let params = {
|
let params = {
|
||||||
appType: "gwcsHelper",
|
appType: "gwcsHelper",
|
||||||
@@ -241,7 +242,6 @@ export default {
|
|||||||
|
|
||||||
if (this.single) {
|
if (this.single) {
|
||||||
// this.messages[this.messages.length - 1]
|
// this.messages[this.messages.length - 1]
|
||||||
|
|
||||||
this.messages.splice(this.messages.length - 1, 1, this.currentMessage)
|
this.messages.splice(this.messages.length - 1, 1, this.currentMessage)
|
||||||
} else {
|
} else {
|
||||||
this.messages.push(this.currentMessage)
|
this.messages.push(this.currentMessage)
|
||||||
@@ -301,6 +301,7 @@ export default {
|
|||||||
},
|
},
|
||||||
parseStreamLine(line) {
|
parseStreamLine(line) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const cleanLine = line.replace(/^data:\s*/, '')
|
const cleanLine = line.replace(/^data:\s*/, '')
|
||||||
if (!cleanLine) return null
|
if (!cleanLine) return null
|
||||||
const data = JSON.parse(cleanLine)
|
const data = JSON.parse(cleanLine)
|
||||||
@@ -373,6 +374,7 @@ export default {
|
|||||||
answer: answer,
|
answer: answer,
|
||||||
isThink: isThink,
|
isThink: isThink,
|
||||||
}
|
}
|
||||||
|
// debugger
|
||||||
this.typingQueue.push(chars)
|
this.typingQueue.push(chars)
|
||||||
if (!this.isTyping) {
|
if (!this.isTyping) {
|
||||||
this.startTypingAnimation(mode)
|
this.startTypingAnimation(mode)
|
||||||
@@ -401,7 +403,6 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const char = chars.shift() || ''
|
const char = chars.shift() || ''
|
||||||
// if (this.single)
|
|
||||||
this.$set(this.currentMessage, isThink ? 'think' : 'text', this.currentMessage[isThink ? 'think' : 'text'] + char)
|
this.$set(this.currentMessage, isThink ? 'think' : 'text', this.currentMessage[isThink ? 'think' : 'text'] + char)
|
||||||
const delay = this.getTypingDelay(char)
|
const delay = this.getTypingDelay(char)
|
||||||
setTimeout(outputChar, delay)
|
setTimeout(outputChar, delay)
|
||||||
@@ -435,6 +436,10 @@ export default {
|
|||||||
this.$emit('update:autoScrollEnabled', true)
|
this.$emit('update:autoScrollEnabled', true)
|
||||||
this.axiosGetAiChat()
|
this.axiosGetAiChat()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
cancelSend() {
|
||||||
|
this.requestSingle.abort()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<!-- {{ messagesList }} -->
|
{{ messagesList }}
|
||||||
<div v-for="(message, index) in messagesList" :key="index" class="message-item">
|
<div v-for="(message, index) in messagesList" :key="index" class="message-item">
|
||||||
<!-- {{ message }} -->
|
<!-- {{ message }} -->
|
||||||
<!--用户消息-->
|
<!--用户消息-->
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
<span class="speakLoadingToast pv10">
|
<span class="speakLoadingToast pv10">
|
||||||
<van-loading type="spinner" :color="primaryColor" size="20px" v-if="!message.text" />
|
<van-loading type="spinner" :color="primaryColor" size="20px" v-if="!message.text" />
|
||||||
<span class="loading-text">{{ message.text ? '思考完成' : '思考中...' }}</span>
|
<span class="loading-text">{{ message.text ? '思考完成' : '思考中...' }}</span>
|
||||||
<van-icon name="arrow-up" :class="message.showThink ? 'rate360' : 'rate180'" @click="showThink(message)" v-if="message.think" />
|
<van-icon name="arrow-up" :class="message.showThink ? 'rate360' : 'rate180'" @click="showThink(message)"
|
||||||
|
v-if="message.think" />
|
||||||
</span>
|
</span>
|
||||||
<!--开启思考-->
|
<!--开启思考-->
|
||||||
<p v-html="md.render(message.think)" v-if="message.think && message.showThink" class="thinkText" />
|
<p v-html="md.render(message.think)" v-if="message.think && message.showThink" class="thinkText" />
|
||||||
@@ -90,12 +91,14 @@ export default {
|
|||||||
if (new RegExp('^[\u4e00-\u9fa5]+', 'g').test(text)) return text
|
if (new RegExp('^[\u4e00-\u9fa5]+', 'g').test(text)) return text
|
||||||
|
|
||||||
// 捕获 不包含 < 的后置标签 span> /span> a</span>
|
// 捕获 不包含 < 的后置标签 span> /span> a</span>
|
||||||
text = text.replace(/^[\/]?[a-zA-z0-9]+[</\w>]+/g, '')
|
text = text.replace(/^[/]?[a-zA-z0-9]+[</\w>]+/g, '')
|
||||||
// 尝试匹配 </abc> 标签
|
// 尝试匹配 </abc> 标签
|
||||||
text = text.replace(/^<\/\w+>/g, '')
|
text = text.replace(/^<\w+>/g, '')
|
||||||
// 尝试把 </ 标签去除
|
// 尝试把 </ 标签去除
|
||||||
text = text.replace(/[<\/]+$/g, '')
|
// text = text.replace(/[</]+$/g, '')
|
||||||
console.log(text);
|
text = text.replace(/<[^>]*>/g, '')
|
||||||
|
// console.log(text);
|
||||||
|
|
||||||
|
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
:is-search="isSearching"
|
:is-search="isSearching"
|
||||||
:think-ok="isThink"
|
:think-ok="isThink"
|
||||||
@setProductName="setProductName"
|
@setProductName="setProductName"
|
||||||
|
@reload-message="reloadMessage"
|
||||||
v-if="messages.length > 1"
|
v-if="messages.length > 1"
|
||||||
></messageComponent>
|
></messageComponent>
|
||||||
<div class="mb20" v-else>
|
<div class="mb20" v-else>
|
||||||
@@ -137,6 +138,9 @@ export default {
|
|||||||
},
|
},
|
||||||
initBotMessage() {
|
initBotMessage() {
|
||||||
this.messages = []
|
this.messages = []
|
||||||
|
},
|
||||||
|
reloadMessage({message,index}){
|
||||||
|
this.$refs.chatMessage.reloadMessage({message,index})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||
Reference in New Issue
Block a user