You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
414 lines
13 KiB
414 lines
13 KiB
<template>
|
|
<view class="client-chat" style="height: 100%">
|
|
<view ref="first" clazz="pop-demo" :use-arrow="true">
|
|
<view class="pop-demo-list" v-for="pop in popperList" :key="pop.id">
|
|
<v-button type="link" kind="primary" @click="openSearchUrl(pop, pop.id)">
|
|
{{ pop.id }}.{{ pop.name }}
|
|
<v-icon remote name="arrow_right_line" size="12" valign="-1"></v-icon>
|
|
</v-button>
|
|
</view>
|
|
</view>
|
|
<view class="qa-item" v-for="(item, index) in msgList" :key="index">
|
|
<!-- 时间戳 -->
|
|
<view class="timestamp"
|
|
v-if="index === 0 || (index !== 0 && item.timestamp && (Number(item.timestamp) - Number(msgList[index - 1].timestamp)) > timestampGap)">
|
|
{{ moment(new Date(String(item.timestamp).length === 10 ? item.timestamp * 1000 :
|
|
Number(item.timestamp))).format('MM-DD HH:mm') }}
|
|
</view>
|
|
|
|
<!-- 问题 -->
|
|
<view class="question-item" v-if="item.is_from_self">
|
|
<v-spinner status="default" class="qs-loading" v-if="item.is_loading"></v-spinner>
|
|
<!-- <VueMarkdown class="question-text" style="max-width: 352px" :source="item.content"
|
|
:anchorAttributes="{ target: '_blank' }" :linkify="false" /> -->
|
|
</view>
|
|
<!-- 答案 -->
|
|
<view class="answer-item" v-if="!item.is_from_self">
|
|
<!-- 头像 -->
|
|
<view class="answer-avatar">
|
|
<img class="robot-avatar" :src="item.from_avatar" />
|
|
</view>
|
|
|
|
<!-- 答案信息 -->
|
|
<view class="answer-info" :ref="item.record_id">
|
|
<view v-if="item.agent_thought && item.agent_thought.procedures && item.agent_thought.procedures.length > 0">
|
|
<!-- 思考部分 -->
|
|
<MsgThought
|
|
v-for="(thought, i) in item.agent_thought.procedures"
|
|
:key="i"
|
|
:content="thought.debugging.content"
|
|
:title="thought.title"
|
|
:titleIcon="thought.icon"
|
|
:nodeName="thought.name"
|
|
:status="thought.status"
|
|
:elapsed="thought.elapsed"
|
|
:detailVisible="thought.detailVisible"
|
|
/>
|
|
</view>
|
|
<view class="loading" v-if="item.loading_message">正在思考中</view>
|
|
<!-- 回复主体 -->
|
|
<MsgContent :showTags="true"
|
|
:recordId="item.record_id"
|
|
:isReplaceLinks="true"
|
|
:loadingMessage="item.loading_message"
|
|
:content="item.content"
|
|
:isFinal="item.is_final"
|
|
:isMdExpand="item.isMdExpand"
|
|
@littleTagClick="littleTagClick"
|
|
/>
|
|
<!-- 参考来源 -->
|
|
<Reference v-if="item.references && item.references.length>0" :references-list="item.references"/>
|
|
<!-- 运行状态 -->
|
|
<TokensBoardBfr class="tokens-board-class" :showDtl="true" :tokensData="item.tokens_msg"></TokensBoardBfr>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import clone from 'clone';
|
|
// import VueMarkdown from 'vue-markdown';
|
|
import elementResizeDetectorMaker from 'element-resize-detector';
|
|
import { scrollToBottom } from './../utils/util';
|
|
import { MESSAGE_TYPE, ACCESS_TYPE } from './../constants';
|
|
import TokensBoardBfr from './tokens-board-brif.vue';
|
|
import Reference from './reference-component.vue';
|
|
|
|
export default {
|
|
name: 'ClientChat',
|
|
components: {
|
|
// VueMarkdown,
|
|
Reference,
|
|
TokensBoardBfr
|
|
},
|
|
data () {
|
|
return {
|
|
popperList: [],
|
|
oldPopDemo: null,
|
|
loading: false,
|
|
historyLoading: false,
|
|
timestampGap: 5 * 60, // 两次问题间隔大于5min,则展示时间戳(接口侧返回的时间戳是秒级)
|
|
msgList: [], // 对话消息列表
|
|
robotName: '', // 机器人名称
|
|
chatBoxHeight: document.body.clientHeight,
|
|
jsScrolling: false,
|
|
userScrolling: false
|
|
};
|
|
},
|
|
created () {
|
|
// 监听用户端/管理端体验侧的ws事件
|
|
this.listenClientAndManageEvent();
|
|
// 监听公共的ws事件
|
|
this.listenCommonEvent();
|
|
},
|
|
mounted () {
|
|
const erd = elementResizeDetectorMaker();
|
|
const bodyDom = document.body;
|
|
|
|
erd.listenTo(bodyDom, (element) => {
|
|
this.chatBoxHeight = element.clientHeight - 113; // 57+56 头部的高度
|
|
});
|
|
|
|
document.addEventListener('click', this.handleOutsideClick);
|
|
|
|
const sDom = document.querySelector('.client-chat');
|
|
sDom.addEventListener('scroll', () => {
|
|
if (this.msgList[this.msgList.length - 1].is_final === false && !this.jsScrolling) {
|
|
this.userScrolling = true;
|
|
} else {
|
|
this.jsScrolling = false;
|
|
}
|
|
});
|
|
},
|
|
beforeDestroy () {
|
|
// 移除全局事件监听器
|
|
document.removeEventListener('click', this.handleOutsideClick);
|
|
},
|
|
methods: {
|
|
openSearchUrl (refer, index) {
|
|
window.open(refer.url);
|
|
},
|
|
// 监听用户端/管理端体验侧的ws事件
|
|
listenClientAndManageEvent () {
|
|
// 从缓存获取机器人信息
|
|
let cachedConfig = null;
|
|
if (ACCESS_TYPE === 'ws') {
|
|
cachedConfig = this.$clientData.getConfigInfo();
|
|
} else {
|
|
cachedConfig = this.$SseCls.sseQueryConfigInfo();
|
|
}
|
|
if (cachedConfig) {
|
|
this.robotName = cachedConfig.name;
|
|
}
|
|
|
|
// 监听答案消息队列变更事件
|
|
this.$eventHub.$on('client_msgContentChange', (res) => {
|
|
const { chatsContent, type } = res;
|
|
|
|
// PS:若新消息不属于当前机器人,则在 $clientData 中监听到ws消息后判断并屏蔽。不在此处判断和屏蔽
|
|
this.renderMsgList(chatsContent, type);
|
|
});
|
|
},
|
|
// 监听公共的ws事件
|
|
listenCommonEvent () {
|
|
this.$eventHub.$on('data_history', () => {
|
|
this.historyLoading = false;
|
|
});
|
|
|
|
this.$eventHub.$on('data_historyError', () => {
|
|
this.historyLoading = false;
|
|
});
|
|
},
|
|
// 渲染消息会话页面
|
|
renderMsgList (data, type) {
|
|
// 无需滚动至底部的ws事件:用户端拉取历史记录、用户端停止生成、坐席端取历史记录、点赞点踩
|
|
const noScrollEvt = [MESSAGE_TYPE.HISTORY, MESSAGE_TYPE.STOP, MESSAGE_TYPE.WORKBENCH_HISTORY, MESSAGE_TYPE.FEEDBACK];
|
|
const list = data.map(el => {
|
|
return { ...el, showPop: true };
|
|
});
|
|
this.msgList = clone(list);
|
|
// console.log('=======更新消list========', clone(list));
|
|
|
|
// 对话框滚动至底部(部分ws事件类型无需执行滚动)
|
|
this.$nextTick(() => {
|
|
const sDom = document.querySelector('.client-chat');
|
|
|
|
if (!sDom) return;
|
|
|
|
if (!this.userScrolling && (!noScrollEvt.includes(type))) {
|
|
this.jsScrolling = true;
|
|
scrollToBottom(sDom, sDom.scrollHeight);
|
|
}
|
|
if (this.msgList.length > 0 && this.msgList[this.msgList.length - 1].is_final === true) {
|
|
this.userScrolling = false;
|
|
}
|
|
});
|
|
},
|
|
handleOutsideClick (event) {
|
|
if (!this.oldPopDemo) { return; };
|
|
const firstElement = document.getElementsByClassName('pop-demo')[0];
|
|
if (this.oldPopDemo.contains(event.target) || firstElement.contains(event.target)) {
|
|
} else {
|
|
if (this.oldPopDemo) {
|
|
this.$refs['first'] && this.$refs['first'].unbindTrigger(this.oldPopDemo);
|
|
}
|
|
// 调用你想要执行的方法
|
|
this.$refs['first'] && this.$refs['first'].hide();
|
|
this.oldPopDemo = null;
|
|
}
|
|
},
|
|
littleTagClick (e, r) {
|
|
const findMsg = this.$clientData.getMsgById(r);
|
|
let innerDome = e.querySelectorAll('.little-tags');
|
|
let outerTextArr = [];
|
|
if (innerDome && innerDome.length > 0) {
|
|
innerDome.forEach(dom => {
|
|
outerTextArr.push(dom.outerText);
|
|
});
|
|
}
|
|
this.popperList = findMsg.references.filter(e => outerTextArr.includes(e.id));
|
|
if (e) {
|
|
this.$refs['first'] && this.$refs['first'].bindTrigger(e, 'manual');
|
|
this.$refs['first'] && this.$refs['first'].update();
|
|
this.$refs['first'] && this.$refs['first'].show();
|
|
this.oldPopDemo = e;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.client-chat::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
.pop-demo{
|
|
// background-color: pink;
|
|
padding: 10px;
|
|
display: flex;
|
|
min-width: var(--size-l);
|
|
padding: var(--spacing-base);
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
// align-items: center;
|
|
gap: var(--spacing-tight);
|
|
|
|
border-radius: var(--radius-normal);
|
|
border: 0.5px solid var(--color-border-normal);
|
|
|
|
background: var(--color-bg-2);
|
|
/* shadow/--shadow-medium */
|
|
box-shadow: var(--shadow-medium-x-1) var(--shadow-medium-y-2) var(--shadow-medium-blur-1) var(--shadow-medium-spread-1) var(--shadow-medium-color-1), var(--shadow-medium-x-2) var(--shadow-medium-y-2) var(--shadow-medium-blur-2) var(--shadow-medium-spread-2) var(--shadow-medium-color-2), var(--shadow-medium-x-3) var(--shadow-medium-y-3) var(--shadow-medium-blur-3) var(--shadow-medium-spread-3) var(--shadow-medium-color-3);
|
|
|
|
.v-popper__arrow{
|
|
display: block;
|
|
}
|
|
.pop-demo-list{
|
|
color: var(--color-link-normal);
|
|
/* caption/--caption-regular */
|
|
font-family: var(--font-family-normal);
|
|
font-size: 12px;
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
line-height: 16px; /* 133.333% */
|
|
.v-button {
|
|
text-decoration: none;
|
|
text-align: left;
|
|
}
|
|
}
|
|
}
|
|
.client-chat {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
overflow-y: overlay;
|
|
padding: 0 12px;
|
|
|
|
.loading {
|
|
margin: 1em 0;
|
|
width: 150px;
|
|
|
|
&:after {
|
|
content: ".";
|
|
animation: ellipsis 1.5s steps(1, end) infinite;
|
|
}
|
|
}
|
|
|
|
@keyframes ellipsis {
|
|
0% {
|
|
content: ".";
|
|
}
|
|
|
|
33% {
|
|
content: "..";
|
|
}
|
|
|
|
66% {
|
|
content: "...";
|
|
}
|
|
|
|
100% {
|
|
content: ".";
|
|
}
|
|
}
|
|
|
|
.qa-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
margin-bottom: 16px;
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
color: var(--color-text-primary);
|
|
|
|
.timestamp {
|
|
font-weight: 400;
|
|
font-size: 12px;
|
|
line-height: 16px;
|
|
text-align: center;
|
|
color: var(--color-text-caption);
|
|
margin: 16px 0;
|
|
}
|
|
|
|
.question-item {
|
|
display: flex;
|
|
align-items: center;
|
|
width: fit-content;
|
|
text-align: center;
|
|
align-self: flex-end;
|
|
padding-left: 44px;
|
|
|
|
.qs-error {
|
|
min-width: 16px;
|
|
margin-right: 10px;
|
|
color: var(--color-error-normal);
|
|
}
|
|
|
|
.qs-loading {
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.question-text {
|
|
background: #DBE8FF;
|
|
border-radius: 6px;
|
|
padding: 0 12px;
|
|
text-align: left;
|
|
word-break: break-all;
|
|
word-wrap: break-word;
|
|
|
|
code {
|
|
white-space: break-spaces;
|
|
}
|
|
|
|
img {
|
|
max-width: 80%;
|
|
}
|
|
}
|
|
}
|
|
|
|
.summary-item {
|
|
align-self: center;
|
|
margin: 12px 0;
|
|
}
|
|
|
|
.answer-item {
|
|
display: flex;
|
|
|
|
.contacter-avatar {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
margin-right: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.answer-info {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 12px;
|
|
background: #F4F5F7;
|
|
border-radius: 6px;
|
|
width: calc(100% - 67px);
|
|
|
|
.answer-expand {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
width: 44px;
|
|
height: 24px;
|
|
margin-bottom: 12px;
|
|
background: var(--color-bg-2);
|
|
box-shadow: var(--shadow-small-light);
|
|
border-radius: 16px;
|
|
align-self: center;
|
|
}
|
|
|
|
.stop-ws {
|
|
color: var(--color-text-caption);
|
|
margin-left: 5px;
|
|
}
|
|
|
|
.answer-source {
|
|
margin: 12px 0;
|
|
font-size: 14px;
|
|
color: var(--color-text-caption);
|
|
text-align: left;
|
|
|
|
.v-button {
|
|
text-decoration: none;
|
|
text-align: left;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.qa-item:last-child {
|
|
padding-bottom: 120px;
|
|
}
|
|
}
|
|
</style>
|
|
|