<template>

    <div class="chatInfor">

        <div class="chatInfor-content">
            <el-scrollbar height="97%" id="chatBox" ref="scrollbarRef" v-loading="loading" width="928px"
                element-loading-text="数据加载中。。。" :element-loading-spinner="svg"
                element-loading-svg-view-box="-10, -10, 50, 50" element-loading-background="rgba(122, 122, 122, 0)">

                <div class="chatInfor-content-item" v-for="(item, index) in chatList" :key="index">
                    <div class="chatInfor-content-item-time" v-if="item.role == 'user'">{{ item.created_at }}</div>
                    <div class="chatInfor-content-item-users" v-if="item.role == 'user'">

                        <p>
                        <div v-for="(items, index) in item.content">
                            <div v-if="items.type == 'text'"> {{ items.text }}</div>
                            <div style="display: flex;margin-left: -16px;" v-show="items.imgList">
                                <div v-for="(itemimage, index) in items.imgList"
                                    style="margin-left:16px;margin-top:8px">
                                    <el-image :src="itemimage" :zoom-rate="1.2" :max-scale="7"
                                        class="chatInfor-input-button-imglist-item-img" :min-scale="0.2"
                                        :preview-src-list="items.imgList" :initial-index="0" fit="cover"
                                        style="height:72px;" />
                                </div>
                            </div>
                            <!-- <img :src="items.text" v-if="items.imgList.length > 0" /> -->
                        </div>
                        </p>
                        <img :src="icon ? proxy.$loginUrl + icon : hqdInforImg">
                        <!-- <img src="@/assets/function/chat/person-image.png" /> -->
                    </div>
                    <div class="chatInfor-content-item-chats" v-if="item.role == 'assistant' && item.progress == false">
                        <img src="@/assets/function/chat/hqd-image.png" />
                        <p v-if="!item.content[0].text">
                        <div class="loader">
                            <div class="loader__circle"></div>
                            <div class="loader__circle"></div>
                            <div class="loader__circle"></div>
                            <div class="loader__circle"></div>
                            <div class="loader__circle"></div>
                        </div>
                        </p>
                        <p v-else>
                            <span v-html="md.render(item.content[0].text)" :id="`id${index}`"></span>
                        <div class="chatInfor-content-item-chats-button" v-if="item.isRecepting == false">
                            <div class="ebutton" @click="reBuild(index)">
                                <SvgIcon name="chat-reset" class="chat-btn" />
                                <span>重新生成</span>
                            </div>
                            <div class="ebutton" @click="tranlate(item, index)">
                                <SvgIcon name="chat-trans" class="chat-btn" />
                                <span>翻译</span>
                            </div>
                            <div class="ebutton" @click="onCopy(item)">
                                <SvgIcon name="chat-copy" class="chat-btn" />
                                <span>复制</span>
                            </div>
                        </div>
                        </p>
                        <!-- <div>111</div> -->
                    </div>
                    <div class="chatInfor-content-item-chats" v-if="item.role == 'assistant' && item.progress == true">
                        <img src="@/assets/function/chat/hqd-image.png" />
                        <p v-if="!respContent">
                        <div class="loader">
                            <div class="loader__circle"></div>
                            <div class="loader__circle"></div>
                            <div class="loader__circle"></div>
                            <div class="loader__circle"></div>
                            <div class="loader__circle"></div>
                        </div>
                        </p>

                        <p v-html="md.render(respContent)" v-if="respContent"></p>
                    </div>

                </div>
            </el-scrollbar>
        </div>

        <div class="chatInfor-input">
            <el-input type="textarea" class="el-text" v-model="infor" resize="none" ref="textInput"
                @keyup.ctrl.enter.native="lineBreak" @keydown.enter.exact.native="sendMessage" autosize
                placeholder="请输入你想要咨询的内容">

            </el-input>
            <div class="chatInfor-input-button">
                <div class="chatInfor-input-button-imglist">
                    <div class="chatInfor-input-button-imglist-item" v-for="(item, index) in imgList" :key="index">
                        <el-image :src="item" :zoom-rate="1.2" :max-scale="7"
                            class="chatInfor-input-button-imglist-item-img" :min-scale="0.2" :preview-src-list="imgList"
                            :initial-index="0" fit="cover" style="height:32px;" />
                        <SvgIcon name="chat-close" class="chat-close" @click="deleteImage(item, index)" />
                        <!-- <img :src="proxy.$loginUrl + item" class="chatInfor-input-button-imglist-item-img"> -->
                    </div>
                </div>
                <div style="display: flex;align-items: center;justify-content: center;">
                    <el-upload v-model:file-list="fileList" class="upload-demo" :http-request="handleImageUpload"
                        :before-upload="beforeUpload" :on-remove="handleRemove" list-type="picture"
                        :show-file-list="false">
                        <SvgIcon name="chat-upload" class="chat-robot" v-if="!isRecepting" />
                    </el-upload>
                    <SvgIcon name="chat-send" class="chat-robots" @click="sendMessage" v-if="!isRecepting" />
                </div>

                <div class="loader-3" v-if="isRecepting">
                    <div class="circle"></div>
                    <div class="circle"></div>
                    <div class="circle"></div>
                    <div class="circle"></div>
                    <div class="circle"></div>
                </div>
            </div>

        </div>

    </div>

