wujingjing
2024-12-17 c0b572e397be7d1832bbc8ac58ebae20aae5df92
优化组件
已修改2个文件
已添加3个文件
995 ■■■■■ 文件已修改
src/components/chat/Chat.vue 551 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/assistant/index.vue 284 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/components/shareLink/index.vue 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/user/index.vue 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/Chat.vue
@@ -8,269 +8,262 @@
                ></span>
                <div class="h-full relative" v-loading="chatListLoading" :style="{ width: chatWidth }">
                    <template v-if="computedMessageList?.length > 0">
                        <div
                            class="flex px-4 py-6 rounded-lg relative"
                            :class="{ 'flex-row-reverse': item.role === RoleEnum.user, 'px-10': isShareCheck }"
                            v-for="(item, msgIndex) of computedMessageList"
                            :key="`${item.historyId}_${item.role}`"
                        >
                            <div class="absolute top-0 left-[72px] text-[#8d8e99]">{{ item?.createTime }}</div>
                            <!-- :class="{ 'top-[30px]': item.role === RoleEnum.user, 'top-[30px]': item.role === RoleEnum.assistant }" -->
                        <div v-for="(item, msgIndex) of computedMessageList" :key="`${item.historyId}_${item.role}`">
                            <UserMsg
                                :msg="item"
                                @copyMsg="copyClick"
                                @shareClick="shareClick"
                                @setCommonQuestion="setCommonQuestionClick"
                                v-if="item.role === RoleEnum.user"
                            ></UserMsg>
                            <el-checkbox
                                v-if="isShareCheck"
                                class="absolute left-0 top-[28px]"
                                size="large"
                                v-model="item.isChecked"
                                @change="(isChecked) => shareCheckChange(isChecked as boolean, item)"
                            ></el-checkbox>
                            <img
                                class="rounded-full size-12 flex-0"
                                :class="{ 'mr-4': item.role === RoleEnum.assistant, 'ml-4': item.role === RoleEnum.user }"
                                :src="roleImageMap[item.role]"
                                alt=""
                                srcset=""
                            />
                            <div class="flex-auto flex" :class="{ 'justify-end': item.role === RoleEnum.user }">
                                <div class="inline-flex flex-col" :class="{ 'w-full': item.role === RoleEnum.assistant }">
                                    <div class="w-full">
                                        <div
                                            class="rounded-[6px] p-4 leading-relaxed"
                                            :style="{ backgroundColor: item.role === RoleEnum.user ? 'rgb(197 224 255)' : 'white' }"
                                            :class="{ group: item.role === RoleEnum.user }"
                                        >
                                            <!-- #region ====================== æ„å›¾åˆ†æž ======================-->
                                            <div class="flex flex-col" v-if="item?.stepList?.length > 0">
                            <div v-else class="flex px-4 py-6 rounded-lg relative" :class="{ 'px-10': isShareCheck }">
                                <div class="absolute top-0 left-[72px] text-[#8d8e99]">{{ item?.createTime }}</div>
                                <!-- :class="{ 'top-[30px]': item.role === RoleEnum.user, 'top-[30px]': item.role === RoleEnum.assistant }" -->
                                <img
                                    class="rounded-full size-12 flex-0"
                                    :class="{ 'mr-4': item.role === RoleEnum.assistant }"
                                    :src="roleImageMap[item.role]"
                                    alt=""
                                    srcset=""
                                />
                                <div class="flex-auto flex">
                                    <div class="inline-flex flex-col" :class="{ 'w-full': item.role === RoleEnum.assistant }">
                                        <div class="w-full">
                                            <div class="rounded-[6px] p-4 leading-relaxed bg-white">
                                                <!-- #region ====================== æ„å›¾åˆ†æž ======================-->
                                                <div class="flex items-center">
                                                    <span class="mr-2">意图分析:</span>
                                                    <div
                                                        @click="toggleStepList(item)"
                                                        class="cursor-pointer border border-gray-300 border-solid w-fit px-2 flex items-center space-x-2 rounded-lg hover:bg-gray-100 active:bg-gray-200"
                                                    >
                                                        <span>
                                                            {{ toggleStepLabel(item) }}
                                                        </span>
                                                        <span
                                                            class="ywifont"
                                                            :class="{ 'ywicon-unfold': !item.stepIsShow, 'ywicon-fold': item.stepIsShow }"
                                                        ></span>
                                                <div class="flex flex-col" v-if="item?.stepList?.length > 0">
                                                    <!-- #region ====================== æ„å›¾åˆ†æž ======================-->
                                                    <div class="flex items-center">
                                                        <span class="mr-2">意图分析:</span>
                                                        <div
                                                            @click="toggleStepList(item)"
                                                            class="cursor-pointer border border-gray-300 border-solid w-fit px-2 flex items-center space-x-2 rounded-lg hover:bg-gray-100 active:bg-gray-200"
                                                        >
                                                            <span>
                                                                {{ toggleStepLabel(item) }}
                                                            </span>
                                                            <span
                                                                class="ywifont"
                                                                :class="{ 'ywicon-unfold': !item.stepIsShow, 'ywicon-fold': item.stepIsShow }"
                                                            ></span>
                                                        </div>
                                                    </div>
                                                    <!-- #endregion -->
                                                    <!-- #region ====================== è¿‡ç¨‹è¾“出 ======================-->
                                                    <el-steps v-show="item.stepIsShow" class="mt-3" direction="vertical" :active="activeStep">
                                                        <el-step
                                                            v-for="(subItem, index) in item.stepList"
                                                            :title="subItem.title"
                                                            :status="stepEnumMap[subItem.status]"
                                                        >
                                                            <template
                                                                #icon
                                                                v-if="index + 1 === item.stepList.length && isTalking && msgIndex === computedMessageList.length - 1"
                                                            >
                                                                <span class="ywifont ywicon-loading1 animate-spin !text-[24px]"></span>
                                                            </template>
                                                            <template #title>
                                                                <span class="">
                                                                    {{ subItem.title }}
                                                                    <span v-if="subItem.ms" class="text-green-600">{{ `(${subItem.ms})` }}</span></span
                                                                >
                                                            </template>
                                                            <template #description v-if="subItem?.subStep?.length > 0">
                                                                <div class="my-1 flex flex-col gap-1 text-[14px]">
                                                                    <component
                                                                        :key="`${item.historyId}-${index + 1}-${multiChatIndex + 1}`"
                                                                        v-for="(multiChatItem, multiChatIndex) in subItem.subStep"
                                                                        :order="`${index + 1}-${multiChatIndex + 1}`"
                                                                        :item="multiChatItem"
                                                                        :is="multiChatTypeMapCom[multiChatItem.type]"
                                                                        :disabled="
                                                                            !(index + 1 === item.stepList.length && isTalking && msgIndex === computedMessageList.length - 1)
                                                                        "
                                                                    />
                                                                </div>
                                                            </template>
                                                        </el-step>
                                                    </el-steps>
                                                    <!-- #endregion -->
                                                </div>
                                                <!-- #endregion -->
                                                <!-- #region ====================== è¿‡ç¨‹è¾“出 ======================-->
                                                <el-steps v-show="item.stepIsShow" class="mt-3" direction="vertical" :active="activeStep">
                                                    <el-step
                                                        v-for="(subItem, index) in item.stepList"
                                                        :title="subItem.title"
                                                        :status="stepEnumMap[subItem.status]"
                                                <!-- #region ====================== ç”¨æˆ·æ“ä½œæŒ‰é’® ======================-->
                                                <div
                                                    v-if="item.role === RoleEnum.user && item.content?.values && !isSharePage && !isShareCheck"
                                                    class="absolute flex items-center bottom-0 group invisible"
                                                >
                                                    <div
                                                        class="bg-[#fff] flex items-center relative mr-4 space-x-2 flex-nowrap rounded-[6px] py-2 px-2 group-hover:visible"
                                                    >
                                                        <template
                                                            #icon
                                                            v-if="index + 1 === item.stepList.length && isTalking && msgIndex === computedMessageList.length - 1"
                                                        >
                                                            <span class="ywifont ywicon-loading1 animate-spin !text-[24px]"></span>
                                                        </template>
                                                        <template #title>
                                                            <span class="">
                                                                {{ subItem.title }}
                                                                <span v-if="subItem.ms" class="text-green-600">{{ `(${subItem.ms})` }}</span></span
                                                            >
                                                        </template>
                                                        <template #description v-if="subItem?.subStep?.length > 0">
                                                            <div class="my-1 flex flex-col gap-1 text-[14px]">
                                                                <component
                                                                    :key="`${item.historyId}-${index + 1}-${multiChatIndex + 1}`"
                                                                    v-for="(multiChatItem, multiChatIndex) in subItem.subStep"
                                                                    :order="`${index + 1}-${multiChatIndex + 1}`"
                                                                    :item="multiChatItem"
                                                                    :is="multiChatTypeMapCom[multiChatItem.type]"
                                                                    :disabled="!(index + 1 === item.stepList.length && isTalking && msgIndex === computedMessageList.length - 1)"
                                                        <el-tooltip effect="dark" content="复制" placement="top">
                                                            <div class="flex items-center justify-center size-[20px]">
                                                                <i
                                                                    class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] font-medium !text-[15px] hover:!text-[18px]"
                                                                    @click="copyUserClick(item)"
                                                                />
                                                            </div>
                                                        </template>
                                                    </el-step>
                                                </el-steps>
                                                        </el-tooltip>
                                                        <el-tooltip effect="dark" content="设为常用语" placement="top">
                                                            <div class="flex items-center justify-center size-[20px]">
                                                                <i
                                                                    class="p-2 ywifont ywicon-cubelifangti cursor-pointer hover:text-[#0284ff] text-[#000] font-[590] !text-[15px] hover:!text-[18px]"
                                                                    @click="setCommonQuestionClick(item)"
                                                                />
                                                            </div>
                                                        </el-tooltip>
                                                        <el-tooltip effect="dark" content="分享" placement="top">
                                                            <div class="flex items-center justify-center size-[15px]">
                                                                <i
                                                                    :class="{ 'text-[#0284ff]': item.state === AnswerState.Unlike }"
                                                                    class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
                                                                    @click="shareClick(item)"
                                                                />
                                                            </div>
                                                        </el-tooltip>
                                                    </div>
                                                </div>
                                                <!-- #endregion -->
                                                <!-- #region ====================== æ¶ˆæ¯å†…容 ======================-->
                                                <template v-if="item.content?.values">
                                                    <!-- #region ====================== æŠ¥é”™ä¿¡æ¯ ======================-->
                                                    <div v-if="item.content.errCode === ErrorCode.Message" class="flex-column w-full">
                                                        <p class="text-red-500">
                                                            {{ item.content.errMsg }}
                                                        </p>
                                                        <div class="mt-3 flex" v-if="showFixQuestion(item)">
                                                            <div class="text-gray-600 flex-0 mb-auto py-3">
                                                                {{ '猜你想问:' }}
                                                            </div>
                                                            <div class="flex-auto space-x-2 space-y-1 inline-flex flex-wrap items-center">
                                                                <div
                                                                    v-for="fixItem in item.content.origin?.sample_question"
                                                                    :key="fixItem"
                                                                    class="bg-gray-200 p-3 hover:bg-[#c5e0ff] hover:text-[#1c86ff] cursor-pointer rounded-lg first-of-type:ml-2 first-of-type:mt-1"
                                                                    @click="fixQuestionClick(fixItem, item.content.origin)"
                                                                >
                                                                    {{ fixItem }}
                                                                </div>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- #endregion -->
                                                    <!-- #region ====================== å›žç­”组件 ======================-->
                                                    <template v-else>
                                                        <component
                                                            :conclusion="item.conclusion"
                                                            :is="answerTypeMapCom[item.content.type]"
                                                            :data="item.content.values"
                                                            :originData="item"
                                                            :isTalking="isTalking && msgIndex === computedMessageList.length - 1"
                                                        />
                                                        <div
                                                            v-if="item.role === RoleEnum.assistant && item.content.origin?.ext_call_list"
                                                            class="flex font-bold items-center mt-6"
                                                        >
                                                            <div class="flex-0 mb-auto -mr-4">关联功能:</div>
                                                            <div class="space-x-5 flex flex-wrap">
                                                                <div
                                                                    v-for="callItem in item.content.origin?.ext_call_list"
                                                                    :key="callItem.call_ext_id"
                                                                    @click="relativeQueryClick(callItem)"
                                                                    class="cursor-pointer hover:underline first-of-type:ml-5"
                                                                >
                                                                    {{ callItem.question }}
                                                                </div>
                                                            </div>
                                                        </div>
                                                    </template>
                                                    <!-- #endregion -->
                                                </template>
                                                <!-- #endregion -->
                                                <!-- #region ====================== é™„加内容 ======================-->
                                                <!-- #region ====================== åœæ­¢ ======================-->
                                                <span v-if="item.isStopMsg && item?.role === RoleEnum.assistant" class="text-gray-400 text-[12px]"
                                                    >(已停止)</span
                                                >
                                                <!-- parseContent è¿”回为 null -->
                                                <p v-if="!item.content && !isTalking && !item.isStopMsg" class="text-red-500">暂无数据</p>
                                                <!-- #endregion -->
                                                <!-- #endregion -->
                                            </div>
                                            <!-- #endregion -->
                                            <!-- #region ====================== ç”¨æˆ·æ“ä½œæŒ‰é’® ======================-->
                                            <!-- #region ====================== ai æ¶ˆæ¯æ“ä½œ ======================-->
                                            <div
                                                v-if="item.role === RoleEnum.user && item.content?.values && !isSharePage && !isShareCheck"
                                                class="absolute flex items-center bottom-0 group invisible"
                                                v-if="item.role === RoleEnum.assistant && item.content?.values && !isSharePage && !isShareCheck"
                                                class="absolute flex items-center right-0 mr-4 mt-2 space-x-2"
                                            >
                                                <div
                                                    class="bg-[#fff] flex items-center relative mr-4 space-x-2 flex-nowrap rounded-[6px] py-2 px-2 group-hover:visible"
                                                    class="flex items-center justify-center size-[15px]"
                                                    v-if="item.content?.type === AnswerType.Text || item.content?.type === AnswerType.Knowledge"
                                                >
                                                    <el-tooltip effect="dark" content="复制" placement="top">
                                                        <div class="flex items-center justify-center size-[20px]">
                                                    <i
                                                        class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] hover:!text-[18px]"
                                                        @click="copyClick(item)"
                                                    />
                                                </div>
                                                <template v-if="item.content.errCode !== ErrorCode.Message">
                                                    <el-tooltip effect="dark" content="点赞" placement="top">
                                                        <div class="flex items-center justify-center size-[15px]">
                                                            <i
                                                                class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] font-medium !text-[15px] hover:!text-[18px]"
                                                                @click="copyUserClick(item)"
                                                                :class="{ 'text-[#0284ff]': item.state === AnswerState.Like }"
                                                                class="p-2 ywifont ywicon-dianzan cursor-pointer hover:text-[#0284ff] font-medium hover:!text-[18px]"
                                                                @click="likeClick(item)"
                                                            />
                                                        </div>
                                                    </el-tooltip>
                                                    <el-tooltip effect="dark" content="设为常用语" placement="top">
                                                        <div class="flex items-center justify-center size-[20px]">
                                                            <i
                                                                class="p-2 ywifont ywicon-cubelifangti cursor-pointer hover:text-[#0284ff] text-[#000] font-[590] !text-[15px] hover:!text-[18px]"
                                                                @click="setCommonQuestionClick(item)"
                                                            />
                                                        </div>
                                                    </el-tooltip>
                                                    <el-tooltip effect="dark" content="分享" placement="top">
                                                    <el-tooltip effect="dark" content="点踩" placement="top">
                                                        <div class="flex items-center justify-center size-[15px]">
                                                            <i
                                                                :class="{ 'text-[#0284ff]': item.state === AnswerState.Unlike }"
                                                                class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
                                                                @click="shareClick(item)"
                                                                class="p-2 ywifont ywicon-buzan cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
                                                                @click="unLikeClick(item)"
                                                            />
                                                        </div>
                                                    </el-tooltip>
                                                </div>
                                            </div>
                                            <!-- #endregion -->
                                            <!-- #region ====================== æ¶ˆæ¯å†…容 ======================-->
                                            <template v-if="item.content?.values">
                                                <!-- #region ====================== æŠ¥é”™ä¿¡æ¯ ======================-->
                                                <div v-if="item.content.errCode === ErrorCode.Message" class="flex-column w-full">
                                                    <p class="text-red-500">
                                                        {{ item.content.errMsg }}
                                                    </p>
                                                    <div class="mt-3 flex" v-if="showFixQuestion(item)">
                                                        <div class="text-gray-600 flex-0 mb-auto py-3">
                                                            {{ '猜你想问:' }}
                                                        </div>
                                                        <div class="flex-auto space-x-2 space-y-1 inline-flex flex-wrap items-center">
                                                            <div
                                                                v-for="fixItem in item.content.origin?.sample_question"
                                                                :key="fixItem"
                                                                class="bg-gray-200 p-3 hover:bg-[#c5e0ff] hover:text-[#1c86ff] cursor-pointer rounded-lg first-of-type:ml-2 first-of-type:mt-1"
                                                                @click="fixQuestionClick(fixItem, item.content.origin)"
                                                            >
                                                                {{ fixItem }}
                                                            </div>
                                                        </div>
                                                    </div>
                                                </div>
                                                <!-- #endregion -->
                                                <!-- #region ====================== å›žç­”组件 ======================-->
                                                <template v-else>
                                                    <component
                                                        :conclusion="item.conclusion"
                                                        :is="answerTypeMapCom[item.content.type]"
                                                        :data="item.content.values"
                                                        :originData="item"
                                                        :isTalking="isTalking && msgIndex === computedMessageList.length - 1"
                                                    />
                                                    <div
                                                        v-if="item.role === RoleEnum.assistant && item.content.origin?.ext_call_list"
                                                        class="flex font-bold items-center mt-6"
                                                    >
                                                        <div class="flex-0 mb-auto -mr-4">关联功能:</div>
                                                        <div class="space-x-5 flex flex-wrap">
                                                            <div
                                                                v-for="callItem in item.content.origin?.ext_call_list"
                                                                :key="callItem.call_ext_id"
                                                                @click="relativeQueryClick(callItem)"
                                                                class="cursor-pointer hover:underline first-of-type:ml-5"
                                                            >
                                                                {{ callItem.question }}
                                                            </div>
                                                        </div>
                                                    </div>
                                                </template>
                                                <!-- #endregion -->
                                            </template>
                                            <!-- #endregion -->
                                            <!-- #region ====================== é™„加内容 ======================-->
                                            <!-- #region ====================== åœæ­¢ ======================-->
                                            <span v-if="item.isStopMsg && item?.role === RoleEnum.assistant" class="text-gray-400 text-[12px]"
                                                >(已停止)</span
                                            >
                                            <!-- parseContent è¿”回为 null -->
                                            <p v-if="!item.content && !isTalking && !item.isStopMsg" class="text-red-500">暂无数据</p>
                                            <!-- #endregion -->
                                            <!-- #endregion -->
                                        </div>
                                        <!-- #region ====================== ai æ¶ˆæ¯æ“ä½œ ======================-->
                                        <div
                                            v-if="item.role === RoleEnum.assistant && item.content?.values && !isSharePage && !isShareCheck"
                                            class="absolute flex items-center right-0 mr-4 mt-2 space-x-2"
                                        >
                                            <div
                                                class="flex items-center justify-center size-[15px]"
                                                v-if="item.content?.type === AnswerType.Text || item.content?.type === AnswerType.Knowledge"
                                            >
                                                <i
                                                    class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] hover:!text-[18px]"
                                                    @click="copyClick(item)"
                                                />
                                                <el-tooltip effect="dark" content="分享" placement="top">
                                                    <div class="flex items-center justify-center size-[15px]">
                                                        <i
                                                            class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
                                                            @click="shareClick(item)"
                                                        />
                                                    </div>
                                                </el-tooltip>
                                                <el-tooltip effect="dark" content="反馈" placement="top">
                                                    <div class="flex items-center justify-center size-[15px] relative">
                                                        <i
                                                            class="p-2 ywifont ywicon-wentifankui cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
                                                            @click="
                                                                ($event) =>
                                                                    feedbackClick(
                                                                        $event,
                                                                        item,
                                                                        computedMessageList
                                                                            .filter((v) => v.role === RoleEnum.assistant)
                                                                            .findIndex((v) => v.historyId === item.historyId)
                                                                    )
                                                            "
                                                        />
                                                        <FeedbackPanel
                                                            v-show="feedbackIsShow && currentFeedbackMapItem === item"
                                                            ref="feedbackPanelRef"
                                                            v-model:isShow="feedbackIsShow"
                                                            v-model:content="feedbackContent"
                                                            :chatItem="currentFeedbackMapItem"
                                                            :position="feedbackPosition"
                                                        />
                                                    </div>
                                                </el-tooltip>
                                            </div>
                                            <template v-if="item.content.errCode !== ErrorCode.Message">
                                                <el-tooltip effect="dark" content="点赞" placement="top">
                                                    <div class="flex items-center justify-center size-[15px]">
                                                        <i
                                                            :class="{ 'text-[#0284ff]': item.state === AnswerState.Like }"
                                                            class="p-2 ywifont ywicon-dianzan cursor-pointer hover:text-[#0284ff] font-medium hover:!text-[18px]"
                                                            @click="likeClick(item)"
                                                        />
                                                    </div>
                                                </el-tooltip>
                                                <el-tooltip effect="dark" content="点踩" placement="top">
                                                    <div class="flex items-center justify-center size-[15px]">
                                                        <i
                                                            :class="{ 'text-[#0284ff]': item.state === AnswerState.Unlike }"
                                                            class="p-2 ywifont ywicon-buzan cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
                                                            @click="unLikeClick(item)"
                                                        />
                                                    </div>
                                                </el-tooltip>
                                            </template>
                                            <el-tooltip effect="dark" content="分享" placement="top">
                                                <div class="flex items-center justify-center size-[15px]">
                                                    <i
                                                        class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
                                                        @click="shareClick(item)"
                                                    />
                                                </div>
                                            </el-tooltip>
                                            <el-tooltip effect="dark" content="反馈" placement="top">
                                                <div class="flex items-center justify-center size-[15px] relative">
                                                    <i
                                                        class="p-2 ywifont ywicon-wentifankui cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
                                                        @click="
                                                            ($event) =>
                                                                feedbackClick(
                                                                    $event,
                                                                    item,
                                                                    computedMessageList
                                                                        .filter((v) => v.role === RoleEnum.assistant)
                                                                        .findIndex((v) => v.historyId === item.historyId)
                                                                )
                                                        "
                                                    />
                                                    <FeedbackPanel
                                                        v-show="feedbackIsShow && currentFeedbackMapItem === item"
                                                        ref="feedbackPanelRef"
                                                        v-model:isShow="feedbackIsShow"
                                                        v-model:content="feedbackContent"
                                                        :chatItem="currentFeedbackMapItem"
                                                        :position="feedbackPosition"
                                                    />
                                                </div>
                                            </el-tooltip>
                                            <!-- #endregion -->
                                        </div>
                                        <!-- #endregion -->
                                    </div>
                                </div>
                            </div>
