Files
aida_front/src/component/home/chat/index.vue

629 lines
18 KiB
Vue
Raw Normal View History

2025-05-20 16:47:27 +08:00
<template>
<div class="chat" :class="{active:!openChat}" @click.stop="">
2025-05-21 20:05:08 +08:00
<div class="top" :class="{active:!isChattingRecords}" @click="()=>{isChattingRecords=!isChattingRecords}">
<i class="fi fi-br-angle-small-down"></i>
</div>
<div v-show="!openChat" class="left" @click="()=>{openChat = !openChat;isChattingRecords=true}">
2025-05-20 16:47:27 +08:00
<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">
2025-06-03 14:57:18 +08:00
<div class="icon">
<img src="@/assets/images/icon/favicon.png" alt="">
</div>
2025-05-21 20:05:08 +08:00
<div class="text" v-show="item.content.think || item.content.message || item.content.img || item.content.color">
<span class="content">
<div class="showThink" :class="{active:item.content?.isThink}" v-show="item.content.think" @click="()=>item.content?.isThink?(item.content.isThink = false):(item.content.isThink = true)">
<div>已深度思考</div>
<i class="fi fi-br-angle-small-down"></i>
</div>
<div class="think" v-show="item.content?.isThink">{{item.content.think}}</div>
<div class="txt">{{item.content.message}}</div>
</span>
2025-05-20 16:47:27 +08:00
<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>
2025-05-21 20:05:08 +08:00
<i class="fi fi-br-loading" v-if="!item.content.think && !item.content.message && !item.content.img && !item.content.color"></i>
2025-05-20 16:47:27 +08:00
</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>
2025-05-21 20:05:08 +08:00
<i class="fi fi-rs-paperclip-vertical">
2025-05-20 16:47:27 +08:00
<input type="file" @change="handleFileUpload($event)">
</i>
2025-05-28 10:28:07 +08:00
<div class="enableThinking" :class="{active:enableThinking}" @click="()=>enableThinking = !enableThinking">Deep Thinking</div>
2025-05-20 16:47:27 +08:00
</div>
<div class="sendBox">
2025-05-22 14:26:47 +08:00
<div class="maxNum">{{ chatContent?.length }}/10000</div>
<div class="send" :class="{active:chatContent?.length>0}" @click="sendChat">
2025-05-20 16:47:27 +08:00
<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)=>{
2025-06-03 14:57:18 +08:00
if(newValue && (data.selectObject.httpType == 'SERIES_DESIGN' || data.selectObject.httpType == 'SINGLE_DESIGN')){
2025-05-20 16:47:27 +08:00
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
2025-05-22 14:26:47 +08:00
let fileList
if(data.filList){
fileList = JSON.parse(JSON.stringify(data.filList))
}else{
fileList = []
}
2025-05-20 16:47:27 +08:00
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(',') : ''
2025-05-21 20:05:08 +08:00
data.chatList.push({content:{message:data.chatContent,think:''},role:'user',fileList:fileList})
data.chatList.push({content:{message:'',think:''},role:'system'})
2025-05-20 16:47:27 +08:00
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
2025-06-09 10:25:54 +08:00
console.log('收到数据:', JSON.parse(event.data));
2025-05-20 16:47:27 +08:00
// 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;
2025-06-09 10:25:54 +08:00
const eventData = JSON.parse(event.data)
if(eventData.type == 'text'){
data.chatList[data.chatList.length-1].content.message+=eventData.content
}else if(eventData.type == 'think'){
data.chatList[data.chatList.length-1].content.think+=eventData.content
}else if(eventData.type == "tools_response"){
let nameList = ['moodboard','printboard','sketchboard','generate_color_code']
let getData = ''
if(nameList.indexOf(eventData.tools_name) > -1){
if(data.chatList[data.chatList.length - 1].content.message)data.chatList.push({content:{message:''},role:'system'})
if(eventData.tools_name == 'generate_color_code'){
data.chatList[data.chatList.length-1].content.color = JSON.parse(JSON.parse(event.data).content).receiveCollectionElementList
getData = 'colorboard'
}else{
data.chatList[data.chatList.length-1].content.img = JSON.parse(JSON.parse(event.data).content).receiveCollectionElementList
getData = eventData.tools_name
}
2025-05-20 16:47:27 +08:00
data.chatList.push({content:{message:''},role:'system'})
2025-06-09 10:25:54 +08:00
}else{
2025-05-20 16:47:27 +08:00
}
2025-06-09 10:25:54 +08:00
emit('chatChange',{type:eventData.type,module:getData})
2025-05-20 16:47:27 +08:00
}
2025-06-09 10:25:54 +08:00
//emit('chatChange',{type:JSON.parse(event.data).status})
2025-05-20 16:47:27 +08:00
};
eventSource.onerror = function(error) {
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
2025-05-21 20:05:08 +08:00
if(rv.content[rv.content.length - index -1].isImage == 2){
2025-05-20 16:47:27 +08:00
rv.content[rv.content.length - index -1].content={
img : JSON.parse(rv.content[rv.content.length - index -1].content)
}
2025-05-21 20:05:08 +08:00
}else if(rv.content[rv.content.length - index -1].isImage==1){
2025-05-20 16:47:27 +08:00
rv.content[rv.content.length - index -1].content={
color : JSON.parse(rv.content[rv.content.length - index -1].content)
}
}else{
2025-05-21 20:05:08 +08:00
let think = ''
let message = ''
2025-05-22 11:18:11 +08:00
if(text.split('[TEXT]').length > 1 && text.split('[THINK]').length > 1){
2025-05-21 20:05:08 +08:00
think = text.split('[TEXT]')[0]
text.split('[TEXT]').forEach((text:any,index:number) => {
if(index == 0)return
message += ('[TEXT]'+text)
});
}else{
message = text
}
2025-05-20 16:47:27 +08:00
rv.content[rv.content.length - index -1].content = {
2025-05-21 20:05:08 +08:00
message:message,
think:think,
2025-05-20 16:47:27 +08:00
}
}
}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
2025-05-21 20:05:08 +08:00
// let setRecords = ()=>{
// data.isChattingRecords = false
// document.removeEventListener('click',setRecords)
// }
// document.addEventListener('click',setRecords)
2025-05-20 16:47:27 +08:00
}
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{
2025-05-21 20:05:08 +08:00
> .content{
> .txt{
}
> .showThink{
display: none;
}
}
2025-05-20 16:47:27 +08:00
> .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;
2025-06-09 10:25:54 +08:00
// -webkit-user-select: none;
2025-06-03 14:57:18 +08:00
> .icon{
> img{
width: 2.5rem;
height: 2.5rem;
}
}
2025-05-20 16:47:27 +08:00
> .text{
// display: inline-block;
// width: min-content;
max-width: 100%;
width: 100%;
// width: min-content;
// word-wrap: break-word;
2025-05-21 20:05:08 +08:00
> .content{
> .txt{
}
> .showThink{
cursor: pointer;
display: flex;
padding: .7rem 1.4rem;
margin-bottom: 1.2rem;
background: rgb(237 237 237);
border-radius: 1rem;
align-items: center;
&.active{
>i{
transform: rotate(180deg);
}
}
> i{
display: flex;
transition: all .3s;
}
}
> .think{
margin-bottom: 2rem;
padding-left: 2rem;
color: #8b8b8b;
white-space: pre-wrap;
border-left: 2px solid #e5e5e5;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
}
2025-05-20 16:47:27 +08:00
> .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;
}
}
}
2025-05-28 10:28:07 +08:00
> .enableThinking{
width: 10rem;
padding: .2rem .4rem;
text-align: center;
font-size: 1.4rem;
border: 1px solid #000;
border-radius: .4rem;
cursor: pointer;
margin-left: 1rem;
&.active{
background: #000;
color: #fff;
}
}
2025-05-20 16:47:27 +08:00
}
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;
2025-05-28 10:28:07 +08:00
2025-05-20 16:47:27 +08:00
> .maxNum{
font-size: 1.2rem;
margin-right: .8rem;
font-weight: 400;
}
> .send{
opacity: .5;
cursor: no-drop;
&.active{
opacity: 1;
cursor: pointer;
}
}
}
}
}
}
2025-05-21 20:05:08 +08:00
> .right,> .left,>.top{
2025-05-20 16:47:27 +08:00
display: flex;
align-items: center;
cursor: pointer;
width: 3rem;
justify-content: center;
> i{
display: flex;
font-size: 2rem;
transition: all .3s;
}
}
2025-05-21 20:05:08 +08:00
> .top{
width: 100%;
margin: 0 3rem;
height: 3rem;
&.active{
> i{
transform: rotate(180deg);
}
}
> i{
}
}
2025-05-20 16:47:27 +08:00
> .right{
border-left: 1px solid #e5e5e5;
> i{
transform: rotate(270deg);
}
}
> .left{
border-right: 1px solid #e5e5e5;
> i{
transform: rotate(90deg);
}
}
2025-05-21 20:05:08 +08:00
flex-wrap: wrap;
2025-05-20 16:47:27 +08:00
}
</style>