</template>
<script setup lang="ts">
import moment from "moment";
import { ref, getCurrentInstance, nextTick,onActivated } from 'vue'
const { proxy } = getCurrentInstance() as any;
import SvgIcon from '@/components/index.vue';
import { getChatDetails, uploadFile } from '@/api/chat'
import MarkdownIt from 'markdown-it'
import { ElScrollbar, ElMessage } from 'element-plus'
import type { UploadProps, UploadUserFile } from 'element-plus'
import useClipboard from 'vue-clipboard3';
import hqdInforImg from "@/assets/function/chat/person-image.png";
const md = new MarkdownIt()
const loading = ref(false);
const { toClipboard } = useClipboard()
const disabled = ref(false);
const fileList = ref<UploadUserFile[]>([]);
const chatId = ref('');
const imgList: any = ref([]);
const imgUrl: any = ref([]);
const respContent = ref('');
const sendfileType = ref(1);
const icon = localStorage.getItem('icon');
onActivated(()=>{
    scrollToBottom()
})
const textInput = ref();
const isRecepting = ref(false); //判断是否已完成发送
const infor = ref('')
const svg = `
        <path class="path" d="
          M 30 15
          L 28 17
          M 25.61 25.61
          A 15 15, 0, 0, 1, 15 30
          A 15 15, 0, 1, 1, 27.99 7.5
          L 15 15
        " style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/>
      `
const chatList = ref<any>([])
const currentPost = ref('')
const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>()
const getData = async (val: any) => {
    loading.value = true;
    chatId.value = val;
    const res: any = await getChatDetails({ s_id: val, s_size: 99 });
    if (res.status == 1) {

        console.log(res.json_data.data_list)
        res.json_data.data_list.map((item: any) => {
            item.progress = false;
            item.isRecepting = false;
            if (item.role == 'user') {
                item.content[0].imgList = [];
                item.content.map((items: any) => {

                    if (items.type == 'image_url') {
                        item.content[0].imgList.push(items.text)
                    }
                })
            }
        })
        chatList.value = res.json_data.data_list;
        console.log(chatList.value)
        nextTick(() => {
            scrollToBottom()
            loading.value = false;
        })
    } else {
        ElMessage({
            message: '系统好像出了点问题呢',
            type: 'error',
        })
        loading.value = false;

    }

}
//处理换行逻辑,ctrl+enter换行,enter发送消息
const lineBreak = () => {
    const textarea = textInput.value.textarea;
    const start = textarea.selectionStart;
    const end = textarea.selectionEnd;
    const value = textarea.value;
    const newValue = value.substring(0, start) + "\n" + value.substring(end);
    textarea.value = newValue;
    textarea.selectionStart = textarea.selectionEnd = start + 1;
}

