Files
gangyan/chat_web_front/src/views/chat/index.vue

1570 lines
42 KiB
Vue
Raw Normal View History

<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 class="titleHead"></span>
<span>海无涯</span>
<span class="titleHead" style="margin-left: 40px"></span>
<span>技卓越</span>
</div>
<div class="titleInfo">
提升信息处理效率促进科研创新优化工艺流程冶金行业AI助手
</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 chatIdpromt是对话
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 = [];
}
//如果是第一次创建对话先获取对话的idchatNumber并且刷新历史记录
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>