品牌升级: - 全站品牌从"知冶"更名为"战知" - 更换 favicon、侧边栏 logo、登录页 logo - 更新登录页标语和首页欢迎语 应用广场重构: - 从后端数据库驱动改为前端静态配置,按分类 tab 展示 - 新增工具卡片 UI,支持 logo 图片和 emoji 图标 新增工具部署: - Stirling PDF (端口18080) - PDF 处理工具箱 - Excalidraw (端口18081) - 手绘风格白板,集成 AI 绘图 - TrWebOCR (端口18083) - 中文离线 OCR - LibreTranslate (端口18084) - 中英翻译引擎 - PPTist (端口18085) - 在线 PPT 编辑器 - PPTist AI 后端 (端口18086) - 对接 deepseek-v3 生成大纲/PPT/写作 - Excalidraw AI 代理 (端口18082) - 对接 deepseek-v3 生成 Mermaid 图 其他: - 智能场景仅保留"选题推荐" - vite 代理配置增加 /pdf/ 和 /draw/ 路由 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1567 lines
41 KiB
Vue
1567 lines
41 KiB
Vue
<template>
|
||
<div class="chat">
|
||
<div class="scenarioBtn" @click="showScenario">
|
||
<img
|
||
src="../../assets/images/chat/scenarioImg.png"
|
||
class="scenarioImg"
|
||
alt=""
|
||
/>
|
||
<span class="scenarioText">场景</span>
|
||
</div>
|
||
<ChatHistory
|
||
ref="chatHistory"
|
||
@showHistory="showHistory"
|
||
v-show="historyStatus"
|
||
:status="historyStatus"
|
||
@newChat="newChat"
|
||
@viewHistory="viewHistory"
|
||
@updateHistoryTitle="updateHistoryTitle"
|
||
></ChatHistory>
|
||
<div class="chatAll" ref="chatDiv">
|
||
<div class="content" v-if="!chatStatus">
|
||
<div class="title">
|
||
<span>聚尖端之力,创多维平台</span>
|
||
</div>
|
||
<div class="titleInfo">
|
||
聚合科技动能,扩展创新疆界,引领行业跃迁升级
|
||
</div>
|
||
<div class="operate">
|
||
<div class="scenario">
|
||
<div
|
||
class="scenarioItem"
|
||
v-for="(item, index) in fetchSmartPromptListBack"
|
||
:key="index"
|
||
@click="selectScenarioItem(item)"
|
||
>
|
||
<div class="scenarioItemContainer">
|
||
<div class="scenarioItemTitle">{{ item.title }}</div>
|
||
<div class="scenarioItemInfo" :title="item.desc">
|
||
{{ item.desc }}
|
||
</div>
|
||
</div>
|
||
<img src="../../assets/images/chat/jump.png" alt=""/>
|
||
</div>
|
||
</div>
|
||
<div class="carousel">
|
||
<el-carousel arrow="never" pause-on-hover height="292px">
|
||
<el-carousel-item>
|
||
<router-link to="/writing">
|
||
<img
|
||
class="carouselImg"
|
||
src="../../assets/images/chat/carouselWriting.png"
|
||
alt=""
|
||
/>
|
||
</router-link>
|
||
</el-carousel-item>
|
||
<el-carousel-item>
|
||
<router-link to="/knowledgeBase">
|
||
<img
|
||
class="carouselImg"
|
||
src="../../assets/images/chat/carouselStudy.png"
|
||
alt=""
|
||
/>
|
||
</router-link>
|
||
</el-carousel-item>
|
||
<!-- <el-carousel-item>
|
||
<img
|
||
class="carouselImg"
|
||
src="../../assets/images/chat/carouselTranslation.png"
|
||
alt=""
|
||
/>
|
||
</el-carousel-item>-->
|
||
</el-carousel>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="dialogue"
|
||
:style="{ width: `${widthChat}px` }"
|
||
v-if="chatStatus"
|
||
>
|
||
<div class="fixedTop">
|
||
<span class="chatTitle" :title="chatTitle" v-show="!changeTitleStatus">{{
|
||
chatTitle
|
||
}}</span>
|
||
<el-input
|
||
v-model="chatTitle"
|
||
@blur="changeTtile"
|
||
@keydown.enter="changeTtile"
|
||
v-if="changeTitleStatus"
|
||
/>
|
||
<el-dropdown v-show="!changeTitleStatus" trigger="click">
|
||
<img
|
||
src="../../assets/images/chat/select.png"
|
||
class="selectImg"
|
||
alt=""
|
||
/>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item
|
||
@click="
|
||
() => {
|
||
changeTitleStatus = true;
|
||
}
|
||
"
|
||
>修改名称
|
||
</el-dropdown-item
|
||
>
|
||
<el-dropdown-item @click="delChat"
|
||
><span style="color: red">删除</span></el-dropdown-item
|
||
>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
<div class="blurryWrap" v-if="scrollTop != 0">
|
||
<div class="blurry"></div>
|
||
</div>
|
||
<div class="dialogueView" ref="scrollableDiv" @scroll="onScroll">
|
||
<div class="chatDialogue">
|
||
<div class="chatContent">
|
||
<Message
|
||
v-for="(item, index) in chatInfo"
|
||
:key="index"
|
||
:chatInfo="item"
|
||
@setTextarea="setTextarea"
|
||
@restSend="restSend"
|
||
@delChatInfo="delChatInfo(item, index)"
|
||
@send="send"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="blurryWrap">
|
||
<div class="blurry" v-if="scrollBottom > 1 && scrollTop != 0"></div>
|
||
</div>
|
||
<div>
|
||
<div class="interlocution">
|
||
<div class="btns">
|
||
<div class="btn" @click="newChat">
|
||
<div class="btnImg newChatImg"/>
|
||
<span>新建对话</span>
|
||
</div>
|
||
<div
|
||
class="btn"
|
||
:class="{ btned: historyStatus }"
|
||
style="margin-left: 16px"
|
||
@click="showHistory()"
|
||
>
|
||
<div class="btnImg historyImg" style="width: 24px;height: 24px"/>
|
||
<span>历史对话</span>
|
||
</div>
|
||
</div>
|
||
<div class="chatTextarea">
|
||
<div class="chatScenario" v-show="scenaried != ''">
|
||
<span class="chatScenarioText">{{ scenarioTitle }}</span>
|
||
<span class="exitChatScenario" @click="exitChatScenario"
|
||
>退出场景</span
|
||
>
|
||
</div>
|
||
<el-input
|
||
v-model="textarea"
|
||
:rows="1"
|
||
:class="{ noPlaceholder: noPlaceholder }"
|
||
:autosize="{ maxRows: 6 }"
|
||
type="textarea"
|
||
:placeholder="
|
||
placeholder != ''
|
||
? placeholder
|
||
: '请输入你想提的问题,字数不能超过1000字'
|
||
"
|
||
@keydown.enter="keyDown"
|
||
/>
|
||
<div class="chatOperates">
|
||
<div class="chatOperatesLeft">
|
||
<div :class="isDeepThink?'deep-think deep-think-active':'deep-think'" @click="changeDeepThinkStatus()">
|
||
<img src="@/assets/images/chat/deepThink.png" v-show="!isDeepThink" alt="">
|
||
<img src="@/assets/images/chat/deepThinkActive.png" v-show="isDeepThink" alt="">
|
||
<span>DeepSeek-R1</span>
|
||
</div>
|
||
</div>
|
||
<div class="chatOperatesRight">
|
||
<el-upload
|
||
v-model:file-list="fileList"
|
||
class="upload-demo"
|
||
:action="`${baseURL}/gpt/file/upload/temp_knowledgeBase`"
|
||
:before-upload="beforeAvatarUpload"
|
||
:headers= generateAuthInfo()
|
||
accept=".xlsx, .xls, .doc, .pdf, .md, .docx,.png,.jpg"
|
||
name="files"
|
||
:data="{ chatNumber }"
|
||
:on-success="handleAvatarSuccess"
|
||
:on-error="handleAvatarError"
|
||
:show-file-list="false"
|
||
|
||
>
|
||
<img
|
||
src="../../assets/images/chat/file.png"
|
||
class="fileImg"
|
||
alt=""
|
||
/>
|
||
</el-upload>
|
||
|
||
<div
|
||
class="sendBtn"
|
||
:class="{ notAllowed: !notAllowed }"
|
||
@click="debounce(() => { send(); })"
|
||
v-show="!sendStatus"
|
||
|
||
>
|
||
<img
|
||
src="../../assets/images/chat/send.png"
|
||
class="sendImg"
|
||
alt=""
|
||
v-show="!notAllowed"
|
||
/>
|
||
<img
|
||
src="../../assets/images/chat/sendWhite.png"
|
||
class="sendImg"
|
||
alt=""
|
||
v-show="notAllowed"
|
||
/>
|
||
<span>发送</span>
|
||
</div>
|
||
<div class="stopBtn" @click="handleStop" v-show="sendStatus">
|
||
<img
|
||
src="../../assets/images/chat/stopChat.png"
|
||
class="stopImg"
|
||
alt=""
|
||
/>
|
||
<span>停止响应</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="footer">
|
||
以上内容均由AI生成, 仅供参考和借鉴。©{{
|
||
currentYear
|
||
}}{{ PROJECT_PROPERTIES.projectName }}_浪潮软件科技有限公司
|
||
<!-- <a
|
||
target="_blank"
|
||
|
||
style="color: #0969da; text-decoration: none"
|
||
>用户协议</a
|
||
>
|
||
|
|
||
<span class="clicked">隐私政策</span>-->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<Scenario
|
||
@showScenario="showScenario"
|
||
ref="scenarioRef"
|
||
v-show="scenarioStatus"
|
||
:fetchSmartPromptList="fetchSmartPromptList"
|
||
:scenaried="scenaried"
|
||
@selectScenarioItem="selectScenarioItem"
|
||
@getFetchSmartPrompt="getFetchSmartPrompt"
|
||
:chatType="chatType"
|
||
:isDeepThink="isDeepThink"
|
||
@changeDeepThinkStatus="changeDeepThinkStatus"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import {nextTick, onMounted, onUnmounted, reactive, ref, watch,} from "vue";
|
||
import {
|
||
deleteChatMessageById,
|
||
delHistoryChatById,
|
||
editHistoryChatTitleById,
|
||
fetchChatAPI,
|
||
fetchConversationId,
|
||
fetchSmartPrompt,
|
||
interrupt,
|
||
getTitle
|
||
} from "@/api";
|
||
import Message from "@/components/Message.vue";
|
||
import ChatHistory from "@/components/ChatHistory.vue";
|
||
import Scenario from "./Scenario.vue";
|
||
import type {UploadProps, UploadUserFile} from "element-plus";
|
||
import {ElMessage} from "element-plus";
|
||
import {useAuthStore} from "@/store";
|
||
import {PROJECT_PROPERTIES} from "@/config/properties";
|
||
import {debounce, streamResultVerify} from "@/utils";
|
||
import {generateAuthInfo} from "@/utils/request/headerUtils";
|
||
import {useRoute} from "vue-router";
|
||
|
||
const route = useRoute();
|
||
// 对话输入框
|
||
const textarea = ref<string>("");
|
||
|
||
//是否在对话中
|
||
const chatStatus = ref<boolean>(false);
|
||
|
||
//历史记录展示
|
||
const historyStatus = ref<boolean>(false);
|
||
|
||
//场景模块展示
|
||
const scenarioStatus = ref<boolean>(false);
|
||
|
||
//当前对话是否为第一条消息
|
||
const firstChat = ref(true);
|
||
|
||
// 对话类型,0:普通对话 1:文件对话
|
||
const chatType = ref(0);
|
||
|
||
const chatNumber = ref("");
|
||
const conversationId = ref("");
|
||
|
||
// 对话内容
|
||
const chatInfo = ref([]);
|
||
|
||
// 上传文件时禁止发送
|
||
const notAllowed = ref(false);
|
||
|
||
const noPlaceholder = ref(false);
|
||
|
||
const changePlaceholder = (val) => {
|
||
noPlaceholder.value = val;
|
||
};
|
||
|
||
// text提示语
|
||
const placeholder = ref("请输入你想提的问题,字数不能超过1000字");
|
||
|
||
// 发送,停止响应切换
|
||
const sendStatus = ref<boolean>(false);
|
||
|
||
const baseURL = import.meta.env.VITE_GLOB_API_CTX;
|
||
|
||
// 选择的场景key
|
||
const scenaried = ref("");
|
||
|
||
// 对话标题
|
||
const chatTitle = ref("未命名");
|
||
|
||
// 场景value
|
||
const scenarioTitle = ref("");
|
||
|
||
// 对话id
|
||
const chatId = ref(-1);
|
||
|
||
// 文件内容
|
||
const fileContext = ref("");
|
||
|
||
// 标题是否在修改中
|
||
const changeTitleStatus = ref(false);
|
||
|
||
const knowledgeId = ref("");
|
||
|
||
// 文件名
|
||
const fileName = ref("");
|
||
|
||
// 当前年份
|
||
const currentYear = ref("");
|
||
|
||
let controller = null;
|
||
|
||
// 历史记录
|
||
const chatHistory = ref(null);
|
||
|
||
// 删除某条对话
|
||
const delChatInfo = async (item, index) => {
|
||
try {
|
||
await deleteChatMessageById(item.user.conversationId);
|
||
chatInfo.value.splice(index, 1);
|
||
} catch (error: any) {
|
||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
||
}
|
||
};
|
||
|
||
// 查看历史对话
|
||
const viewHistory = (data) => {
|
||
chatInfo.value = [];
|
||
firstChat.value = false;
|
||
chatStatus.value = true;
|
||
chatType.value = data.chatType;
|
||
chatTitle.value = data.chatTitle;
|
||
scenaried.value = data.scenaried;
|
||
placeholder.value = data.placeholder;
|
||
chatId.value = data.chatId;
|
||
scenarioTitle.value = data.scenarioTitle;
|
||
chatNumber.value = data.chatNumber;
|
||
let chatInfoBack = [...data.chatInfo];
|
||
let info = {};
|
||
if (data.chatType === 1) {
|
||
fileName.value = data.fileName;
|
||
knowledgeId.value = data.knowledgeId;
|
||
fileContext.value = data.fileContext;
|
||
|
||
const suffix = data.fileName.match(/[^.]+$/)[0];
|
||
info = {
|
||
user: {content: data.fileName, suffix},
|
||
};
|
||
chatInfo.value.push(info);
|
||
chatInfoBack.shift();
|
||
}
|
||
for (let i = 0; i < chatInfoBack.length;) {
|
||
let index = chatInfoBack[i + 1].content.indexOf("**参考文献:**");
|
||
let aiStrDoc = "";
|
||
let aisStr = "";
|
||
if (
|
||
chatInfoBack[i + 1].content &&
|
||
chatInfoBack[i + 1].content.indexOf("**参考文献:**") != -1
|
||
) {
|
||
aiStrDoc = chatInfoBack[i + 1].content.substring(index + 9);
|
||
aisStr =
|
||
chatInfoBack[i + 1].content.substring(0, index + 9) +
|
||
`<div class="docs">\n${aiStrDoc}</div>`;
|
||
} else {
|
||
aisStr = chatInfoBack[i + 1].content;
|
||
}
|
||
info = {
|
||
user: {
|
||
content: chatInfoBack[i].content,
|
||
conversationId: chatInfoBack[i].messageId,
|
||
},
|
||
ai: { content: aisStr, thinkContent: chatInfoBack[i + 1].think},
|
||
};
|
||
chatInfo.value.push(info);
|
||
i = i + 2;
|
||
}
|
||
};
|
||
|
||
// 更新历史对话名称
|
||
const updateHistoryTitle = (data) => {
|
||
if (chatId.value === data.id) {
|
||
chatTitle.value = data.prompt;
|
||
}
|
||
};
|
||
|
||
// 重新发送
|
||
const restSend = (text) => {
|
||
textarea.value = text;
|
||
send();
|
||
};
|
||
|
||
// 修改对话标题
|
||
const changeTtile = async () => {
|
||
if (chatTitle.value.trim() === "") {
|
||
ElMessage.warning("对话名称不可以为空!");
|
||
return;
|
||
}
|
||
try {
|
||
let res = await editHistoryChatTitleById({
|
||
id: chatId.value,
|
||
prompt: chatTitle.value,
|
||
});
|
||
if (res && res.code === 200) {
|
||
ElMessage.success("修改成功");
|
||
|
||
if (historyStatus.value) {
|
||
chatHistory.value.getHistoryList();
|
||
}
|
||
changeTitleStatus.value = false;
|
||
return;
|
||
}
|
||
|
||
ElMessage.error("修改失败");
|
||
} catch (error: any) {
|
||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
||
}
|
||
};
|
||
|
||
// 设置输入框的值
|
||
const setTextarea = (text) => {
|
||
textarea.value = text;
|
||
};
|
||
|
||
// 删除对话
|
||
const delChat = async () => {
|
||
try {
|
||
let res = await delHistoryChatById(chatId.value);
|
||
if (res && res.code === 200) {
|
||
ElMessage.success("删除成功");
|
||
|
||
if (historyStatus.value) {
|
||
chatHistory.value.getHistoryList();
|
||
}
|
||
newChat();
|
||
return;
|
||
}
|
||
|
||
ElMessage.error("删除失败");
|
||
} catch (error: any) {
|
||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
||
}
|
||
};
|
||
|
||
// 场景选择
|
||
const selectScenarioItem = (item) => {
|
||
if (isDeepThink.value) {
|
||
ElMessage.warning("您已选择智慧场景,已为您取消深度思考");
|
||
changeDeepThinkStatus(false);
|
||
}
|
||
scenaried.value = item.name;
|
||
scenarioTitle.value = item.title;
|
||
placeholder.value = item.prompt;
|
||
};
|
||
|
||
// 停止响应
|
||
const handleStop = async () => {
|
||
controller.abort();
|
||
controller = null;
|
||
const content = chatInfo.value[chatInfo.value.length - 1].ai.content;
|
||
let messageData = {
|
||
messageId: conversationId.value,
|
||
content,
|
||
};
|
||
try {
|
||
await interrupt(messageData);
|
||
} catch (error: any) {
|
||
ElMessage.warn('响应已停止');
|
||
}
|
||
if (
|
||
chatInfo.value[chatInfo.value.length - 1].ai.content == "" ||
|
||
chatInfo.value[chatInfo.value.length - 1].ai.content == "思考中..."
|
||
) {
|
||
chatInfo.value[chatInfo.value.length - 1].ai.content = "已停止响应";
|
||
}
|
||
|
||
sendStatus.value = false;
|
||
};
|
||
|
||
// 退出场景
|
||
const exitChatScenario = () => {
|
||
scenaried.value = "";
|
||
scenarioTitle.value = "";
|
||
placeholder.value = "请输入你想提的问题,字数不能超过1000字";
|
||
};
|
||
|
||
// 场景列表
|
||
let fetchSmartPromptList = ref([]);
|
||
|
||
//场景列表备份
|
||
let fetchSmartPromptListBack = ref([]);
|
||
|
||
//文件列表
|
||
const fileList = ref<UploadUserFile[]>([]);
|
||
|
||
// 上传文件
|
||
const beforeAvatarUpload: UploadProps["beforeUpload"] = async (rawFile) => {
|
||
if (scenaried.value) {
|
||
ElMessage.warning("您正在进行文件对话,已退出智慧场景。");
|
||
}
|
||
if (rawFile.size / 1024 / 1024 > 10) {
|
||
ElMessage.error("文件大小不能超过10M");
|
||
return false;
|
||
}
|
||
notAllowed.value = true;
|
||
const suffix = rawFile.name.match(/[^.]+$/)[0];
|
||
|
||
newChat();
|
||
|
||
// chatTitle.value = rawFile.name;
|
||
|
||
chatType.value = 1;
|
||
|
||
scenaried.value = "";
|
||
scenarioTitle.value = "";
|
||
placeholder.value = "请输入你想提的问题,字数不能超过1000字";
|
||
|
||
if (firstChat.value) {
|
||
await getFetchChatAPI(rawFile.name);
|
||
}
|
||
|
||
const info = reactive({
|
||
user: {content: rawFile.name, suffix},
|
||
});
|
||
|
||
chatInfo.value.push(info);
|
||
|
||
return true;
|
||
};
|
||
|
||
// 文件上传成功
|
||
const handleAvatarSuccess: UploadProps["onSuccess"] = (
|
||
response,
|
||
uploadFile
|
||
) => {
|
||
response.data.data.context.length > 30000
|
||
? (fileContext.value = response.data.data.summary)
|
||
: (fileContext.value = response.data.data.context);
|
||
knowledgeId.value = response.data.data.id;
|
||
fileName.value = response.fileName;
|
||
firstChat.value ? (firstChat.value = false) : "";
|
||
notAllowed.value = false;
|
||
};
|
||
|
||
// 上传失败
|
||
const handleAvatarError: UploadProps["onError"] = (error) => {
|
||
chatType.value = 0;
|
||
ElMessage.error(error);
|
||
};
|
||
|
||
// 新建对话
|
||
const newChat = () => {
|
||
firstChat.value = true;
|
||
chatStatus.value = true;
|
||
chatTitle.value = "未命名";
|
||
chatType.value = 0;
|
||
sendStatus.value = false;
|
||
chatInfo.value = [];
|
||
};
|
||
|
||
onUnmounted(() => {
|
||
if (controller) {
|
||
controller.abort();
|
||
}
|
||
|
||
// 销毁全局的回车事件
|
||
//window.removeEventListener("keydown", keyDown, false);
|
||
});
|
||
|
||
// 获取场景
|
||
const getFetchSmartPrompt = async () => {
|
||
try {
|
||
const res = await fetchSmartPrompt();
|
||
fetchSmartPromptList.value = [];
|
||
fetchSmartPromptListBack.value = [];
|
||
let k = 0;
|
||
for (let i in res.data.llm_chat) {
|
||
for (let m in res.data.llm_chat[i].prompt) {
|
||
fetchSmartPromptList.value.push(res.data.llm_chat[i].prompt[m]);
|
||
if (k < 3) {
|
||
fetchSmartPromptListBack.value.push(res.data.llm_chat[i].prompt[m]);
|
||
}
|
||
k++;
|
||
}
|
||
}
|
||
} catch (error: any) {
|
||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
||
}
|
||
};
|
||
|
||
watch(() => route.path, async (newValue, oldValue) => {
|
||
if (newValue !== oldValue && newValue === '/index') {
|
||
window.location.reload();
|
||
}
|
||
if (newValue !== oldValue && newValue === '/chat') {
|
||
newChat();
|
||
}
|
||
})
|
||
|
||
// 获取对话 chatNumber chatId,promt是对话
|
||
const getFetchChatAPI = async (prompt) => {
|
||
const chatData = {
|
||
prompt,
|
||
type: chatType.value,
|
||
};
|
||
try {
|
||
const fetchChatAPIData = await fetchChatAPI(chatData);
|
||
chatNumber.value = fetchChatAPIData.data.chatNumber;
|
||
chatId.value = fetchChatAPIData.data.id;
|
||
} catch (error: any) {
|
||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
||
}
|
||
};
|
||
const autoScroll = ref(true);
|
||
|
||
//根据所写的问题获取conversationId
|
||
const getFetchConversationId = async (prompt) => {
|
||
let model = isDeepThink.value ? 'R1-70B' : 'QIANWEN'
|
||
const messageData = {
|
||
chatNumber: chatNumber.value,
|
||
prompt,
|
||
model: model,
|
||
promptName: scenaried.value != "" ? scenaried.value : "default",
|
||
promptTitle: scenarioTitle.value != "" ? scenarioTitle.value : "default",
|
||
promptPrompt: placeholder.value,
|
||
chatId: chatId.value,
|
||
};
|
||
try {
|
||
const fetchConversationIdData = await fetchConversationId(messageData);
|
||
conversationId.value = fetchConversationIdData.data;
|
||
} catch (error: any) {
|
||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
||
}
|
||
};
|
||
// 对话,流式对话
|
||
const getFetchChatAPIProcess = async () => {
|
||
const info = reactive({
|
||
user: { content: textarea.value.trim(), conversationId: "" },
|
||
ai: { content: "", question: [], contentStatus: false, thinkContent: '' },
|
||
});
|
||
chatInfo.value.push(info);
|
||
textarea.value = "";
|
||
let aiQuestion = ``;
|
||
if (!controller) {
|
||
controller = new AbortController();
|
||
}
|
||
if (chatType.value === 0) {
|
||
const response = await fetch(
|
||
`${baseURL}/app/chat/stream?conversationId=${
|
||
conversationId.value
|
||
}&promptName=${scenaried.value != "" ? scenaried.value : "default"}`,
|
||
{
|
||
method: "GET",
|
||
headers: {
|
||
Accept: "text/plain",
|
||
Authorization: `Bearer ${useAuthStore().token}`
|
||
},
|
||
signal: controller.signal,
|
||
}
|
||
);
|
||
|
||
const reader = response.body.getReader();
|
||
const decoder = new TextDecoder();
|
||
let done = false;
|
||
let result = "";
|
||
let picTemp = false;
|
||
// 逐块读取流数据
|
||
while (!done) {
|
||
const {value, done: doneReading} = await reader.read();
|
||
done = doneReading;
|
||
result += decoder.decode(value, {stream: true});
|
||
// 校验token是否失效
|
||
streamResultVerify(result);
|
||
// 分割数据并逐条展示
|
||
const lines = result.split("\n");
|
||
for (let i = 0; i < lines.length - 1; i++) {
|
||
let lineObj = JSON.parse(lines[i]);
|
||
if (
|
||
!info.user.conversationId ||
|
||
info.user.conversationId != lineObj.conversationId
|
||
) {
|
||
info.user.conversationId = lineObj.conversationId;
|
||
}
|
||
if (lineObj.question) {
|
||
aiQuestion = lineObj.question;
|
||
} else if (lineObj.think) {
|
||
info.ai.thinkContent += lineObj.think;
|
||
} else if (lineObj.text) {
|
||
let index = info.ai.content.indexOf("思考中...");
|
||
if (index != -1) {
|
||
let text = info.ai.content.substring(0, index);
|
||
info.ai.content = text;
|
||
}
|
||
if (info.ai.content.indexOf(".png)") != -1 && info.ai.content.indexOf("http") != -1) {
|
||
info.ai.content = info.ai.content.replace("(http", "http");
|
||
info.ai.content = info.ai.content.replace(".png)", ".png");
|
||
|
||
picTemp = true;
|
||
}
|
||
info.ai.content += lineObj.text;
|
||
} else if (lineObj.docs) {
|
||
info.ai.content += `\n**参考文献:**`;
|
||
info.ai.content += `<div class="docs">\n${lineObj.docs}</div>`;
|
||
}
|
||
}
|
||
// 保留最后一块数据,等待下一次读取
|
||
result = lines[lines.length - 1];
|
||
// 自动滚动到最新位置
|
||
}
|
||
if (picTemp) {
|
||
info.ai.content = info.ai.content.replace("http", "(http");
|
||
info.ai.content = info.ai.content.replace(".png", ".png)");
|
||
}
|
||
aiQuestion=aiQuestion.replace(/\n\n/g, "<strip>");
|
||
let questionArr = aiQuestion.split('<strip>');
|
||
for (let i = 0; i < questionArr.length; i++) {
|
||
if(questionArr[i].trim()){
|
||
let start = questionArr[i].indexOf(":");
|
||
info.ai.question.push(questionArr[i].slice(start + 1).trim());
|
||
}
|
||
}
|
||
} else {
|
||
// 文件对话
|
||
const response = await fetch(`${baseURL}/gpt/file/fileTalk`, {
|
||
method: "POST",
|
||
headers: {
|
||
Accept: "text/plain",
|
||
Authorization: `Bearer ${useAuthStore().token}`,
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({
|
||
conversationId: conversationId.value,
|
||
knowledgeId: knowledgeId.value,
|
||
fileName: fileName.value,
|
||
context: fileContext.value,
|
||
}),
|
||
signal: controller.signal,
|
||
});
|
||
|
||
const reader = response.body.getReader();
|
||
const decoder = new TextDecoder();
|
||
let done = false;
|
||
let result = "";
|
||
|
||
// 逐块读取流数据
|
||
while (!done) {
|
||
const {value, done: doneReading} = await reader.read();
|
||
done = doneReading;
|
||
result += decoder.decode(value, {stream: true});
|
||
// 校验token是否失效
|
||
streamResultVerify(result);
|
||
// 分割数据并逐条展示
|
||
const lines = result.split("\n");
|
||
for (let i = 0; i < lines.length - 1; i++) {
|
||
let lineObj = JSON.parse(lines[i]);
|
||
if (
|
||
!info.user.conversationId ||
|
||
info.user.conversationId != lineObj.conversationId
|
||
) {
|
||
info.user.conversationId = lineObj.conversationId;
|
||
}
|
||
if (lineObj.question) {
|
||
aiQuestion = lineObj.question;
|
||
} else if (lineObj.answer) {
|
||
let index = info.ai.content.indexOf("思考中...");
|
||
if (index != -1) {
|
||
let text = info.ai.content.substring(0, index);
|
||
info.ai.content = text;
|
||
}
|
||
info.ai.content += lineObj.answer;
|
||
} else if (lineObj.docs) {
|
||
info.ai.content += `\n**参考文献:**`;
|
||
info.ai.content += `<div class="docs">\n${lineObj.docs}</div>`;
|
||
}
|
||
}
|
||
// 保留最后一块数据,等待下一次读取
|
||
result = lines[lines.length - 1];
|
||
}
|
||
aiQuestion=aiQuestion.replace(/\n\n/g, "<strip>");
|
||
let questionArr = aiQuestion.split(`<strip>`);
|
||
for (let i = 0; i < questionArr.length; i++) {
|
||
if(questionArr[i].trim()){
|
||
let start = questionArr[i].indexOf(":");
|
||
info.ai.question.push(questionArr[i].slice(start + 1).trim());
|
||
}
|
||
}
|
||
}
|
||
};
|
||
// 发送消息
|
||
let send = async () => {
|
||
if (textarea.value.trim() === "" || sendStatus.value) {
|
||
return;
|
||
}
|
||
autoScroll.value = true;
|
||
sendStatus.value = true;
|
||
|
||
if (chatInfo.value.length > 0 && chatType.value === 0) {
|
||
chatInfo.value[chatInfo.value.length - 1].ai.question = [];
|
||
}
|
||
//如果是第一次创建对话,先获取对话的id,chatNumber,并且刷新历史记录
|
||
if (firstChat.value) {
|
||
await getFetchChatAPI(textarea.value.trim());
|
||
if (historyStatus.value) {
|
||
chatHistory.value.getHistoryList(false);
|
||
}
|
||
}
|
||
//获取conversationId
|
||
await getFetchConversationId(textarea.value.trim());
|
||
|
||
if (!chatStatus.value) {
|
||
chatStatus.value = true;
|
||
}
|
||
|
||
//流式会话
|
||
await getFetchChatAPIProcess();
|
||
firstChat.value ? (firstChat.value = false) : "";
|
||
sendStatus.value = false;
|
||
|
||
// 获取对话名称
|
||
await createTitle();
|
||
// 刷新历史记录
|
||
if (historyStatus.value) {
|
||
chatHistory.value.getHistoryList(false);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 根据对话获取对话名称
|
||
*/
|
||
const createTitle = async () => {
|
||
if (chatTitle.value !== "未命名" ) return;
|
||
|
||
let firstChat;
|
||
if (chatType.value === 0 ) {
|
||
if (chatInfo.value.length !== 1) return;
|
||
firstChat = chatInfo.value[0];
|
||
} else {
|
||
if (chatInfo.value.length !== 2) return;
|
||
firstChat = chatInfo.value[1];
|
||
}
|
||
|
||
let params = {
|
||
conversation_id: chatNumber.value,
|
||
history: [
|
||
{
|
||
role: 'user',
|
||
content: firstChat.user.content,
|
||
}, {
|
||
role: "assistant",
|
||
content: firstChat.ai.content
|
||
}
|
||
]
|
||
}
|
||
|
||
try {
|
||
let res = await getTitle(params);
|
||
if (res && res.code === 200) {
|
||
chatTitle.value = res.data.title;
|
||
}
|
||
} catch (error: any) {
|
||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 发送按钮的回车事件
|
||
*/
|
||
let keyDown = (e) => {
|
||
if (e.key === 'Enter' && !e.shiftKey) {
|
||
e.preventDefault();
|
||
if (!sendStatus.value) {
|
||
debounce(() => {
|
||
send();
|
||
});
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 控制历史对话框的显隐
|
||
*/
|
||
function showHistory() {
|
||
if (historyStatus.value) {
|
||
historyStatus.value = false;
|
||
} else {
|
||
historyStatus.value = true;
|
||
}
|
||
}
|
||
|
||
// 展示场景
|
||
function showScenario(status) {
|
||
scenarioStatus.value = status;
|
||
}
|
||
|
||
const widthChat = ref(0);
|
||
|
||
// 使用 ref 定义响应式变量
|
||
const chatDiv = ref<HTMLElement | null>(null);
|
||
// 计算右边距函数
|
||
const calculateRightMargin = () => {
|
||
if (chatDiv.value) {
|
||
const rect = chatDiv.value.getBoundingClientRect();
|
||
const viewportWidth = window.innerWidth;
|
||
widthChat.value = viewportWidth - rect.left - 1;
|
||
} else {
|
||
console.warn("元素未挂载或未找到");
|
||
}
|
||
};
|
||
|
||
const scrollableDiv = ref<HTMLDivElement | null>(null);
|
||
const scrollTop = ref<number>(0);
|
||
const scrollBottom = ref<number>(0);
|
||
|
||
const onScroll = () => {
|
||
if (scrollableDiv.value) {
|
||
if (scrollBottom.value > 1 && scrollTop.value != 0) {
|
||
autoScroll.value = false;
|
||
}
|
||
|
||
scrollTop.value = scrollableDiv.value.scrollTop;
|
||
scrollBottom.value =
|
||
scrollableDiv.value.scrollHeight -
|
||
scrollableDiv.value.scrollTop -
|
||
scrollableDiv.value.clientHeight;
|
||
}
|
||
};
|
||
const scrollToBottom = () => {
|
||
if (scrollableDiv.value) {
|
||
scrollableDiv.value.scrollTop = scrollableDiv.value.scrollHeight;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 深度思考
|
||
*/
|
||
const isDeepThink = ref<boolean>(false);
|
||
const changeDeepThinkStatus = (status: boolean = !isDeepThink.value) => {
|
||
if (scenaried.value && status) {
|
||
ElMessage.warning("您已选择深度思考,为您自动退出智慧场景。");
|
||
scenaried.value = "";
|
||
scenarioTitle.value = "";
|
||
placeholder.value = "请输入你想提的问题,字数不能超过1000字";
|
||
}
|
||
isDeepThink.value = status;
|
||
}
|
||
|
||
watch(
|
||
chatInfo,
|
||
() => {
|
||
if (autoScroll.value) {
|
||
nextTick(() => {
|
||
scrollToBottom();
|
||
});
|
||
}
|
||
},
|
||
{deep: true}
|
||
);
|
||
|
||
watch(
|
||
[historyStatus, scenarioStatus],
|
||
() => {
|
||
nextTick(() => {
|
||
calculateRightMargin();
|
||
});
|
||
},
|
||
{immediate: true}
|
||
);
|
||
|
||
watch(textarea, (newValue) => {
|
||
if (newValue.trim() === "") {
|
||
notAllowed.value = false;
|
||
} else {
|
||
notAllowed.value = true;
|
||
}
|
||
});
|
||
|
||
onMounted(() => {
|
||
getFetchSmartPrompt();
|
||
nextTick(() => {
|
||
onScroll(); // 初始化时调用一次以设置初始值
|
||
});
|
||
window.addEventListener("resize", calculateRightMargin);
|
||
// 获取当前年份
|
||
currentYear.value = new Date().getFullYear();
|
||
|
||
// 添加全局回车事件
|
||
// window.addEventListener("keydown", keyDown);
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.chat {
|
||
display: flex;
|
||
flex: 1;
|
||
height: 100vh;
|
||
|
||
.scenarioBtn {
|
||
width: 96px;
|
||
height: 42px;
|
||
background: #fafbff;
|
||
border-radius: 8px 8px 8px 8px;
|
||
border: 1px solid #d5ddff;
|
||
position: absolute;
|
||
right: 32px;
|
||
top: 32px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
|
||
.scenarioImg {
|
||
width: 24px;
|
||
height: 20px;
|
||
}
|
||
|
||
.scenarioText {
|
||
margin-left: 8px;
|
||
font-weight: 400;
|
||
font-size: 16px;
|
||
color: #004ea0;
|
||
line-height: 30px;
|
||
}
|
||
}
|
||
|
||
.chatAll {
|
||
margin: 0 auto;
|
||
width: 49.479vw;
|
||
max-width: 950px;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
|
||
// .contentWrap {
|
||
// // z-index: 9999;
|
||
// margin-top: 19.46vh;
|
||
// overflow-y: auto;
|
||
// }
|
||
.content {
|
||
// width: 100%;
|
||
// width: 49.479vw;
|
||
overflow-y: auto;
|
||
margin-top: 19.46vh;
|
||
position: relative;
|
||
|
||
.title {
|
||
font-weight: bold;
|
||
font-family: "PingFangSC-Bold";
|
||
font-size: 52px;
|
||
color: #000000;
|
||
line-height: 58px;
|
||
|
||
.titleHead {
|
||
color: #004ea0;
|
||
}
|
||
}
|
||
|
||
.titleInfo {
|
||
margin-top: 16px;
|
||
font-weight: 400;
|
||
font-size: 20px;
|
||
color: #000000;
|
||
line-height: 30px;
|
||
text-align: left;
|
||
}
|
||
|
||
.operate {
|
||
display: flex;
|
||
margin-top: 40px;
|
||
width: 100%;
|
||
|
||
.scenario {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 292px;
|
||
width: calc(100% - 262px);
|
||
flex: 1;
|
||
justify-content: space-between;
|
||
|
||
.scenarioItem {
|
||
img {
|
||
width: 7px;
|
||
height: 14px;
|
||
}
|
||
|
||
cursor: pointer;
|
||
width: 100%;
|
||
height: 84px;
|
||
background: #e6edff;
|
||
border-radius: 8px 8px 8px 8px;
|
||
border: 1px solid #fafbff;
|
||
padding-left: 20px;
|
||
padding-right: 20px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.scenarioItemContainer {
|
||
width: calc(100% - 10px);
|
||
|
||
.scenarioItemTitle {
|
||
font-weight: bold;
|
||
font-family: "PingFangSC-Bold";
|
||
font-size: 18px;
|
||
color: #000000;
|
||
line-height: 30px;
|
||
}
|
||
|
||
.scenarioItemInfo {
|
||
font-weight: 400;
|
||
font-size: 16px;
|
||
color: #858a94;
|
||
line-height: 30px;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.carousel {
|
||
width: 262px;
|
||
height: 292px;
|
||
margin-left: 20px;
|
||
|
||
.carouselImg {
|
||
cursor: pointer;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
::v-deep .el-carousel__button {
|
||
width: 10px;
|
||
}
|
||
|
||
::v-deep .el-carousel__indicators .el-carousel__indicator.is-active {
|
||
.el-carousel__button {
|
||
background-color: #004ea0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.content::-webkit-scrollbar {
|
||
width: 0; /* 隐藏滚动条 */
|
||
background: transparent; /* 保持背景透明 */
|
||
}
|
||
|
||
/* 针对Firefox */
|
||
.content {
|
||
scrollbar-width: none; /* 隐藏滚动条 */
|
||
}
|
||
|
||
.blurryWrap {
|
||
height: 30px;
|
||
width: 49.479vw;
|
||
max-width: 950px;
|
||
position: relative;
|
||
|
||
.blurry {
|
||
height: 20px;
|
||
width: 100%;
|
||
position: absolute;
|
||
top: -20px;
|
||
// background-color: #edf2ffcc; /* 半透明背景 */
|
||
background: linear-gradient(
|
||
to top,
|
||
#edf2ff 0%,
|
||
rgba(255, 255, 255, 0) 100%
|
||
);
|
||
z-index: 10;
|
||
pointer-events: none;
|
||
// border-top-left-radius: 5px;
|
||
// border-top-right-radius: 5px;
|
||
}
|
||
}
|
||
|
||
.dialogue {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
// margin-bottom: 41px;
|
||
overflow-y: auto;
|
||
|
||
.blurryWrap {
|
||
height: 1px;
|
||
width: 49.479vw;
|
||
max-width: 950px;
|
||
position: relative;
|
||
|
||
.blurry {
|
||
height: 20px;
|
||
width: 100%;
|
||
position: absolute;
|
||
top: 40px;
|
||
// background-color: #edf2ffcc; /* 半透明背景 */
|
||
background: linear-gradient(
|
||
to top,
|
||
rgba(255, 255, 255, 0) 0%,
|
||
#edf2ff 100%
|
||
);
|
||
z-index: 10;
|
||
pointer-events: none;
|
||
// border-top-left-radius: 5px;
|
||
// border-top-right-radius: 5px;
|
||
}
|
||
}
|
||
|
||
.fixedTop {
|
||
width: 49.479vw;
|
||
max-width: 950px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin-top: 23px;
|
||
height: 25px;
|
||
font-weight: 400;
|
||
font-size: 18px;
|
||
color: #000000;
|
||
line-height: 30px;
|
||
|
||
.el-input {
|
||
margin-right: 12px;
|
||
width: 200px;
|
||
}
|
||
|
||
.chatTitle {
|
||
margin-right: 12px;
|
||
max-width: 250px;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
text-align: center;
|
||
}
|
||
|
||
.selectImg {
|
||
width: 8px;
|
||
height: 4px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
:focus-visible {
|
||
outline: none !important;
|
||
}
|
||
}
|
||
|
||
.dialogueView {
|
||
margin-top: 42px;
|
||
height: calc(100% - 48px);
|
||
width: 100%;
|
||
overflow-y: auto;
|
||
|
||
.chatDialogue {
|
||
height: 100%;
|
||
width: 49.479vw;
|
||
max-width: 950px;
|
||
|
||
.chatContent {
|
||
width: 100%;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.interlocution {
|
||
width: 100%;
|
||
margin-bottom: 16px;
|
||
|
||
// z-index: 99999;
|
||
.btns {
|
||
display: flex;
|
||
|
||
.btn {
|
||
width: 124px;
|
||
height: 42px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
background: #ffffff;
|
||
border-radius: 8px;
|
||
border: 1px solid #d5ddff;
|
||
font-weight: 400;
|
||
font-size: 16px;
|
||
color: #858a94;
|
||
line-height: 30px;
|
||
|
||
.btnImg {
|
||
width: 20px;
|
||
height: 20px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.newChatImg {
|
||
background: url("../../assets/images/chat/newChat.png") no-repeat center;
|
||
background-size: 100%;
|
||
}
|
||
|
||
.historyImg {
|
||
background: url("../../assets/images/chat/history.png") no-repeat center;
|
||
background-size: 100%;
|
||
}
|
||
}
|
||
|
||
.btn:hover {
|
||
border: 1px solid #004ea0;
|
||
color: #004ea0;
|
||
|
||
.newChatImg {
|
||
background: url("../../assets/images/chat/newChated.png") no-repeat center;
|
||
background-size: 100%;
|
||
}
|
||
|
||
.historyImg {
|
||
background: url("../../assets/images/chat/historyed.png") no-repeat center;
|
||
background-size: 100%;
|
||
}
|
||
}
|
||
|
||
.btned {
|
||
border: 1px solid #004ea0;
|
||
color: #004ea0;
|
||
|
||
.newChatImg {
|
||
background: url("../../assets/images/chat/newChated.png") no-repeat center;
|
||
background-size: 100%;
|
||
}
|
||
|
||
.historyImg {
|
||
background: url("../../assets/images/chat/historyed.png") no-repeat center;
|
||
background-size: 100%;
|
||
}
|
||
}
|
||
}
|
||
|
||
.chatTextarea {
|
||
width: 100%;
|
||
// height: 104px;
|
||
margin-top: 16px;
|
||
padding-bottom: 16px;
|
||
background: #ffffff;
|
||
border-radius: 8px 8px 8px 8px;
|
||
border: 1px solid #d5ddff;
|
||
|
||
.el-textarea {
|
||
font-weight: 400;
|
||
font-size: 18px;
|
||
color: #858a94;
|
||
line-height: 30px;
|
||
}
|
||
|
||
.chatScenario {
|
||
width: 100%;
|
||
height: 49px;
|
||
background: #d5ddff;
|
||
padding-left: 16px;
|
||
padding-right: 16px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.chatScenarioText {
|
||
font-weight: bold;
|
||
font-family: "PingFangSC-Bold";
|
||
font-size: 18px;
|
||
color: #004ea0;
|
||
line-height: 30px;
|
||
}
|
||
|
||
.exitChatScenario {
|
||
cursor: pointer;
|
||
font-weight: 400;
|
||
font-size: 16px;
|
||
color: #858a94;
|
||
line-height: 30px;
|
||
}
|
||
|
||
.exitChatScenario:hover {
|
||
color: #004ea0;
|
||
}
|
||
}
|
||
|
||
::v-deep .el-textarea__inner {
|
||
width: 100%;
|
||
/* height: 53px !important;*/
|
||
min-height: 53px !important;
|
||
outline: none;
|
||
border: none;
|
||
resize: none;
|
||
box-shadow: none;
|
||
background: #ffffff;
|
||
color: #000000;
|
||
padding-left: 16px;
|
||
padding-right: 16px;
|
||
padding-top: 16px;
|
||
padding-bottom: 10px;
|
||
}
|
||
|
||
.chatOperates {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
.chatOperatesLeft {
|
||
padding-left: 16px;
|
||
.deep-think {
|
||
width: 108px;
|
||
height: 28px;
|
||
background: #E6EDFF;
|
||
border-radius: 14px 14px 14px 14px;
|
||
border: 1px solid #D5DDFF;
|
||
font-size: 12px;
|
||
color: #858A94;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
img {
|
||
width: 12px;
|
||
margin-right: 3px;
|
||
}
|
||
}
|
||
.deep-think-active {
|
||
background: #E6EDFF;
|
||
border: 1px solid #004ea0;
|
||
color: #004ea0;
|
||
}
|
||
}
|
||
.chatOperatesRight {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
padding-left: 16px;
|
||
padding-right: 16px;
|
||
|
||
.fileImg {
|
||
height: 28px;
|
||
margin-right: 16px;
|
||
}
|
||
|
||
.sendBtn {
|
||
width: 92px;
|
||
height: 38px;
|
||
cursor: pointer;
|
||
background: #004ea0;
|
||
border-radius: 22px 22px 22px 22px;
|
||
border: 1px solid #004ea0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 400;
|
||
font-size: 16px;
|
||
color: #fafbff;
|
||
line-height: 30px;
|
||
|
||
.sendImg {
|
||
margin-right: 8px;
|
||
width: 20px;
|
||
height: 18.53px;
|
||
}
|
||
}
|
||
|
||
.notAllowed {
|
||
cursor: default !important;
|
||
background: #ffffff !important;
|
||
color: #858a94 !important;
|
||
border: 1px solid #858a94 !important;
|
||
}
|
||
|
||
.stopBtn {
|
||
width: 120px;
|
||
height: 38px;
|
||
cursor: pointer;
|
||
background: #ffffff;
|
||
border-radius: 22px 22px 22px 22px;
|
||
border: 1px solid #004ea0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 400;
|
||
font-size: 16px;
|
||
color: #004ea0;
|
||
line-height: 30px;
|
||
|
||
.stopImg {
|
||
margin-right: 8px;
|
||
width: 20px;
|
||
height: 18.53px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.footer {
|
||
width: 100%;
|
||
text-align: right;
|
||
margin-bottom: 16px;
|
||
font-weight: 400;
|
||
font-size: 14px;
|
||
color: #b9b9b9;
|
||
line-height: 30px;
|
||
|
||
.clicked {
|
||
color: #004ea0;
|
||
}
|
||
}
|
||
}
|
||
|
||
.noPlaceholder {
|
||
::v-deep .el-textarea__inner::placeholder {
|
||
color: transparent !important;
|
||
/* 或者 rgba(0, 0, 0, 0) */
|
||
}
|
||
}
|
||
}
|
||
</style> |