//滚动条置底
const scrollToBottom = () => {
    const element: any = document.getElementById("chatBox");
    scrollbarRef.value!.setScrollTop(element.clientHeight)
};
//发送消息
const sendMessage = (e: any) => {
    e.preventDefault();
    openWebSocketInfor();

}
//翻译
const tranlate = (val: any, vals: any) => {
    isRecepting.value = true;
    val.isRecepting = true;
    const element: any = document.getElementById(`id${vals}`);
    const text = element.innerText;
    let ws = new WebSocket(`wss://chat.hqdoa.com/ws/knowledge/?s_id=${chatId.value
        }&s_type=1&s_chat=${text.replace(/#/g, "")}&s_other=`);
    val.content[0].text = "";
    element.scrollIntoView();
    ws.onmessage = e => {
        const response = JSON.parse(e.data);
        if (response.message === "success") {
            const data = response.data;
            if (data.is_stream) {
                // 开始接收信息
                val.content[0].text += data.text_stream;
                isRecepting.value = true;
            } else {
                // val.content[0].text = data.text_stream;
                // resetData()
                val.isRecepting = false;
                val.progress = false;
                isRecepting.value = false;
                disabled.value = false;
            }
        }
    };
    ws.onerror = handleError;

}
const updateChatList = (val1: any, val2: any) => {
    // if(val1)
    // const websocketurl = `wss://chat.hqdoa.com/ws/knowledge/?s_id=${chatId.value}&s_type=0&s_chat=${infor.value}&s_other=`;
    let contentList: any = [];
    contentList = val2;
    console.log(val2);
    // const contentList = [{ text: infor.value, type: 'text' }];
    chatList.value.push({
        role: 'user',
        created_at: moment(new Date()).format("YYYY-MM-DD HH:mm:ss"),
        role_info: { img: localStorage.getItem("icon") },
        content: contentList,
    })

}
const openWebSocketInfor = () => {
    console.log(infor)
    if (isRecepting.value == true) {
        ElMessage({
            message: '请等待回答结束后再发起新的提问哦',
            type: 'warning',
        })
    } else {
        let websocketurl = `wss://chat.hqdoa.com/ws/knowledge/?s_id=${chatId.value}&s_type=0`;
        respContent.value = '';
        console.log(imgList.value)
        if (infor.value == '' && imgList.value.length == 0) {
            ElMessage({
                message: '警告,发送的信息不能为空',
                type: 'warning',
            })
        } else {
            let contentList = [];
            if (sendfileType.value == 2) {

            } else {
                if (imgList.value.length) {
                    // contentList = imgList.value.map((item: any) => {
                    //     return { text: item, type: "text", imgList: }
                    // })
                    contentList.push({ text: infor.value, type: 'text', imgList: imgList.value })
                } else {
                    contentList.push({ text: infor.value, type: 'text' });

                }
                updateChatList("text", contentList);
                websocketurl += `&s_chat=${infor.value}&s_other=${imgUrl.value.join(',')}`;
            }


            nextTick(() => {
                scrollToBottom()
                // loading.value = false;
            })
            sendMsgEvent(websocketurl)
        }
    }



}
const sendMsgEvent = (val: any) => {
    isRecepting.value = true;
    respContent.value = '';
    let ws = new WebSocket(val);
    const currentItem = {
        isRecepting: true,
        role: "assistant",
        created_at: moment(new Date()).format("YYYY-MM-DD HH:mm:ss"),
        content: [{ text: "", annotations: [], type: "text" }],
        progress: true,
    };
    chatList.value.push(currentItem)
    // const count = 1;
    ws.onopen = () => {
        console.log("连接成功");


    };
    ws.onmessage = e => {
        const response = JSON.parse(e.data);
        if (response.message == 'success') {
            const data = response.data;

            if (data.is_stream) {
                respContent.value += data.text_stream;
                isRecepting.value = true;
                infor.value = '';
            } else {
                console.log(currentItem)
                // respContent.value = '';
                currentItem.content[0].text = respContent.value;
                resetData()
                currentItem.isRecepting = false;
                currentItem.progress = false;
                isRecepting.value = false;
                disabled.value = false;
            }
            // chatList.value.push(currentItem)
            scrollToBottom()
        } else {
            isRecepting.value = false;
            ElMessage({
                message: response.message,
                type: 'error',
            })
            respContent.value = '';
        }
    }
    ws.onerror = handleError
}
const handleError = (e: any) => {
    console.log(e);
    if (e.type == 'error') {
        ElMessage({
            message: '系统出了点问题哦,请重新发起问题提问',
            type: 'error',
        })
    }
    isRecepting.value = false;
};
//重置输入框
const resetData = () => {
    infor.value = '';
    imgList.value = [];
    imgUrl.value = [];
}
const handleRemove = () => {

}