@@ -325,18 +318,18 @@
            <div class="sticky bottom-0 w-full p-6 bg-[rgb(247,248,250)] flex justify-center" v-if="isShareCheck"></div>
        </div>
        <CustomDrawer v-model:isShow="drawerIsShow" @updateChatInput="updateChatInput" />
        <el-dialog title="分享链接" v-model="shareCodeIsShow" width="12%" modal-append-to-body lock-scroll :before-close="closeShareClick">
        <!-- <el-dialog title="分享链接" v-model="shareCodeIsShow" width="12%" modal-append-to-body lock-scroll :before-close="closeShareClick">
            <div class="w100 h100 flex justify-center items-center flex-col text-center">
                <div class="qrcode h100" ref="qrcodeRef"></div>
            </div>
        </el-dialog>
        </el-dialog> -->
        <ShareLinkDlg ref="shareLinkDlgRef" />
    </div>
</template>
<script setup lang="ts">
import type { CancelTokenSource } from 'axios';
import axios from 'axios';
import { ElMessage } from 'element-plus';
import { findLast, orderBy } from 'lodash-es';
import moment from 'moment';
import QRCode from 'qrcodejs2-fixes';
@@ -354,6 +347,8 @@
import CustomDrawer from '/@/components/drawer/CustomDrawer.vue';
import { SHARE_URL } from '/@/constants';
import { Logger } from '/@/model/logger/Logger';
import ShareLinkDlg from './components/shareLink/index.vue';
import router from '/@/router';
import {
    activeChatRoom,
@@ -366,11 +361,13 @@
    isSharePage,
    roomConfig,
} from '/@/stores/chatRoom';
import UserMsg from './user/index.vue';
import { multiChatTypeMapCom } from '/@/components/chat/chatComponents/multiChat';
import emitter from '/@/utils/mitt';
import { ErrorCode } from '/@/utils/request';
import { toMyFixed } from '/@/utils/util';
import { ElMessage } from 'element-plus';
import { useCompRef } from '/@/utils/types';
const chatWidth = '75%';
const voicePageIsShow = ref(false);
let isTalking = ref(false);
@@ -878,82 +875,12 @@
//#region ====================== åˆ†äº« ======================
const resetShare = () => {
    computedMessageList.value.forEach((item) => {
        item.isChecked = false;
    });
    isShareCheck.value = false;
};
const shareLinkDlgRef = useCompRef(ShareLinkDlg);
const isShareCheck = ref(false);
const qrcodeRef = ref<HTMLElement | null>(null);
const shareCodeIsShow = ref(false); //是否弹窗显示分享二维码
const shareCoderUrl = ref('');
const shareClick = async (item: ChatMessage) => {
    item.isChecked = true;
    shareCheckChange(true, item);
    // ç›®å‰åªåˆ†äº«ä¸€ä¸ªï¼Œä¸è¿›å…¥å¤šé€‰æ¨¡å¼ï¼Œåˆ†äº«å¤šä¸ª
    // isShareCheck.value = true;
    shareCodeIsShow.value = true;
    const url = await generateShareUrl();
    shareCoderUrl.value = url;
    const qrCodeElement = document.querySelector('.qrcode');
    // ElMessage.success('已复制分享链接');
    nextTick(() => {
        (<HTMLElement>qrCodeElement && qrCodeElement).innerHTML = '';
        new QRCode(qrCodeElement, {
            text: url,
            width: 100,
            height: 100,
            colorDark: '#000000',
            colorLight: '#ffffff',
        });
    });
    copyShareCodeClick();
    shareLinkDlgRef.value.openShare(item);
};
//链接复制
const copyShareCodeClick = () => {
    const url = shareCoderUrl.value;
    if (!url) return;
    toClipboard(url);
    ElMessage.success('已复制分享链接');
};
//关闭分享弹窗
const closeShareClick = () => {
    shareCodeIsShow.value = false;
    resetShare();
};
const shareCheckChange = (isChecked: boolean, item: ChatMessage) => {
    const toFindRole = item.role === RoleEnum.user ? RoleEnum.assistant : RoleEnum.user;
    const foundMapItem = computedMessageList.value.find(
        (msgItem) => msgItem.historyId === item.historyId && msgItem.role === toFindRole
    );
    if (!foundMapItem) return;
    foundMapItem.isChecked = isChecked;
};
const generateShareUrl = async () => {
    const shareList = computedMessageList.value.filter((item) => item.isChecked && item.role === RoleEnum.user && !!item.historyId);
    if (shareList.length === 0) {
        // ElMessage.warning('请选择要分享的内容');
        return;
    }
    const historyIdStr = shareList.map((item) => item.historyId).join(',');
    const res = await shareChatHistoryByPost({
        history_ids: historyIdStr,
        share_days: 1,
    }).finally(() => {
        resetShare();
    });
    if (!res.values) return;
    const shareId = Object.values(res.values)[0];
    if (!shareId) return;
    const shareLink = `${SHARE_URL}?id=${shareId}`;
    return shareLink;
};
//#endregion
</script>
src/components/chat/assistant/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,284 @@
<template>
    <div class="flex px-4 py-6 rounded-lg relative">
        <div class="absolute top-0 left-[72px] text-[#8d8e99]">{{ msg?.createTime }}</div>
        <img
            class="rounded-full size-12 flex-0 mr-4"
            :src="roleImageMap[msg.role]"
            alt=""
            srcset=""
        />
        <div class="flex-auto flex">
            <div class="inline-flex flex-col w-full" >
                <div class="w-full">
                    <div class="rounded-[6px] p-4 leading-relaxed bg-white">
                        <!-- #region ====================== æ„å›¾åˆ†æž ======================-->
                        <div class="flex flex-col" v-if="msg?.stepList?.length > 0">
                            <!-- #region ====================== æ„å›¾åˆ†æž ======================-->
                            <div class="flex items-center">
                                <span class="mr-2">意图分析:</span>
                                <div
                                    @click="toggleStepList(msg)"
                                    class="cursor-pointer border border-gray-300 border-solid w-fit px-2 flex items-center space-x-2 rounded-lg hover:bg-gray-100 active:bg-gray-200"
                                >
                                    <span>
                                        {{ toggleStepLabel(msg) }}
                                    </span>
                                    <span class="ywifont" :class="{ 'ywicon-unfold': !msg.stepIsShow, 'ywicon-fold': msg.stepIsShow }"></span>
                                </div>
                            </div>
                            <!-- #endregion -->
                            <!-- #region ====================== è¿‡ç¨‹è¾“出 ======================-->
                            <el-steps v-show="msg.stepIsShow" class="mt-3" direction="vertical" :active="activeStep">
                                <el-step v-for="(subItem, index) in msg.stepList" :title="subItem.title" :status="stepEnumMap[subItem.status]">
                                    <template
                                        #icon
                                        v-if="index + 1 === msg.stepList.length && isTalking && msgIndex === computedMessageList.length - 1"
                                    >
                                        <span class="ywifont ywicon-loading1 animate-spin !text-[24px]"></span>
                                    </template>
                                    <template #title>
                                        <span class="">
                                            {{ subItem.title }}
                                            <span v-if="subItem.ms" class="text-green-600">{{ `(${subItem.ms})` }}</span></span
                                        >
                                    </template>
                                    <template #description v-if="subItem?.subStep?.length > 0">
                                        <div class="my-1 flex flex-col gap-1 text-[14px]">
                                            <component
                                                :key="`${msg.historyId}-${index + 1}-${multiChatIndex + 1}`"
                                                v-for="(multiChatItem, multiChatIndex) in subItem.subStep"
                                                :order="`${index + 1}-${multiChatIndex + 1}`"
                                                :item="multiChatItem"
                                                :is="multiChatTypeMapCom[multiChatItem.type]"
                                                :disabled="!(index + 1 === msg.stepList.length && isTalking && msgIndex === computedMessageList.length - 1)"
                                            />
                                        </div>
                                    </template>
                                </el-step>
                            </el-steps>
                            <!-- #endregion -->
                        </div>
                        <!-- #endregion -->
                        <!-- #region ====================== ç”¨æˆ·æ“ä½œæŒ‰é’® ======================-->
                        <div
                            v-if="msg.role === RoleEnum.user && msg.content?.values && !isSharePage && !isShareCheck"
                            class="absolute flex items-center bottom-0 group invisible"
                        >
                            <div class="bg-[#fff] flex items-center relative mr-4 space-x-2 flex-nowrap rounded-[6px] py-2 px-2 group-hover:visible">
                                <el-tooltip effect="dark" content="复制" placement="top">
                                    <div class="flex items-center justify-center size-[20px]">
                                        <i
                                            class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] font-medium !text-[15px] hover:!text-[18px]"
                                            @click="copyUserClick(msg)"
                                        />
                                    </div>
                                </el-tooltip>
                                <el-tooltip effect="dark" content="设为常用语" placement="top">
                                    <div class="flex items-center justify-center size-[20px]">
                                        <i
                                            class="p-2 ywifont ywicon-cubelifangti cursor-pointer hover:text-[#0284ff] text-[#000] font-[590] !text-[15px] hover:!text-[18px]"
                                            @click="setCommonQuestionClick(msg)"
                                        />
                                    </div>
                                </el-tooltip>
                                <el-tooltip effect="dark" content="分享" placement="top">
                                    <div class="flex items-center justify-center size-[15px]">
                                        <i
                                            :class="{ 'text-[#0284ff]': msg.state === AnswerState.Unlike }"
                                            class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
                                            @click="shareClick(msg)"
                                        />
                                    </div>
                                </el-tooltip>
                            </div>
                        </div>
                        <!-- #endregion -->
                        <!-- #region ====================== æ¶ˆæ¯å†…容 ======================-->
                        <template v-if="msg.content?.values">
                            <!-- #region ====================== æŠ¥é”™ä¿¡æ¯ ======================-->
                            <div v-if="msg.content.errCode === ErrorCode.Message" class="flex-column w-full">
                                <p class="text-red-500">
                                    {{ msg.content.errMsg }}
                                </p>
                                <div class="mt-3 flex" v-if="showFixQuestion(msg)">
                                    <div class="text-gray-600 flex-0 mb-auto py-3">
                                        {{ '猜你想问:' }}
                                    </div>
                                    <div class="flex-auto space-x-2 space-y-1 inline-flex flex-wrap items-center">
                                        <div
                                            v-for="fixItem in msg.content.origin?.sample_question"
                                            :key="fixItem"
                                            class="bg-gray-200 p-3 hover:bg-[#c5e0ff] hover:text-[#1c86ff] cursor-pointer rounded-lg first-of-type:ml-2 first-of-type:mt-1"
                                            @click="fixQuestionClick(fixItem, msg.content.origin)"
                                        >
                                            {{ fixItem }}
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <!-- #endregion -->
                            <!-- #region ====================== å›žç­”组件 ======================-->
                            <template v-else>
                                <component
                                    :conclusion="msg.conclusion"
                                    :is="answerTypeMapCom[msg.content.type]"
                                    :data="msg.content.values"
                                    :originData="msg"
                                    :isTalking="isTalking && msgIndex === computedMessageList.length - 1"
                                />
                                <div
                                    v-if="msg.role === RoleEnum.assistant && msg.content.origin?.ext_call_list"
                                    class="flex font-bold items-center mt-6"
                                >
                                    <div class="flex-0 mb-auto -mr-4">关联功能:</div>
                                    <div class="space-x-5 flex flex-wrap">
                                        <div
                                            v-for="callItem in msg.content.origin?.ext_call_list"
                                            :key="callItem.call_ext_id"
                                            @click="relativeQueryClick(callItem)"
                                            class="cursor-pointer hover:underline first-of-type:ml-5"
                                        >
                                            {{ callItem.question }}
                                        </div>
                                    </div>
                                </div>
                            </template>
                            <!-- #endregion -->
                        </template>
                        <!-- #endregion -->
                        <!-- #region ====================== é™„加内容 ======================-->
                        <!-- #region ====================== åœæ­¢ ======================-->
                        <span v-if="msg.isStopMsg && msg?.role === RoleEnum.assistant" class="text-gray-400 text-[12px]">(已停止)</span>
                        <!-- parseContent è¿”回为 null -->
                        <p v-if="!msg.content && !isTalking && !msg.isStopMsg" class="text-red-500">暂无数据</p>
                        <!-- #endregion -->
                        <!-- #endregion -->
                    </div>
                    <!-- #region ====================== ai æ¶ˆæ¯æ“ä½œ ======================-->
                    <div
                        v-if="msg.role === RoleEnum.assistant && msg.content?.values && !isSharePage && !isShareCheck"
                        class="absolute flex items-center right-0 mr-4 mt-2 space-x-2"
                    >
                        <div
                            class="flex items-center justify-center size-[15px]"
                            v-if="msg.content?.type === AnswerType.Text || msg.content?.type === AnswerType.Knowledge"
                        >
                            <i class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] hover:!text-[18px]" @click="copyClick(msg)" />
                        </div>
                        <template v-if="msg.content.errCode !== ErrorCode.Message">
                            <el-tooltip effect="dark" content="点赞" placement="top">
                                <div class="flex items-center justify-center size-[15px]">
                                    <i
                                        :class="{ 'text-[#0284ff]': msg.state === AnswerState.Like }"
                                        class="p-2 ywifont ywicon-dianzan cursor-pointer hover:text-[#0284ff] font-medium hover:!text-[18px]"
                                        @click="likeClick(msg)"
                                    />
                                </div>
                            </el-tooltip>
                            <el-tooltip effect="dark" content="点踩" placement="top">
                                <div class="flex items-center justify-center size-[15px]">
                                    <i
                                        :class="{ 'text-[#0284ff]': msg.state === AnswerState.Unlike }"
                                        class="p-2 ywifont ywicon-buzan cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
                                        @click="unLikeClick(msg)"
                                    />
                                </div>
                            </el-tooltip>
                        </template>
                        <el-tooltip effect="dark" content="分享" placement="top">
                            <div class="flex items-center justify-center size-[15px]">
                                <i
                                    class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
                                    @click="shareClick(msg)"
                                />
                            </div>
                        </el-tooltip>
                        <el-tooltip effect="dark" content="反馈" placement="top">
                            <div class="flex items-center justify-center size-[15px] relative">
                                <i
                                    class="p-2 ywifont ywicon-wentifankui cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]"
                                    @click="
                                        ($event) =>
                                            feedbackClick(
                                                $event,
                                                msg,
                                                computedMessageList
                                                    .filter((v) => v.role === RoleEnum.assistant)
                                                    .findIndex((v) => v.historyId === msg.historyId)
                                            )
                                    "
                                />
                                <FeedbackPanel
                                    v-show="feedbackIsShow && currentFeedbackMapItem === msg"
                                    ref="feedbackPanelRef"
                                    v-model:isShow="feedbackIsShow"
                                    v-model:content="feedbackContent"
                                    :chatItem="currentFeedbackMapItem"
                                    :position="feedbackPosition"
                                />
                            </div>
                        </el-tooltip>
                    </div>
                    <!-- #endregion -->
                </div>
            </div>
        </div>
    </div>
