<template>
    <div class="ai_career_interview_chat">
        <div class="left-box">
            <div class="profile">
                <img  src="@/assets/images/environment/ai-career-profile.png" alt="404">
            </div>
            <div class="introduce-text">
                在生涯访谈中，通过与不同职业人物对话，可以了解不同职业的发展前景、职业要求，为尽早规划职业发展 xxxx
                <p>访谈时你可以提出以下问题：</p>
            </div>
            <div class="question-list">
                <p class="question-list-item">您为什么要选择这一职业？</p>
                <p class="question-list-item">这项职业每天的核心任务是什么？</p>
                <p class="question-list-item">这项职业的工作环境如何？（室内/室外，办公室/工厂，团队工作/独自工作等）</p>
                <p class="question-list-item">从事这项职业需要哪些学历、证书、经验或关键能力？</p>
                <p class="question-list-item">这项职业在招聘或晋升时，更看重哪些个人特质或阶段特征？</p>
                <p class="question-list-item">大学里哪些专业与这项职业直接相关？</p>
                <p class="question-list-item">为了从事这项职业，在校期间应做哪些具体准备？</p>
                <p class="question-list-item">请分享一个最有成就感的项目/瞬间，以及一次印象深刻的挫折，您从中学到的关键经验是什么？</p>
                <p class="question-list-item">这项职业未来的发展前景如何？</p>
                <p class="question-list-item">这项职业进一步提升的途径有哪些？（培训、证书、项目、学历等）</p>
                <p class="question-list-item">如果未来的我将选择从事该职业，您会给我哪些具体建议？</p>
                <p class="question-list-item">请用时间轴描述一个典型工作日从上班到下班的详细流程？</p>
            </div>

        </div>

        <div class="center-box">
            <div class="center-header">
                <div style="margin-left: 30px"></div>
                <div>小新老师</div>
                <div class="header-exit-btn">退出</div>
            </div>
            <div v-loading="loadingHistoryRecord" class="chat-container">
                <div class="message-list" ref="chatContainerRef">
                    <div v-for="(item, index) in chatRecordList" :key="item.id">
                        <div v-if="item.isUser == 1" class="user-message">
                            <img src="@/assets/images/common/ai/user.png" alt="icon" style="margin-left: 4px"/>
                            <span class="user-message-content">{{item.content}}</span>
                        </div>
                        <div v-else class="bot-message">
                            <img src="@/assets/images/common/ai/system.png" alt="icon" style="margin-left: 4px"/>
                            <div style="margin-left: 4px;">
                                <div class="bot-message-content">
                                    <div v-if="index === chatRecordList.length-1">
                                        <div v-if="item.isTyping">
                                            <div v-html="item.content"></div>
                                            <span class="loading-dots" style="display: flex;justify-content: center">
                                                <span class="dot"></span>
                                                <span class="dot"></span>
                                            </span>
                                        </div>
                                        <div v-else>
                                            <div v-html="item.content"></div>
                                        </div>
                                    </div>
                                    <div v-else>
                                        <div v-show="!expandIndexList.includes(index)" v-html="item.content.substring(0, 80)"></div>
                                        <div v-show="expandIndexList.includes(index)" v-html="item.content"></div>
                                    </div>
                                </div>
                                <div style="margin: 4px">
                                    <el-text>{{item.createTime}}</el-text>
                                </div>
                            </div>
                            <div v-if="index !== chatRecordList.length-1">
                                <el-button v-if="item.content.length>80" style="margin-left: 4px"
                                           @click="foldOrExpandMessage(index, expandIndexList.includes(index))">
                                    {{expandIndexList.includes(index)?'折叠':'展开'}}
                                </el-button>
                            </div>
                            <el-button @click="ttsStartPlay(item.content, index)">{{playActiveIndex === index && playButtonFlag ?'停止':'播放'}}</el-button>
                        </div>
                    </div>
                </div>
            </div>
            <div class="input-container">
                <el-input
                        v-model="inputMessage"
                        type="textarea"
                        placeholder="请输入消息"
                        @keyup.enter="sendMessage"
                ></el-input>
                <el-button @click="sendMessage" :disabled="isSending" type="primary">发送</el-button>
            </div>
        </div>
    </div>