//处理文件上传前的逻辑
const beforeUpload = (file: any) => {
    const isJPG = ["image/jpeg", "image/png"].includes(file.type);
    if (!isJPG) {
        ElMessage({
            message: '上传文件封面只能是JPG/png格式!',
            type: 'error',
        })
    }
    return isJPG;
};
//上传文件
const handleImageUpload = async (file: any) => {
    const formData = new FormData();
    formData.append("s_file", file.file);
    const data: any = await uploadFile(formData);
    imgUrl.value.push(data.file_path)
    imgList.value.push(proxy.$loginUrl + data.file_path);
    console.log(imgList.value)

}
//删除图片
const deleteImage = (val1: any, val2: any) => {
    console.log(val1, val2)
    imgList.value.splice(val2, 1)
    imgUrl.value.splice(val2, 1)
}
//重新发送
const reBuild = (val: any) => {
    console.log(1)
    console.log(val)
    let websocketurl = `wss://chat.hqdoa.com/ws/knowledge/?s_id=${chatId.value}&s_type=0`;
    if (isRecepting.value) {
        ElMessage({
            message: '请等待回答结束后再发起新的提问哦',
            type: 'warning',
        })
        return false;
    }
    // let resetItem = '';
    let resetItem = findIndexAsk(val);
    chatList.value.push(resetItem);
    console.log(resetItem)
    // console.log(resetItem, '????')
    let imgList: any = [];
    const chaTtext = resetItem.content[0].text;

    if (resetItem.content[0].imgList) {
        const arr = resetItem.content[0].imgList.map(getUrlAfterStatic)
        imgList.value = arr;
        websocketurl += `&s_chat=${chaTtext}&s_other=${imgList.value.join(",")}`;
    } else {
        websocketurl += `&s_chat=${chaTtext}&s_other=`;
    }


    sendMsgEvent(websocketurl);
    nextTick(() => {
        scrollToBottom()
        // loading.value = false;
    })
}
//截取字符
const getUrlAfterStatic = (url: string) => {
    const startIndex = url.indexOf('/static');
    if (startIndex === -1) {
        return null; // 或者抛出一个错误,表示没有找到 /static
    }
    return url.substring(startIndex);
}
//根据Index查找提问的问题
const findIndexAsk = (index: any) => {
    let resetItem;
    const currentItem = chatList.value[index];
    scrollToBottom();
    if (currentItem.role !== "user") {
        for (let i = index; i > -1; i--) {
            const item = chatList.value[i];
            if (item.role === "user") {
                resetItem = item;
                break;
            }
        }
        console.log(resetItem, 'gggg')
    } else {
        resetItem = currentItem;

    }
    return resetItem
}
//复制功能(注:复制内容只能是String类型)
const onCopy = async (msg: any) => {
    console.log(msg.content[0])
    try {
        // 复制
        await toClipboard(msg.content[0].text)
        ElMessage({
            message: '复制成功',
            type: 'success',
        })
        console.log(1)
        // 复制成功
    } catch (e) {
        // 复制失败
    }
}
defineExpose({
    getData
})
</script>
<style lang="scss">
.el-text {
    width: 100%;
    max-width: 928px;
    // min-width: 800px;
    // min-height: 24px;
    // margin-left: 64px;
    border: none !important;
    --el-input-border-color: none !important;
    --el-input-focus-border: none !important;
    --el-input-border: none !important;
    --el-input-focus-border-color: none !important;
    --el-input-hover-border-color: none !important;
    --el-input-clear-hover-color: none !important;
    border-radius: 0px 0px 8px 8px;

    .el-textarea__inner {
        // height: auto !important;
        // min-height: 24px !important;
        font-size: 16px;
        padding: 16px 16px 16px 16px;

        min-height: 48px !important;
        max-height: 80px !important;
        line-height: 24px;
        // line-height: 56px !important;
        border: none !important;
        border-radius: 8px 8px 0px 0px;
        // border-radius: 8px;

    }
}
</style>
<style lang="scss" scoped>
.upload-demo {
    display: flex;
    justify-content: center;
    align-items: center
}

