|
|
@@ -0,0 +1,671 @@
|
|
|
+<template>
|
|
|
+ <el-card class="agent-dialog-card">
|
|
|
+ <div class="dialog-container">
|
|
|
+ <!-- 对话历史区域 -->
|
|
|
+ <div class="chat-history" ref="chatHistoryRef">
|
|
|
+ <div v-for="(message, index) in messages" :key="index"
|
|
|
+ :class="['message-item', message.role === 'user' ? 'user-message' : 'ai-message']">
|
|
|
+ <div class="message-avatar">
|
|
|
+ <el-avatar :size="40" :icon="message.role === 'user' ? 'el-icon-user' : 'el-icon-robot'" />
|
|
|
+ </div>
|
|
|
+ <div class="message-content">
|
|
|
+ <div class="message-bubble">
|
|
|
+ <div class="message-text" v-html="parseMarkdown(message.content)"></div>
|
|
|
+ </div>
|
|
|
+ <div class="message-time">{{ message.timestamp }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <div v-if="loading" class="loading-item">
|
|
|
+ <div class="message-avatar">
|
|
|
+ <el-avatar size="40" icon="el-icon-robot" />
|
|
|
+ </div>
|
|
|
+ <div class="message-content">
|
|
|
+ <div class="message-bubble">
|
|
|
+ <div class="loading-spinner">
|
|
|
+ <el-icon class="is-loading"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
|
|
+ <path fill="currentColor"
|
|
|
+ d="M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896zm0 832a384 384 0 1 0 0-768 384 384 0 0 0 0 768z">
|
|
|
+ </path>
|
|
|
+ <path fill="currentColor"
|
|
|
+ d="M831.9 512a319.9 319.9 0 0 1-33.9 124.3l-73.6-36.8a256.4 256.4 0 0 0 27.2-99.5h-88.7v-64h149.3a320.3 320.3 0 0 1-20.3 64zm-14.5-128a319.9 319.9 0 0 1-31.4 120.9l-73.6-36.8a256.4 256.4 0 0 0 25.6-96.7H580.6v-64h156.8a320.1 320.1 0 0 1-19 64zm-118.8-128h-78.1v-97.9a256 256 0 0 0-170.6 0V256h-78.1a320 320 0 0 1 326.8-320z">
|
|
|
+ </path>
|
|
|
+ </svg></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="loading-text">思考中...</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 快速开始按钮区域 -->
|
|
|
+ <div class="quick-start-section">
|
|
|
+ <el-button type="info" @click="quickStartReflux">反酸计算</el-button>
|
|
|
+ <el-button type="success" @click="quickStartReduce">降酸计算</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 输入区域 -->
|
|
|
+ <div class="input-container">
|
|
|
+ <el-input v-model="inputMessage" type="textarea" :rows="2" placeholder="请输入您的问题..." resize="none"
|
|
|
+ @keyup.enter.exact="sendMessage" :disabled="loading" />
|
|
|
+
|
|
|
+ <!-- 文件上传区域 -->
|
|
|
+ <div class="file-upload-section" v-if="selectedFiles.length > 0">
|
|
|
+ <div class="file-list">
|
|
|
+ <div v-for="(file, index) in selectedFiles" :key="index" class="file-item">
|
|
|
+ <div class="file-info">
|
|
|
+ <span class="file-name">{{ file.name }}</span>
|
|
|
+ </div>
|
|
|
+ <el-button size="small" type="danger" @click="removeFile(index)">删除</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="input-actions">
|
|
|
+ <input ref="fileInput" type="file" multiple accept="*/*" style="display: none" @change="handleFileSelect" />
|
|
|
+ <el-button type="secondary" @click="triggerFileUpload" :disabled="loading">
|
|
|
+ 上传文件
|
|
|
+ </el-button>
|
|
|
+ <el-button type="primary" @click="sendMessage" :loading="loading"
|
|
|
+ :disabled="(!inputMessage.trim() && selectedFiles.length === 0) || loading">
|
|
|
+ 发送
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="clearMessages">清空</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, reactive, nextTick, onMounted } from 'vue';
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
+import axios from 'axios';
|
|
|
+import { api5000 } from '@/utils/request';
|
|
|
+
|
|
|
+interface Message {
|
|
|
+ role: 'user' | 'assistant';
|
|
|
+ content: string;
|
|
|
+ timestamp: string;
|
|
|
+}
|
|
|
+
|
|
|
+const messages = ref<Message[]>([]);
|
|
|
+const inputMessage = ref('');
|
|
|
+const loading = ref(false);
|
|
|
+const chatHistoryRef = ref<HTMLElement | null>(null);
|
|
|
+const fileInput = ref<HTMLInputElement | null>(null);
|
|
|
+
|
|
|
+interface SelectedFile {
|
|
|
+ file: File;
|
|
|
+ preview: string;
|
|
|
+ name: string;
|
|
|
+ type: string;
|
|
|
+}
|
|
|
+
|
|
|
+const selectedFiles = ref<SelectedFile[]>([]);
|
|
|
+
|
|
|
+// 格式化时间
|
|
|
+const formatTime = (): string => {
|
|
|
+ const now = new Date();
|
|
|
+ return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
|
|
|
+};
|
|
|
+
|
|
|
+// 滚动到底部
|
|
|
+const scrollToBottom = () => {
|
|
|
+ nextTick(() => {
|
|
|
+ if (chatHistoryRef.value) {
|
|
|
+ chatHistoryRef.value.scrollTop = chatHistoryRef.value.scrollHeight;
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 发送消息
|
|
|
+const sendMessage = async () => {
|
|
|
+ const message = inputMessage.value.trim();
|
|
|
+ if ((!message && selectedFiles.value.length === 0) || loading.value) return;
|
|
|
+
|
|
|
+ // 构建用户消息内容
|
|
|
+ let userContent = message;
|
|
|
+ if (selectedFiles.value.length > 0) {
|
|
|
+ selectedFiles.value.forEach((file, index) => {
|
|
|
+ if (file.type.startsWith('image/')) {
|
|
|
+ userContent += `\n[1]})`;
|
|
|
+ } else {
|
|
|
+ userContent += `\n[${file.name}](file:///${file.name})`;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加用户消息
|
|
|
+ messages.value.push({
|
|
|
+ role: 'user',
|
|
|
+ content: userContent,
|
|
|
+ timestamp: formatTime(),
|
|
|
+ });
|
|
|
+ inputMessage.value = '';
|
|
|
+ scrollToBottom();
|
|
|
+
|
|
|
+ // 显示加载状态
|
|
|
+ loading.value = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 构建FormData
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append('input', message);
|
|
|
+
|
|
|
+ // 添加文件
|
|
|
+ selectedFiles.value.forEach((file) => {
|
|
|
+ formData.append('files', file.file);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 调用API
|
|
|
+ const response = await api5000.post('/agent/langchain', formData, {
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'multipart/form-data'
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 隐藏加载状态
|
|
|
+ loading.value = false;
|
|
|
+
|
|
|
+ // 清空选中的文件
|
|
|
+ selectedFiles.value = [];
|
|
|
+
|
|
|
+ // 处理响应
|
|
|
+ if (response.data && response.data.response) {
|
|
|
+ messages.value.push({
|
|
|
+ role: 'assistant',
|
|
|
+ content: response.data.response,
|
|
|
+ timestamp: formatTime(),
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ messages.value.push({
|
|
|
+ role: 'assistant',
|
|
|
+ content: '抱歉,我无法理解您的问题,请尝试换一种方式提问。',
|
|
|
+ timestamp: formatTime(),
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('API调用失败:', error);
|
|
|
+ loading.value = false;
|
|
|
+ ElMessage.error('网络错误,请稍后重试');
|
|
|
+ messages.value.push({
|
|
|
+ role: 'assistant',
|
|
|
+ content: '抱歉,网络连接失败,请稍后重试。',
|
|
|
+ timestamp: formatTime(),
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ scrollToBottom();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 清空消息
|
|
|
+const clearMessages = () => {
|
|
|
+ messages.value = [];
|
|
|
+};
|
|
|
+
|
|
|
+// 触发文件选择
|
|
|
+const triggerFileUpload = () => {
|
|
|
+ fileInput.value?.click();
|
|
|
+};
|
|
|
+
|
|
|
+// 处理文件选择
|
|
|
+const handleFileSelect = (event: Event) => {
|
|
|
+ const target = event.target as HTMLInputElement;
|
|
|
+ const files = target.files;
|
|
|
+ if (!files || files.length === 0) return;
|
|
|
+
|
|
|
+ for (let i = 0; i < files.length; i++) {
|
|
|
+ const file = files[i];
|
|
|
+ const selectedFile: SelectedFile = {
|
|
|
+ file,
|
|
|
+ preview: '',
|
|
|
+ name: file.name,
|
|
|
+ type: file.type
|
|
|
+ };
|
|
|
+
|
|
|
+ // 为图片生成预览
|
|
|
+ if (file.type.startsWith('image/')) {
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = (e) => {
|
|
|
+ selectedFile.preview = e.target?.result as string;
|
|
|
+ };
|
|
|
+ reader.readAsDataURL(file);
|
|
|
+ } else {
|
|
|
+ // 非图片文件使用默认预览
|
|
|
+ selectedFile.preview = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ selectedFiles.value.push(selectedFile);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清空文件输入,以便可以重复选择同一个文件
|
|
|
+ target.value = '';
|
|
|
+};
|
|
|
+
|
|
|
+// 删除文件
|
|
|
+const removeFile = (index: number) => {
|
|
|
+ selectedFiles.value.splice(index, 1);
|
|
|
+};
|
|
|
+
|
|
|
+// 解析Markdown格式
|
|
|
+const parseMarkdown = (text: string): string => {
|
|
|
+ let parsedText = text;
|
|
|
+
|
|
|
+ // 处理换行
|
|
|
+ parsedText = parsedText.replace(/\n/g, '<br>');
|
|
|
+
|
|
|
+ // 处理Markdown格式,使用递归方法确保正确处理嵌套
|
|
|
+ // 1. 首先处理加粗格式 **文本**
|
|
|
+ parsedText = parsedText.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
|
|
+
|
|
|
+ // 2. 然后处理斜体格式 *文本*
|
|
|
+ // 使用非贪婪匹配,确保正确处理混合格式
|
|
|
+ parsedText = parsedText.replace(/\*(.*?)\*/g, (match, content) => {
|
|
|
+ // 递归处理内容中的Markdown格式
|
|
|
+ return `<em>${content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')}</em>`;
|
|
|
+ });
|
|
|
+
|
|
|
+ return parsedText;
|
|
|
+};
|
|
|
+
|
|
|
+// 快速开始反酸计算
|
|
|
+const quickStartReflux = () => {
|
|
|
+ // 生成符合反酸模型要求的自然语言输入
|
|
|
+ inputMessage.value = `我想预测一下土壤的反酸情况,目前土壤的氢离子浓度是0.5 cmol/kg,铝离子浓度是0.3 cmol/kg,有机质含量20.0 g/kg,硝态氮20.0 mg/kg,铵态氮10.0 mg/kg,阳离子交换量6.0 cmol/kg。请帮我分析一下未来可能的酸化趋势。`;
|
|
|
+};
|
|
|
+
|
|
|
+// 快速开始降酸计算
|
|
|
+const quickStartReduce = () => {
|
|
|
+ // 生成符合降酸模型要求的自然语言输入
|
|
|
+ inputMessage.value = `我的土壤现在pH值是5.0,想提高到6.5,土壤的氢离子浓度是0.5 cmol/kg,铝离子浓度是0.3 cmol/kg,有机质含量20.0 g/kg,硝态氮20.0 mg/kg,铵态氮10.0 mg/kg,阳离子交换量6.0 cmol/kg。请问需要施加多少生石灰?`;
|
|
|
+};
|
|
|
+
|
|
|
+// 初始化欢迎消息
|
|
|
+const initWelcomeMessage = () => {
|
|
|
+ messages.value.push({
|
|
|
+ role: 'assistant',
|
|
|
+ content: '你好!我是酸化模型助手,有什么可以帮助你的吗?',
|
|
|
+ timestamp: formatTime(),
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ initWelcomeMessage();
|
|
|
+ scrollToBottom();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 整体容器样式 */
|
|
|
+.agent-dialog-card {
|
|
|
+ width: 100%;
|
|
|
+ max-width: 100%;
|
|
|
+ margin: 0 auto;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: 16px;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* 头部样式 */
|
|
|
+.card-header {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ text-align: center;
|
|
|
+ color: #333333;
|
|
|
+ padding: 20px;
|
|
|
+ background-color: #fafafa;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 对话容器 */
|
|
|
+.dialog-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+ min-height: 600px;
|
|
|
+ height: calc(100vh - 160px);
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 快速开始按钮区域 */
|
|
|
+.quick-start-section {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ justify-content: flex-start;
|
|
|
+ padding: 16px;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: 12px;
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 快速开始按钮样式 */
|
|
|
+.quick-start-section .el-button {
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
+ background-color: transparent;
|
|
|
+ color: #333333;
|
|
|
+ padding: 8px 16px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.quick-start-section .el-button:hover {
|
|
|
+ border-color: #1976d2;
|
|
|
+ color: #1976d2;
|
|
|
+ background-color: rgba(25, 118, 210, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+/* 聊天历史区域 */
|
|
|
+.chat-history {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 300px;
|
|
|
+ max-height: calc(100vh - 300px);
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 0;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: 12px;
|
|
|
+ scroll-behavior: smooth;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+/* 消息项样式 */
|
|
|
+.message-item {
|
|
|
+ display: flex;
|
|
|
+ margin-bottom: 24px;
|
|
|
+ animation: fadeIn 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.user-message {
|
|
|
+ flex-direction: row-reverse;
|
|
|
+}
|
|
|
+
|
|
|
+/* 头像样式 */
|
|
|
+.message-avatar {
|
|
|
+ margin: 0 12px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 消息内容样式 */
|
|
|
+.message-content {
|
|
|
+ flex: 1;
|
|
|
+ max-width: 70%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.user-message .message-content {
|
|
|
+ align-items: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+.ai-message .message-content {
|
|
|
+ align-items: flex-start;
|
|
|
+}
|
|
|
+
|
|
|
+/* 消息气泡样式 */
|
|
|
+.message-bubble {
|
|
|
+ padding: 16px 20px;
|
|
|
+ border-radius: 18px;
|
|
|
+ word-wrap: break-word;
|
|
|
+ line-height: 1.6;
|
|
|
+ max-width: 100%;
|
|
|
+ display: inline-block;
|
|
|
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+/* 用户消息气泡 */
|
|
|
+.user-message .message-bubble {
|
|
|
+ background-color: #1976d2;
|
|
|
+ color: white;
|
|
|
+ border-bottom-right-radius: 6px;
|
|
|
+ align-self: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+/* AI消息气泡 */
|
|
|
+.ai-message .message-bubble {
|
|
|
+ background-color: #f0f4f8;
|
|
|
+ color: #333333;
|
|
|
+ border-bottom-left-radius: 6px;
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+/* 消息文本样式 */
|
|
|
+.message-text {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 400;
|
|
|
+}
|
|
|
+
|
|
|
+.message-text strong {
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.message-text em {
|
|
|
+ font-style: italic;
|
|
|
+}
|
|
|
+
|
|
|
+/* 消息时间样式 */
|
|
|
+.message-time {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999999;
|
|
|
+ margin-top: 8px;
|
|
|
+ padding: 0 10px;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+
|
|
|
+.user-message .message-time {
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+/* 加载状态样式 */
|
|
|
+.loading-item {
|
|
|
+ display: flex;
|
|
|
+ margin-bottom: 24px;
|
|
|
+ animation: fadeIn 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-spinner {
|
|
|
+ display: inline-block;
|
|
|
+ margin-right: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-text {
|
|
|
+ font-size: 15px;
|
|
|
+ color: #666666;
|
|
|
+ animation: pulse 1.5s infinite;
|
|
|
+}
|
|
|
+
|
|
|
+/* 输入容器样式 */
|
|
|
+.input-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 16px;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: 12px;
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
+ min-height: 150px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 输入操作按钮区域 */
|
|
|
+.input-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 12px;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* 文件上传区域 */
|
|
|
+.file-upload-section {
|
|
|
+ margin: 10px 0;
|
|
|
+ padding: 12px;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid #e8e8e8;
|
|
|
+}
|
|
|
+
|
|
|
+.file-list {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.file-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid #e8e8e8;
|
|
|
+ flex: 1 0 calc(50% - 6px);
|
|
|
+ min-width: 200px;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.file-item:hover {
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
+ transform: translateY(-1px);
|
|
|
+}
|
|
|
+
|
|
|
+.file-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.file-name {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333333;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* 上传图片样式 */
|
|
|
+.uploaded-image {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 200px;
|
|
|
+ width: auto;
|
|
|
+ height: auto;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin: 12px 0;
|
|
|
+ display: block;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ transition: transform 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.uploaded-image:hover {
|
|
|
+ transform: scale(1.02);
|
|
|
+}
|
|
|
+
|
|
|
+/* 文件链接样式 */
|
|
|
+.file-link {
|
|
|
+ color: #1976d2;
|
|
|
+ text-decoration: none;
|
|
|
+ word-break: break-all;
|
|
|
+ font-weight: 500;
|
|
|
+ transition: color 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.file-link:hover {
|
|
|
+ color: #1565c0;
|
|
|
+ text-decoration: underline;
|
|
|
+}
|
|
|
+
|
|
|
+/* 动画效果 */
|
|
|
+@keyframes fadeIn {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(8px);
|
|
|
+ }
|
|
|
+
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes pulse {
|
|
|
+
|
|
|
+ 0%,
|
|
|
+ 100% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ 50% {
|
|
|
+ opacity: 0.6;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 滚动条样式 */
|
|
|
+.chat-history::-webkit-scrollbar {
|
|
|
+ width: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.chat-history::-webkit-scrollbar-track {
|
|
|
+ background: #f1f1f1;
|
|
|
+ border-radius: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.chat-history::-webkit-scrollbar-thumb {
|
|
|
+ background: #c1c1c1;
|
|
|
+ border-radius: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.chat-history::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: #a8a8a8;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式设计 */
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .agent-dialog-card {
|
|
|
+ max-width: 100%;
|
|
|
+ margin: 0;
|
|
|
+ border-radius: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dialog-container {
|
|
|
+ height: 500px;
|
|
|
+ padding: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-content {
|
|
|
+ max-width: 80%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-item {
|
|
|
+ flex: 1 0 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .quick-start-section {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 480px) {
|
|
|
+ .dialog-container {
|
|
|
+ height: 400px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-content {
|
|
|
+ max-width: 90%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .input-actions {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: stretch;
|
|
|
+ }
|
|
|
+
|
|
|
+ .input-actions .el-button {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-item {
|
|
|
+ flex: 1 0 100%;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|