</template>

<script setup>
    import headComponent from '@/views/xjc-integratedmachine/components/head_component.vue'
    import Drag_component from "@/views/xjc-integratedmachine/components/drag_component.vue";
    import {onMounted, ref, nextTick} from "vue";
    import { getToken } from '@/utils/auth'
    // md转换为html
    import { marked } from 'marked'
    // 语音识别
    import CryptoJS from 'crypto-js';
    import * as RecorderManager from "/public/ai/iat/dist/index.umd.js"
    // 语音播放
    import {Base64} from 'js-base64'
    import * as AudioPlayer from "/public/ai/tts/dist/index.umd.js"
    const router = useRouter()
    const route = useRoute()

    const param = route.query


    const loadingHistoryRecord = ref(false)
    // 聊天记录
    let chatRecordList = ref([])
    // md流式数据
    const streamMarkdownData = ref('');
    // html流式数据
    let streamHtmlData = ref('');
    // 流式加载状态
    const isLoading = ref(false);
    // 读取器实例
    let reader = null;
    // AbortController用于中止请求
    let controller = null;
    // 输入的问题
    let inputMessage = ref('')
    // 发送标识
    let isSending = ref(false)
    // 选中的消息，用于展开折叠
    let expandIndexList = ref([])
    // 播放按钮状态
    let playActiveIndex = ref(0)
    let playButtonFlag = ref(false)
    // 查看所有聊天记录
    function list() {
        loadingHistoryRecord.value = true
        let queryForm = {
            pageNum: 1,
            pageSize: 10000
        }
        aiChatRecordList(queryForm).then(resp =>{
            chatRecordList.value = resp.rows;
            loadingHistoryRecord.value = false
            setTimeout(()=>{
                scrollToBottom();
            }, 100);
            if(resp.total == 0){
                sayHi();
            }
        })
    }

    function addRecord(content) {
        let queryForm = {
            content: content,
            isUser: 0
        }
        aiChatRecordAdd(queryForm).then(resp =>{
            console.log(resp)

        })
    }

    function sayHi() {
        const botMsg = {
            isUser: false,
            content: '你好，我是来帮助你进行学习规划、选科辅导以及志愿填报的生涯教育专家。如果你有关于这些方面的问题，欢迎随时向我咨询！', // 增量填充
            isTyping: false, // 显示加载动画
        }
        chatRecordList.value.push(botMsg)
    }

    const sendMessage = () => {
        if (inputMessage.value.trim()) {
            sendRequest(inputMessage.value.trim())
            inputMessage.value = ''
        }
    }

    const sendRequest = async(message) => {
        // 用户信息
        const userMessage = {
            isUser: true,
            content: message,
            isTyping: false
        }
        // 消息加入聊天记录
        chatRecordList.value.push(userMessage)
        const botMsg = {
            isUser: false,
            content: '', // 增量填充
            isTyping: true, // 显示加载动画
        }
        chatRecordList.value.push(botMsg)
        const lastMsg = chatRecordList.value[chatRecordList.value.length - 1]


        try{
            isLoading.value = true;
            streamMarkdownData.value = ''; // 清空之前的数据

            // 创建AbortController以便可以中止请求
            controller = new AbortController();

            // 请求体
            let form = {
                "content": message? message: "你是谁？"
            }
            // 发送fetch请求
            const response = await fetch('/dev-api/ai/chat/record/stream', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + getToken()
                },
                body: JSON.stringify(form)
            });

            // 获取可读流的读取器
            reader = response.body.getReader();
            const decoder = new TextDecoder('utf-8');

            // 循环读取流数据
            while (true) {
                const { done, value } = await reader.read();
                if (done) break; // 如果流读取完成则退出循环

                // 解码并追加数据
                let chunk = decoder.decode(value, { stream: true });
                chunk = chunk.replace(/\n\n/g, '').replace(/data:/g, '')
                streamMarkdownData.value += chunk;
                streamHtmlData.value = marked(streamMarkdownData.value)
                lastMsg.content = marked(streamMarkdownData.value)
            }
        }catch (error) {
            // 如果是手动中止，不显示错误
            if (error.name !== 'AbortError') {
                console.error('流式读取失败:', error);
                streamHtmlData.value = 'Error: ' + error.message;
            }
        } finally {
            isLoading.value = false;
            let htmlData = marked(streamMarkdownData.value);
            addRecord(htmlData);
            streamHtmlData.value = htmlData;
            lastMsg.content = marked(streamMarkdownData.value)
            lastMsg.isTyping = false
            // 组件挂载后自动滚动到底部
            scrollToBottom();
        }
    }

    const stopMessage = () => {
        if (reader) {
            // 取消读取
            reader.cancel().catch(() => {});
            reader = null;
        }
        if (controller) {
            // 中止请求
            controller.abort();
            controller = null;
        }
        isLoading.value = false;
    }

    function foldOrExpandMessage(index, flag){
        if(flag){
            expandIndexList.value = expandIndexList.value.filter((item)=>{
                return item !== index;
            })
        }else{
            expandIndexList.value.push(index)
        }
    }

    /* 语音识别 ↓*/
    // 控制录音弹窗
    let iatVoiceOpen = false
    // 是否开始录音
    let iatStartVoiceStatus = false
    // 识别中状态
    let iatIdentifyStatus = false
    // 录音对象
    let iatRecorder = null
    let iatTranscription = ''
    let iatBtnStatus = ''
    let iatResultText = ''
    let iatResultTextTemp = ''
    let iatCountdownInterval = null
    let iatWS = null
    let iatRecognition = null
    let iatButtonDisabled = true
    let iatLoading = false
    let xfIatKeys = {
        APPID: '5a2643f4',
        APISecret: 'MTg4MWIzY2VmYTg2YTEwMjliMDY1N2Iz',
        APIKey: '8b1a53486bec887eb817b4410aa743ed',
    }
    // 初始化语音识别
    function initSpeechRecognition(){
        // 初始化录音
        iatRecorder = new window.RecorderManager('/ai/iat/dist');
        // 开始录音
        iatRecorder.onStart = () => {
            iatChangeBtnStatus('OPEN');
        };
        iatRecorder.onFrameRecorded = ({ isLastFrame, frameBuffer }) => {
            if (iatWS.readyState === iatWS.OPEN) {
                iatWS.send(
                    JSON.stringify({
                        data: {
                            status: isLastFrame ? 2 : 1,
                            format: 'audio/L16;rate=16000',
                            encoding: 'raw',
                            audio: toBase64(frameBuffer),
                        },
                    }),
                );
                if (isLastFrame) {
                    iatChangeBtnStatus('CLOSING');
                }
            }
        };
        iatRecorder.onStop = () => {
            console.log('录音结束，停止定时器');
            clearInterval(iatCountdownInterval);
            iatStartVoiceStatus = false;
        };

    }

    async function iatStartVoice() {
        if (iatLoading) {
            return;
        }
        iatVoiceOpen = true;
        await iatPlayIatVoice();
    }

    async function iatPlayIatVoice() {
        if (iatLoading) {
            return;
        }
        iatStartVoiceStatus = !iatStartVoiceStatus;
        // 浏览器自带的识别
        if (iatRecognition) {
            if (iatStartVoiceStatus) {
                iatRecognition.start();
            } else {
                iatRecognition.stop();
            }
            return;
        }
        if (iatStartVoiceStatus) {
            iatConnectWebSocket();
        } else {
            iatRecorder.stop();
        }
    }

    /**
     * 关闭录音弹窗
     */
    function iatCloseVoiceOpen() {
        iatVoiceOpen = false;
        iatStartVoiceStatus = false;
        if (iatRecorder) {
            iatRecorder.stop();
        }
        if (iatRecognition) {
            iatRecognition.stop();
        }
        iatTranscription = '';
    }

    function iatRenderResult(resultData) {
        // 识别结束
        const jsonData = JSON.parse(resultData);
        if (jsonData.data && jsonData.data.result) {
            const data = jsonData.data.result;
            let str = '';
            const { ws } = data;
            for (let i = 0; i < ws.length; i += 1) {
                str += ws[i].cw[0].w;
            }
            // 开启wpgs会有此字段(前提：在控制台开通动态修正功能)
            // 取值为 "apd"时表示该片结果是追加到前面的最终结果；取值为"rpl" 时表示替换前面的部分结果，替换范围为rg字段
            if (data.pgs) {
                if (data.pgs === 'apd') {
                    // 将resultTextTemp同步给resultText
                    iatResultText = iatResultTextTemp;
                }
                // 将结果存储在resultTextTemp中
                iatResultTextTemp = iatResultText + str;
            } else {
                iatResultText += str;
            }
            inputMessage.value = iatResultTextTemp || iatResultText || '';
        }
        if (jsonData.code === 0 && jsonData.data.status === 2) {
            iatWS.close();
        }
        if (jsonData.code !== 0) {
            iatWS.close();
            console.error(jsonData);
        }
    }

    function iatConnectWebSocket() {
        const websocketUrl = iatGetWebSocketUrl();
        if ('WebSocket' in window) {
            iatWS = new window.WebSocket(websocketUrl);
        } else if ('MozWebSocket' in window) {
            iatWS = new window.MozWebSocket(websocketUrl);
        } else {
            message.error('浏览器不支持WebSocket');
            return;
        }
        iatChangeBtnStatus('CONNECTING');
        iatWS.onopen = e => {
            console.log('iatWS.onopen', e);
            // 开始录音
            iatRecorder.start({
                sampleRate: 16000,
                frameSize: 1280,
            });
            const params = {
                common: {
                    app_id: xfIatKeys.APPID,
                },
                business: {
                    language: 'zh_cn',
                    domain: 'iat',
                    accent: 'mandarin',
                    vad_eos: 5000,
                    dwa: 'wpgs',
                    nbest: 1,
                    wbest: 1,
                },
                data: {
                    status: 0,
                    format: 'audio/L16;rate=16000',
                    encoding: 'raw',
                },
            };
            iatWS.send(JSON.stringify(params));
        };
        iatWS.onmessage = e => {
            iatRenderResult(e.data);
        };
        iatWS.onerror = e => {
            console.error(e);
            iatRecorder.stop();
            iatChangeBtnStatus('CLOSED');
        };
        iatWS.onclose = e => {
            console.log(e);
            iatRecorder.stop();
            iatChangeBtnStatus('CLOSED');
        };
    }

    function iatGetWebSocketUrl() {
        const { APIKey, APISecret } = xfIatKeys;
        if (!APIKey) {
            message.error('语音识别配置未生效');
            return null;
        }
        // 请求地址根据语种不同变化
        let url = 'wss://iat-api.xfyun.cn/v2/iat';
        const host = 'iat-api.xfyun.cn';
        const apiKey = APIKey;
        const apiSecret = APISecret;
        const date = new Date().toGMTString();
        const algorithm = 'hmac-sha256';
        const headers = 'host date request-line';
        const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`;
        const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
        const signature = CryptoJS.enc.Base64.stringify(signatureSha);
        const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
        const authorization = btoa(authorizationOrigin);
        url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
        return url;
    }

    function iatCountdown() {
        let seconds = 60;
        console.log(`录音中（${seconds}s）`);
        iatCountdownInterval = setInterval(() => {
            seconds -= 1;
            if (seconds <= 0) {
                clearInterval(iatCountdownInterval);
                iatRecorder.stop();
            } else {
                console.log(`录音中（${seconds}s）`);
            }
        }, 1000);
    }

    function iatChangeBtnStatus(status) {
        iatBtnStatus = status;
        if (status === 'CONNECTING') {
            console.log('建立连接中');
            iatResultText = '';
            iatResultTextTemp = '';
        } else if (status === 'OPEN') {
            if (iatRecorder) {
                iatCountdown();
            }
        } else if (status === 'CLOSING') {
            console.log('关闭连接中');
        } else if (status === 'CLOSED') {
            console.log('开始录音');
        }
    }

    function toBase64(buffer) {
        let binary = '';
        const bytes = new Uint8Array(buffer);
        const len = bytes.byteLength;
        for (let i = 0; i < len; i += 1) {
            binary += String.fromCharCode(bytes[i]);
        }
        return window.btoa(binary);
    }

    /* 语音识别 ↑*/

    /*语音合成 ↓*/
    // 合成对象
    let audioPlayer = null
    // 按钮状态："UNDEFINED" "CONNECTING" "PLAY" "STOP"
    let btnStatus = "UNDEFINED";
    //
    let ttsWS = null;

    function initSpeechSynthesis(){
        // 初始化语音播放
        audioPlayer = new window.AudioPlayer("/ai/tts/dist");
        // 开始播放
        audioPlayer.onPlay = () => {
            changeBtnStatus("PLAY");
        };
        audioPlayer.onStop = (audioDatas) => {
            console.log('停止播放');
            // 修改为undefined，下次播放重头开始
            btnStatus === "PLAY" && changeBtnStatus("UNDEFINED");
        };
    }

    function changeBtnStatus(status) {
        btnStatus = status;
        if (status === "UNDEFINED") {
            console.log("立即合成");
        } else if (status === "CONNECTING") {
            console.log("正在合成");
        } else if (status === "PLAY") {
            console.log("停止播放");
        } else if (status === "STOP") {
            console.log("重新播放");
        }
    }

    function getWebSocketUrl() {
        const { APIKey, APISecret } = xfIatKeys;
        if (!APIKey) {
            message.error('语音合成配置未生效');
            return null;
        }
        let url = "wss://tts-api.xfyun.cn/v2/tts";
        let host = "tts-api.xfyun.cn";
        const apiKey = APIKey;
        const apiSecret = APISecret;
        let date = new Date().toGMTString();
        let algorithm = "hmac-sha256";
        let headers = "host date request-line";
        let signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/tts HTTP/1.1`;
        let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
        let signature = CryptoJS.enc.Base64.stringify(signatureSha);
        let authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
        let authorization = btoa(authorizationOrigin);
        url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
        return url;
    }

    function encodeText(text, type) {
        if (type === "unicode") {
            let buf = new ArrayBuffer(text.length * 4);
            let bufView = new Uint16Array(buf);
            for (let i = 0, strlen = text.length; i < strlen; i++) {
                bufView[i] = text.charCodeAt(i);
            }
            let binary = "";
            let bytes = new Uint8Array(buf);
            let len = bytes.byteLength;
            for (let i = 0; i < len; i++) {
                binary += String.fromCharCode(bytes[i]);
            }
            return window.btoa(binary);
        } else {
            return Base64.encode(text);
        }
    }

    function ttsStartPlay(text, index) {
        playActiveIndex.value = index
        playButtonFlag.value = !playButtonFlag.value;
        if (btnStatus === "UNDEFINED") {
            // 开始合成
            connectWebSocket(text.replace(/<\/?.+?\/?>/g,''));
        } else if (btnStatus === "CONNECTING") {
            // 停止合成
            changeBtnStatus("UNDEFINED");
            ttsWS?.close();
            audioPlayer.reset();
            return;
        } else if (btnStatus === "PLAY") {
            audioPlayer.stop();
        } else if (btnStatus === "STOP") {
            audioPlayer.play();
        }
    };

    function connectWebSocket(text) {
        const url = getWebSocketUrl();
        if ("WebSocket" in window) {
            ttsWS = new WebSocket(url);
        } else if ("MozWebSocket" in window) {
            ttsWS = new MozWebSocket(url);
        } else {
            alert("浏览器不支持WebSocket");
            return;
        }
        changeBtnStatus("CONNECTING");

        ttsWS.onopen = (e) => {
            audioPlayer.start({
                autoPlay: true,
                sampleRate: 16000,
                resumePlayDuration: 1000
            });
            changeBtnStatus("PLAY");
            let tte = "UTF8";
            let params = {
                common: {
                    app_id: xfIatKeys.APPID,
                },
                business: {
                    aue: "raw",
                    auf: "audio/L16;rate=16000",
                    vcn: "xiaoyan",
                    // vcn: "aisbabyxu",
                    // vcn: "aisjinger",
                    // vcn: "aisxping",
                    // vcn: "aisjinger",
                    // vcn: "x4_lingxiaoyao_em",
                    // vcn: "x4_lingxiaoyao_en",
                    speed: +50,
                    volume: +50,
                    pitch: +50,
                    bgs: 1,
                    tte,
                },
                data: {
                    status: 2,
                    text: encodeText(text, tte),
                },
            };
            ttsWS.send(JSON.stringify(params));
        };
        ttsWS.onmessage = (e) => {
            let jsonData = JSON.parse(e.data);
            // 合成失败
            if (jsonData.code !== 0) {
                console.error(jsonData);
                changeBtnStatus("UNDEFINED");
                return;
            }
            audioPlayer.postMessage({
                type: "base64",
                data: jsonData.data.audio,
                isLastData: jsonData.data.status === 2,
            });
            if (jsonData.code === 0 && jsonData.data.status === 2) {
                ttsWS.close();
            }
        };
        ttsWS.onerror = (e) => {
            console.error(e);
        };
        ttsWS.onclose = (e) => {
            // console.log(e);
        };
    }

    /*语音合成 ↑*/

    let chatContainerRef = ref(null)
    function scrollToBottom(){
        if(chatContainerRef.value.scrollHeight){
            chatContainerRef.value.scrollTop = chatContainerRef.value.scrollHeight+20;
        }
    }

    onMounted(()=>{
        nextTick(()=>{
            initSpeechRecognition();
            initSpeechSynthesis();
            list();
        })

    })

