| | |
| | | <template> |
| | | <!-- 内容--> |
| | | <div class="header___lEPyH"> |
| | | <div class="chatHeader"> |
| | | <div class="chatHeaderBox"> |
| | | <span class="title">{{agentTitle}}</span> |
| | | <!-- <a-popover--> |
| | | <!-- position="bottom"--> |
| | | <!-- trigger="click"--> |
| | | <!-- >--> |
| | | <!-- <a-button border--> |
| | | <!-- >{{agentTitle}}--> |
| | | <!-- <icon-down style="margin-left: 4px" />--> |
| | | <!-- </a-button>--> |
| | | <!-- <template #content>--> |
| | | <!-- <a-button--> |
| | | <!-- type="text"--> |
| | | <!-- class="button"--> |
| | | <!-- style="color: #2a2a2b"--> |
| | | <!-- @click="handleClick()"--> |
| | | <!-- >--> |
| | | <!-- <template #icon>--> |
| | | <!-- <icon-edit />--> |
| | | <!-- </template>--> |
| | | <!-- 修改名称--> |
| | | <!-- </a-button>--> |
| | | <!-- <!– <a-divider style="margin: 10px 0" />–>--> |
| | | <!-- </template>--> |
| | | <!-- </a-popover>--> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <a-scrollbar |
| | | ref="scrollbar" |
| | | id="home" |
| | | id="agentHome" |
| | | class="chat-list" |
| | | style=" |
| | | width: 90%; |
| | | width: 80%; |
| | | overflow: auto; |
| | | height: calc(100vh - 380px); |
| | | margin: 0px auto 20px; |
| | | " |
| | | > |
| | | <div class="chat-item" v-for="sessionDetail in sessionDetailList"> |
| | | <div class="chat-item" v-for="(sessionDetail,index) in sessionDetailList"> |
| | | <a-comment v-if="sessionDetail.role === 'user'"> |
| | | <template #avatar> |
| | | <img |
| | |
| | | /> |
| | | </template> |
| | | <template #content> |
| | | <a-card :class="{ chatItemAnswer: theme === 'light' }"> |
| | | <div :class="{ light: theme === 'light' }" |
| | | >{{ sessionDetail.content }} |
| | | </div> |
| | | </a-card> |
| | | <!-- <a-card :class="{ chatItemAnswer: theme === 'light' }">--> |
| | | <!-- <div :class="{ light: theme === 'light' }"--> |
| | | <!-- >{{ sessionDetail.content }}--> |
| | | <!-- </div>--> |
| | | <!-- </a-card>--> |
| | | <a-textarea |
| | | readonly |
| | | auto-size |
| | | v-model="sessionDetail.content" |
| | | :class="{ chatItemAnswer: theme === 'light' }" |
| | | :style="{backgroundColor: theme === 'light' ? '#ffffff' : '#000000'}" |
| | | style="border: none" |
| | | > |
| | | </a-textarea> |
| | | </template> |
| | | <template #actions> |
| | | <span class="action" v-if="index != 0" @click="copy(sessionDetail.content)"> |
| | | <icon-copy /> 复制 |
| | | </span> |
| | | <span class="action" v-if="index != 0 && index == sessionDetailList.length - 1" @click="reGenerate()"> |
| | | <icon-refresh /> 重新生成 |
| | | </span> |
| | | </template> |
| | | </a-comment> |
| | | <a-comment v-else-if="sessionDetail.role === 'last'"> |
| | |
| | | auto-size |
| | | v-model="displayedText" |
| | | :class="{ chatItemAnswer: theme === 'light' }" |
| | | :style="{backgroundColor: theme === 'light' ? '#ffffff' : '#000000'}" |
| | | style="border: none" |
| | | > |
| | | </a-textarea> |
| | | </template> |
| | | <!-- <template #actions>--> |
| | | <!-- <div class="action"--> |
| | | <!-- @click="stopChat"--> |
| | | <!-- style="background: var(--color-bg-2);color: var(--color-primary-light-4);" v-if="displayedText != ''">--> |
| | | <!-- <icon-record-stop />--> |
| | | <!-- 停止生成--> |
| | | <!-- </div>--> |
| | | <!-- </template>--> |
| | | </a-comment> |
| | | </div> |
| | | <!-- <div class="chartStart" v-if="isStart" @click="startChat" |
| | |
| | | show-word-limit |
| | | :disabled="chatDis" |
| | | :class="{ textItemAnswer: theme === 'dark' }" |
| | | :style="{backgroundColor: theme === 'light' ? '#ffffff' : '#000000'}" |
| | | style="border: none" |
| | | :auto-size="{ |
| | | minRows: 12, |
| | | maxRows: 5, |
| | |
| | | <a-button |
| | | :disabled="chatDis" |
| | | @click="sentClick" |
| | | type="primary" |
| | | type="text" |
| | | style="border-radius: 24px" |
| | | :loading="loading" |
| | | size="large" |
| | | >发送</a-button |
| | | > |
| | | <icon-send size="32" style="color: #0960bd;"/> |
| | | </a-button |
| | | > |
| | | </div> |
| | | </div> |
| | | <a-modal v-model:visible="visible" title="修改名称" @before-open="handleOpened" @cancel="handleCancel" :footer="false" title-align="start"> |
| | | <a-form ref="formRef" :rules="rules" :model="agentObj" @submit="handleSubmit" > |
| | | <a-form-item field="name" label="名称"> |
| | | <a-input v-model="agentTitle" placeholder="请输入名称"/> |
| | | </a-form-item> |
| | | <a-form-item> |
| | | <div style="width: 100%;text-align: right"> |
| | | <a-button @click="visible = false">取消</a-button> |
| | | <a-button style="margin-left: 10px" type="primary" html-type="submit">确定</a-button> |
| | | </div> |
| | | </a-form-item> |
| | | </a-form> |
| | | </a-modal> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { defineProps, ref, watch, defineEmits, onMounted, reactive, computed, nextTick } from "vue"; |
| | | import { defineProps, ref, watch, defineEmits, onMounted, reactive, computed, nextTick, onBeforeUnmount } from "vue"; |
| | | import { Message } from '@arco-design/web-vue'; |
| | | import { useAppStore } from "@/store"; |
| | | import { getAuthorization } from "@/utils/auth"; |
| | | import { EventSourceParserStream } from 'eventsource-parser/stream'; |
| | | import { agentResetApi, agentSetApi, getAgentSessionDetailsApi } from "@/api/agentSession"; |
| | | import EventBus from '@/utils/EventBus'; |
| | | import useClipboard from "vue-clipboard3"; |
| | | import { addSessionApi, getSessionDetailsApi } from "@/api/session"; |
| | | const props = defineProps({ |
| | | modalObj: Object, |
| | | }); |
| | |
| | | const dialogId = ref(''); |
| | | const chatDis = ref(false); |
| | | const loading = ref(false); |
| | | const agentTitle = ref("未命名会话"); |
| | | |
| | | |
| | | const currIndex = ref(0); |
| | |
| | | const agentList = ref([]); |
| | | const selectValue = ref(''); |
| | | const sectionList = ref({}); |
| | | |
| | | let chatObj = reactive({}); |
| | | const isStopChat = ref(false) |
| | | const appStore = useAppStore(); |
| | | const theme = computed(() => { |
| | | return appStore.theme; |
| | | }); |
| | | |
| | | const visible = ref(false); |
| | | |
| | | |
| | | |
| | | const rules = { |
| | | name: [ |
| | | { |
| | | required: true, |
| | | message:'名称不允许为空', |
| | | }, |
| | | ], |
| | | } |
| | | |
| | | const handleSubmit = async({values, errors}) => { |
| | | if(errors) return; |
| | | // agentObj.name = agentTitle.value; |
| | | // agentObj.conversation_id = agentObj.id; |
| | | console.log(agentObj, 'agentObj'); |
| | | |
| | | |
| | | // let chatData = { |
| | | // id:agentObj.id, |
| | | // conversation_id:agentObj.id, |
| | | // name: agentTitle.value |
| | | // } |
| | | // const { code, data } = await addSessionApi(chatData); |
| | | // if (data) { |
| | | // Message.success("修改成功"); |
| | | // handleCancel() |
| | | // queryAgentSessionDetail(agentObj.id); |
| | | // } |
| | | } |
| | | |
| | | const handleClick = () => { |
| | | visible.value = true; |
| | | }; |
| | | const handleCancel = () => { |
| | | visible.value = false; |
| | | } |
| | | |
| | | const handleOpened =(el) => { |
| | | // Object.assign(form,{ |
| | | // name: '',// 用户名 |
| | | // }); |
| | | // formRef.value.resetFields(); |
| | | } |
| | | |
| | | |
| | | // 初始化页面 |
| | | const initPage = async () => { |
| | | agentSet(); |
| | | agentReset(); |
| | | agentCompletion(); |
| | | queryAgentSessionDetail(agentObj.id); |
| | | }; |
| | | |
| | | const createNewAgent = async (session) => { |
| | | Object.assign(agentObj, session); |
| | | initPage(); |
| | | |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | // 调用set方法 |
| | | const agentSet = async () => { |
| | |
| | | title: agentObj.title, |
| | | dsl: agentObj.dsl, |
| | | }); |
| | | if (res.code === 200) { |
| | | console.log(res,'agentSet'); |
| | | if (res.code == 0) { |
| | | // sessionDetailList.value = res.data.dsl.messages; |
| | | // Message.success('修改成功'); |
| | | agentReset(); |
| | | } |
| | | } |
| | | // 调用reset方法 |
| | |
| | | const res = await agentResetApi({ |
| | | id: agentObj.id, |
| | | }); |
| | | if (res.code === 200) { |
| | | if (res.code == 0) { |
| | | // Message.success('修改成功'); |
| | | agentCompletion(); |
| | | } |
| | | } |
| | | // 调用completion方法 |
| | |
| | | if (code == 0) { |
| | | console.log(data,'会话详情'); |
| | | sessionDetailList.value = data.dsl.messages; |
| | | // refreshScroll(); //刷新滚动条位置 |
| | | agentTitle.value = `和${data.title}的会话` || '未命名会话'; |
| | | refreshScroll(); //刷新滚动条位置 |
| | | } |
| | | }; |
| | | |
| | | |
| | | const querySessionDetail = async (session) => { |
| | | sectionList.value = session; |
| | | activeSessionId.value = session.id; |
| | | const { code, data } = await getSessionDetailsApi(session.id); |
| | | if (code === 200) { |
| | | sessionDetailList.value = data.message; |
| | | refreshScroll(); //刷新滚动条位置 |
| | | } |
| | | }; |
| | | |
| | | |
| | | const { toClipboard } = useClipboard(); |
| | | const copy = async (text) => { |
| | | await toClipboard(text); //参数为要复制的文本 |
| | | } |
| | | |
| | | |
| | | const sentClick = () => { |
| | | sendMessage('click'); |
| | | }; |
| | | |
| | | // 重新生成 |
| | | const reGenerate = () => { |
| | | let inputContent = sessionDetailList.value[sessionDetailList.value.length-2].content |
| | | startChat(inputContent) |
| | | }; |
| | | |
| | | const sendMessage = async (event) => { |
| | | if (event.keyCode == 13 || event === 'click') { |
| | | if (!event.shiftKey) { |
| | |
| | | event.preventDefault(); // 阻止默认行为,即不换行 |
| | | } |
| | | |
| | | chatDis.value = true; |
| | | loading.value = true; |
| | | if (!activeSessionId.value) { |
| | | Message.warning('请选择会话'); |
| | | chatDis.value = false; |
| | | loading.value = false; |
| | | return; |
| | | } |
| | | // chatDis.value = true; |
| | | // loading.value = true; |
| | | // if (!agentObj.id) { |
| | | // Message.warning('请选择会话'); |
| | | // chatDis.value = false; |
| | | // loading.value = false; |
| | | // return; |
| | | // } |
| | | |
| | | // if (displayedText.value) { |
| | | // querySessionList(); |
| | | // } |
| | | |
| | | if (inputMsg.value) { |
| | | sessionDetailList.value.push({ |
| | | content: inputMsg.value, |
| | | role: 'user', |
| | | }); |
| | | sessionDetailList.value.push({ role: 'last' }); |
| | | refreshScroll(); |
| | | const response = await fetch( |
| | | '/api/v1/canvas/completion', |
| | | { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Authorization': getAuthorization(), |
| | | 'Content-Type': 'application/json', |
| | | }, |
| | | body: JSON.stringify({ |
| | | conversation_id: activeSessionId.value, |
| | | messages: inputMsg.value, |
| | | }), |
| | | } |
| | | ); |
| | | |
| | | const reader = response?.body |
| | | ?.pipeThrough(new TextDecoderStream()) |
| | | .pipeThrough(new EventSourceParserStream()) |
| | | .getReader(); |
| | | currIndex.value = 0; |
| | | while (true) { |
| | | const x = await reader?.read(); |
| | | if (x) { |
| | | const { done, value } = x; |
| | | console.log(x, 999); |
| | | try { |
| | | const val = JSON.parse(value?.data || ''); |
| | | const d = val?.data; |
| | | if (typeof d !== 'boolean') { |
| | | console.info('data:', d); |
| | | streamStr.value = d.content; |
| | | startDisplayStr(); |
| | | } |
| | | } catch (e) { |
| | | console.warn(e); |
| | | } |
| | | if (done) { |
| | | console.info('done'); |
| | | displayedText.value = ''; |
| | | queryAgentSessionDetail(agentObj.id); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | chatDis.value = false; |
| | | loading.value = false; |
| | | startChat(inputMsg.value) |
| | | inputMsg.value = ''; |
| | | } else { |
| | | Message.warning('消息不能为空'); |
| | |
| | | } |
| | | } |
| | | }; |
| | | |
| | | |
| | | const startChat = async(valMsg)=>{ |
| | | |
| | | sessionDetailList.value.push({ |
| | | content: valMsg, |
| | | role: 'user', |
| | | }); |
| | | sessionDetailList.value.push({ role: 'last' }); |
| | | refreshScroll(); |
| | | const response = await fetch( |
| | | '/api/v1/canvas/completion', |
| | | { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Authorization': getAuthorization(), |
| | | 'Content-Type': 'application/json', |
| | | }, |
| | | body: JSON.stringify({ |
| | | id: agentObj.id, |
| | | message: valMsg, |
| | | }), |
| | | } |
| | | ); |
| | | |
| | | const reader = response?.body |
| | | ?.pipeThrough(new TextDecoderStream()) |
| | | .pipeThrough(new EventSourceParserStream()) |
| | | .getReader(); |
| | | currIndex.value = 0; |
| | | while (true) { |
| | | const x = await reader?.read(); |
| | | if (x) { |
| | | const { done, value } = x; |
| | | // console.log(x, 999); |
| | | try { |
| | | const val = JSON.parse(value?.data || ''); |
| | | const d = val?.data; |
| | | if (typeof d !== 'boolean') { |
| | | console.info('data:', d); |
| | | streamStr.value = d.content; |
| | | startDisplayStr(); |
| | | } |
| | | } catch (e) { |
| | | console.warn(e); |
| | | } |
| | | if (done) { |
| | | console.info('done'); |
| | | displayedText.value = ''; |
| | | queryAgentSessionDetail(agentObj.id); |
| | | EventBus.emit('queryAppUsageList'); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | chatDis.value = false; |
| | | loading.value = false; |
| | | } |
| | | |
| | | const stopChat=async()=>{ |
| | | // const { code, data } = await stopChatApi(activeSessionId.value); |
| | | // if (code === 200) { |
| | | // Message.success("已停止"); |
| | | // queryNewSessionDetail(activeSessionId.value); |
| | | // } |
| | | loading.value = false; |
| | | chatDis.value = false; |
| | | isStopChat.value = true; |
| | | console.log('stopChat'); |
| | | console.log(displayedText.value, 'displayedText'); |
| | | console.log(sessionDetailList.value, 'sessionDetailList'); |
| | | let inputText = sessionDetailList.value[sessionDetailList.value.length-2].content |
| | | // 数组合并 |
| | | let lastArr = [{ |
| | | content: inputText, |
| | | role: "user" |
| | | },{ |
| | | content: displayedText.value, |
| | | role: "assistant" |
| | | }] |
| | | sessionDetailList.value = sessionDetailList.value.splice(0, sessionDetailList.value.length-2).concat(lastArr); |
| | | console.log(sessionDetailList.value, 'sessionDetailList2'); |
| | | console.log(chatObj, 'chatObj对象'); |
| | | chatObj.message = chatObj.message.concat(lastArr); |
| | | // clearTimeout(timer!); |
| | | // timer = null; |
| | | // const { code, data } = await addSessionApi(chatObj); |
| | | // if (data) { |
| | | // //停止定时器 |
| | | // clearTimeout(timer!); |
| | | // timer = null; |
| | | // displayedText.value = ""; |
| | | // queryNewSessionDetail(activeSessionId.value); |
| | | // } |
| | | |
| | | |
| | | } |
| | | |
| | | const handleShiftEnter = (event) => { |
| | | event.preventDefault(); |
| | |
| | | const scrollbar = ref(null); |
| | | const refreshScroll = () => { |
| | | nextTick(() => { |
| | | const container = document.getElementById('home'); |
| | | const container = document.getElementById('agentHome'); |
| | | scrollbar.value.scrollTop(container.scrollHeight); |
| | | }); |
| | | }; |
| | | |
| | | |
| | | onMounted(() => { |
| | | |
| | | EventBus.on('createAgent', (data) => { |
| | | createNewAgent(data); |
| | | }); |
| | | EventBus.on('queryAgentSessionDetail', (data) => { |
| | | Object.assign(agentObj, data); |
| | | // queryAgentSessionDetail(data.id); |
| | | querySessionDetail(data); |
| | | }); |
| | | }); |
| | | onBeforeUnmount(() => { |
| | | EventBus.off('createAgent'); |
| | | EventBus.off('queryAgentSessionDetail'); |
| | | }); |
| | | |
| | | watch( |
| | | () => props.modalObj, |
| | | (newVal, oldVal) => { |
| | | console.log(newVal,'监听变化'); |
| | | Object.assign(agentObj, newVal); |
| | | // Object.assign(agentObj, newVal); |
| | | //调用agent初始化方法 |
| | | if(JSON.stringify(newVal) != '{}'){ |
| | | initPage(); |
| | | // initPage(); |
| | | } |
| | | },{ |
| | | immediate: true, |
| | |
| | | .chat_bottom { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 78%; |
| | | margin: 0 auto; |
| | | .center-bottom { |
| | | // position: absolute; |
| | | // width: 90%; |
| | |
| | | |
| | | .btn-send { |
| | | position: absolute !important; |
| | | right: 10px; |
| | | bottom: 10px; |
| | | right: 4px; |
| | | top: 80px; |
| | | z-index: 10; |
| | | } |
| | | :deep(.arco-btn-size-large) { |
| | |
| | | } |
| | | } |
| | | } |
| | | .header___lEPyH { |
| | | width: 100%; |
| | | height: 46px; |
| | | position: relative; |
| | | backdrop-filter: blur(15px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | -webkit-backdrop-filter: blur(15px); |
| | | .chatHeaderBox { |
| | | width: auto; |
| | | border-radius: 8px; |
| | | padding: 4px 20px; |
| | | transition: all var(--animation-duration) var(--animation-transition); |
| | | display: flex; |
| | | align-items: flex-end; |
| | | overflow: hidden; /* 隐藏超出的内容 */ |
| | | text-overflow: ellipsis; /* 使用省略号来代替被隐藏的文字 */ |
| | | white-space: nowrap; /* 不换行,使内容在一行内显示 */ |
| | | max-width: 200px; |
| | | .title{ |
| | | color: var(--color-text-1); |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .action { |
| | | cursor: pointer; |
| | | display: inline-block; |
| | | padding: 0 10px; |
| | | color: var(--color-text-1); |
| | | line-height: 24px; |
| | | background: transparent; |
| | | border-radius: 2px; |
| | | cursor: pointer; |
| | | transition: all 0.1s ease; |
| | | font-size: 12px; |
| | | } |
| | | .action:hover { |
| | | background: var(--color-fill-3); |
| | | } |
| | | </style> |