|
@@ -0,0 +1,1551 @@
|
|
|
+<template>
|
|
|
+ <div class="layout">
|
|
|
+ <div class="menu">
|
|
|
+ <!-- Logo -->
|
|
|
+ <div class="logo">
|
|
|
+ <img
|
|
|
+ src="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*eco6RrQhxbMAAAAAAAAAAAAADgCCAQ/original"
|
|
|
+ :draggable="false"
|
|
|
+ alt="logo"
|
|
|
+ class="logo-img"
|
|
|
+ />
|
|
|
+ <span>Ant Design X</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- New Conversation 按钮 -->
|
|
|
+ <button class="new-conversation-btn" @click="addNewConversation">
|
|
|
+ <!-- <PlusOutlined /> -->
|
|
|
+ <img class="tab-icon" src="./svg/plus.svg" alt="" />
|
|
|
+ <span>New Conversation</span>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <!-- 会话列表 -->
|
|
|
+ <div class="conversation-list">
|
|
|
+ <div
|
|
|
+ v-for="conversation in conversations"
|
|
|
+ :key="conversation.id"
|
|
|
+ :class="[
|
|
|
+ 'conversation-item',
|
|
|
+ { active: currentConversationId === conversation.id },
|
|
|
+ ]"
|
|
|
+ @click="switchConversation(conversation.id)"
|
|
|
+ >
|
|
|
+ {{ conversation.title }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="chat">
|
|
|
+ <!-- 欢迎消息 -->
|
|
|
+ <div v-if="messages.length === 0" class="welcome-section">
|
|
|
+ <div class="welcome-header">
|
|
|
+ <img class="avatar" src="path-to-your-avatar" alt="Ant Design X" />
|
|
|
+ <div class="welcome-info">
|
|
|
+ <h2>Hello, I'm Ant Design X</h2>
|
|
|
+ <p>
|
|
|
+ Base on Ant Design, AGI product interface solution, create a
|
|
|
+ better intelligent vision~
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <div class="action-buttons">
|
|
|
+ <button class="icon-btn"><!-- <ShareAltOutlined /> --></button>
|
|
|
+ <button class="icon-btn"><!-- <EllipsisOutlined /> --></button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Do you want 部分 -->
|
|
|
+ <div class="suggestion-section">
|
|
|
+ <h3>Do you want?</h3>
|
|
|
+
|
|
|
+ <div class="cards-container">
|
|
|
+ <!-- Hot Topics 卡片 -->
|
|
|
+ <div class="suggestion-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <img
|
|
|
+ src="./svg/hot-for-ux.svg"
|
|
|
+ alt="hot topics"
|
|
|
+ class="tab-icon"
|
|
|
+ />
|
|
|
+ <h4>Hot Topics</h4>
|
|
|
+ </div>
|
|
|
+ <p class="card-subtitle">What are you interested in?</p>
|
|
|
+ <div class="suggestion-items">
|
|
|
+ <div class="suggestion-item">What's new in X?</div>
|
|
|
+ <div class="suggestion-item">What's AGI?</div>
|
|
|
+ <div class="suggestion-item">Where is the doc?</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Design Guide 卡片 -->
|
|
|
+ <div class="suggestion-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <img src="./svg/read.svg" alt="hot topics" class="tab-icon" />
|
|
|
+ <h4>Design Guide</h4>
|
|
|
+ </div>
|
|
|
+ <p class="card-subtitle">How to design a good product?</p>
|
|
|
+ <div class="suggestion-items">
|
|
|
+ <div class="suggestion-item">
|
|
|
+ <!-- <HeartOutlined /> -->
|
|
|
+ Know the well
|
|
|
+ </div>
|
|
|
+ <div class="suggestion-item">
|
|
|
+ <!-- <SmileOutlined /> -->
|
|
|
+ Set the AI role
|
|
|
+ </div>
|
|
|
+ <div class="suggestion-item">
|
|
|
+ <!-- <CommentOutlined /> -->
|
|
|
+ Express the feeling
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 修改消息列表部分 -->
|
|
|
+ <div v-else class="message-list">
|
|
|
+ <div
|
|
|
+ v-for="message in messages"
|
|
|
+ :key="message.id"
|
|
|
+ :class="[
|
|
|
+ 'message-item',
|
|
|
+ message.role === 'user' ? 'user' : 'assistant',
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ <div class="message-bubble">
|
|
|
+ <div class="message-content">
|
|
|
+ {{ message.content }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 输入框 -->
|
|
|
+ <div class="input-container">
|
|
|
+ <!-- 标签栏 -->
|
|
|
+ <div class="tabs-wrapper">
|
|
|
+ <div class="tab-item active">
|
|
|
+ <img src="./svg/hot-for-ux.svg" alt="hot topics" class="tab-icon" />
|
|
|
+ <span>Hot Topics</span>
|
|
|
+ </div>
|
|
|
+ <div class="tab-item">
|
|
|
+ <img src="./svg/read.svg" alt="hot topics" class="tab-icon" />
|
|
|
+ <span>Design Guide</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 添加附件面板 -->
|
|
|
+ <div v-if="showAttachments" class="attachments-panel">
|
|
|
+ <div class="panel-content">
|
|
|
+ <div class="attachments-header">
|
|
|
+ <span>Attachments</span>
|
|
|
+ <button class="close-btn" @click="toggleAttachments">
|
|
|
+ <img src="./svg/close.svg" alt="" />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-if="attachedFiles.length === 0"
|
|
|
+ style="display: flex; justify-content: center"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="upload-area"
|
|
|
+ :class="{ 'has-files': attachedFiles.length > 0 }"
|
|
|
+ @click="triggerFileInput"
|
|
|
+ @drop.prevent="handleFileDrop"
|
|
|
+ @dragover.prevent
|
|
|
+ @dragenter.prevent
|
|
|
+ >
|
|
|
+ <template>
|
|
|
+ <img
|
|
|
+ src="./svg/upload.svg"
|
|
|
+ alt="upload"
|
|
|
+ class="upload-icon"
|
|
|
+ />
|
|
|
+ <div class="upload-text">Upload files</div>
|
|
|
+ <div class="upload-hint">
|
|
|
+ Click or drag files to this area to upload
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <input
|
|
|
+ type="file"
|
|
|
+ ref="fileInput"
|
|
|
+ style="display: none"
|
|
|
+ @change="handleFileUpload"
|
|
|
+ multiple
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 上传内容列表 -->
|
|
|
+ <div v-if="attachedFiles.length !== 0">
|
|
|
+ <div class="file-list">
|
|
|
+ <div
|
|
|
+ v-for="file in attachedFiles"
|
|
|
+ :key="file.name"
|
|
|
+ class="file-item"
|
|
|
+ :class="{ 'is-image': getFileType(file) }"
|
|
|
+ >
|
|
|
+ <div class="file-wrapper">
|
|
|
+ <img
|
|
|
+ :src="require(`${getFileIcon(file)}`)"
|
|
|
+ alt="file"
|
|
|
+ :class="getFileType(file) ? 'file-icon' : ''"
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ class="remove-btn"
|
|
|
+ v-if="getFileType(file)"
|
|
|
+ @click.stop="removeFile(file)"
|
|
|
+ >
|
|
|
+ ×
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="file-info" v-if="!getFileType(file)">
|
|
|
+ <span class="file-name">{{ file.name }}</span>
|
|
|
+ <span class="file-size">{{
|
|
|
+ formatFileSize(file.size)
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ <button
|
|
|
+ class="remove-btn"
|
|
|
+ v-if="!getFileType(file)"
|
|
|
+ @click.stop="removeFile(file)"
|
|
|
+ >
|
|
|
+ ×
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <button class="add-more-btn" @click.stop="triggerFileInput">
|
|
|
+ <img src="./svg/plus.svg" alt="add" />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <input
|
|
|
+ type="file"
|
|
|
+ ref="fileInput"
|
|
|
+ style="display: none"
|
|
|
+ @change="handleFileUpload"
|
|
|
+ multiple
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <!-- <div v-if="attachedFiles.length" class="attached-files">
|
|
|
+ <div
|
|
|
+ v-for="file in attachedFiles"
|
|
|
+ :key="file.name"
|
|
|
+ class="file-item"
|
|
|
+ >
|
|
|
+ <FileOutlined />
|
|
|
+ <span>{{ file.name }}</span>
|
|
|
+ <button class="remove-btn" @click="removeFile(file)">
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div> -->
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 输入框部分 -->
|
|
|
+ <div class="input-wrapper">
|
|
|
+ <button class="attachment-btn" @click="toggleAttachments">
|
|
|
+ <img src="./svg/attachment.svg" alt="attachment" class="tab-icon" />
|
|
|
+ </button>
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ class="message-input"
|
|
|
+ v-model="inputContent"
|
|
|
+ @keyup.enter="sendMessage"
|
|
|
+ placeholder="Type a message..."
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ class="send-btn"
|
|
|
+ @click="sendMessage"
|
|
|
+ :disabled="!inputContent && attachedFiles.length === 0"
|
|
|
+ >
|
|
|
+ <img src="./svg/up.svg" alt="send" />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="right_menu" :class="{ collapsed: isRightMenuCollapsed }">
|
|
|
+ <div class="collapse-button" @click="toggleRightMenu">
|
|
|
+ <img
|
|
|
+ :src="
|
|
|
+ isRightMenuCollapsed
|
|
|
+ ? require('./svg/left.svg')
|
|
|
+ : require('./svg/right.svg')
|
|
|
+ "
|
|
|
+ alt="toggle"
|
|
|
+ class="collapse-icon"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="right-menu-content">
|
|
|
+ <!-- 这里添加右侧菜单的内容 -->
|
|
|
+ <h3>Right Menu</h3>
|
|
|
+ <p>Add your content here</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import {
|
|
|
+ PlusOutlined,
|
|
|
+ FireOutlined,
|
|
|
+ ReadOutlined,
|
|
|
+ HeartOutlined,
|
|
|
+ SmileOutlined,
|
|
|
+ CommentOutlined,
|
|
|
+ CloudUploadOutlined,
|
|
|
+ PaperClipOutlined,
|
|
|
+ ShareAltOutlined,
|
|
|
+ EllipsisOutlined,
|
|
|
+ CloseOutlined,
|
|
|
+ FileOutlined,
|
|
|
+ ArrowUpOutlined,
|
|
|
+ UploadOutlined,
|
|
|
+} from "@ant-design/icons-vue";
|
|
|
+import Welcome from "./components/Welcome.vue";
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "Independent",
|
|
|
+ components: {
|
|
|
+ PlusOutlined,
|
|
|
+ FireOutlined,
|
|
|
+ ReadOutlined,
|
|
|
+ HeartOutlined,
|
|
|
+ SmileOutlined,
|
|
|
+ CommentOutlined,
|
|
|
+ CloudUploadOutlined,
|
|
|
+ PaperClipOutlined,
|
|
|
+ ShareAltOutlined,
|
|
|
+ EllipsisOutlined,
|
|
|
+ CloseOutlined,
|
|
|
+ FileOutlined,
|
|
|
+ ArrowUpOutlined,
|
|
|
+ UploadOutlined,
|
|
|
+ Welcome,
|
|
|
+ },
|
|
|
+
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ headerOpen: false,
|
|
|
+ content: "",
|
|
|
+ activeKey: "0",
|
|
|
+ attachedFiles: [],
|
|
|
+ isRequesting: false,
|
|
|
+ messages: [],
|
|
|
+ inputContent: "",
|
|
|
+ isLoading: false,
|
|
|
+ quickPrompts: [
|
|
|
+ {
|
|
|
+ key: "1",
|
|
|
+ icon: "fas fa-fire",
|
|
|
+ label: "Hot Topics",
|
|
|
+ description: "What are you interested in?",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "2",
|
|
|
+ icon: "fas fa-book",
|
|
|
+ label: "Design Guide",
|
|
|
+ description: "How to design a good product?",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ roles: {
|
|
|
+ ai: {
|
|
|
+ placement: "start",
|
|
|
+ typing: { step: 5, interval: 20 },
|
|
|
+ styles: {
|
|
|
+ content: {
|
|
|
+ borderRadius: 16,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ local: {
|
|
|
+ placement: "end",
|
|
|
+ variant: "shadow",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ senderPromptsItems: [
|
|
|
+ {
|
|
|
+ key: "1",
|
|
|
+ description: "Hot Topics",
|
|
|
+ icon: {
|
|
|
+ component: FireOutlined,
|
|
|
+ props: { style: { color: "#FF4D4F" } },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "2",
|
|
|
+ description: "Design Guide",
|
|
|
+ icon: {
|
|
|
+ component: ReadOutlined,
|
|
|
+ props: { style: { color: "#1890FF" } },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ placeholderPromptsItems: [
|
|
|
+ {
|
|
|
+ key: "1",
|
|
|
+ label: {
|
|
|
+ title: "Hot Topics",
|
|
|
+ icon: {
|
|
|
+ component: FireOutlined,
|
|
|
+ props: { style: { color: "#FF4D4F" } },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ description: "What are you interested in?",
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ key: "1-1",
|
|
|
+ description: "What's new in X?",
|
|
|
+ style: {
|
|
|
+ background: "#F5F5F5",
|
|
|
+ padding: "8px 12px",
|
|
|
+ borderRadius: "4px",
|
|
|
+ marginTop: "12px",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "1-2",
|
|
|
+ description: "What's AGI?",
|
|
|
+ style: {
|
|
|
+ background: "#F5F5F5",
|
|
|
+ padding: "8px 12px",
|
|
|
+ borderRadius: "4px",
|
|
|
+ marginTop: "8px",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "1-3",
|
|
|
+ description: "Where is the doc?",
|
|
|
+ style: {
|
|
|
+ background: "#F5F5F5",
|
|
|
+ padding: "8px 12px",
|
|
|
+ borderRadius: "4px",
|
|
|
+ marginTop: "8px",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "2",
|
|
|
+ label: {
|
|
|
+ title: "Design Guide",
|
|
|
+ icon: {
|
|
|
+ component: ReadOutlined,
|
|
|
+ props: { style: { color: "#1890FF" } },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ description: "How to design a good product?",
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ key: "2-1",
|
|
|
+ description: "Know the well",
|
|
|
+ icon: HeartOutlined,
|
|
|
+ style: {
|
|
|
+ background: "#F5F5F5",
|
|
|
+ padding: "8px 12px",
|
|
|
+ borderRadius: "4px",
|
|
|
+ marginTop: "12px",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "2-2",
|
|
|
+ description: "Set the AI role",
|
|
|
+ icon: SmileOutlined,
|
|
|
+ style: {
|
|
|
+ background: "#F5F5F5",
|
|
|
+ padding: "8px 12px",
|
|
|
+ borderRadius: "4px",
|
|
|
+ marginTop: "8px",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "2-3",
|
|
|
+ description: "Express the feeling",
|
|
|
+ icon: CommentOutlined,
|
|
|
+ style: {
|
|
|
+ background: "#F5F5F5",
|
|
|
+ padding: "8px 12px",
|
|
|
+ borderRadius: "4px",
|
|
|
+ marginTop: "8px",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ showAttachments: false,
|
|
|
+ isTyping: false,
|
|
|
+ typingConfig: {
|
|
|
+ step: 5,
|
|
|
+ interval: 20,
|
|
|
+ },
|
|
|
+ isRightMenuCollapsed: false,
|
|
|
+ /* 消息框 */
|
|
|
+ conversations: [
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ title: "What is Ant Design X?",
|
|
|
+ messages: [],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ currentConversationId: 1,
|
|
|
+ conversationCounter: 1,
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ computed: {
|
|
|
+ bubbleItems() {
|
|
|
+ if (this.messages.length === 0) {
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ content: {
|
|
|
+ component: "Welcome",
|
|
|
+ props: {
|
|
|
+ variant: "borderless",
|
|
|
+ icon: "https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp",
|
|
|
+ title: "Hello, I'm Ant Design X",
|
|
|
+ description:
|
|
|
+ "Base on Ant Design, AGI product interface solution...",
|
|
|
+ extra: {
|
|
|
+ buttons: [
|
|
|
+ { icon: ShareAltOutlined },
|
|
|
+ { icon: EllipsisOutlined },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ variant: "borderless",
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.messages.map(({ id, message, status }) => ({
|
|
|
+ key: id,
|
|
|
+ loading: status === "loading",
|
|
|
+ role: status === "local" ? "local" : "ai",
|
|
|
+ content: message,
|
|
|
+ }));
|
|
|
+ },
|
|
|
+
|
|
|
+ attachmentsNode() {
|
|
|
+ return {
|
|
|
+ component: "a-badge",
|
|
|
+ props: {
|
|
|
+ dot: this.attachedFiles.length > 0 && !this.headerOpen,
|
|
|
+ },
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ component: "a-button",
|
|
|
+ props: {
|
|
|
+ type: "text",
|
|
|
+ icon: PaperClipOutlined,
|
|
|
+ onClick: () => this.setHeaderOpen(!this.headerOpen),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ senderHeader() {
|
|
|
+ return {
|
|
|
+ title: "Attachments",
|
|
|
+ open: this.headerOpen,
|
|
|
+ styles: {
|
|
|
+ content: {
|
|
|
+ padding: 0,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ attachments: {
|
|
|
+ beforeUpload: () => false,
|
|
|
+ items: this.attachedFiles,
|
|
|
+ onChange: this.handleFileChange,
|
|
|
+ placeholder: (type) =>
|
|
|
+ type === "drop"
|
|
|
+ ? { title: "Drop file here" }
|
|
|
+ : {
|
|
|
+ icon: CloudUploadOutlined,
|
|
|
+ title: "Upload files",
|
|
|
+ description: "Click or drag files to this area to upload",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ methods: {
|
|
|
+ onSubmit(content) {
|
|
|
+ if (!content) return;
|
|
|
+ this.sendRequest(content);
|
|
|
+ this.content = "";
|
|
|
+ },
|
|
|
+
|
|
|
+ onPromptsItemClick(info) {
|
|
|
+ this.sendRequest(info.data.description);
|
|
|
+ },
|
|
|
+
|
|
|
+ onAddConversation() {
|
|
|
+ const newKey = `${this.conversationsItems.length}`;
|
|
|
+ this.conversationsItems.push({
|
|
|
+ key: newKey,
|
|
|
+ label: `New Conversation ${this.conversationsItems.length}`,
|
|
|
+ });
|
|
|
+ this.activeKey = newKey;
|
|
|
+ },
|
|
|
+
|
|
|
+ onConversationClick(key) {
|
|
|
+ this.activeKey = key;
|
|
|
+ this.messages = [];
|
|
|
+ },
|
|
|
+
|
|
|
+ handleFileChange(info) {
|
|
|
+ this.attachedFiles = info.fileList;
|
|
|
+ },
|
|
|
+
|
|
|
+ setHeaderOpen(value) {
|
|
|
+ this.headerOpen = value;
|
|
|
+ },
|
|
|
+
|
|
|
+ async sendRequest(messageData) {
|
|
|
+ const id = Date.now();
|
|
|
+
|
|
|
+ // 构建消息内容
|
|
|
+ let messageContent = messageData.text || "";
|
|
|
+ if (messageData.files.length > 0) {
|
|
|
+ const fileNames = messageData.files.map((f) => f.name).join(", ");
|
|
|
+ messageContent += messageContent
|
|
|
+ ? `\nFiles: ${fileNames}`
|
|
|
+ : `Files: ${fileNames}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加用户消息
|
|
|
+ this.messages.push({
|
|
|
+ id,
|
|
|
+ message: messageContent,
|
|
|
+ status: "local",
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.isTyping = true;
|
|
|
+ const response = `Mock success return. You said: ${messageContent}`;
|
|
|
+
|
|
|
+ // 逐字显示响应
|
|
|
+ let displayedResponse = "";
|
|
|
+ for (let i = 0; i < response.length; i += this.typingConfig.step) {
|
|
|
+ displayedResponse = response.slice(0, i + this.typingConfig.step);
|
|
|
+ this.messages.push({
|
|
|
+ id: id + 1,
|
|
|
+ message: displayedResponse,
|
|
|
+ status: "ai",
|
|
|
+ });
|
|
|
+ await new Promise((resolve) =>
|
|
|
+ setTimeout(resolve, this.typingConfig.interval)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ this.isTyping = false;
|
|
|
+ this.isRequesting = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ toggleAttachments() {
|
|
|
+ this.showAttachments = !this.showAttachments;
|
|
|
+ },
|
|
|
+
|
|
|
+ removeFile(file) {
|
|
|
+ this.attachedFiles = this.attachedFiles.filter((f) => f !== file);
|
|
|
+ },
|
|
|
+
|
|
|
+ triggerFileInput() {
|
|
|
+ this.$refs.fileInput.click();
|
|
|
+ },
|
|
|
+
|
|
|
+ handleFileUpload(event) {
|
|
|
+ const files = Array.from(event.target.files);
|
|
|
+ this.attachedFiles = [...this.attachedFiles, ...files];
|
|
|
+ event.target.value = ""; // 重置input以允许重复上传相同文件
|
|
|
+ },
|
|
|
+
|
|
|
+ handlePromptClick(prompt) {
|
|
|
+ this.sendRequest(prompt.description);
|
|
|
+ },
|
|
|
+
|
|
|
+ handleSend() {
|
|
|
+ if (!this.content && this.attachedFiles.length === 0) return;
|
|
|
+
|
|
|
+ // 创建要发送的消息对象
|
|
|
+ const messageData = {
|
|
|
+ text: this.content,
|
|
|
+ files: this.attachedFiles,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 发送消息
|
|
|
+ this.sendRequest(messageData);
|
|
|
+
|
|
|
+ // 清空输入和附件
|
|
|
+ this.content = "";
|
|
|
+ this.attachedFiles = [];
|
|
|
+ this.showAttachments = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ handleFileDrop(event) {
|
|
|
+ const files = Array.from(event.dataTransfer.files);
|
|
|
+ this.attachedFiles = [...this.attachedFiles, ...files];
|
|
|
+ },
|
|
|
+
|
|
|
+ formatFileSize(bytes) {
|
|
|
+ if (bytes === 0) return "0 B";
|
|
|
+ const k = 1024;
|
|
|
+ const sizes = ["B", "KB", "MB", "GB"];
|
|
|
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
|
+ },
|
|
|
+
|
|
|
+ getFileIcon(file) {
|
|
|
+ // 获取文件扩展名
|
|
|
+ const ext = file.name.split(".").pop().toLowerCase();
|
|
|
+
|
|
|
+ // 判断是否为图片
|
|
|
+ const imageTypes = ["jpg", "jpeg", "png", "gif", "webp"];
|
|
|
+ if (imageTypes.includes(ext)) {
|
|
|
+ // 如果是图片,返回文件预览
|
|
|
+ return URL.createObjectURL(file);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他文件类型返回默认文档图标
|
|
|
+ return "./svg/word.svg"; // 确保有这个默认文档图标
|
|
|
+ },
|
|
|
+ getFileType(file) {
|
|
|
+ const ext = file.name.split(".").pop().toLowerCase();
|
|
|
+ // 判断是否为图片
|
|
|
+ const imageTypes = ["jpg", "jpeg", "png", "gif", "webp"];
|
|
|
+ if (imageTypes.includes(ext)) {
|
|
|
+ // 如果是图片,返回文件预览
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ // 更新发送消息的方法
|
|
|
+ async sendMessage() {
|
|
|
+ if (!this.inputContent.trim()) return;
|
|
|
+
|
|
|
+ const currentConversation = this.conversations.find(
|
|
|
+ c => c.id === this.currentConversationId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!currentConversation) return;
|
|
|
+
|
|
|
+ const newMessage = {
|
|
|
+ id: Date.now(),
|
|
|
+ role: 'user',
|
|
|
+ content: this.inputContent
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加消息到当前会话
|
|
|
+ currentConversation.messages.push(newMessage);
|
|
|
+ this.messages = currentConversation.messages;
|
|
|
+
|
|
|
+ this.isLoading = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 模拟API请求
|
|
|
+ const response = await this.mockApiRequest(this.inputContent);
|
|
|
+
|
|
|
+ // 添加AI回复到当前会话
|
|
|
+ const aiMessage = {
|
|
|
+ id: Date.now() + 1,
|
|
|
+ role: 'assistant',
|
|
|
+ content: response
|
|
|
+ };
|
|
|
+ currentConversation.messages.push(aiMessage);
|
|
|
+ this.messages = currentConversation.messages;
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error:", error);
|
|
|
+ } finally {
|
|
|
+ this.isLoading = false;
|
|
|
+ this.inputContent = '';
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handlePromptClick(prompt) {
|
|
|
+ this.inputContent = prompt.description;
|
|
|
+ this.sendMessage();
|
|
|
+ },
|
|
|
+
|
|
|
+ async mockApiRequest(message) {
|
|
|
+ // 模拟API延迟
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
|
+ return `This is a response to: ${message}`;
|
|
|
+ },
|
|
|
+
|
|
|
+ shareAction() {
|
|
|
+ // 实现分享功能
|
|
|
+ console.log("Share clicked");
|
|
|
+ },
|
|
|
+
|
|
|
+ moreAction() {
|
|
|
+ // 实现更多功能
|
|
|
+ console.log("More clicked");
|
|
|
+ },
|
|
|
+
|
|
|
+ toggleRightMenu() {
|
|
|
+ this.isRightMenuCollapsed = !this.isRightMenuCollapsed;
|
|
|
+ },
|
|
|
+
|
|
|
+ /* 添加消息 */
|
|
|
+ // 添加新会话
|
|
|
+ addNewConversation() {
|
|
|
+ this.conversationCounter++;
|
|
|
+ const newConversation = {
|
|
|
+ id: Date.now(),
|
|
|
+ title: `New Conversation ${this.conversationCounter}`,
|
|
|
+ messages: [],
|
|
|
+ };
|
|
|
+ this.conversations.push(newConversation);
|
|
|
+ this.switchConversation(newConversation.id);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 切换会话
|
|
|
+ switchConversation(conversationId) {
|
|
|
+ this.currentConversationId = conversationId;
|
|
|
+ // 更新当前显示的消息列表
|
|
|
+ const conversation = this.conversations.find(
|
|
|
+ (c) => c.id === conversationId
|
|
|
+ );
|
|
|
+ this.messages = conversation ? conversation.messages : [];
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ watch: {
|
|
|
+ activeKey(newKey) {
|
|
|
+ if (newKey !== undefined) {
|
|
|
+ this.messages = [];
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.layout {
|
|
|
+ display: flex;
|
|
|
+ width: 100%;
|
|
|
+ height: 100vh;
|
|
|
+}
|
|
|
+
|
|
|
+.menu {
|
|
|
+ background: #f7f7f8;
|
|
|
+ width: 260px;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ border-right: 1px solid rgba(0, 0, 0, 0.06);
|
|
|
+}
|
|
|
+
|
|
|
+.conversations {
|
|
|
+ padding: 0 12px;
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.chat {
|
|
|
+ flex: 1;
|
|
|
+ padding: 24px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 24px;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.messages {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding-right: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.sender {
|
|
|
+ box-shadow: none;
|
|
|
+ border: 1px solid #e5e6eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.logo {
|
|
|
+ height: 64px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.logo-img {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.logo span {
|
|
|
+ margin-left: 12px;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+}
|
|
|
+
|
|
|
+.new-conversation-btn {
|
|
|
+ margin: 0 12px 12px;
|
|
|
+ height: 40px;
|
|
|
+ background: rgba(235, 245, 255, 0.7);
|
|
|
+ border: none;
|
|
|
+ border-radius: 8px;
|
|
|
+ color: #1677ff;
|
|
|
+ font-size: 14px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: background-color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.new-conversation-btn:hover {
|
|
|
+ background: rgba(235, 245, 255, 0.9);
|
|
|
+}
|
|
|
+
|
|
|
+.conversation-list {
|
|
|
+ flex: 1;
|
|
|
+ padding: 0 8px;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.conversation-item {
|
|
|
+ padding: 12px;
|
|
|
+ margin: 4px 0;
|
|
|
+ border-radius: 8px;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: background-color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.conversation-item:hover {
|
|
|
+ background: rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.conversation-item.active {
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
|
+}
|
|
|
+
|
|
|
+/* 滚动条样式 */
|
|
|
+.conversation-list::-webkit-scrollbar {
|
|
|
+ width: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.conversation-list::-webkit-scrollbar-thumb {
|
|
|
+ background: rgba(0, 0, 0, 0.15);
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.conversation-list::-webkit-scrollbar-track {
|
|
|
+ background: transparent;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.welcome-message) {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.welcome-message .title) {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.welcome-message .description) {
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.prompt-card) {
|
|
|
+ background: #f7f8fa;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 16px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.prompt-card-title) {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.prompt-option) {
|
|
|
+ background: #f5f5f5;
|
|
|
+ padding: 8px 12px;
|
|
|
+ border-radius: 4px;
|
|
|
+ margin-top: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: background 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.prompt-option:hover) {
|
|
|
+ background: #e8e8e8;
|
|
|
+}
|
|
|
+
|
|
|
+.input-container {
|
|
|
+ width: 50%;
|
|
|
+ margin: auto auto 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 标签栏样式 */
|
|
|
+.tabs-wrapper {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ padding: 4px 12px;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 14px;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+ background: transparent;
|
|
|
+ transition: background-color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-item:hover {
|
|
|
+ background: rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.tab-item.active {
|
|
|
+ background: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-item .anticon {
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.input-wrapper {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 8px 12px;
|
|
|
+ border: 1px solid #e5e6eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: white;
|
|
|
+}
|
|
|
+
|
|
|
+.message-input {
|
|
|
+ flex: 1;
|
|
|
+ border: none;
|
|
|
+ outline: none;
|
|
|
+ padding: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ background: transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.message-input::placeholder {
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+}
|
|
|
+
|
|
|
+.attachment-btn,
|
|
|
+.send-btn {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.attachment-btn:hover,
|
|
|
+.send-btn:hover {
|
|
|
+ background: rgba(0, 0, 0, 0.04);
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+}
|
|
|
+
|
|
|
+.attachments-panel {
|
|
|
+ /* position: absolute;
|
|
|
+ bottom: calc(100% + 8px);
|
|
|
+ left: 0;
|
|
|
+ right: 0; */
|
|
|
+ background: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-content {
|
|
|
+ background: white;
|
|
|
+}
|
|
|
+
|
|
|
+.attachments-header {
|
|
|
+ padding: 12px 16px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.attachments-header span {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+}
|
|
|
+
|
|
|
+.close-btn {
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 4px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+}
|
|
|
+
|
|
|
+.upload-area {
|
|
|
+ padding: 24px;
|
|
|
+ /* border: 2px dashed #d9d9d9; */
|
|
|
+ border-radius: 8px;
|
|
|
+ text-align: center;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ /* background: #fafafa; */
|
|
|
+}
|
|
|
+
|
|
|
+/* .upload-area:hover {
|
|
|
+ border-color: #1677ff;
|
|
|
+ background: #f0f7ff;
|
|
|
+} */
|
|
|
+
|
|
|
+.upload-area.has-files {
|
|
|
+ padding: 12px;
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-icon {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-text {
|
|
|
+ font-size: 14px;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-hint {
|
|
|
+ font-size: 12px;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+}
|
|
|
+
|
|
|
+.file-list {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.file-item {
|
|
|
+ position: relative;
|
|
|
+ width: 260px;
|
|
|
+ padding: 8px 12px;
|
|
|
+ margin: 4px 0;
|
|
|
+ background: #f5f5f5;
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.file-item.is-image {
|
|
|
+ width: 68px;
|
|
|
+ padding: 0;
|
|
|
+ margin: 4px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.file-wrapper {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.file-icon {
|
|
|
+ width: 68px;
|
|
|
+ height: 68px;
|
|
|
+ object-fit: cover;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.remove-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: -8px;
|
|
|
+ right: -8px;
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ padding: 0;
|
|
|
+ border: none;
|
|
|
+ background: #545454;
|
|
|
+ border-radius: 50%;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1;
|
|
|
+ color: #fff;
|
|
|
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.remove-btn:hover {
|
|
|
+ opacity: 0.8;
|
|
|
+}
|
|
|
+
|
|
|
+.file-info {
|
|
|
+ margin-top: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.file-name {
|
|
|
+ display: block;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+ font-size: 14px;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+}
|
|
|
+
|
|
|
+.file-size {
|
|
|
+ display: block;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+ font-size: 12px;
|
|
|
+ margin-top: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.add-more-btn {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 68px;
|
|
|
+ height: 68px;
|
|
|
+ border: 1px dashed #d9d9d9;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ margin: 4px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.add-more-btn:hover {
|
|
|
+ border-color: #1677ff;
|
|
|
+ background: #f0f7ff;
|
|
|
+}
|
|
|
+
|
|
|
+/* 欢迎区域样式 */
|
|
|
+.welcome-section {
|
|
|
+ max-width: 800px;
|
|
|
+ margin: 0 auto;
|
|
|
+ width: 50%;
|
|
|
+}
|
|
|
+
|
|
|
+.welcome-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 16px;
|
|
|
+ margin-bottom: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border-radius: 50%;
|
|
|
+}
|
|
|
+
|
|
|
+.welcome-info {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.welcome-info h2 {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+ margin: 0 0 4px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.welcome-info p {
|
|
|
+ font-size: 14px;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.action-buttons {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.icon-btn {
|
|
|
+ padding: 8px;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+}
|
|
|
+
|
|
|
+/* 建议区域样式 */
|
|
|
+.suggestion-section {
|
|
|
+ margin-top: 32px;
|
|
|
+}
|
|
|
+
|
|
|
+.suggestion-section h3 {
|
|
|
+ font-size: 14px;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.cards-container {
|
|
|
+ display: flex;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.suggestion-card {
|
|
|
+ flex: 1;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 16px;
|
|
|
+ background: #ffffff; /* 替换 var(--ant-color-bg-container) */
|
|
|
+ border-radius: 8px; /* 替换 var(--ant-border-radius-lg) */
|
|
|
+ transition: border 0.3s, background 0.3s; /* 替换 var(--ant-motion-duration-slow) */
|
|
|
+ border: 1px solid #f0f0f0; /* 替换组合的 border 属性 */
|
|
|
+}
|
|
|
+:root {
|
|
|
+ --ant-color-bg-container: #ffffff;
|
|
|
+ --ant-border-radius-lg: 8px;
|
|
|
+ --ant-motion-duration-slow: 0.3s;
|
|
|
+ --ant-line-width: 1px;
|
|
|
+ --ant-line-type: solid;
|
|
|
+ --ant-color-border-secondary: #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.card-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-header h4 {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.icon.red {
|
|
|
+ color: #ff4d4f;
|
|
|
+}
|
|
|
+
|
|
|
+.icon.blue {
|
|
|
+ color: #1890ff;
|
|
|
+}
|
|
|
+
|
|
|
+.card-subtitle {
|
|
|
+ font-size: 14px;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.suggestion-items {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.suggestion-item {
|
|
|
+ padding: 8px 12px;
|
|
|
+ background: #f5f5f5;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.suggestion-item:hover {
|
|
|
+ background: #e8e8e8;
|
|
|
+}
|
|
|
+.tab-icon {
|
|
|
+ width: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.attached-files {
|
|
|
+ padding: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.file-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.file-item:hover {
|
|
|
+ background: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.remove-btn {
|
|
|
+ margin-left: auto;
|
|
|
+ padding: 4px;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+}
|
|
|
+
|
|
|
+.remove-btn:hover {
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+}
|
|
|
+
|
|
|
+.send-btn:disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-area {
|
|
|
+ /* border: 2px dashed #d9d9d9; */
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 20px;
|
|
|
+ text-align: center;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: border-color 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+/* .upload-area:hover,
|
|
|
+.upload-area.drag-over {
|
|
|
+ border-color: #1677ff;
|
|
|
+} */
|
|
|
+
|
|
|
+.file-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ width: 236px;
|
|
|
+ height: 68px;
|
|
|
+ padding: 8px 12px;
|
|
|
+ margin: 4px 0;
|
|
|
+ background: #f5f5f5;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.file-item .remove-btn {
|
|
|
+ margin-left: auto;
|
|
|
+ padding: 4px 8px;
|
|
|
+ background: transparent;
|
|
|
+ border: none;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+/* 聊天内容显示框 */
|
|
|
+.message-list {
|
|
|
+ width: 50%;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 20px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.message-item {
|
|
|
+ display: flex;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 用户消息靠右显示 */
|
|
|
+.user {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+.message-bubble {
|
|
|
+ max-width: 80%;
|
|
|
+ padding: 12px 16px;
|
|
|
+ border-radius: 12px;
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
+}
|
|
|
+
|
|
|
+/* 用户消息的气泡样式 */
|
|
|
+.user .message-bubble {
|
|
|
+ background: #1677ff;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+/* AI消息的气泡样式 */
|
|
|
+.assistant .message-bubble {
|
|
|
+ background: #f7f7f8;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+}
|
|
|
+
|
|
|
+.message-content {
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.5;
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-break: break-word;
|
|
|
+}
|
|
|
+
|
|
|
+.right_menu {
|
|
|
+ width: 300px;
|
|
|
+ height: 100%;
|
|
|
+ background: #f7f7f8;
|
|
|
+ border-left: 1px solid rgba(0, 0, 0, 0.06);
|
|
|
+ position: relative;
|
|
|
+ transition: width 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.right_menu.collapsed {
|
|
|
+ width: 0px;
|
|
|
+}
|
|
|
+
|
|
|
+.collapse-button {
|
|
|
+ position: absolute;
|
|
|
+ left: -20px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 20px;
|
|
|
+ height: 40px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid rgba(0, 0, 0, 0.06);
|
|
|
+ border-right: none;
|
|
|
+ border-radius: 4px 0 0 4px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.collapse-button:hover {
|
|
|
+ background: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.collapse-icon {
|
|
|
+ width: 12px;
|
|
|
+ height: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.right-menu-content {
|
|
|
+ padding: 20px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.right_menu.collapsed .right-menu-content {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+</style>
|