chat聊天功能
This commit is contained in:
531
src/component/home/chat/index.vue
Normal file
531
src/component/home/chat/index.vue
Normal file
@@ -0,0 +1,531 @@
|
||||
<template>
|
||||
<div class="chat" :class="{active:!openChat}" @click.stop="">
|
||||
<div v-show="!openChat" class="left" @click="()=>{openChat = !openChat;isChattingRecords=false}">
|
||||
<i class="fi fi-br-angle-small-down"></i>
|
||||
</div>
|
||||
<div class="chatBox">
|
||||
<div class="chattingRecords" v-show="chatList.length > 0 && isChattingRecords">
|
||||
<div class="itemBox" ref="chatBox">
|
||||
<div class="item" v-for="item in chatList" :class="{user:item.role == 'user'}">
|
||||
<div class="textBox">
|
||||
<div class="icon">ICON</div>
|
||||
<div class="text" v-show="item.content.message || item.content.img || item.content.color">
|
||||
<span>{{item.content.message}}</span>
|
||||
<div class="fileBox">
|
||||
<div v-if="item?.fileList?.length > 0" class="item" v-for="fileItem in item.fileList">
|
||||
<div>{{fileItem.name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="imgBox">
|
||||
<img v-if="item.content?.img?.length > 0" v-for="imgItem in item.content?.img" :src="imgItem.minioUrl" alt="">
|
||||
</div>
|
||||
<div class="colorBox">
|
||||
<div v-if="item.content?.color?.length > 0" class="item" v-for="colorItem in item.content?.color">
|
||||
<div class="color" :style="{'background-color':`rgba(${colorItem.rgb.replace(/\s+/g, ',')})`}"></div>
|
||||
<div class="text">{{ colorItem.rgb.replace(/\s+/g, ',') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fi fi-br-loading" v-if="!item.content.message && !item.content.img && !item.content.color"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content" @click="openChattingRecords">
|
||||
<textarea ref="textarea" @input="inputText($event)" @keydown.enter.prevent="sendChat" placeholder="Write your message"></textarea>
|
||||
<div class="btn">
|
||||
<div class="uploadBox">
|
||||
<div class="filList">
|
||||
<div class="item" v-for="item,index in filList">
|
||||
<div>{{item.name}}</div>
|
||||
<span class="icon iconfont icon-shanchu" @click="deleteFile(item,index)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fi fi-br-upload">
|
||||
<input type="file" @change="handleFileUpload($event)">
|
||||
</i>
|
||||
</div>
|
||||
<div class="sendBox">
|
||||
<div class="enableThinking" :class="{active:enableThinking}" @click="()=>enableThinking = !enableThinking">Deep Thinking</div>
|
||||
<div class="maxNum">{{ chatContent.length }}/10000</div>
|
||||
<div class="send" :class="{active:chatContent.length>0}" @click="sendChat">
|
||||
<i class="fi fi-ss-paper-plane-top"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="openChat" class="right" @click="()=>{openChat = !openChat;isChattingRecords=false}">
|
||||
<i class="fi fi-br-angle-small-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent,computed,ref,inject,nextTick,createVNode,toRefs, reactive, watch} from 'vue'
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { Https } from "@/tool/https";
|
||||
import { useStore } from "vuex";
|
||||
import { Modal,message,Upload,CascaderProps } from 'ant-design-vue';
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { setCookie, getCookie, WriteCookie } from "@/tool/cookie";
|
||||
export default defineComponent({
|
||||
components:{
|
||||
},
|
||||
props:{
|
||||
},
|
||||
emits:['chatChange'],
|
||||
setup(props,{emit}) {
|
||||
const store = useStore();
|
||||
const data = reactive({
|
||||
chatContent:'',
|
||||
openChat:true,
|
||||
chatList:[
|
||||
] as any,
|
||||
isChattingRecords:false,
|
||||
selectObject:computed(()=>store.state.Workspace.probjects) as any,//选择的项目
|
||||
filList:[] as any,
|
||||
setIsShowMark:inject('setIsShowMark') as any,
|
||||
isFinish:true,
|
||||
enableThinking:false,//深度思考
|
||||
})
|
||||
const dataDom = reactive({
|
||||
textarea:null as any,
|
||||
chatBox:null as any,
|
||||
})
|
||||
watch(()=>data.selectObject.id,(newValue,oldValue)=>{
|
||||
if(newValue){
|
||||
getChatHistory(newValue)
|
||||
}
|
||||
})
|
||||
const inputText = (e:any)=>{
|
||||
if(e.target.value.length <= 1000){
|
||||
data.chatContent = e.target.value
|
||||
}else{
|
||||
e.target.value = data.chatContent
|
||||
}
|
||||
e.target.style.height = `${e.target.scrollHeight}px`;
|
||||
}
|
||||
const sendChat = ()=>{
|
||||
if(!data.isFinish)return
|
||||
if(!data.chatContent)return
|
||||
let fileList = JSON.parse(JSON.stringify(data.filList))
|
||||
let fileUrl = (fileList.filter((item:any)=>item.type == 'file').length > 0) ? fileList.filter((item:any)=>item.type == 'file')[0].minioPath : ''
|
||||
let imageUrlList = (fileList.filter((item:any)=>item.type == 'image').length > 0)? fileList.filter((item:any)=>item.type == 'image').map((item:any)=>item.minioPath).join(',') : ''
|
||||
data.chatList.push({content:{message:data.chatContent},role:'user',fileList:fileList})
|
||||
data.chatList.push({content:{message:''},role:'system'})
|
||||
const eventSource = new EventSource(`${process.env.VUE_APP_BASE_URL}${Https.httpUrls.llmStream}?token=${getCookie('token')}&prompt=${data.chatContent}&projectId=${data.selectObject.id}&fileUrl=${fileUrl}&imageUrlList=${imageUrlList}&enableThinking=${data.enableThinking}`);
|
||||
data.chatContent = ''
|
||||
dataDom.textarea.value = ''
|
||||
data.filList = []
|
||||
eventSource.onmessage = function(event) {
|
||||
data.isFinish = false
|
||||
// console.log('收到数据:', JSON.parse(event.data));
|
||||
// if(event.data.status == 'DESIGN_SIGNAL'){
|
||||
// emit('chatChange',{type:'design'})
|
||||
// }else if(event.data.status == 'RUNNING'){
|
||||
// data.chatList[data.chatList.length-1].content.message+=JSON.parse(event.data).content
|
||||
// }
|
||||
const container = dataDom.chatBox;
|
||||
container.scrollTop = container.scrollHeight;
|
||||
if(JSON.parse(event.data).status == "[RUNNING]"){
|
||||
data.chatList[data.chatList.length-1].content.message+=JSON.parse(event.data).content
|
||||
}else{
|
||||
if(JSON.parse(event.data).status == "[DESIGN_SIGNAL]"){
|
||||
|
||||
}else if(JSON.parse(event.data).status == "[COLOR_SIGNAL]"){
|
||||
data.chatList.push({content:{message:''},role:'system'})
|
||||
data.chatList[data.chatList.length-1].content.color = JSON.parse(JSON.parse(event.data).tools_data).receiveCollectionElementList
|
||||
data.chatList.push({content:{message:''},role:'system'})
|
||||
}else{
|
||||
data.chatList.push({content:{message:''},role:'system'})
|
||||
data.chatList[data.chatList.length-1].content.img = JSON.parse(JSON.parse(event.data).tools_data).receiveCollectionElementList
|
||||
data.chatList.push({content:{message:''},role:'system'})
|
||||
}
|
||||
console.log(data.chatList[data.chatList.length-1].content)
|
||||
emit('chatChange',{type:JSON.parse(event.data).status})
|
||||
}
|
||||
|
||||
};
|
||||
eventSource.onerror = function(error) {
|
||||
console.log(error,EventSource.CLOSED,eventSource.readyState)
|
||||
if (eventSource.readyState === EventSource.CLOSED) {
|
||||
data.chatList[data.chatList.length-1].content.message='服务器繁忙,请稍后再试。'
|
||||
|
||||
} else {
|
||||
eventSource.close()
|
||||
data.isFinish = true
|
||||
}
|
||||
};
|
||||
}
|
||||
const getChatHistory = (objectId:number)=>{
|
||||
let value = {
|
||||
projectId:objectId,
|
||||
page:1,
|
||||
pageSize:100,
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.getChatHistory,value).then((rv)=>{
|
||||
if(rv){
|
||||
rv.content.forEach((item:any,index:number) => {
|
||||
if(rv.content[rv.content.length - index -1].role == 'system'){
|
||||
let text = rv.content[rv.content.length - index -1].content
|
||||
if(rv.content[rv.content.length - index -1].isImage == 1){
|
||||
rv.content[rv.content.length - index -1].content={
|
||||
img : JSON.parse(rv.content[rv.content.length - index -1].content)
|
||||
}
|
||||
}else if(rv.content[rv.content.length - index -1].isImage==2){
|
||||
rv.content[rv.content.length - index -1].content={
|
||||
color : JSON.parse(rv.content[rv.content.length - index -1].content)
|
||||
}
|
||||
}else{
|
||||
rv.content[rv.content.length - index -1].content = {
|
||||
message:text
|
||||
}
|
||||
}
|
||||
}else{
|
||||
let content = JSON.parse(rv.content[rv.content.length - index -1].content)
|
||||
content.fileList = []
|
||||
if(content.file || content.image){
|
||||
let getName = (url:any)=>{
|
||||
let minio = url.splice('?')[0]
|
||||
return minio.splice('/')[minio.splice('/').length-1]
|
||||
}
|
||||
if(content.file){
|
||||
content.file.forEach((item:any)=>{
|
||||
content.fileList.push({name:getName(item),type:'file',url:item})
|
||||
})
|
||||
}
|
||||
if(content.image){
|
||||
content.image.forEach((item:any)=>{
|
||||
content.fileList.push({name:getName(item),type:'image',url:item})
|
||||
})
|
||||
}
|
||||
}
|
||||
rv.content[rv.content.length - index -1].content = content
|
||||
}
|
||||
data.chatList.push(rv.content[rv.content.length - index -1])
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
const openChattingRecords = ()=>{
|
||||
data.isChattingRecords = true
|
||||
let setRecords = ()=>{
|
||||
data.isChattingRecords = false
|
||||
document.removeEventListener('click',setRecords)
|
||||
}
|
||||
document.addEventListener('click',setRecords)
|
||||
}
|
||||
const handleFileUpload = (event:any)=>{
|
||||
if (event.target.files[0].size > 5 * 1024 * 1024) { // 5MB
|
||||
message.info('The file size cannot exceed 5MB.');
|
||||
return
|
||||
}
|
||||
let type = event.target.files[0].type.startsWith('image/')
|
||||
if(type){
|
||||
if(data.filList.filter((item:any)=>item.type == 'image').length >= 5){
|
||||
message.info('You can only upload five pictures.');
|
||||
return
|
||||
}
|
||||
}else{
|
||||
if(data.filList.filter((item:any)=>item.type == 'file').length >= 1){
|
||||
message.info('You can only upload one file.');
|
||||
return
|
||||
}
|
||||
}
|
||||
data.setIsShowMark(true)
|
||||
const formData = new FormData();
|
||||
formData.append('file', event.target.files[0]);
|
||||
let config:any = {
|
||||
headers:{'Content-Type':'multipart/form-data','Accept':'*/*' },
|
||||
params:formData,
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.llmUploadFile,formData,config)
|
||||
.then((rv: any) => {
|
||||
let obj = {
|
||||
name:event.target.files[0].name,
|
||||
type:type?'image':'file',
|
||||
minioPath:rv[0],
|
||||
url:rv[1],
|
||||
}
|
||||
data.filList.push(obj)
|
||||
// data.filList.unshift(rv)
|
||||
data.setIsShowMark(false)
|
||||
}
|
||||
).catch(rv=>{
|
||||
data.setIsShowMark(false)
|
||||
})
|
||||
}
|
||||
const deleteFile = (item:any,index:number)=>{
|
||||
data.filList.splice(index,1)
|
||||
}
|
||||
return{
|
||||
...toRefs(dataDom),
|
||||
...toRefs(data),
|
||||
inputText,
|
||||
sendChat,
|
||||
openChattingRecords,
|
||||
handleFileUpload,
|
||||
deleteFile,
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.chat{
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 999;
|
||||
bottom: 3.2rem;
|
||||
top: auto;
|
||||
width: 50%;
|
||||
transition: all .3s;
|
||||
border-radius: 2.4rem;
|
||||
display: flex;
|
||||
border: 1px solid #e5e5e5;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
&.active{
|
||||
left: auto;
|
||||
right: 0;
|
||||
transform: translateX(calc(100% - 3rem));
|
||||
}
|
||||
> .chatBox{
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
> .chattingRecords{
|
||||
> .itemBox{
|
||||
width: 100%;
|
||||
padding: 1.6rem 2rem;
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
> .item{
|
||||
display: flex;
|
||||
line-height: 1.75;
|
||||
// width: min-content;
|
||||
&.user{
|
||||
> .textBox{
|
||||
margin-left: auto;
|
||||
background: #f5f5f5;
|
||||
max-width: 60%;
|
||||
> .text{
|
||||
> .fileBox{
|
||||
> .item{
|
||||
height: 3rem;
|
||||
padding: .5rem 1rem;
|
||||
background: #efeff1;
|
||||
border-radius: .5rem;
|
||||
margin-right: 1rem;
|
||||
font-size: 1.4rem;
|
||||
line-height: 2rem;
|
||||
display: flex;
|
||||
> div{
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
max-width: 10rem;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .icon{
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
> .textBox{
|
||||
display: flex;
|
||||
padding: 1.2rem 2rem;
|
||||
border-radius: 2.4rem;
|
||||
|
||||
> .text{
|
||||
// display: inline-block;
|
||||
// width: min-content;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
// width: min-content;
|
||||
// word-wrap: break-word;
|
||||
> .imgBox{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
img{
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
cursor: pointer;
|
||||
margin: .5rem;
|
||||
}
|
||||
}
|
||||
> .colorBox{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
>.item{
|
||||
margin: .5rem;
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid;
|
||||
width: 10rem;
|
||||
> .color{
|
||||
width: 10rem;
|
||||
height: 7rem;
|
||||
}
|
||||
> .text{
|
||||
font-size: 1.4rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .icon{
|
||||
margin-right: 1.2rem;
|
||||
}
|
||||
|
||||
> i{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: loading 1s linear infinite;
|
||||
@keyframes loading {
|
||||
from{
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to{
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
> .content{
|
||||
background: #f5f5f5;
|
||||
// border-radius: 2.4rem;
|
||||
> textarea{
|
||||
padding: 1.6rem 2rem 0;
|
||||
background: #f5f5f5;
|
||||
width: 100%;
|
||||
min-height: 7.2rem;
|
||||
// border-radius: 2.4rem;
|
||||
font-weight: 400;
|
||||
line-height: 2rem;
|
||||
font-size: 1.4rem;
|
||||
resize: none;
|
||||
border: none;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
> .btn{
|
||||
padding: 0 1.2rem 1.2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
> .uploadBox{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> .filList{
|
||||
display: flex;
|
||||
> .item{
|
||||
height: 3rem;
|
||||
padding: .5rem 1rem;
|
||||
background: #efeff1;
|
||||
border-radius: .5rem;
|
||||
margin-right: 1rem;
|
||||
font-size: 1.4rem;
|
||||
line-height: 2rem;
|
||||
display: flex;
|
||||
> div{
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
max-width: 10rem;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
> span{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i{
|
||||
font-size: 2rem;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
> input{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
&::-webkit-file-upload-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .sendBox{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> .enableThinking{
|
||||
width: 10rem;
|
||||
padding: .2rem .4rem;
|
||||
text-align: center;
|
||||
font-size: 1.4rem;
|
||||
border: 1px solid #000;
|
||||
border-radius: .4rem;
|
||||
cursor: pointer;
|
||||
margin-right: 1rem;
|
||||
&.active{
|
||||
background: #000;
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
}
|
||||
> .maxNum{
|
||||
font-size: 1.2rem;
|
||||
margin-right: .8rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
> .send{
|
||||
opacity: .5;
|
||||
cursor: no-drop;
|
||||
&.active{
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .right,> .left{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: 3rem;
|
||||
justify-content: center;
|
||||
> i{
|
||||
display: flex;
|
||||
font-size: 2rem;
|
||||
transition: all .3s;
|
||||
}
|
||||
}
|
||||
> .right{
|
||||
border-left: 1px solid #e5e5e5;
|
||||
> i{
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
}
|
||||
> .left{
|
||||
border-right: 1px solid #e5e5e5;
|
||||
> i{
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user