|
@@ -0,0 +1,561 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <!-- 右侧AI问答 -->
|
|
|
|
|
+ <div class="number-people">
|
|
|
|
|
+ <div class="content-box">
|
|
|
|
|
+ <!-- AI对话框 -->
|
|
|
|
|
+ <div class="chat-dialog">
|
|
|
|
|
+ <!-- 对话消息列表 -->
|
|
|
|
|
+ <div class="message-list">
|
|
|
|
|
+ <div v-if="imageAllList.length > 0">
|
|
|
|
|
+ <div v-for="(item, index) in imageAllList" :key="index">
|
|
|
|
|
+ <!-- 用户消息 -->
|
|
|
|
|
+ <div class="user-message" v-if="item.type === 'user'">
|
|
|
|
|
+ {{ item.content }}
|
|
|
|
|
+ <div class="user-image-list" v-if="item.imageUrl">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ style="width: fit-content; height: 180px; margin: 10px;"
|
|
|
|
|
+ :src="item.imageUrl"
|
|
|
|
|
+ :preview-src-list="[item.imageUrl]"
|
|
|
|
|
+ fit="cover"
|
|
|
|
|
+ show-progress
|
|
|
|
|
+ >
|
|
|
|
|
+ <template
|
|
|
|
|
+ #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon @click="prev"><Back /></el-icon>
|
|
|
|
|
+ <el-icon @click="next"><Right /></el-icon>
|
|
|
|
|
+ <el-icon @click="setActiveItem(item.imageList.length - 1)">
|
|
|
|
|
+ <DArrowRight />
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
|
|
|
|
|
+ <el-icon @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })"><ZoomIn /></el-icon>
|
|
|
|
|
+ <el-icon @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })"><RefreshRight /></el-icon>
|
|
|
|
|
+ <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
|
|
|
|
|
+ <el-icon @click="reset"><Refresh /></el-icon>
|
|
|
|
|
+ <el-icon @click="download(activeIndex)"><Download /></el-icon>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-image>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- AI生成图片对话框 -->
|
|
|
|
|
+ <div class="ai-message" v-if="item.type !== 'user'">
|
|
|
|
|
+ {{ item.content }}
|
|
|
|
|
+ <span v-if="item.loading" class="loading-dots">
|
|
|
|
|
+ <span class="dot"></span>
|
|
|
|
|
+ <span class="dot"></span>
|
|
|
|
|
+ <span class="dot"></span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <!-- 显示AI返回的HTML内容 -->
|
|
|
|
|
+ <div v-if="item.htmlContent" v-html="item.htmlContent" class="ai-html-content"></div>
|
|
|
|
|
+ <div class="image-list" v-if="item.imageList && item.imageList.length > 0">
|
|
|
|
|
+ <video
|
|
|
|
|
+ v-for="(video, index) in item.imageList"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ style="width: 60%; max-width: 600px; height: auto; margin: 10px;border-radius: 2%;"
|
|
|
|
|
+ :src="video"
|
|
|
|
|
+ controls
|
|
|
|
|
+ playsinline
|
|
|
|
|
+ >
|
|
|
|
|
+ 您的浏览器不支持视频播放
|
|
|
|
|
+ </video>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 输入框和发送按钮 -->
|
|
|
|
|
+ <div class="input-section">
|
|
|
|
|
+ <input
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ v-model="displayedPrompt"
|
|
|
|
|
+ placeholder="描述任何画面..."
|
|
|
|
|
+ @keyup.enter="sendMessage"
|
|
|
|
|
+ style="flex: 1; margin-right: 8px;"
|
|
|
|
|
+ />
|
|
|
|
|
+ <ImageUpload v-model="uploadedImage" ref="imageUploadRef"/>
|
|
|
|
|
+ <!-- 语音输入按钮 -->
|
|
|
|
|
+ <VoiceInput
|
|
|
|
|
+ @voiceRecognized="handleVoiceRecognized"
|
|
|
|
|
+ @recordingStatusChanged="handleRecordingStatusChanged"
|
|
|
|
|
+ lang="zh-CN"
|
|
|
|
|
+ maxDuration="10"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 终止按钮 -->
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-if="conversationInProgress"
|
|
|
|
|
+ @click="stopStream"
|
|
|
|
|
+ class="stop-btn"
|
|
|
|
|
+ title="终止问答"
|
|
|
|
|
+ >
|
|
|
|
|
+ <img :src="stopicon" alt="停止" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button v-if="!conversationInProgress"
|
|
|
|
|
+ @click="sendMessage">发送</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import {ref, onMounted, defineEmits, computed} from 'vue'
|
|
|
|
|
+import {VisionThink} from '@/api/questions.js'
|
|
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
|
|
+
|
|
|
|
|
+import VoiceInput from '../voice/VoiceInput.vue'
|
|
|
|
|
+
|
|
|
|
|
+// 导入全局状态
|
|
|
|
|
+import { globalState } from '@/utils/globalState.js'
|
|
|
|
|
+// 终止按钮
|
|
|
|
|
+import stopicon from "@/assets/icon/stopicon.png";
|
|
|
|
|
+// 消息组件
|
|
|
|
|
+import {Message} from "@/utils/message/Message.js";
|
|
|
|
|
+
|
|
|
|
|
+// 上传参考图
|
|
|
|
|
+import ImageUpload from '@/components/ImageUpload/index.vue';
|
|
|
|
|
+// 导入getModelIdByType接口
|
|
|
|
|
+import {getModelIdByType, ModelPlatformEnum} from '@/api/teachers.js'
|
|
|
|
|
+import { ModelTypeEnum } from '@/api/teachers.js'
|
|
|
|
|
+
|
|
|
|
|
+// 定义props
|
|
|
|
|
+const props = defineProps({
|
|
|
|
|
+ //根据需求传输
|
|
|
|
|
+ isCourse: { type: Boolean, default: false},
|
|
|
|
|
+ cacheDataKey: { type: String, default: localStorage.getItem('token') + "_ai_visionThink" },
|
|
|
|
|
+ cacheDataHistoryKey: { type: String, default: localStorage.getItem('token') + "_ai_visionThink_history" },
|
|
|
|
|
+ preDialogueList: { type: Array, default: () => []},
|
|
|
|
|
+ replySupplement: { type: String, default: ''},
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 定义emits
|
|
|
|
|
+const emits = defineEmits(['saveProgress'])
|
|
|
|
|
+
|
|
|
|
|
+//历史记录缓存集合
|
|
|
|
|
+const cacheDataHistoryList = ref([]);
|
|
|
|
|
+
|
|
|
|
|
+// 存储上传的图片
|
|
|
|
|
+const uploadedImage = ref('https://learn-ai.com.cn/admin-api/infra/file/29/get/20251217/网页地图level1-32_1765957974334.png');
|
|
|
|
|
+const imageUploadRef = ref(null);
|
|
|
|
|
+
|
|
|
|
|
+// 对话状态变量
|
|
|
|
|
+const conversationInProgress = ref(false); // 对话是否正在进行中
|
|
|
|
|
+const router = useRouter()
|
|
|
|
|
+
|
|
|
|
|
+// modelId响应式变量
|
|
|
|
|
+const modelId = ref(0)
|
|
|
|
|
+// 保存记录
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ try{
|
|
|
|
|
+ // 获取modelId
|
|
|
|
|
+ const modelRes = await getModelIdByType({ type: ModelTypeEnum.VISION_THINK, platform: ModelPlatformEnum.DOUBAO })
|
|
|
|
|
+ modelId.value = modelRes.data
|
|
|
|
|
+ }catch(error){
|
|
|
|
|
+ console.error('保存记录失败:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 取缓存历史记录
|
|
|
|
|
+ let cacheDataHistoryStr = localStorage.getItem(props.cacheDataHistoryKey);
|
|
|
|
|
+ if (cacheDataHistoryStr) {
|
|
|
|
|
+ cacheDataHistoryList.value = JSON.parse(cacheDataHistoryStr) || [];
|
|
|
|
|
+ imageAllList.value.push(...cacheDataHistoryList.value);
|
|
|
|
|
+ }else{
|
|
|
|
|
+ // 加入预置对话
|
|
|
|
|
+ imageAllList.value.push(...props.preDialogueList);
|
|
|
|
|
+ cacheDataHistoryList.value.push(...props.preDialogueList);
|
|
|
|
|
+ if (cacheDataHistoryList.value.length > 0) localStorage.setItem(props.cacheDataHistoryKey, JSON.stringify(cacheDataHistoryList.value));
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 消息列表和输入内容的响应式变量
|
|
|
|
|
+const messages = ref([])
|
|
|
|
|
+const inputMessage = ref('')
|
|
|
|
|
+
|
|
|
|
|
+// 语音输入状态跟踪
|
|
|
|
|
+const isVoiceRecording = ref(false); // 当前是否正在录音
|
|
|
|
|
+const voiceRecognizedText = ref(""); // 实时语音识别结果
|
|
|
|
|
+
|
|
|
|
|
+// 用于控制输入框显示的内容
|
|
|
|
|
+const displayedPrompt = computed({
|
|
|
|
|
+ get() {
|
|
|
|
|
+ // 录音时,显示inputMessage.value + 实时语音识别结果
|
|
|
|
|
+ if (isVoiceRecording.value) {
|
|
|
|
|
+ return inputMessage.value ? `${inputMessage.value} ${voiceRecognizedText.value}` : voiceRecognizedText.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 不录音时,只显示inputMessage.value
|
|
|
|
|
+ return inputMessage.value;
|
|
|
|
|
+ },
|
|
|
|
|
+ set(newValue) {
|
|
|
|
|
+ // 只在用户手动输入时更新inputMessage.value
|
|
|
|
|
+ if (!isVoiceRecording.value) {
|
|
|
|
|
+ inputMessage.value = newValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 语音输入识别结果处理
|
|
|
|
|
+const handleVoiceRecognized = (text) => {
|
|
|
|
|
+ if (isVoiceRecording.value) {
|
|
|
|
|
+ // 在同一次录音过程中,只更新临时变量,不修改inputMessage.value
|
|
|
|
|
+ voiceRecognizedText.value = text;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 在录音结束时,将最终的语音内容追加到inputMessage.value
|
|
|
|
|
+ inputMessage.value = inputMessage.value ? `${inputMessage.value} ${text}` : text;
|
|
|
|
|
+ // 清空临时变量
|
|
|
|
|
+ voiceRecognizedText.value = "";
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 处理录音状态变化
|
|
|
|
|
+const handleRecordingStatusChanged = (status) => {
|
|
|
|
|
+ const wasRecording = isVoiceRecording.value;
|
|
|
|
|
+ isVoiceRecording.value = status;
|
|
|
|
|
+
|
|
|
|
|
+ // 如果是从录音状态切换到非录音状态,需要将临时的语音识别结果追加到inputMessage.value
|
|
|
|
|
+ if (wasRecording && !isVoiceRecording.value) {
|
|
|
|
|
+ if (voiceRecognizedText.value) {
|
|
|
|
|
+ inputMessage.value = inputMessage.value ? `${inputMessage.value} ${voiceRecognizedText.value}` : voiceRecognizedText.value;
|
|
|
|
|
+ // 清空临时变量
|
|
|
|
|
+ voiceRecognizedText.value = "";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 停止操作函数
|
|
|
|
|
+const stopStream = async () => {
|
|
|
|
|
+ // 直接设置为 false
|
|
|
|
|
+ conversationInProgress.value = false;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 发送消息函数
|
|
|
|
|
+const sendMessage = async() => {
|
|
|
|
|
+ if (uploadedImage.value) {
|
|
|
|
|
+ // 标记对话进行中
|
|
|
|
|
+ conversationInProgress.value = true;
|
|
|
|
|
+ // 先保存内容 再置空输入框
|
|
|
|
|
+ let content = inputMessage.value;
|
|
|
|
|
+ inputMessage.value = ''
|
|
|
|
|
+ // 创建用户消息对象,包含可能的图片
|
|
|
|
|
+ const userMessage = {
|
|
|
|
|
+ type: 'user',
|
|
|
|
|
+ content: content,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 如果有上传的图片,添加到用户消息中
|
|
|
|
|
+ if (uploadedImage.value) {
|
|
|
|
|
+ userMessage.imageUrl = uploadedImage.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 添加用户消息到消息列表
|
|
|
|
|
+ imageAllList.value.push(userMessage);
|
|
|
|
|
+ imageAllList.value.push({
|
|
|
|
|
+ type: 'ai',
|
|
|
|
|
+ content: "正在处理您的请求,请稍等",
|
|
|
|
|
+ loading: true
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 通过emit事件通知父组件保存进度
|
|
|
|
|
+ emits('saveProgress', "aiCount", 1)
|
|
|
|
|
+ if (props.isCourse){
|
|
|
|
|
+ emits('saveProgress', "course", 100)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 同步历史记录缓存
|
|
|
|
|
+ cacheDataHistoryList.value.push(userMessage);
|
|
|
|
|
+ localStorage.setItem(props.cacheDataHistoryKey, JSON.stringify(cacheDataHistoryList.value));
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+
|
|
|
|
|
+ const res = await VisionThink({
|
|
|
|
|
+ "modelId": modelId.value,
|
|
|
|
|
+ "prompt":content,
|
|
|
|
|
+ "promptImage":[uploadedImage.value]
|
|
|
|
|
+ });
|
|
|
|
|
+ console.log("视觉理解结果", res);
|
|
|
|
|
+
|
|
|
|
|
+ // 移除加载中的消息
|
|
|
|
|
+ imageAllList.value.pop();
|
|
|
|
|
+
|
|
|
|
|
+ // 添加AI返回的HTML内容
|
|
|
|
|
+ let aiMsg = {
|
|
|
|
|
+ type: 'ai',
|
|
|
|
|
+ content: "",
|
|
|
|
|
+ htmlContent: res.data?.result || "请求失败,请稍后重试!",
|
|
|
|
|
+ };
|
|
|
|
|
+ imageAllList.value.push(aiMsg);
|
|
|
|
|
+
|
|
|
|
|
+ // 添加补充信息
|
|
|
|
|
+ if (props.replySupplement) {
|
|
|
|
|
+ imageAllList.value.push({
|
|
|
|
|
+ type: "system",
|
|
|
|
|
+ content: props.replySupplement,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 同步缓存历史记录
|
|
|
|
|
+ cacheDataHistoryList.value.push(aiMsg);
|
|
|
|
|
+ localStorage.setItem(props.cacheDataHistoryKey, JSON.stringify(cacheDataHistoryList.value));
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('视觉理解失败:', error);
|
|
|
|
|
+ // 移除加载中的消息
|
|
|
|
|
+ imageAllList.value.pop();
|
|
|
|
|
+ // 添加错误消息
|
|
|
|
|
+ imageAllList.value.push({
|
|
|
|
|
+ type: 'ai',
|
|
|
|
|
+ content: "处理失败,请稍后重试"
|
|
|
|
|
+ });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ // 图片生成请求完成后更新状态
|
|
|
|
|
+ conversationInProgress.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果没有上传图片,显示提示信息
|
|
|
|
|
+ Message().error('请先上传参考图!', true);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 调用子组件的方法清除预览图
|
|
|
|
|
+ imageUploadRef.value?.clearPreview();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// 生成图片
|
|
|
|
|
+import { ElIcon } from 'element-plus'
|
|
|
|
|
+import {
|
|
|
|
|
+ Back,
|
|
|
|
|
+ DArrowRight,
|
|
|
|
|
+ Download,
|
|
|
|
|
+ Refresh,
|
|
|
|
|
+ RefreshLeft,
|
|
|
|
|
+ RefreshRight,
|
|
|
|
|
+ Right,
|
|
|
|
|
+ ZoomIn,
|
|
|
|
|
+ ZoomOut,
|
|
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+const imageAllList = ref([]) // 对话的消息列表
|
|
|
|
|
+const imageList = ref([]) // image 列表
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+@use 'sass:math';
|
|
|
|
|
+// 定义rpx转换函数
|
|
|
|
|
+@function rpx($px) {
|
|
|
|
|
+ @return math.div($px, 750) * 100vw;
|
|
|
|
|
+}
|
|
|
|
|
+// 用户图片列表样式
|
|
|
|
|
+.user-image-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ margin-top: rpx(5);
|
|
|
|
|
+}
|
|
|
|
|
+//===========================================全局除了删除侧边栏内容唯一改动的地方
|
|
|
|
|
+.number-people {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ background-color: #ece9fd;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-box {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ // margin-top: rpx(10);
|
|
|
|
|
+ // margin-bottom: rpx(10);
|
|
|
|
|
+ margin: rpx(7);
|
|
|
|
|
+ border-radius: rpx(15);
|
|
|
|
|
+ background: rgba($color: #ffffff, $alpha: 0.5);
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+//左侧展览区图标
|
|
|
|
|
+.img-box {
|
|
|
|
|
+ margin-top: rpx(50);
|
|
|
|
|
+ color: #a39dce;
|
|
|
|
|
+}
|
|
|
|
|
+// 对话框
|
|
|
|
|
+.chat-dialog {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.message-list {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ padding: rpx(15);
|
|
|
|
|
+}
|
|
|
|
|
+/* 自定义滚动条样式 */
|
|
|
|
|
+.message-list::-webkit-scrollbar {
|
|
|
|
|
+ width: rpx(2); /* 滚动条宽度 */
|
|
|
|
|
+}
|
|
|
|
|
+.message-list::-webkit-scrollbar-track {
|
|
|
|
|
+ background: #f1effd; /* 滚动条轨道背景色 */
|
|
|
|
|
+ border-radius: rpx(4);
|
|
|
|
|
+}
|
|
|
|
|
+.message-list::-webkit-scrollbar-thumb {
|
|
|
|
|
+ background: #e2ddfc; /* 滚动条滑块颜色 */
|
|
|
|
|
+ border-radius: rpx(4);
|
|
|
|
|
+}
|
|
|
|
|
+.message-list::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
+ background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-list .user-message {
|
|
|
|
|
+ background-color: #ffffff;
|
|
|
|
|
+ margin-left: auto; // 消息靠右显示
|
|
|
|
|
+ margin-right: 0; // 重置右边距
|
|
|
|
|
+ max-width: rpx(400);
|
|
|
|
|
+ font-size: rpx(8);
|
|
|
|
|
+ width: fit-content; // 宽度随文字内容变化
|
|
|
|
|
+ border-radius: rpx(5);
|
|
|
|
|
+ padding: rpx(5);
|
|
|
|
|
+ text-align: left; // 文字左对齐
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.message-list .ai-message {
|
|
|
|
|
+ background-color: #ffdd55;
|
|
|
|
|
+ margin-left: 0; // 消息靠左显示
|
|
|
|
|
+ margin-right: auto; // 重置右边距
|
|
|
|
|
+ margin-bottom: rpx(10);
|
|
|
|
|
+ width: fit-content;
|
|
|
|
|
+ max-width: rpx(400);
|
|
|
|
|
+ padding: rpx(5);
|
|
|
|
|
+ font-size: rpx(8);
|
|
|
|
|
+ border-radius: rpx(5);
|
|
|
|
|
+ text-align: left; // 文字左对齐
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 加载动画效果
|
|
|
|
|
+.loading-dots {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ margin-left: rpx(5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.loading-dots .dot {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ width: rpx(3);
|
|
|
|
|
+ height: rpx(3);
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background-color: #333;
|
|
|
|
|
+ margin: 0 rpx(1);
|
|
|
|
|
+ animation: loading-dot 1.4s infinite ease-in-out both;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.loading-dots .dot:nth-child(1) {
|
|
|
|
|
+ animation-delay: -0.32s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.loading-dots .dot:nth-child(2) {
|
|
|
|
|
+ animation-delay: -0.16s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@keyframes loading-dot {
|
|
|
|
|
+ 0%, 80%, 100% {
|
|
|
|
|
+ transform: scale(0);
|
|
|
|
|
+ }
|
|
|
|
|
+ 40% {
|
|
|
|
|
+ transform: scale(1);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+.image-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* AI返回的HTML内容样式 */
|
|
|
|
|
+.ai-html-content {
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ border-radius: 5px;
|
|
|
|
|
+ background-color: rgba(255, 255, 255, 0.8);
|
|
|
|
|
+ max-width: 100%;
|
|
|
|
|
+ overflow-x: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 确保HTML内容中的图片适应容器 */
|
|
|
|
|
+.ai-html-content img {
|
|
|
|
|
+ max-width: 100%;
|
|
|
|
|
+ height: auto;
|
|
|
|
|
+ border-radius: 3px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 确保HTML内容中的文本样式 */
|
|
|
|
|
+.ai-html-content p {
|
|
|
|
|
+ margin: 5px 0;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+.content-demo {
|
|
|
|
|
+ background-color: #f4f2fa;
|
|
|
|
|
+ border-radius: 15px;
|
|
|
|
|
+ padding: 30px 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.input-section {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ padding: rpx(10);
|
|
|
|
|
+ gap: rpx(5);
|
|
|
|
|
+ .speech-btn {
|
|
|
|
|
+ padding: rpx(5) rpx(10);
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid #ffce1b;
|
|
|
|
|
+ border-radius: rpx(5);
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ &.recording {
|
|
|
|
|
+ background: #ffeeba;
|
|
|
|
|
+ border-color: #ffc107;
|
|
|
|
|
+
|
|
|
|
|
+ .el-icon {
|
|
|
|
|
+ color: #dc3545;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .el-icon {
|
|
|
|
|
+ font-size: rpx(8);
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 终止按钮样式
|
|
|
|
|
+ .stop-btn {
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ img {
|
|
|
|
|
+ width: rpx(20);
|
|
|
|
|
+ height: rpx(20);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+.input-section input {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ padding: rpx(5);
|
|
|
|
|
+ font-size: rpx(7);
|
|
|
|
|
+ border: 1px solid #ccc;
|
|
|
|
|
+ border-radius: rpx(5);
|
|
|
|
|
+}
|
|
|
|
|
+.input-section button {
|
|
|
|
|
+ padding: rpx(5) rpx(15);
|
|
|
|
|
+ background: linear-gradient(
|
|
|
|
|
+ to bottom,
|
|
|
|
|
+ #fee78a,
|
|
|
|
|
+ #ffce1b
|
|
|
|
|
+ ); /* 设置悬停、聚焦、点击状态下的背景色 */
|
|
|
|
|
+ color: black;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ font-size: rpx(7);
|
|
|
|
|
+ border-radius: rpx(5);
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ box-shadow: 0 0px 2px rgba(0, 0, 0, 0.3);
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.image-upload-section {
|
|
|
|
|
+ padding: rpx(10);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|