</script>

<style  scoped lang="scss">
    p{
        margin: 0;
        padding: 0;
    }
    .ai_career_interview_chat{
        background: url('@/assets/images/environment/ai-career-background.png') no-repeat;
        background-size: 1920px 1080px;
        z-index:10;
        width: 100%;
        height: 1080px;
        display: flex;

        .left-box{
            /*position: absolute;*/
            margin-top: 18px;
            margin-left: 44px;
            width: 428px;
            height: 1044px;
            background: rgba(255,255,255,0.4);
            border-radius: 10px 10px 10px 10px;
            .profile {
                img{
                    width: 117px;
                    height: 136px;
                    margin-top: 48px;
                    margin-left: 145px;
                }
            }
            .introduce-text{
                width: 392px;
                margin-left: 14px;
                font-weight: 400;
                font-size: 28px;
                color: #7D7C7C;
                line-height: 40px;
                text-align: left;
                font-style: normal;
                text-transform: none;
            }
            .question-list{
                width: 392px;
                height: 500px;
                overflow: auto;
                margin-left: 14px;
                font-weight: 400;
                font-size: 28px;
                color: #7D7C7C;
                line-height: 40px;
                text-align: left;
                font-style: normal;
                text-transform: none;
                .question-list-item{
                    cursor: pointer;
                }
            }
        }

        .center-box{
            width: 1390px;
            height: 1044px;
            margin-top: 18px;
            margin-left: 14px;
            background: rgba(255,255,255,0.2);
            border-radius: 10px 10px 10px 10px;
            .center-header{
                width: 1390px;
                height: 95px;
                background: #D8F9F2;
                border-radius: 10px 10px 0px 0px;
                font-weight: bold;
                font-size: 30px;
                color: #333333;
                line-height: 52px;
                letter-spacing: 3px;
                text-align: left;
                font-style: normal;
                text-transform: none;
                display: flex;
                justify-content: space-between;
                align-items: center;
                .header-exit-btn{
                    width: 136px;
                    height: 74px;
                    background: #FB5451;
                    border-radius: 10px 10px 10px 10px;
                    font-weight: 400;
                    font-size: 24px;
                    color: #FFFFFF;
                    /*text-align: center;*/
                    font-style: normal;
                    text-transform: none;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    margin-right: 30px;
                }
            }

        }
    }

</style>
