1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273 |
- <template>
- <div class="chat-container">
- <div class="content">
- <div class="messages">
- <ul>
- <li
- v-for="message in messages"
- :key="message.id"
- :class="[
- message.user,
- message.user === 'user' ? 'user-message' : '',
- ]"
- >
- <!-- 添加判断,如果是机器人的消息也显示头像 -->
- <div class="message_name">
- <div
- class="avatar"
- :class="{
- 'user-avatar': message.user === 'user',
- 'bot-avatar': message.user === 'bot',
- }"
- ></div>
- <div class="name">
- <span
- class="sender-name"
- style="color: #666666"
- v-if="message.user === 'user'"
- >你</span
- >
- <span class="sender-name" v-if="message.user === 'bot'"
- >AI助理</span
- >
- </div>
- </div>
- <!-- 使用 v-html 来渲染 Markdown 文本 -->
- <div class="message-content">
- <div class="message-text">
- <!-- 如果有图片,先显示图片 -->
- <div v-if="message.imageUrls && message.imageUrls.length" class="message-images">
- <img
- v-for="(url, index) in message.imageUrls"
- :key="index"
- :src="url"
- class="uploaded-image"
- alt="Uploaded content"
- />
- </div>
- <!-- 如果有文档,显示文档预览 -->
- <div v-if="message.documentUrls && message.documentUrls.length" class="message-documents">
- <div
- v-for="(doc, index) in message.documentUrls"
- :key="index"
- class="document-preview-item"
- >
- <i class="el-icon-document"></i>
- <span class="document-name">{{ getDocumentName(doc) }}</span>
- <a :href="doc" target="_blank" class="document-link">查看文档</a>
- </div>
- </div>
- <!-- 显示文本内容 -->
- <div v-html="renderMarkdown(message.message)" @click="handleLinkClick"></div>
- </div>
- </div>
- </li>
- </ul>
- </div>
- <!-- <div class="input-container">
- <textarea
- v-model="userInput"
- rows="4"
- @keydown="handleKeydown"
- placeholder="发送消息..."
- ></textarea>
- <button @click="sendMessage" class="btn">发送</button>
- </div> -->
- <div class="input-container">
- <!-- 文件上传预览区 -->
- <div
- class="upload-preview"
- v-if="uploadFiles.length > 0 || documents.length > 0"
- >
- <div
- v-for="(file, index) in uploadFiles"
- :key="'img-' + index"
- class="preview-item image-preview"
- >
- <div class="preview-content">
- <img
- v-if="file.type.startsWith('image/')"
- :src="file.preview"
- class="preview-image"
- alt="preview"
- />
- </div>
- <i class="el-icon-close remove-file" @click="removeFile(index)"></i>
- </div>
- <div
- v-for="(doc, index) in documents"
- :key="'doc-' + index"
- class="preview-item document-preview"
- >
- <div class="preview-content">
- <i class="el-icon-document"></i>
- <span class="doc-name">{{ doc.name }}</span>
- </div>
- <i
- class="el-icon-close remove-file"
- @click="removeDocument(index)"
- ></i>
- </div>
- </div>
- <!-- 输入区域 -->
- <div class="input-area">
- <textarea
- v-model="userInput"
- rows="4"
- @keydown="handleKeydown"
- placeholder="发送消息..."
- ></textarea>
- <!-- 工具栏 -->
- <div class="toolbar">
- <div class="left-tools">
- <input
- type="file"
- ref="fileInput"
- multiple
- @change="handleFileUpload"
- accept="image/*,.pdf,.doc,.docx,.txt"
- style="display: none"
- />
- <button class="tool-btn" @click="$refs.fileInput.click()">
- <i class="el-icon-upload2"></i>
- </button>
- </div>
- <!-- <button @click="playAudio" class="btn">播放音频</button> -->
- <button @click="sendMessage" class="btn">发送</button>
- </div>
- </div>
- </div>
- <!-- </div> -->
- </div>
- <LoginModal
- :visible.sync="showLoginModal"
- @close="showLoginModal = false"
- @login-success="handleLoginSuccess"
- />
- </div>
- </template>
- <script>
- import ChatBox from "@/components/webAi/index.vue";
- import LoginModal from "@/components/LoginModal";
- import MarkdownIt from "markdown-it";
- import { pcInnerAi, getMinioURl } from "@/api/api";
- import { modelList, get_default } from "@/api/knowledge";
- import axios from "axios";
- const md = new MarkdownIt();
- export default {
- components: {
- ChatBox,
- LoginModal,
- },
- data() {
- return {
- showLoginModal: false,
- isThinking: false,
- thinkingDots: "",
- messages: [
- {
- user: "bot",
- messageType: "TEXT",
- message: "欢迎使用智能AI助理",
- html: "",
- time: "",
- done: true,
- },
- ],
- generating: false,
- userInput: "",
- websocket: null,
- wsUrl:
- "http://58.246.234.210:7860/api/v1/run/3ef7369e-b617-40d2-9e2e-54f3ee76ed2e?stream=false",
- tweaks: {},
- isLoggedIn: false, // 添加登录状态标志
- idArray: [],
- minioUrls: [], // 新增:用于存储 Minio URL
- AImodel: "1", //模型
- AIknowledgeBase: "1", //知识库
- AIFile: "1", //文件
- AIform: {
- modelLibrary: "ollama",
- modelType: "",
- modelName: "",
- knowledgeBase: "",
- documentDirectory: "",
- document: "",
- },
- currentChat_id: "",
- phone: "",
- uploadFiles: [], // 存储上传的文件
- documents: [], // 新增:专门存储文档类型文件的数组
- currentAudio: null, // 添加当前播放的音频实例
- };
- },
- mounted() {
- this.checkAIData();
- if (this.$route.name == "ai") {
- /* 外部知识库 */
- /* 外部知识库 */
- const AIDataString = localStorage.getItem("AIData");
- if (!AIDataString) {
- this.showLoginModal = true;
- return;
- }
- try {
- const AIData = JSON.parse(AIDataString);
- if (AIData && AIData.data) {
- console.log(AIData);
- this.wsUrl = AIData.data.url || "";
- this.phone = AIData.data.phone || "";
- this.tweaks = AIData.data.tweaks || {};
- } else {
- console.error("Invalid AIData structure");
- this.showLoginModal = true;
- }
- } catch (error) {
- console.error("Error parsing AIData:", error);
- this.showLoginModal = true;
- }
- } else {
- /* 内部知识库 */
- pcInnerAi().then((res) => {
- if (res.status !== 200) return;
- this.wsUrl = res.data.url;
- this.tweaks = res.data.tweaks;
- });
- }
- this.init();
- },
- methods: {
- /* 播放音频 */
- playAudio(audioBase64) {
- // 如果有正在播放的音频,先停止它
- if (this.currentAudio) {
- this.currentAudio.pause();
- this.currentAudio = null;
- }
- // 创建新的音频实例并播放
- const audio = new Audio(audioBase64);
- this.currentAudio = audio;
-
- audio.play().catch((error) => {
- console.error("Error playing audio:", error);
- this.currentAudio = null;
- });
- },
- async handleFileUpload(event) {
- const files = Array.from(event.target.files);
- for (const file of files) {
- try {
- const formData = new FormData();
- formData.append("file", file);
- const response = await axios.post(
- `${process.env.VUE_APP_BASE_AI_API}/upload/file`, //process.env.VUE_APP_BASE_API
- formData,
- {
- headers: {
- "Content-Type": "multipart/form-data",
- },
- }
- );
- const fileUrl = response.data.data.fileUrl;
- const fileInfo = {
- name: file.name,
- type: file.type,
- preview: fileUrl,
- url: fileUrl,
- };
- // 根据文件类型分别存储
- if (file.type.startsWith("image/")) {
- this.uploadFiles.push(fileInfo);
- } else {
- // 文档类型:pdf, doc, docx, txt 等
- this.documents.push(fileInfo);
- }
- } catch (error) {
- console.error("文件上传失败:", error);
- this.$message.error(`文件 ${file.name} 上传失败`);
- }
- }
- event.target.value = "";
- },
- removeFile(index) {
- this.uploadFiles.splice(index, 1);
- },
- removeDocument(index) {
- this.documents.splice(index, 1);
- },
- /* */
- checkAIData() {
- const aiData = localStorage.getItem("AIData");
- this.showLoginModal = !aiData;
- },
- handleLoginSuccess() {
- this.showLoginModal = false;
- // 处理登录成功后的逻辑,例如刷新数据或更新状态
- },
- /* 聊天记录 */
- selectChat(index) {
- this.currentChatIndex = index;
- // Load the selected chat messages
- // This is where you would typically load the messages for the selected chat
- },
- newChat() {
- this.chatHistory.push({
- title: `聊天 ${this.chatHistory.length + 1}`,
- preview: "新的聊天...",
- });
- this.currentChatIndex = this.chatHistory.length - 1;
- // Clear the current messages and start a new chat
- this.messages = [];
- },
- handleLinkClick(event) {
- if (event.target.tagName.toLowerCase() === "a") {
- event.preventDefault();
- // 获取文件名
- const fullFileName = event.target.textContent.trim();
- // 分离文件名和后缀
- const lastDotIndex = fullFileName.lastIndexOf(".");
- const fileName =
- lastDotIndex > -1
- ? fullFileName.slice(0, lastDotIndex)
- : fullFileName;
- const fileExtension =
- lastDotIndex > -1 ? fullFileName.slice(lastDotIndex + 1) : "";
- // 编码文件名和后缀以便于URL传参
- const encodedFileName = encodeURIComponent(fileName);
- const encodedFileExtension = encodeURIComponent(fileExtension);
- // 构建新的URL,包含文件名和后缀作为查询参数
- const newUrl = `#/preview?fileName=${encodedFileName}&fileExtension=${encodedFileExtension}`;
- try {
- localStorage.setItem("href", event.target.href);
- } catch (e) {
- console.warn("无法存储 URL 到 localStorage:", e);
- }
- // 使用新的URL打开窗口
- const newWindow = window.open(newUrl, "_blank");
- if (newWindow) {
- newWindow.focus();
- } else {
- alert("弹出窗口被阻止,请允许此网站的弹出窗口。");
- }
- }
- },
- handleLoginSuccess() {
- this.showLoginModal = false;
- this.isLoggedIn = true;
- // 登录成功后,可以继续发送消息
- this.sendMessage();
- },
- getUuid() {
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
- /[xy]/g,
- function (c) {
- var r = (Math.random() * 16) | 0,
- v = c == "x" ? r : (r & 0x3) | 0x8;
- return v.toString(16);
- }
- );
- },
- connectWebSocket() {
- const wsUrl =
- "http://58.246.234.210:7860/api/v1/run/2f1faff2-99df-4821-87d6-43d693084c4b?stream=false";
- // 这里做一个简单的鉴权,只有符合条件的鉴权才能握手成功
- // 移除这里的fetch调用,改用WebSocket
- this.websocket = new WebSocket(wsUrl);
- /* this.websocket.onerror = (event) => {
- console.error("WebSocket 连接错误:", event);
- };
- */
- this.websocket.onclose = (event) => {
- console.log("WebSocket 连接关闭:", event);
- };
- this.websocket.onopen = (event) => {
- console.log("WebSocket 连接已建立:", event);
- };
- this.websocket.onmessage = (event) => {
- // 解收到的消息
- const result = JSON.parse(event.data);
- // 检查消息是否完结
- if (result.done) {
- this.messages[this.messages.length - 1].done = true;
- return;
- }
- if (this.messages[this.messages.length - 1].done) {
- // 添加的消息
- this.messages.push({
- time: Date.now(),
- message: result.content,
- messageType: "TEXT",
- user: "bot",
- done: false,
- });
- } else {
- // 更新最后一条消息
- let lastMessage = this.messages[this.messages.length - 1];
- lastMessage.message += result.content;
- this.messages[this.messages.length - 1] = lastMessage;
- }
- };
- },
- async sendMessage() {
- if (this.$route.name == "ai") {
- if (!localStorage.getItem("AIData")) {
- this.showLoginModal = true;
- return;
- }
- }
- const chatId = this.getUuid();
- const wsUrl = `${process.env.VUE_APP_BASE_AI_API}/chatbot/multimodal`; //process.env.VUE_APP_BASE_AI_API /chatbot/multimodal /api/chat/chatgpt
- let message = this.userInput.trim();
- const imageUrls = this.uploadFiles
- .filter((file) => file.type.startsWith("image/"))
- .map((file) => file.url);
- // 获取文档 URL
- const documentUrls = this.documents.map((doc) => doc.url);
- if (message || imageUrls.length > 0 || documentUrls.length > 0) {
- message = message.replace(/(\r\n|\r|\n)/g, " \n");
- this.messages.push({
- time: Date.now(),
- message: message,
- messageType: "TEXT",
- user: "user",
- imageUrls: imageUrls, // 添加图片URLs
- documentUrls: documentUrls, // 添加文档URLs
- done: true,
- });
- this.userInput = "";
- this.uploadFiles = [];
- this.documents = [];
- const botMessage = {
- user: "bot",
- messageType: "TEXT",
- message: "",
- html: "",
- time: Date.now(),
- done: false,
- };
- this.messages.push(botMessage);
- const thinkingController = new AbortController();
- const thinkingPromise = this.simulateThinking(
- botMessage,
- thinkingController.signal
- );
- try {
- const response = await axios({
- method: "post",
- url: wsUrl,
- data: {
- message: message,
- chat_config_id: "2",
- user_id: this.phone,
- session_id: this.session_id || "",
- source: "pc",
- image_urls: imageUrls,
- documents: documentUrls, // 添加文档 URLs
- //history_limit: 10
- },
- });
- this.session_id = response.data.data.session_id;
- if (response.data.code !== 200) {
- throw new Error(
- `HTTP error! status: ${response.status}, message: ${response.data.message}`
- );
- }
- thinkingController.abort();
- await thinkingPromise;
- const additionalInput = response.data.data.document_ids;
- this.idArray = additionalInput;
- const idObject = { document_ids: this.idArray };
- console.log("ID Object:", idObject);
- await this.getMinioUrls(idObject);
- /* if (this.$route.name !== "ai") {} */
- // 如果有音频,播放音频
- if (
- response.data.data.audio_info &&
- response.data.data.audio_info.audio
- ) {
- this.playAudio(response.data.data.audio_info.audio);
- }
- this.handleResponse(response.data, botMessage);
- } catch (error) {
- console.error("Error sending message:", error);
- thinkingController.abort();
- await thinkingPromise;
- botMessage.done = true;
- botMessage.message = "抱歉,发生了错误,请稍后再试。";
- if (error.response) {
- console.error("Response data:", error.response.data);
- console.error("Response status:", error.response.status);
- } else if (error.request) {
- console.error("No response received:", error.request);
- } else {
- console.error("Error message:", error.message);
- }
- }
- }
- },
- async getMinioUrls(idObject) {
- this.minioUrls = []; // 清空之前的 URL
- try {
- console.log("Sending to backend:", JSON.stringify(idObject));
- const response = await axios.post(
- `${process.env.VUE_APP_BASE_AI_API}/milvus/getMinioURlbyDocid`,
- idObject,
- {
- headers: {
- "Content-Type": "application/json",
- },
- }
- );
- if (response.status === 200 && response.data) {
- this.minioUrls = response.data.data; // 假设返回的是 URL 数组
- console.log("Received Minio URLs:", this.minioUrls);
- } else {
- console.error("Failed to get Minio URLs");
- }
- } catch (error) {
- console.error("Error fetching Minio URLs:", error);
- if (error.response) {
- console.error("Response data:", error.response.data);
- console.error("Response status:", error.response.status);
- console.error("Response headers:", error.response.headers);
- } else if (error.request) {
- console.error("No response received:", error.request);
- } else {
- console.error("Error message:", error.message);
- }
- }
- },
- async simulateThinking(message, signal) {
- this.isThinking = true;
- const dotInterval = 200; // 每 200ms 更新次点
- return new Promise((resolve) => {
- const updateDots = () => {
- if (signal.aborted) {
- clearInterval(intervalId);
- this.isThinking = false;
- this.thinkingDots = "";
- message.message = "";
- resolve();
- return;
- }
- this.thinkingDots += ".";
- if (this.thinkingDots.length > 3) {
- this.thinkingDots = "";
- }
- message.message = "思考中" + this.thinkingDots;
- };
- const intervalId = setInterval(updateDots, dotInterval);
- signal.addEventListener("abort", () => {
- clearInterval(intervalId);
- this.isThinking = false;
- this.thinkingDots = "";
- message.message = "";
- resolve();
- });
- });
- },
- async handleResponse(value, existingMessage) {
- const data = value.data.answer;
- existingMessage.messageType = data;
- /* existingMessage.time = data.timestamp; this.$route.name !== "ai" &&*/
- existingMessage.message = "";
- let mainText = data;
- let sourceText = "";
- if (this.minioUrls && this.minioUrls.length > 0) {
- sourceText =
- "\n\n<div class='source-section'><h3>相关资料来源:</h3><ol>";
- this.minioUrls.forEach((url, index) => {
- sourceText += `<li><a href="${url.url}" target="_blank" class="source-link">${url.object_name}</a></li>`;
- });
- sourceText += "</ol></div>";
- }
- console.log(mainText);
- // 先进行主要内容的打字效果
- await this.typeMessage(existingMessage, mainText);
- // 直接添加资料来源部分
- if (sourceText) {
- existingMessage.message += sourceText;
- existingMessage.html = this.renderMarkdown(existingMessage.message);
- }
- },
- typeMessage(message, fullText) {
- return new Promise((resolve) => {
- const delay = 30;
- let i = 0;
- const typeChar = () => {
- if (i < fullText.length) {
- message.message += fullText[i];
- i++;
- setTimeout(typeChar, delay);
- } else {
- message.done = true;
- message.html = this.renderMarkdown(message.message);
- resolve();
- }
- };
- typeChar();
- });
- },
- renderMarkdown(rawMarkdown) {
- const md = new MarkdownIt({
- html: true,
- breaks: true,
- linkify: true,
- });
- // 自定义链接渲染规则
- md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
- const token = tokens[idx];
- const hrefIndex = token.attrIndex("href");
- const href = token.attrs[hrefIndex][1];
- return `<span class="custom-link" data-href="${href}">`;
- };
- md.renderer.rules.link_close = () => "</span>";
- const renderedHtml = md.render(rawMarkdown);
- // 使用 setTimeout 来确保 DOM 更新后再添加事件监听器
- setTimeout(() => {
- const links = document.querySelectorAll(".custom-link");
- links.forEach((link) => {
- link.addEventListener("click", (e) => {
- e.preventDefault();
- const href = link.getAttribute("data-href");
- window.open(href, "_blank");
- });
- });
- }, 0);
- return renderedHtml;
- },
- handleKeydown(event) {
- // Check if 'Enter' is pressed without the 'Alt' key
- if (event.key === "Enter" && !(event.shiftKey || event.altKey)) {
- event.preventDefault(); // Prevent the default action to avoid line break in textarea
- this.sendMessage();
- } else if (event.key === "Enter" && event.altKey) {
- // Allow 'Alt + Enter' to insert a newline
- const cursorPosition = event.target.selectionStart;
- const textBeforeCursor = this.userInput.slice(0, cursorPosition);
- const textAfterCursor = this.userInput.slice(cursorPosition);
- // Insert the newline character at the cursor position
- this.userInput = textBeforeCursor + "\n" + textAfterCursor;
- // Move the cursor to the right after the inserted newline
- this.$nextTick(() => {
- event.target.selectionStart = cursorPosition + 1;
- event.target.selectionEnd = cursorPosition + 1;
- });
- }
- },
- beforeDestroy() {
- if (this.websocket) {
- this.websocket.close();
- }
- },
- /* 获取列表 */
- init() {
- /* modelList().then((res) => {
- console.log(res);
- }); */
- get_default().then((res) => {
- if (res.status !== 200) return;
- this.currentChat_id = res.data.id;
- });
- },
- getDocumentName(url) {
- // 从URL中提取文件名
- const parts = url.split('/');
- const fileName = parts[parts.length - 1];
- // 解码URL编码的文件名
- return decodeURIComponent(fileName);
- }
- },
- updated() {
- const messagesContainer = this.$el.querySelector(".messages");
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
- },
- };
- </script>
- <style scoped>
- .chat-container {
- position: relative;
- display: flex;
- flex-direction: column;
- margin-bottom: 150px;
- /* 或者按照实际需要调整高度 */
- }
- .chat-container .header {
- width: 1200px;
- margin: 10px auto 0;
- border: 1px solid #d7d7d7;
- border-radius: 10px;
- }
- .form-container {
- max-width: 1200px; /* 或者其他适合你布局的宽度 */
- margin: 0 auto; /* 这会使容器水平居中 */
- padding: 0 15px; /* 添加一些左右内边距 */
- }
- .el-form-item {
- margin-bottom: 15px;
- }
- .el-select {
- width: 100%;
- }
- .generate-button {
- margin-top: 10px; /* 在小屏幕上给按钮一些上边距 */
- margin-right: 25px;
- }
- @media (max-width: 768px) {
- .selection-summary {
- flex-direction: column;
- }
- .generate-button {
- margin-left: 0;
- margin-top: 15px;
- align-self: flex-end; /* 在小屏幕上将按钮右对齐 */
- }
- }
- .selection-summary {
- background-color: #f2f2f2;
- border-radius: 4px;
- padding: 15px;
- margin: 15px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-right: 85px;
- border-radius: 10px;
- }
- .summary-content {
- flex-grow: 1;
- }
- .summary-label {
- font-weight: bold;
- color: #7f7f7f;
- margin-right: 10px;
- }
- .summary-text {
- color: #606266;
- margin: 0;
- margin-top: 3px;
- font-size: 14px;
- }
- .title {
- width: 1200px;
- margin: 0 auto 15px;
- display: flex;
- align-items: center;
- }
- .title_info {
- cursor: pointer;
- color: #1296db;
- font-size: 14px;
- margin-left: 10px;
- }
- /* .chat-container .header ::v-deep .el-select .el-input--medium .el-input__inner{
- border: none;
- }
- .chat-container .header ::v-deep .el-select .el-input--medium .el-input__suffix .el-input__suffix-inner{
- display: none;
- } */
- .chat-container .content {
- display: flex;
- justify-content: flex-start;
- }
- .chat-container .content .left {
- margin-left: 10px;
- width: 330px;
- height: 87vh;
- }
- .chat-item {
- border-radius: 10px;
- padding: 10px;
- border-bottom: 1px solid #e8edf2;
- background-color: #f5f5fa;
- margin-bottom: 10px;
- }
- .chat-header {
- display: flex;
- align-items: center;
- margin-bottom: 5px;
- }
- .avatar {
- width: 40px;
- height: 40px;
- border-radius: 50%;
- margin-right: 10px;
- }
- .user-info {
- flex-grow: 1;
- }
- .user-info .info_header {
- display: flex;
- align-items: flex-end;
- }
- .username {
- font-weight: bold;
- }
- .timestamp {
- margin-left: 13px;
- font-size: 0.8em;
- color: #888;
- }
- .more-options {
- cursor: pointer;
- }
- .chat-message {
- font-weight: bold;
- margin-top: 8px;
- margin-bottom: 10px;
- }
- .chat-preview {
- font-size: 0.9em;
- color: #666;
- }
- /* Adjust existing styles */
- .left {
- width: 300px; /* Increased width to accommodate the new layout */
- }
- .chat-history {
- width: 330px;
- height: 100%;
- overflow-y: auto;
- }
- .messages {
- width: 1180px; /* 设置消息列表宽度为屏幕宽度的 80% */
- max-width: 90%; /* 限制最大宽度,避免超出视口 */
- margin: 0 auto; /* 上下保持原有的 margin,左右自动调整以居中 */
- overflow-y: auto; /* 如果消息太多允许滚动 */
- height: calc(100vh - 230px); /* 高度需要根据输入框和其他元素的高度调整 */
- padding: 10px; /* 或你希望的内边距大小 */
- margin-bottom: 110px;
- }
- /* 针对手机端的媒体查询 */
- @media screen and (max-width: 768px) {
- .messages {
- width: 100%;
- max-width: 100%;
- margin: 0;
- margin-bottom: 120px;
- }
- }
- .user-message {
- display: flex;
- flex-direction: column !important;
- align-items: flex-end !important;
- }
- .user-message .message-content {
- order: 1;
- text-align: right;
- }
- .user-message .avatar {
- order: 2;
- margin-left: 10px;
- margin-right: 0;
- }
- .message-text {
- margin-top: 5px;
- }
- .messages li {
- display: flex;
- flex-direction: column;
- align-items: flex-start; /* 这将确保所有子元素对齐到容器的顶部 */
- /* margin-bottom: 10px; */
- }
- .avatar {
- width: 40px; /* 头像的尺寸 */
- height: 40px;
- border-radius: 50%; /* 圆形头像 */
- /*background-color: #e9e0e0; 暂时用灰色代替,你可以用图片替换 */
- align-self: start; /* 这将覆盖 flex 容器的 align-items 并将头像对齐到顶部 */
- margin-right: 10px; /* 按需调整以在头像和消息之间提供空间 */
- }
- .user-avatar {
- background-image: url("../../components/webAi/assets/用户.png");
- background-position: center;
- background-repeat: no-repeat;
- background-size: cover;
- /* background-color: #93939d; */ /* 用户头像颜色 */
- }
- .bot-avatar {
- background-image: url("../../components/webAi/assets/机器人.png");
- background-position: center;
- background-repeat: no-repeat;
- background-size: cover;
- /* background-color: green; */ /* AI头像颜色 */
- }
- /* 针对手机端的媒体查询 */
- @media screen and (max-width: 768px) {
- .user-avatar,
- .bot-avatar {
- width: 30px; /* 或其他更小的尺寸 */
- height: 30px;
- }
- }
- .message_name {
- display: flex;
- align-items: center;
- padding-top: 5px;
- padding-bottom: 5px;
- }
- .name {
- margin-left: 8px;
- /* margin-bottom: 5px; */
- }
- .message-content {
- background-color: #f0f0f0; /* 消息背景 */
- padding-left: 20px; /* 上下左右的内边距 */
- padding-right: 20px; /* 上下左右的内边距 */
- padding-top: 10px; /* 上下左右的内边距 */
- padding-bottom: 0px; /* 上下左的内边距 */
- max-width: calc(90% - 10px); /* 计算头像和间距 */
- border-radius: 7px; /* 边框圆角 */
- /* margin-bottom: 10px; 和下一条消息之间的间距 */
- /* 消息内容样式 */
- display: flex;
- flex-direction: column; /* 排列文本 */
- margin-top: 0;
- }
- .message-text {
- margin: 0;
- white-space: normal; /* 确保文本会换行 */
- word-wrap: break-word; /* 长单词或 URL 会被断开适应容器宽度 */
- overflow: auto;
- font-size: 14px;
- }
- .message-text ::v-deep p {
- font-size: 14px !important;
- margin: 5px 0 10px;
- padding: 0;
- font-size: inherit;
- line-height: 22px;
- font-weight: inherit;
- }
- .message-text ::v-deep ol {
- padding-bottom: 10px;
- }
- .message-text ::v-deep .source-section h3 {
- margin: 20px 0 3px;
- color: #333;
- font-size: 16px;
- }
- .message-text ::v-deep ol li {
- padding-top: 3px;
- }
- .message-text ::v-deep ol li a {
- font-size: 14px;
- color: #1296db;
- }
- .input-container {
- position: absolute; /* 改为绝对定位 */
- bottom: -115px; /* 距离底部的距离 */
- left: 50%;
- transform: translateX(-50%);
- width: 90%; /* 设置宽度为弹窗宽度的90% */
- max-width: 1170px; /* 设置最大宽度 */
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- flex-direction: column;
- padding: 15px;
- background: white;
- border-radius: 10px;
- box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
- }
- textarea {
- font-size: 16px;
- width: 100%; /* 可以根据需要调整 */
- padding: 10px;
- border: 1px solid #ccc;
- border-radius: 4px;
- border: none;
- outline: none;
- resize: none;
- }
- .input-container textarea {
- width: 100%; /* 输入框占据所有可用空间 */
- padding: 10px;
- margin-right: 10px; /* 与按钮的间距 */
- box-sizing: border-box;
- }
- .input-container button {
- min-width: 80px; /* 例如,按钮的最小宽度为 100px */
- white-space: nowrap; /* 防止文字折行 */
- padding: 10px 20px;
- cursor: pointer;
- }
- .sender-name {
- font-weight: bold;
- color: #333;
- margin-left: -5px;
- }
- .btn {
- font-size: 16px;
- color: #ffffff;
- border-radius: 5px;
- border: none;
- background-color: #3cbade;
- padding: 10px 20px; /* 添加内边距 */
- width: auto; /* 让宽度自适应内容 */
- }
- /* 针对手机端的媒体查询 */
- @media screen and (max-width: 768px) {
- .btn {
- font-size: 14px; /* 稍微减小字体大小 */
- padding: 8px 16px; /* 减小内边距 */
- max-width: 80px; /* 限制最大宽度 */
- margin: 0 auto; /* 居中显示 */
- }
- }
- /* 新输入框 */
- .upload-preview {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- padding: 10px 0;
- max-height: 120px;
- overflow-y: auto;
- }
- .preview-item {
- position: relative;
- border-radius: 4px;
- overflow: hidden;
- }
- .image-preview {
- width: 80px;
- height: 40px;
- }
- .image-preview img{
- width: 100%;
- }
- .document-preview {
- width: 120px;
- height: 40px;
- background: #f5f5f5;
- display: flex;
- align-items: center;
- padding: 0 10px;
- }
- .doc-name {
- font-size: 12px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- max-width: 90px;
- }
- .remove-file {
- position: absolute;
- top: 4px;
- right: 4px;
- background: rgba(0, 0, 0, 0.5);
- color: white;
- border-radius: 50%;
- padding: 4px;
- cursor: pointer;
- font-size: 12px;
- z-index: 1;
- }
- .input-area {
- width: 100%;
- }
- .toolbar {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 10px;
- }
- .left-tools {
- display: flex;
- gap: 10px;
- }
- .tool-btn {
- background: none;
- border: none;
- cursor: pointer;
- padding: 5px;
- color: #666;
- font-size: 20px;
- }
- .tool-btn:hover {
- color: #3cbade;
- }
- .document-item {
- width: 120px; /* 文档预览项可以设置得更宽一些 */
- height: 60px;
- background-color: #f5f5f5;
- }
- .document-item .file-info {
- display: flex;
- align-items: center;
- gap: 5px;
- }
- .document-item .file-info i {
- font-size: 20px;
- color: #666;
- }
- .document-item .file-info span {
- font-size: 12px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .message-images {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-bottom: 10px;
- }
- .uploaded-image {
- max-width: 200px;
- max-height: 200px;
- border-radius: 4px;
- object-fit: contain;
- }
- .message-documents {
- display: flex;
- flex-direction: column;
- gap: 8px;
- margin-bottom: 10px;
- }
- .document-preview-item {
- display: flex;
- align-items: center;
- background: #f5f5f5;
- padding: 10px;
- border-radius: 4px;
- gap: 8px;
- }
- .document-preview-item i {
- font-size: 20px;
- color: #666;
- }
- .document-name {
- flex: 1;
- font-size: 14px;
- color: #333;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .document-link {
- color: #3cbade;
- text-decoration: none;
- font-size: 14px;
- padding: 4px 8px;
- border-radius: 4px;
- }
- .document-link:hover {
- background: rgba(60, 186, 222, 0.1);
- }
- </style>
|