</template>
<script setup lang="ts" name="AssistantMsg">
import type { CancelTokenSource } from 'axios';
import axios from 'axios';
import { findLast, orderBy } from 'lodash-es';
import moment from 'moment';
import QRCode from 'qrcodejs2-fixes';
import { computed, nextTick, onActivated, onMounted, ref } from 'vue';
import useClipboard from 'vue-clipboard3';
import { loadAmisSource } from '../amis/load';
import FeedbackPanel from './components/FeedbackPanel.vue';
import { useAssistantContentOpt } from './hooks/useAssistantContentOpt';
import { convertProcessItem, useScrollLoad } from './hooks/useScrollLoad';
import { useScrollToBottom } from './hooks/useScrollToBottom';
import type { ChatContent, StepItem } from './model/types';
import { AnswerState, AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, stepEnumMap, type ChatMessage } from './model/types';
import { extCallQuery, questionStreamByPost, shareChatHistoryByPost } from '/@/api/ai/chat';
import PlayBar from '/@/components/chat/components/playBar/PlayBar.vue';
import CustomDrawer from '/@/components/drawer/CustomDrawer.vue';
import { SHARE_URL } from '/@/constants';
import { Logger } from '/@/model/logger/Logger';
import router from '/@/router';
import {
    activeChatRoom,
    activeGroupType,
    activeLLMId,
    activeRoomId,
    activeSampleId,
    activeSectionAId,
    getRoomConfig,
    isSharePage,
    roomConfig,
} from '/@/stores/chatRoom';
import UserMsg from './user/index.vue';
import { multiChatTypeMapCom } from '/@/components/chat/chatComponents/multiChat';
import emitter from '/@/utils/mitt';
import { ErrorCode } from '/@/utils/request';
import { toMyFixed } from '/@/utils/util';
import { ElMessage } from 'element-plus';
const props = defineProps({
    /** @description å½“前消息 */
    msg: {
        type: Object,
    },
})
</script>
<style scoped lang="scss"></style>
src/components/chat/components/shareLink/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
<template>
    <el-dialog title="分享链接" v-model="shareCodeIsShow" width="12%" modal-append-to-body lock-scroll :before-close="closeShareClick">
        <div class="w100 h100 flex justify-center items-center flex-col text-center">
            <div class="h100" ref="qrcodeRef"></div>
        </div>
    </el-dialog>