.chatInfor {
    max-width: 1048px;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    // min-width: 920px;
    overflow: auto;
    position: relative;
    // padding: 0px 14%;
    height: 100%;
    text-align: left;

    &-content {
        height: 85%;
        max-width: 1048px;
        // overflow-y: scroll;
        width: 100%;

        &-item {
            height: 100%;
            width: 100%;
            padding: 0px 60px;
            font-size: 15px;
            line-height: 24px;
            letter-spacing: .5px;

            &-time {
                color: #A5A5A5;
                font-size: 12px;
                line-height: 20px;
                ;
                text-align: center;
                margin-top: 32px;
            }

            &-users {
                margin-top: 16px;
                display: flex;
                justify-content: right;
                width: 100%;
                // align-items: right;

                img {
                    width: 48px;
                    height: 48px;
                    border-radius: 100px;
                }

                // 
                p {
                    width: fit-content;
                    background-color: var(--vt-c-green);
                    padding: 12px 16px;
                    border-radius: 24px 0px 24px 24px;
                    color: var(--vt-c-selected);
                    margin-right: 16px;
                    max-width: calc(100% - 128px);


                    img {
                        height: 72px;
                        width: 100%;
                    }
                }
            }

            &-chats {
                margin-top: 16px;
                display: flex;
                justify-content: left;
                position: relative;

                img {
                    width: 48px;
                    height: 48px;
                }

                p {
                    margin-left: 16px;
                    width: 100%;
                    background-color: var(--vt-c-white);
                    padding: 12px 16px;
                    max-width: calc(100% - 128px);
                    position: relative;
                    border-radius: 0px 16px 16px 16px;
                    color: var(--vt-c-selected);
                    overflow-x: auto;

                }

                &-button {
                    float: right;
                    // position: absolute;
                    margin-top: 16px;
                    // right: 0px;
                    margin-right: 5px;
                    height: 16px;
                    // margin-bottom:02px;
                    display: flex;
                    align-items: center;
                    cursor: pointer;

                    .ebutton {
                        display: flex;
                        align-items: center;
                        color: #4E5969;

                        .chat-btn {
                            width: 20px;
                            height: 20px;
                            margin-left: 24px;

                        }


                        span {
                            margin-left: 4px;
                            font-size: 14px;

                        }
                    }

                    .ebutton:hover {
                        color: #85E822;




                    }



                }

            }
        }
    }

    .active {
        background-color: var(--el-fill-color-light);

        &-input {
            &-button {
                background-color: var(--el-fill-color-light);
            }
        }
    }

    &-input {
        // height: 85%;
        // max-width: 928px;
        // min-width: 928px;
        // min-width: 800px;
        position: relative;
        width: calc(100% - 248px);
        margin-left: 124px;
        // border-radius: 8px;

        &-button {
            // position: absolute;
            // right: 75px;
            // bottom: 10px;
            width: 100%;
            max-width: 928px;
            // min-width: 800px;
            height: 36px;
            z-index: 100;
            // background-color: #fff;
            display: flex;
            align-items: center;
            // position: absolute;
            // flex-direction: row-reverse;
            justify-content: space-between;
            // text-align: right;
            // margin-left: 64px;
            background-color: #fff;
            border-radius: 0px 0px 8px 8px;

            &-imglist {
                width: 80%;
                height: 100%;
                display: flex;
                align-items: center;

                &-item {
                    margin-left: 16px;
                    position: relative;

                    // width:12px;
                    img {
                        height: 32px;
                        width: auto;
                    }
                }
            }

            // flex-direction: row-reverse;

            .chat-robots {
                width: 24px;
                height: 24px;
                margin-right: 16px;
                cursor: pointer;
            }

            .chat-robot {
                width: 20px;
                height: 20px;
                margin-right: 16px;
                cursor: pointer;
            }

            .chat-close {
                position: absolute;
                width: 14px;
                height: 14px;
                right: -7px;
                top: -5px;
                cursor: pointer;
            }
        }
    }

    &-inputs {
        width: 800px;

    }

    // &-input:hover {
    //     // margin-left:64PX;
    //     border: 1px solid var(--el-color-primary);
    //     max-width: 928px;
    //     border-radius: 8px;
    //     min-width: 800px;
    //     position: relative;
    //     width: calc(100% - 128px);
    //     margin-left: 64px;
    // }






    // overflow-x: auto;
    // overflow-y: scroll;

}