</template>
<script setup lang="ts" name="ShareLinkDlg">
import { unrefElement } from '@vueuse/core';
import QRCode from 'qrcodejs2-fixes';
import { nextTick, ref } from 'vue';
import type { ChatMessage } from '../../model/types';
import { shareChatHistoryByPost } from '/@/api/ai/chat';
import { SHARE_URL } from '/@/constants';
import { ElMessage } from 'element-plus';
import useClipboard from 'vue-clipboard3';
const shareCodeIsShow = ref(false);
const closeShareClick = (item: ChatMessage) => {
    shareCodeIsShow.value = false;
};
const qrcodeRef = ref<HTMLDivElement>(null);
const generateShareUrl = async (item: ChatMessage) => {
    const historyId = item.historyId;
    const res = await shareChatHistoryByPost({
        history_ids: historyId,
        share_days: 1,
    });
    if (!res.values) return;
    const shareId = Object.values(res.values)[0];
    if (!shareId) return;
    const shareLink = `${SHARE_URL}?id=${shareId}`;
    return shareLink;
};
const url = ref('');
const openShare = async (item: ChatMessage) => {
    shareCodeIsShow.value = true;
    url.value = await generateShareUrl(item);
    copyShareCodeClick(url.value);
    const qrCodeEle = unrefElement(qrcodeRef);
    nextTick(() => {
        qrCodeEle.innerHTML = '';
        new QRCode(qrCodeEle, {
            text: url,
            width: 100,
            height: 100,
            colorDark: '#000000',
            colorLight: '#ffffff',
        });
    });
};
const { toClipboard } = useClipboard();
const copyShareCodeClick = (url) => {
    if (!url) return;
    toClipboard(url);
    ElMessage.success('已复制分享链接');
};
defineExpose({
    openShare,
});
</script>
<style scoped lang="scss"></style>
src/components/chat/user/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
<template>
    <div class="flex px-4 py-6 rounded-lg relative flex-row-reverse" :key="`${msg.historyId}_${msg.role}`">
        <img class="rounded-full size-12 flex-0 ml-4" :src="roleImageMap[msg.role]" alt="" srcset="" />
        <div class="flex-auto flex justify-end">
            <div class="inline-flex flex-col">
                <div class="rounded-[6px] p-4 leading-relaxed group" :style="{ backgroundColor: 'rgb(197 224 255)' }">
                    <!-- #region ====================== ç”¨æˆ·æ“ä½œæŒ‰é’® ======================-->
                    <div v-if="msg.content?.values && !isSharePage" class="absolute flex items-center bottom-0 group invisible">
                        <div class="bg-[#fff] flex items-center relative mr-4 space-x-2 flex-nowrap rounded-[6px] py-2 px-2 group-hover:visible">
                            <el-tooltip effect="dark" content="复制" placement="top">
                                <div class="flex items-center justify-center size-[20px]">
                                    <i
                                        class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] font-medium !text-[15px] hover:!text-[18px]"
                                        @click="copyUserClick(msg)"
                                    />
                                </div>
                            </el-tooltip>
                            <el-tooltip effect="dark" content="设为常用语" placement="top">
                                <div class="flex items-center justify-center size-[20px]">
                                    <i
                                        class="p-2 ywifont ywicon-cubelifangti cursor-pointer hover:text-[#0284ff] text-[#000] font-[590] !text-[15px] hover:!text-[18px]"
                                        @click="setCommonQuestionClick(msg)"
                                    />
                                </div>
                            </el-tooltip>
                            <el-tooltip effect="dark" content="分享" placement="top">
                                <div class="flex items-center justify-center size-[15px]">
                                    <i
                                        :class="{ 'text-[#0284ff]': msg.state === AnswerState.Unlike }"
                                        class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
                                        @click="shareClick(msg)"
                                    />
                                </div>
                            </el-tooltip>
                        </div>
                    </div>
                    <!-- #endregion -->
                    <!-- #region ====================== æ¶ˆæ¯å†…容 ======================-->
                    <template v-if="msg.content?.values">
                        <!-- #region ====================== å›žç­”组件 ======================-->
                        <component
                            :conclusion="msg.conclusion"
                            :is="answerTypeMapCom[msg.content.type]"
                            :data="msg.content.values"
                            :originData="msg"
                        />
                        <!-- #endregion -->
                    </template>
                    <!-- #endregion -->
                </div>
            </div>
        </div>
    </div>
</template>
<script setup lang="ts" name="UserMsg">
import { AnswerState, answerTypeMapCom, roleImageMap, type ChatMessage } from '../model/types';
import { isSharePage } from '/@/stores/chatRoom';
const emit = defineEmits<{
    (event: 'copyMsg', msgObj: ChatMessage): void;
    (event: 'shareClick', msgObj: ChatMessage): void;
    (event: 'setCommonQuestion', msgObj: ChatMessage): void;
}>();
const props = defineProps({
    /** @description å½“前消息 */
    msg: {
        type: Object,
    },
});
//用户复制问题
const copyUserClick = (item) => {
    emit('copyMsg', item);
};
//用户问题设置为常用语
const setCommonQuestionClick = (item) => {
    emit('setCommonQuestion', item);
};
const shareClick = (msg) => {
    emit('shareClick', msg);
};
</script>
<style scoped lang="scss"></style>
vite.config.ts
@@ -58,7 +58,7 @@
            host: '0.0.0.0',
            port: env.VITE_PORT as unknown as number,
            open: JSON.parse(env.VITE_OPEN),
            hmr: true,
            hmr: false,
        },
        build: {
            // outDir: 'dist/' + mode.mode,