/* From Uiverse.io by G4b413l */
/* From Uiverse.io by mobinkakei */
.loader-3 {
    width: 9em;
    display: flex;
    justify-content: space-evenly;
}

.circle {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    position: relative;
}

.circle:nth-child(1) {
    background-color: #90be6d;
}

.circle:nth-child(2) {
    background-color: #f9c74f;
}

.circle:nth-child(3) {
    background-color: #f8961e;
}

.circle:nth-child(4) {
    background-color: #f3722c;
}

.circle:nth-child(5) {
    background-color: #f94346;
}

.circle::before {
    content: "";
    width: 100%;
    height: 100%;
    position: absolute;
    border-radius: 50%;
    opacity: 0.5;
    animation: animateLoader38 2s ease-out infinite;
}

.circle:nth-child(1)::before {
    background-color: #90be6d;
}

.circle:nth-child(2)::before {
    background-color: #f9c74f;
    animation-delay: 0.2s;
}

.circle:nth-child(3)::before {
    background-color: #f8961e;
    animation-delay: 0.4s;
}

.circle:nth-child(4)::before {
    background-color: #f3722c;
    animation-delay: 0.6s;
}

.circle:nth-child(5)::before {
    background-color: #f94346;
    animation-delay: 0.8s;
}

@keyframes animateLoader38 {
    0% {
        transform: scale(1);
    }

    50%,
    75% {
        transform: scale(2.5);
    }

    80%,
    100% {
        opacity: 0;
    }
}

.text {
    color: black;
    font-weight: bolder;
}

.loader {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.3em;
    margin-top: 3px;
    // overflow-y: auto
}

.loader::before {
    content: "";
    position: absolute;
    left: 0;
    top: 10px;
    width: 100%;
    // height: 2em;
    filter: blur(45px);
    background-color: #e299ff;
    background-image: radial-gradient(at 52% 57%, hsla(11, 83%, 72%, 1) 0px, transparent 50%),
        radial-gradient(at 37% 57%, hsla(175, 78%, 66%, 1) 0px, transparent 50%);
}

.loader__circle {
    --size__loader: 0.6em;
    width: var(--size__loader);
    height: var(--size__loader);
    border-radius: 50%;
    animation: loader__circle__jumping 2s infinite;
    background-color: #b499ff;
}

.loader__circle:nth-child(2n) {
    animation-delay: 300ms;
    background-color: #e499ff;
}

.loader__circle:nth-child(3n) {
    animation-delay: 600ms;
}

@keyframes loader__circle__jumping {

    0%,
    100% {
        transform: translateY(0px);
    }

    25% {
        transform: translateY(-15px) scale(0.5);
    }

    50% {
        transform: translateY(0px);
    }

    75% {
        transform: translateY(5px) scale(0.9);
    }
}
</style>

 

 

posted on 2024-08-29 15:41  风中追风wty  阅读(30)  评论(0编辑  收藏  举报