<template>
	<view class="status-bar"></view>
	<!-- ===== 新建对话类型选择弹窗 ===== -->
	<view v-if="showNewChatModal" class="ncd-overlay" @click="closeNewChatModal">
		<view class="ncd-card" @click.stop>
			<view class="ncd-header">
				<view class="ncd-title-eng"><text>NEW</text></view>
				<text class="ncd-title-zh">创建对话</text>
				<uni-icons type="closeempty" color="#ff0000" size="24" @click="closeNewChatModal"></uni-icons>
			</view>
			<text>选择对话类型，开启全新会话体验</text>
			<view class="ncd-options">
				<view class="ncd-option ncd-normal">
					<view class="ncd-opt-icon">
						<uni-icons type="chat" color="#000000" size="30"></uni-icons>
					</view>
					<view class="ncd-opt-title" @click="selectNormalChat">
						<view class="ncd-opt-title-1">普通会话</view>
						<view class="ncd-opt-title-2">与AI自由对话,探索任何话题</view>
					</view>
					<uni-icons type="arrow-right" class="arrow-right-style"></uni-icons>
				</view>
		<view class="ncd-option ncd-intelligence" @click="selectAgentChat">
				<view class="ncd-opt-icon">
					<uni-icons type="star" color="#ffffff" size="30"></uni-icons>
				</view>
				<view class="ncd-opt-title">
					<view class="ncd-opt-title-1">智能体会话</view>
					<view class="ncd-opt-title-2">选择专属智能体，获得精准专业服务</view>
				</view>
				<uni-icons type="arrow-right" class="arrow-right-style"></uni-icons>
			</view>
			</view>
		</view>
	</view>

	<!-- 智能体选择弹窗 -->
	<view class="agent-modal-mask" v-if="showAgentModal" @click="closeAgentModal">
		<view class="agent-modal" @click.stop>
			<view class="agent-modal-header">
				<text class="agent-modal-title">选择智能体</text>
				<view class="agent-modal-close" @click="closeAgentModal">
					<uni-icons type="closeempty" size="20" color="#999"></uni-icons>
				</view>
			</view>
			<scroll-view class="agent-list" scroll-y="true">
				<view v-if="agentLoading" class="agent-loading">
					<uni-icons type="spinner-cycle" size="24" color="#1677ff"></uni-icons>
					<text class="agent-loading-text">加载智能体列表...</text>
				</view>
				<view v-else-if="agentList.length === 0" class="agent-empty">
					<text class="agent-empty-text">暂无可用智能体</text>
				</view>
				<view v-else
					v-for="agent in agentList"
					:key="agent._id"
					class="agent-card"
					@click="handleAgentSelect(agent)"
				>
					<view class="agent-card-header">
						<view class="agent-avatar">
							<uni-icons type="vip" size="22" color="#fff"></uni-icons>
						</view>
						<view class="agent-card-info">
							<text class="agent-card-title">{{ agent.title }}</text>
							<!-- <text class="agent-card-category">{{ agent.category_name }}</text> -->
						</view>
					</view>
					<text class="agent-card-desc">{{ agent.description }}</text>
					<!-- <text class="agent-card-welcome" v-if="agent.welcome_message">{{ agent.welcome_message }}</text> -->
				</view>
			</scroll-view>
			<view class="agent-modal-footer">
				<view class="agent-modal-btn agent-modal-btn-cancel" @click="closeAgentModal">
					<text>取消</text>
				</view>
			</view>
		</view>
	</view>

	<view class="chat-page page-container">
		<!-- 弹窗出来时的笼罩层 -->
		<view v-if="isChatSidebar" class="mask" @click="handleChatSidebar"></view>
		<!-- 抽屉侧边栏 -->
		<view class="chat-sidebar" :class="{'sidebar-show' : isChatSidebar}">
			<ChatSidebar :chatList="UserConversations" v-model:showNewChatModal="showNewChatModal"
				v-model:currentSessionId="currentSessionId" v-model:chatType="ChatType"
				@refresh-conversations="takeUserConversations" @select-chat="isChatSidebar = false">
			</ChatSidebar>
		</view>

		<view class="chat-wrapper">
			<view class="chat-hearder">
				<view class="chat-btn-group">
					<!-- 侧边栏显示按钮 -->
					<view class="head-btn" @click="handleChatSidebar">
						<view class="iconfont icon-caidan"></view>
					</view>
					<view class="head-btn" @click="goWorkSpace">
						<view class="iconfont icon-wenjianjia"></view>
					</view>
				<view class="head-btn" @click="goCloudDatabase">
					<view class="iconfont icon-cloud"></view>
				</view>
				<view class="head-btn" @click="handleRefresh">
					<uni-icons type="refreshempty" size="24" color="#52c41a"></uni-icons>
				</view>
				<view class="head-btn log-out" @click="logOut">
						<view class="iconfont icon-tuichu"></view>
					</view>
				</view>

			</view>

			<!-- WYXD 文件入口（从工作区 AI 修改进入时显示） -->
			<view v-if="hasWyxdFile" class="wyxd-banner">
				<view class="wyxd-banner-icon iconfont icon-wenjian"></view>
				<view class="wyxd-banner-text" @click="goWyxdViewer">查看: {{ wyxdFileName }}</view>
				<view class="wyxd-banner-arrow" @click="goWyxdViewer">&rsaquo;</view>
				<view class="wyxd-banner-close" @click.stop="hasWyxdFile = false">&times;</view>
			</view>

			<view class="main-chat">
				<scroll-view class="chat-messages" direction="vertical" scroll-y :scroll-into-view="scrollToView"
					:upper-threshold="50" :scroll-with-animation="!isThinking" @scroll="onScroll" @scrolltoupper="onScrollToUpper">
				<!-- 加载更多提示 -->
				<view v-if="(ChatType === 0 && isAILoadingMore) || (ChatType === 1 && isFriendLoadingMore) || (ChatType === 2 && isGroupLoadingMore)" class="load-more-tip">
					<text>加载更多消息...</text>
				</view>
					<!-- 消息加载中弹窗（仅会话变化时显示） -->
					<view v-if="isLoadingMessages" class="messages-loading-overlay">
						<view class="messages-loading-card">
							<view class="bubble-loading-dots">
								<view class="bubble-dot"></view>
								<view class="bubble-dot"></view>
								<view class="bubble-dot"></view>
							</view>
							<text class="messages-loading-text">加载中...</text>
						</view>
					</view>
					<!-- 与AI的对话内容展示 -->
					<template v-for="(message, index) in currentMessages" :key="index">
						<view v-if="ChatType === 0 && message.role !== 'tool'" class="chat-message" :id="'msg-' + index"
							:class="{'message-user':message.role==='user'}">
							<!-- <view class="chat-avatar" :class="{'chat-avatar-user':message.role==='user'}">
						<image v-if="message.role === 'user' && UserData?.avatar" :src="UserData.avatar"
							class="friend-avatar" mode="aspectFill"></image>
						<view v-else-if="message.role === 'assistant'" class="iconfont icon-Robot"></view>
						<view v-else class="iconfont icon-yonghuziliao"></view>
					</view> -->
							<view v-if="message.role ==='user'" class="chat-avatar"
								:class="{'chat-avatar-user':message.role==='user'}">
								<image v-if="message.role === 'user' && UserData?.avatar" :src="UserData.avatar"
									class="friend-avatar" mode="aspectFill"></image>
								<view v-else class="iconfont icon-yonghuziliao"></view>
								<!-- <view v-if="message.role ==='assistant'" class="iconfont icon-Robot"></view> -->
								<!-- <view v-if="message.role ==='user'" class="iconfont icon-yonghuziliao"></view> -->
							</view>
							<view class="chat-content"
								:class="{'chat-content-user':message.role==='user', 'chat-content-assistant':message.role==='assistant'}"
								@click="handleChatContentClick($event, message)"
								v-if="(message.content && String(message.content).trim() !== '') || message._streaming">
								<!-- 流式加载中：显示跳动的点 -->
								<view
									v-if="message._streaming && (!message.content || String(message.content).trim() === '')"
									class="bubble-loading-dots">
									<view class="bubble-dot"></view>
									<view class="bubble-dot"></view>
									<view class="bubble-dot"></view>
								</view>
								<!-- 文本内容 -->
								<view v-if="parseFileInfo(message.content).textContent"
									v-html="pareseMarkdown(parseFileInfo(message.content).textContent)"></view>
								<!-- 文件卡片列表 -->
								<view v-if="parseFileInfo(message.content).files.length > 0" class="message-file-list">
									<view v-for="(file, idx) in parseFileInfo(message.content).files" :key="idx"
										class="file-item">
										<!-- 图片：jpg/png/gif/webp 等 -->
										<image
											v-if="['jpg','jpeg','png','gif','webp','bmp'].includes(file.extendName.toLowerCase())"
											:src="file.url" mode="widthFix" class="message-image"
											@click="previewImage(file.url)"></image>
										<!-- 普通文件：doc/xlsx/zip/pdf 等 -->
										<view v-else class="message-file" @click="openFile(file.url)">
											<view class="file-icon">📄</view>
											<view class="file-name">{{ file.name }}</view>
											<view class="file-size">{{ formatFileSize(file.fileSize) }}</view>
										</view>
									</view>
								</view>
							</view>
						</view>
					</template>
					<!-- 与好友的对话内容展示 -->
					<view v-if="ChatType === 1" class="chat-message" v-for="(message, index) in currentMessages"
						:key="index" :id="'msg-' + index" :class="{'message-user':message.sender === UserId}">
						<view class="chat-avatar" :class="{'chat-avatar-user':message.sender === UserId}">
							<!-- 自己的消息显示自己的头像 -->
							<image v-if="message.sender === UserId && UserData?.avatar" :src="UserData.avatar"
								class="friend-avatar" mode="aspectFill"></image>
							<!-- 好友的消息显示好友的头像 -->
							<image v-else-if="takeTalkUserAvatar(message.sender)"
								:src="takeTalkUserAvatar(message.sender)" class="friend-avatar" mode="aspectFill">
							</image>
							<!-- 没有头像时显示默认图标 -->
							<view v-else class="iconfont icon-yonghuziliao"></view>
						</view>
						<view class="chat-content" :class="{'chat-content-user':message.sender === UserId}"
							@click="handleChatContentClick($event, message)">
							<view v-if="message.content && String(message.content).trim() !== ''"
								v-html="pareseMarkdown(message.content)"></view>
							<!-- 2. 图片 + 文件 渲染（核心新增） -->
							<view v-if="message.contentJson" class="message-file-list">
								<!-- 遍历文件/图片列表 -->
								<view v-for="(file, idx) in JSON.parse(message.contentJson)" :key="idx"
									class="file-item">
									<!-- 图片：jpg/png/gif/webp 等 -->
									<image
										v-if="['jpg','jpeg','png','gif','webp','bmp'].includes(file.extendName.toLowerCase())"
										:src="file.url" mode="widthFix" class="message-image"
										@click="previewImage(file.url)"></image>

									<!-- 普通文件：doc/xlsx/zip/pdf 等 -->
									<view v-else class="message-file" @click="openFile(file.url)">
										<view class="file-icon">📄</view>
										<view class="file-name">{{ file.name }}</view>
										<view class="file-size">{{ formatFileSize(file.fileSize) }}</view>
									</view>
								</view>
							</view>
						</view>
					</view>
					<!-- 与群聊的对话内容展示 -->
					<view v-if="ChatType === 2" class="chat-message" v-for="(message, index) in currentMessages"
						:key="index" :id="'msg-' + index" :class="{'message-user':message.sender === UserId}">
						<view class="chat-avatar" :class="{'chat-avatar-user':message.sender === UserId}">
							<!-- 自己的消息显示自己的头像 -->
							<image v-if="message.sender === UserId && UserData?.avatar" :src="UserData.avatar"
								class="friend-avatar" mode="aspectFill"></image>
							<!-- 群成员的消息显示群成员的头像 -->
							<image v-else-if="groupMemberList.length > 0 && getGroupMemberAvatarById(message.sender)"
								:src="getGroupMemberAvatarById(message.sender)" class="friend-avatar" mode="aspectFill">
							</image>
							<!-- 没有头像时显示默认图标 -->
							<view v-else class="iconfont icon-yonghuziliao"></view>
						</view>
						<view class="chat-content" :class="{'chat-content-user':message.sender === UserId}"
							@click="handleChatContentClick($event, message)">
							<view v-if="message.message && String(message.message).trim() !== ''"
								v-html="pareseMarkdown(message.message)"></view>
							<!-- 2. 图片 + 文件 渲染（核心新增） -->
							<view v-if="message.contentJson" class="message-file-list">
								<!-- 遍历文件/图片列表 -->
								<view v-for="(file, idx) in JSON.parse(message.contentJson)" :key="idx"
									class="file-item">
									<!-- 图片：jpg/png/gif/webp 等 -->
									<image
										v-if="['jpg','jpeg','png','gif','webp','bmp'].includes(file.extendName.toLowerCase())"
										:src="file.url" mode="widthFix" class="message-image"
										@click="previewImage(file.url)"></image>

									<!-- 普通文件：doc/xlsx/zip/pdf 等 -->
									<view v-else class="message-file" @click="openFile(file.url)">
										<view class="file-icon">📄</view>
										<view class="file-name">{{ file.name }}</view>
										<view class="file-size">{{ formatFileSize(file.fileSize) }}</view>
									</view>
								</view>
							</view>
						</view>
					</view>
					<!-- 底部锚点：两个交替切换，确保流式时每次都能触发滚动到底部 -->
					<view id="chat-bottom-a" style="height: 1rpx;"></view>
					<view id="chat-bottom-b" style="height: 1rpx;"></view>
				</scroll-view>
				<view class="chat-interactive-container" :class="{ 'interactive-disabled': isThinking }">
					<view class="chat-interactive-group">
						<view class="chat-interactive-btn" :class="{ disabled: isThinking }"
							@click="!isThinking && uploadPhoto()">拍照上传</view>
						<view class="chat-interactive-btn" :class="{ disabled: isThinking }"
							@click="!isThinking && uploadFile()">上传文件</view>
					</view>
					<view class="image-list" v-if="previewFileArray.length > 0">
						<view class="image-tags">
							<view class="image-tag" v-for="(item, index) in previewFileArray" :key="index">
								<text class="tag-name">{{ item.name }}</text>
								<text class="tag-close" @click.stop="deleteImage(index)">×</text>
							</view>
						</view>
					</view>
					<view class="chat-input-container" :class="{ 'input-disabled': isThinking }">
						<textarea class="message-input"
							:placeholder="isThinking ? (isSelfSent ? 'AI 正在回复中...' : '电脑正在运行，请稍后') : '输入消息...'"
							:disabled="isThinking" v-model="textMessage" auto-height></textarea>
						<view v-if="isThinking" class="input-btn-group stop-message-btn" @click="stopConversation">
							<view class="iconfont icon-tingzhi"></view>
						</view>
						<view v-else class="input-btn-group send-message-btn" @click="sendMessage()">
							<view class="iconfont icon-fasong"></view>
						</view>
					</view>
				</view>
			</view>

		</view>
	</view>

	<!-- ===== 消息详情弹窗 ===== -->
	<view v-if="showMessageModal" class="ncd-overlay" @click="closeMessageDetail">
		<view class="ncd-card message-detail-card" @click.stop>
			<view class="ncd-header">
				<text class="ncd-title-zh">消息详情</text>
				<uni-icons type="closeempty" color="#ff0000" size="24" @click="closeMessageDetail"></uni-icons>
			</view>
			<scroll-view class="message-detail-content" scroll-y>
				<view v-if="detailLinks.length" class="detail-links-section">
					<view class="detail-links-title">文件 ({{ detailLinks.length }})</view>
					<view v-for="(link, i) in detailLinks" :key="i" class="detail-link-item"
						@click="openLink(link.url)">
						<text class="link-text">{{ link.name }}</text>
					</view>
				</view>
				<view v-else class="detail-empty">
					<text>无可下载文件</text>
				</view>
			</scroll-view>
		</view>
	</view>

	<!-- ===== 自定义下载提示（层级高于弹窗） ===== -->
	<view v-if="downloadToast.show" class="download-toast-overlay" :class="downloadToast.show ? '' : ''">
		<view class="download-toast-card" :class="'download-toast-' + downloadToast.type">
			<view v-if="downloadToast.type === 'loading'" class="download-toast-icon">
				<view class="toast-dot"></view>
				<view class="toast-dot"></view>
				<view class="toast-dot"></view>
			</view>
			<text v-else class="download-toast-icon">{{ downloadToast.type === 'success' ? '✓' : '✕' }}</text>
			<text class="download-toast-text">{{ downloadToast.message }}</text>
		</view>
	</view>


</template>

<script setup>
	import {
		computed,
		getCurrentInstance,
		nextTick,
		onMounted,
		ref,
		watch
	} from 'vue';
	import {
		onLoad,
		onShow,
		onUnload
	} from '@dcloudio/uni-app';
	import ChatSidebar from '@/components/ChatSidebar.vue'
	import {
		getUserConversations,
		getConversationMessages,
		addMessageDict,
		createWorkspace,
		createConversation,
		getUserInfo,
		getUserAvatar,
		conversationMessageDownload,
		getAgentList
	} from '@/utils/cloud-api.js'
	import {
		getToken,
		getCurrentSessionId,
		getTaskCallId,
		getChatType,
		getReceiverId
	} from '@/utils/user-info.js'
	import snarkdown from 'snarkdown'
	// import mpHtml from '@/uni_modules/mp-html/components/mp-html/mp-html.vue'
	import {
		useSocketStore
	} from '@/stores/socket.js';

	import {
		getChatFriend,
		getGroup,
		getFriendMessages,
		getGroupMessages,
		getGroupMemberList,
		uploadFileToServer
	} from '@/utils/friend-api.js'
	import {
		useFriendSocketStore
	} from '@/stores/friend-socket.js'

	import {
		chooseFile
	} from '@/uni_modules/lime-choose-file'
	import socketManager from '../../utils/socket';
import { openFile as openFileNative, downloadAndOpen } from '@/utils/fileOpener.js'

	const friendSocketStore = useFriendSocketStore()
	// 连接实时通讯eriendsocket
	const handleFriendConnect = () => {
		if (friendSocketStore.isConnected) return
		if (!userToken.value || !UserId.value) {
			console.warn('Token或UserId未准备好');
			return
		}
		friendSocketStore.connect({
			token: userToken.value,
			UserId: UserId.value
		})
	}
	//store
	const socketStore = useSocketStore()

	// 聊天类型（ai，好友，群聊）
	const ChatType = ref(0)

	// WYXD 关联数据（从 WorkSpace 的「AI修改」进入时携带）
	const wyxdWorkspaceId = ref('')
	const wyxdFilePath = ref('')
	const wyxdFileName = ref('')
	const wyxdUserToken = ref('')
	const hasWyxdFile = ref(false)


	// marked.setOptions({
	// 	breaks: true, // 自动把换行符转成 <br> 换行
	// 	gfm: true, // 开启 GitHub 标准 Markdown 语法
	// })



	// 将 Markdown 表格转为 HTML 表格（snarkdown 不支持表格语法）
	const convertMarkdownTable = (text) => {
		const lines = text.split('\n')
		let result = []
		let inTable = false
		let tableRows = []
		let alignments = []

		for (let i = 0; i < lines.length; i++) {
			const line = lines[i].trim()
			// 检测表格行：以 | 开头并以 | 结尾
			// 修复：原正则 /^\|.+|&/ 中 | 未转义，导致匹配所有行
			const isTableLine = /^\|.+\|$/.test(line)

			if (isTableLine) {
				// 分隔行（如 |---|---| 或 |:---:|）
				// 修复：原正则在字符类中包含了 | 导致分隔行误判
				if (/^\|[\s\-:]+\|[\s\-:]+\|$/.test(line)) {
					alignments = line.split('|').filter(c => c.trim()).map(c => {
						if (c.trim().startsWith(':') && c.trim().endsWith(':')) return 'center'
						if (c.trim().endsWith(':')) return 'right'
						return 'left'
					})
					continue
				}
				tableRows.push(line)
				inTable = true
			} else {
				if (inTable && tableRows.length > 0) {
					// 结束表格，渲染为 HTML
					result.push(renderTable(tableRows, alignments))
					tableRows = []
					alignments = []
					inTable = false
				}
				result.push(line)
			}
		}
		// 处理文末的表格
		if (inTable && tableRows.length > 0) {
			result.push(renderTable(tableRows, alignments))
		}
		return result.join('\n')
	}

	// 渲染表格行
	const renderTable = (rows, alignments) => {
		let html = '<table border="1" style="border-collapse:collapse;width:100%;margin:8px 0">'
		rows.forEach((row, index) => {
			const tag = index === 0 ? 'th' : 'td'
			const cells = row.split('|').filter(c => c.trim() !== '')
			html += '<tr>'
			cells.forEach((cell, ci) => {
				const align = alignments[ci] ? ` style="text-align:${alignments[ci]}"` : ''
				let cellContent = cell.trim()
				// 1. 先处理 Markdown 粗体 **...**
				cellContent = cellContent.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
				// 2. 再处理 Markdown 斜体 *...*
				cellContent = cellContent.replace(/\*(.+?)\*/g, '<em>$1</em>')
				// 3. 转义剩余的 _ 和 *，防止 snarkdown 将其解析为斜体/加粗
				cellContent = cellContent
					.replace(/_/g, '&#95;')
					.replace(/\*/g, '&#42;')
				html +=
					`<${tag}${align} style="padding:6px 10px;border:1px solid #ddd">${cellContent}</${tag}>`
			})
			html += '</tr>'
		})
		html += '</table>'
		return html
	}

	// 转义所有单独的 _，但保护 __（双下划线粗体）
	// snarkdown 的 [_*] 没有任何单词边界限制，会把单词中的 _ 误解析为斜体标记
	// 如 watermark_mcp、char_spacing 中的 _ 会打开永不闭合的 <em>，导致后面全部斜体
	const escapeLoneUnderscores = (text) => {
		// 1. 先用占位符保护 __ 双下划线（snarkdown 的粗体语法）
		text = text.replace(/__/g, '\x00\x00')
		// 2. 将剩余的单个 _ 转为 HTML 实体，snarkdown 不会处理实体
		text = text.replace(/_/g, '&#95;')
		// 3. 还原 __ 占位符
		text = text.replace(/\x00\x00/g, '__')
		return text
	}

	// 判断链接是否为可下载文件
	const isDownloadLink = (url) => {
		const supportedExtensions = ['html', 'htm', 'xlsx', 'xls', 'pdf', 'zip', 'rar', '7z', 'md', 'markdown',
			'docx', 'doc', 'pptx', 'ppt', 'txt', 'csv', 'json', 'xml', 'yaml', 'yml',
			'jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp', 'ico', 'tiff', 'tif', 'dxf'
		]
		const extPattern = supportedExtensions.join('|')
		const extRegex = new RegExp(`\\.(${extPattern})(?:\\?|$)`, 'i')
		return extRegex.test(url)
	}
	
	// 渲染下载按钮 HTML
	const renderDownloadBtn = (href, displayName) => {
		const ext = ((href.split('.').pop() || '').split('?')[0] || '').toLowerCase()
		const fileInfo = getFileTypeInfo(ext)
		const name = displayName || extractFileNameFromUrl(href)
		return '<span class="chat-inline-link chat-download-btn" data-url="' + href + '">'
			+ '<span class="chat-dl-icon">' + fileInfo.emoji + '</span>'
			+ '<span class="chat-dl-name">' + name + '</span>'
			+ '</span>'
	}
	
	// 支持的文件扩展名（用于原始 URL 检测）
	const supportedExtensions = ['html', 'htm', 'xlsx', 'xls', 'pdf', 'zip', 'rar', '7z', 'md', 'markdown',
		'docx', 'doc', 'pptx', 'ppt', 'txt', 'csv', 'json', 'xml', 'yaml', 'yml',
		'jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp', 'ico', 'tiff', 'tif', 'dxf'
	]
	const extPattern = supportedExtensions.join('|')

	// Markdown转HTML节点
	const pareseMarkdown = (content) => {
		if (!content) return ''

		content = sanitizeContent(content)

		try {
			// 转义单词中的单下划线，防止 snarkdown 误解析为斜体
			content = escapeLoneUnderscores(content)

			// 修复1: URL 中的中文括号编码，防止 snarkdown 截断URL
			// [text](http://xxx/高二(1)班.xlsx) → [text](http://xxx/高二%281%29班.xlsx)
			content = content.replace(
				/\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g,
				(m, name, url) => '[' + name + '](' + url.replace(/\(/g, '%28').replace(/\)/g, '%29') + ')'
			)

			// 修复2: 误用括号 [高二](1)班成绩表 → 高二(1)班成绩表
			// 当 (数字/短文本) 后紧跟中文时，说明不是真实URL，还原为纯文本
			content = content.replace(
				/\[([^\]]+)\]\((\d{1,2}|[^\s)]{1,3})\)(?=[\u4e00-\u9fff])/g,
				'$1($2)'
			)

			// 先转换表格，再用 snarkdown 处理其他 Markdown
			content = convertMarkdownTable(content)
			let html = snarkdown(content)
			
			// 步骤1：暂存 <a> 标签，避免 raw URL 替换误伤 href 内的链接
			const linkPlaceholders = []
			html = html.replace(
				/<a\s+href="([^"]*)"[^>]*>(.*?)<\/a>/gi,
				(match, href, innerText) => {
					const idx = linkPlaceholders.length
					linkPlaceholders.push({ href, innerText })
					return '\x00LINK_' + idx + '\x00'
				}
			)
			
			// 步骤2：将正文中的原始文件 URL 替换为下载按钮
			const rawUrlRegex = new RegExp(
				`https?://[^\\s<>"'']+\\.(${extPattern})(?:[?#][^\\s<>"'']*)?`,
				'gi'
			)
			html = html.replace(rawUrlRegex, (url) => {
				return renderDownloadBtn(url)
			})
			
			// 步骤3：恢复 <a> 标签并分类处理
			html = html.replace(/\x00LINK_(\d+)\x00/g, (_, idx) => {
				const { href, innerText } = linkPlaceholders[idx]
				if (isDownloadLink(href)) {
					const plainText = innerText
						.replace(/<[^>]*>/g, ' ')
						.replace(/&nbsp;/gi, ' ')
						.replace(/\s+/g, ' ')
						.trim()
					return renderDownloadBtn(href, plainText)
				}
				// 普通链接保持原有样式
				return '<span class="chat-inline-link" data-url="' + href + '">' + innerText + '</span>'
			})
			
			return html
		} catch (e) {
			console.error('解析失败', e)
			return content
		}
	}

	// 完全移除 <script> 标签及其内容，并检测表单标签
	const sanitizeContent = (str) => {
		// 检测是否存在 script 标签或表单相关标签
		const hasScript = /<script\b[^>]*>([\s\S]*?)<\/script>/i.test(str);
		const hasFormTags = /<(form|input|select|textarea|button)\b[^>]*>/i.test(str);

		// 先移除所有 script 标签
		let cleanedStr = str.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gi, '');
		// 3. 【新增】检测并转义 minimax:tool_call 标签（只转义这个，不影响其他内容）
		cleanedStr = cleanedStr
			// 转义 <minimax:tool_call> ... </minimax:tool_call>
			.replace(/<\/?minimax:tool_call>/g, m => m.replace(/</g, '&lt;').replace(/>/g, '&gt;'))
			// 转义 <|FunctionCallEnd|> 这类AI标记
			.replace(/<\|[\w]+?\|>/g, m => m.replace(/</g, '&lt;').replace(/>/g, '&gt;'));

		// 如果存在脚本标签或表单标签，添加红色加粗的h1提示
		if (hasScript || hasFormTags) {
			const warningHtml = '<h2 style="color: red; font-weight: bold;">表单仅预览，不可操作！！！</h2>';
			return warningHtml + cleanedStr;
		}

		return cleanedStr;
	}

	// 操作对话内容中的图片和文件
	// 图片预览
	const previewImage = (url) => {
		uni.previewImage({
			urls: [url]
		})
	}
	// 打开文件
	const openFile = (url) => {
		downloadAndOpen(url);
	}
	// 文件大小格式化
	const formatFileSize = (size) => {
		if (!size) return '0KB'
		if (size < 1) {
			return (size * 1024).toFixed(0) + 'KB'
		}
		return size.toFixed(2) + 'MB'
	}

	// 从消息 content 中提取文件信息
	// 格式: [文件信息:[{...}]]
	const parseFileInfo = (content) => {
		if (!content || typeof content !== 'string') return {
			textContent: content || '',
			files: []
		}
		const match = content.match(/\[文件信息:(\[.*?\])\]/)
		if (!match) return {
			textContent: content,
			files: []
		}
		try {
			const files = JSON.parse(match[1])
			const textContent = content.replace(match[0], '').trim()
			return {
				textContent,
				files
			}
		} catch (e) {
			console.error('解析文件信息失败:', e)
			return {
				textContent: content,
				files: []
			}
		}
	}




	// 新建会话相关
	// 新建普通会话
	const selectNormalChat = async () => {
		try {
			const chars = '0123456789abcdef'
			let name = ''
			for (let i = 0; i < 24; i++) {
				name += chars[Math.floor(Math.random() * chars.length)]
			}
			const workspaceId = await createWorkspace(userToken.value, name)
			if (!workspaceId) {
				uni.showToast({
					title: '工作区创建失败',
					icon: 'none'
				})
				return
			}
			const loginInfo = uni.getStorageSync('yxd_login_info')
			let userName = ''
			if (loginInfo) {
				userName = loginInfo.username || '';
			}
			const conversationId = await createConversation(userToken.value, workspaceId, '新会话', userName)
			if (!conversationId) {
				uni.showToast({
					title: '新会话创建失败',
					icon: 'none'
				})
				return
			}
			closeNewChatModal();
			takeUserConversations();

		} catch (error) {
			// ❌ 统一捕获异步错误，防止崩溃
			console.error('新建普通会话失败：', error)
			uni.showToast({
				title: '创建会话失败，请重试',
				icon: 'none'
			})
		}
	}

	const showNewChatModal = ref(false)
	const closeNewChatModal = () => {
		showNewChatModal.value = false;
	}

	// 智能体选择相关
	const showAgentModal = ref(false)
	const agentList = ref([])
	const agentLoading = ref(false)

	const selectAgentChat = async () => {
		try {
			showNewChatModal.value = false
			showAgentModal.value = true
			agentLoading.value = true
			agentList.value = []
			// getAgentList 直接返回智能体对象数组
			const agents = await getAgentList(userToken.value)
			if (agents && Array.isArray(agents)) {
				agentList.value = agents
			} else {
				uni.showToast({ title: '获取智能体列表失败', icon: 'none' })
			}
		} catch (error) {
			console.error('获取智能体列表失败：', error)
			uni.showToast({ title: '获取智能体列表失败，请重试', icon: 'none' })
		} finally {
			agentLoading.value = false
		}
	}

	const closeAgentModal = () => {
		showAgentModal.value = false
		agentList.value = []
	}

	const handleAgentSelect = async (agent) => {
		console.log("点击了",agent.title);
		// try {
		// 	closeAgentModal()
		// 	const chars = '0123456789abcdef'
		// 	let name = ''
		// 	for (let i = 0; i < 24; i++) {
		// 		name += chars[Math.floor(Math.random() * chars.length)]
		// 	}
		// 	const workspaceId = await createWorkspace(userToken.value, name)
		// 	if (!workspaceId) {
		// 		uni.showToast({ title: '工作区创建失败', icon: 'none' })
		// 		return
		// 	}
		// 	const loginInfo = uni.getStorageSync('yxd_login_info')
		// 	const userName = loginInfo ? (loginInfo.username || '') : ''
		// 	const conversationId = await createConversation(userToken.value, workspaceId, agent.title, userName)
		// 	if (!conversationId) {
		// 		uni.showToast({ title: '智能体会话创建失败', icon: 'none' })
		// 		return
		// 	}
		// 	uni.showToast({ title: `已创建「${agent.title}」会话`, icon: 'success' })
		// 	takeUserConversations()
		// } catch (error) {
		// 	console.error('创建智能体会话失败：', error)
		// 	uni.showToast({ title: '创建失败，请重试', icon: 'none' })
		// }
	}

	// 侧边栏相关
	const isChatSidebar = ref(false)
	const handleChatSidebar = () => {
		isChatSidebar.value = !isChatSidebar.value
	}

	// 跳转到工作区
	const goWorkSpace = () => {
		// 保存当前会话id，确保从工作区返回时能恢复
		if (currentSessionId.value) {
			uni.setStorageSync('currentSessionId', currentSessionId.value)
		}
		uni.navigateTo({
			url: '/pages/WorkSpace/WorkSpace'
		})
	}

	// 跳转到 WYXD 查看器（强制重新下载，不走缓存）
	const goWyxdViewer = () => {
		uni.navigateTo({
			url: '/pages/WYXD/WYXD?workspaceId=' + encodeURIComponent(wyxdWorkspaceId.value)
				+ '&filePath=' + encodeURIComponent(wyxdFilePath.value)
				+ '&fileName=' + encodeURIComponent(wyxdFileName.value)
				+ '&userToken=' + encodeURIComponent(wyxdUserToken.value)
				+ '&forceDownload=1'
		})
	}

	// 跳转到云端数据库
	const goCloudDatabase = () => {
		// 保存当前会话id，确保从工作区返回时能恢复
		if (currentSessionId.value) {
			uni.setStorageSync('currentSessionId', currentSessionId.value)
		}
		uni.navigateTo({
			url: '/pages/CloudDatabase/CloudDatabase'
		})
	}

	const logOut = () => {
		uni.reLaunch({
			url: '/pages/Login/Login'
		})
	}

	// 刷新当前会话消息
	const handleRefresh = async () => {
		// 1. 重置输入框状态
		isThinking.value = false
		isSelfSent.value = false
		socketStore.isThinking = false
		textMessage.value = ''
		uploadedFiles.value = []
		previewFileArray.value = []
		// 移除流式消息占位
		if (streamingMessageId.value) {
			allmessages.value = allmessages.value.filter(m => m._id !== streamingMessageId.value)
			streamingMessageId.value = null
		}

		const titleMap = { 0: 'AI消息', 1: '好友消息', 2: '群聊消息' }
		const title = titleMap[ChatType.value] || '消息'
		uni.showLoading({ title: `刷新${title}中...`, mask: true })

		try {
			// 2. 检查当前 token 是否还有效
			const token = getToken()
			if (!token) {
				uni.hideLoading()
				uni.showToast({ title: '登录已失效，请重新登录', icon: 'none', duration: 1500 })
				setTimeout(() => uni.reLaunch({ url: '/pages/Login/Login' }), 1500)
				return
			}
			await getUserInfo(token)

			// 3. 确保 AI 对话模式下 socket 连接正常（PC 离线由 watcher 实时监控）
			if (ChatType.value === 0 && !socketStore.isConnected) {
				await handleConnect()
			}

			// 4. 刷新消息
			switch (ChatType.value) {
				case 0:
					await takeConversationMessages()
					break
				case 1:
					await takeFriendMessages()
					break
				case 2:
					await takeGroupMessages()
					break
				default:
					await takeConversationMessages()
					break
			}
			uni.hideLoading()
			uni.showToast({ title: '已刷新', icon: 'success', duration: 1200 })
		} catch (e) {
			uni.hideLoading()
			// 区分 token 失效和其他错误
			const errMsg = typeof e === 'string' ? e : (e?.message || '')
			if (errMsg.includes('token') || errMsg.includes('Token') || errMsg.includes('登录') || errMsg.includes('过期')) {
				uni.showToast({ title: '登录已失效，请重新登录', icon: 'none', duration: 1500 })
				setTimeout(() => uni.reLaunch({ url: '/pages/Login/Login' }), 1500)
			} else {
				uni.showToast({ title: '刷新失败', icon: 'error', duration: 1500 })
			}
		}
	}

	const previewFileArray = ref([])
	const uploadPhoto = () => {
		console.log('点击了拍照上传');
		uni.chooseImage({
			count: 1,
			sourceType: ['camera', 'album'],
			success: (res) => {
				const tempFiles = res.tempFiles;
				const tempFilePaths = res.tempFilePaths;
				tempFiles.forEach((file, index) => {
					previewFileArray.value.push({
						path: tempFilePaths[index], // 图片路径（用于显示）
						name: file.name || `photo_${Date.now()}_${index}.jpg`, // 文件名
						size: file.size, // 文件大小
						type: 'image', // 文件类型标识
						tempFile: file // 原始文件对象
					});
				});
			},
			fail: (err) => {
				console.error('选择图片失败', err);
			}
		})
	}
	// 删除指定图片
	const deleteImage = (index) => {
		previewFileArray.value.splice(index, 1);
	};

	const fileList = ref([])
	const uploadFile = () => {
		console.log('点击了上传文件');
		chooseFile({
			count: 5,
			type: 'all',
			success: (res) => {
				console.log("成功了");
				// 赋值文件列表
				fileList.value = res.tempFiles;
				// 把所有文件 push 到预览数组（展开运算符 ...）
				previewFileArray.value.push(...res.tempFiles);
				uni.showToast({
					title: `已选择 ${res.tempFiles.length} 个文件`,
					icon: 'success'
				})
			},
			fail: (err) => {
				console.error('选择失败:', err)
				uni.showToast({
					title: '选择失败',
					icon: 'error'
				})
			}
		})
	}
	// 连接socket 
	const handleConnect = () => {
		return new Promise((resolve, reject) => {
			if (socketStore.isConnected) return resolve()

			// 监听连接成功
			const unwatch = watch(() => socketStore.isConnected, (connected) => {
				if (connected) {
					unwatch()
					resolve()
				}
			})

			// 监听连接错误
			const unwatchError = watch(() => socketStore.connectionStatus, (status) => {
				if (status === 'error') {
					unwatch()
					unwatchError()
					reject(new Error('连接失败'))
				}
			})

			socketStore.connect({
				token: userToken.value,
				conversationId: currentSessionId.value
			})
		})
	}

	// // 监听socket连接是否成功
	// watch(()=>socketStore.isConnected,(newVal)=>{
	// 	if(newVal&&)
	// })

	const isThinking = ref(false)
	const isSelfSent = ref(false) // 是否当前设备发起的 AI 对话（用于区分弹窗/仅锁输入）
	const isUploading = ref(false)
	const isLoadingMessages = ref(false) // 消息加载中状态（仅会话变化时显示）
	const uploadedFiles = ref([])
	const streamingMessageId = ref(null) // 流式消息的临时 ID，用于实时更新 AI 回复

	// 消息详情弹窗
	const showMessageModal = ref(false)
	const detailLinks = ref([])

	// 点击气泡内容：拦截链接点击，否则 AI 对话弹出详情
	const handleChatContentClick = (e, message) => {
		// 在事件路径中向上查找 .chat-inline-link 元素
		let el = e.target
		while (el && el.classList) {
			if (el.classList.contains('chat-inline-link')) {
				const url = el.getAttribute('data-url') || (el.dataset && el.dataset.url)
				if (url) {
					openLink(url)
				}
				return
			}
			el = el.parentElement
		}

		// 检查消息内容是否包含链接
		const hasLink = message.content && (
			message.content.includes('http://') ||
			message.content.includes('https://') ||
			/<a\s+[^>]*href\s*=\s*['"][^'"]*['"]/i.test(message.content) ||
			/\[[^\]]*\]\([^)]*\)/i.test(message.content) // 匹配 Markdown 链接格式
		)

		// 只有包含链接时才弹出详情窗
		if (message.role && hasLink) {
			showMessageDetail(message)
		}
	}

	const extractLinks = (text) => {
		const seen = new Set()
		const result = []
		// 1. 匹配 Markdown 链接 [文件名](url)
		const mdLinkRegex = /\[([^\]]*)\]\((https?:\/\/[^\s<>"{}|\\^`\[\]()]+)\)/gi
		const mdLinks = [...text.matchAll(mdLinkRegex)]
		mdLinks.forEach(m => {
			const name = m[1].trim()
			const url = m[2]
			if (!seen.has(url)) {
				seen.add(url)
				result.push({
					url,
					name: name || url
				})
			}
		})
		// 2. 去除已匹配的 [xxx](url) 后，再搜裸 URL
		let cleaned = text.replace(mdLinkRegex, '')
		const bareRegex = /https?:\/\/[^\s<>"{}|\\^`\[\]()]+/gi
		const bareMatches = cleaned.match(bareRegex)
		if (bareMatches) {
			bareMatches.forEach(url => {
				if (!seen.has(url)) {
					seen.add(url)
					result.push({
						url,
						name: extractFileNameFromUrl(url)
					})
				}
			})
		}
		return result
	}
	const extractFileNameFromUrl = (urlStr) => {
		try {
			const segments = new URL(urlStr).pathname.split('/').filter(s => s)
			if (segments.length > 0) {
				return decodeURIComponent(segments[segments.length - 1])
			}
		} catch (e) {
			/* fallback */
		}
		const parts = urlStr.split('/').filter(s => s)
		if (parts.length > 0) {
			const last = parts[parts.length - 1]
			const qIdx = last.indexOf('?')
			return qIdx > -1 ? last.substring(0, qIdx) : last
		}
		return urlStr
	}
	
	// 文件类型 → 图标和标签映射
	const getFileTypeInfo = (ext) => {
		const map = {
			// 文档类
			pdf:  { emoji: '📕', label: 'PDF',           color: '#e74c3c' },
			doc:  { emoji: '📘', label: 'Word',          color: '#2b579a' },
			docx: { emoji: '📘', label: 'Word',          color: '#2b579a' },
			xls:  { emoji: '📗', label: 'Excel',         color: '#217346' },
			xlsx: { emoji: '📗', label: 'Excel',         color: '#217346' },
			ppt:  { emoji: '📙', label: 'PPT',           color: '#d24726' },
			pptx: { emoji: '📙', label: 'PPT',           color: '#d24726' },
			txt:  { emoji: '📄', label: '文本',          color: '#666' },
			md:   { emoji: '📝', label: 'Markdown',      color: '#333' },
			markdown: { emoji: '📝', label: 'Markdown',  color: '#333' },
			csv:  { emoji: '📊', label: 'CSV',           color: '#217346' },
			json: { emoji: '📋', label: 'JSON',          color: '#f0a500' },
			xml:  { emoji: '📋', label: 'XML',           color: '#e67e22' },
			yaml: { emoji: '📋', label: 'YAML',          color: '#e67e22' },
			yml:  { emoji: '📋', label: 'YAML',          color: '#e67e22' },
			// 压缩包
			zip:  { emoji: '📦', label: 'ZIP',           color: '#f39c12' },
			rar:  { emoji: '📦', label: 'RAR',           color: '#f39c12' },
			'7z': { emoji: '📦', label: '7Z',            color: '#f39c12' },
			// 网页
			html: { emoji: '🌐', label: '网页',          color: '#e44d26' },
			htm:  { emoji: '🌐', label: '网页',          color: '#e44d26' },
			// 图片
			jpg:  { emoji: '🖼️', label: '图片',          color: '#9b59b6' },
			jpeg: { emoji: '🖼️', label: '图片',          color: '#9b59b6' },
			png:  { emoji: '🖼️', label: '图片',          color: '#9b59b6' },
			gif:  { emoji: '🖼️', label: 'GIF',           color: '#9b59b6' },
			bmp:  { emoji: '🖼️', label: '图片',          color: '#9b59b6' },
			svg:  { emoji: '🖼️', label: 'SVG',           color: '#9b59b6' },
			webp: { emoji: '🖼️', label: '图片',          color: '#9b59b6' },
			ico:  { emoji: '🖼️', label: '图标',          color: '#9b59b6' },
			tiff: { emoji: '🖼️', label: '图片',          color: '#9b59b6' },
			tif:  { emoji: '🖼️', label: '图片',          color: '#9b59b6' },
			// CAD/设计类
			dxf:  { emoji: '📐', label: 'CAD/DXF',      color: '#2c3e50' },
		}
		return map[ext] || { emoji: '📎', label: ext.toUpperCase(), color: '#888' }
	}
	
	const showMessageDetail = (message) => {
		detailLinks.value = extractLinks(message.content || '')
		showMessageModal.value = true
	}
	const closeMessageDetail = () => {
		showMessageModal.value = false
		detailLinks.value = []
	}
	const downloadToast = ref({
		show: false,
		type: 'loading',
		message: ''
	})
	let downloadToastTimer = null

	const showDownloadToast = (type, message, duration) => {
		if (downloadToastTimer) clearTimeout(downloadToastTimer)
		downloadToast.value = {
			show: true,
			type,
			message
		}
		if (duration > 0) {
			downloadToastTimer = setTimeout(() => {
				downloadToast.value = {
					show: false,
					type: 'loading',
					message: ''
				}
			}, duration)
		}
	}
	const hideDownloadToast = () => {
		if (downloadToastTimer) clearTimeout(downloadToastTimer)
		downloadToast.value = {
			show: false,
			type: 'loading',
			message: ''
		}
	}

	const openLink = (url) => {
		downloadAndHandle(url)
	}

	const downloadAndHandle = async (url) => {
		try {
			showDownloadToast('loading', '下载中...', 0)
			const workspaceId = uni.getStorageSync('workspace_id') || ''
			const result = await conversationMessageDownload(userToken.value, workspaceId, url)
			const downloadUrl = typeof result === 'string' ? result : (result?.url || result?.download_url || '')
			if (!downloadUrl) {
				showDownloadToast('error', '获取下载地址失败', 2500)
				return
			}
			uni.downloadFile({
				url: downloadUrl,
				success: (downloadRes) => {
					if (downloadRes.statusCode === 200) {
						hideDownloadToast()
						openFileNative(downloadRes.tempFilePath, () => {
							showDownloadToast('error', '无法打开此文件', 2500)
						})
					} else {
						showDownloadToast('error', '下载失败', 2500)
					}
				},
				fail: () => {
					showDownloadToast('error', '下载失败', 2500)
				}
			})
		} catch (err) {
			showDownloadToast('error', '下载失败: ' + (err.message || err), 2500)
		}
	}


	const sendMessage = async () => {
		const message = textMessage.value.trim()
		if (!message && previewFileArray.value.length === 0) return

		// 有文件时先上传
		if (previewFileArray.value.length > 0) {
			isUploading.value = true
			uni.showLoading({
				title: '上传文件中...',
				mask: true
			})
			try {
				const results = []
				for (const item of previewFileArray.value) {
					const result = await uploadFileToServer(item.path)
					results.push(result)
				}
				uploadedFiles.value = results
				previewFileArray.value = []
			} catch (err) {
				uni.hideLoading()
				console.error('文件上传失败:', err)
				uni.showToast({
					title: '文件上传失败，请重试',
					icon: 'error'
				})
				isUploading.value = false
				return
			}
			uni.hideLoading()
			isUploading.value = false
		}

		if (ChatType.value === 0) sendAIMessage();
		if (ChatType.value === 1) sendFriendMessage();
		if (ChatType.value === 2) sendGroupMessage();
	}
	// 发送好友消息
	const sendFriendMessage = async () => {
		const newMessageData = {
			"sender": UserId.value,
			"receiver": receiverId,
			"content": textMessage.value,
			"taskId": null,
			"avatar": null,
			"createTime": "",
			"messageType": "null",
			"contentJson": hasFiles ? JSON.stringify(uploadedFiles.value) : 'null'
		}
		allmessages.value = [...allmessages.value, newMessageData]
		if (!friendSocketStore.isConnecting) await handleFriendConnect();
		const receiverId = getReceiverId()
		const hasFiles = uploadedFiles.value.length > 0
		const body = {
			"command": 1,
			"sender": UserId.value,
			"receiver": receiverId,
			"avatar": "",
			"sessionId": currentSessionId.value,
			"message": textMessage.value,
			"callBackMessage": false
		}
		if (hasFiles) {
			body.contentType = 1
			body.uploadVos = uploadedFiles.value
		}
		const data = {
			"version": "1.1",
			"body": body
		}
		friendSocketStore.send(data)
		// const newMessageData = {
		// 	"sender": UserId.value,
		// 	"receiver": receiverId,
		// 	"content": textMessage.value,
		// 	"taskId": null,
		// 	"avatar": null,
		// 	"createTime": "",
		// 	"messageType": "null",
		// 	"contentJson": hasFiles ? JSON.stringify(uploadedFiles.value) : 'null'
		// }
		// allmessages.value = [...allmessages.value, newMessageData]
		textMessage.value = ''
		uploadedFiles.value = []
		setTimeout(() => {
			takeFriendMessages()
		}, 1000)
	}

	// 发送群聊消息
	const sendGroupMessage = async () => {
		const newMessageData = {
			"id": allmessages.value.length + 1,
			"message": textMessage.value,
			"messageType": hasFiles ? 1 : 0,
			"groupId": null,
			"createTime": '',
			"contentJson": hasFiles ? JSON.stringify(uploadedFiles.value) : '',
			"sender": UserId.value
		}
		allmessages.value = [...allmessages.value, newMessageData]
		if (!friendSocketStore.isConnecting) await handleFriendConnect();
		const hasFiles = uploadedFiles.value.length > 0
		const body = {
			"command": 9,
			"groupId": currentSessionId.value,
			"message": textMessage.value || "",
			"callBackMessage": hasFiles ? false : true,
			"messageType": hasFiles ? 1 : 0,
			"sender": UserId.value
		}
		if (hasFiles) {
			body.uploadVos = uploadedFiles.value
		}
		const data = {
			"version": "1.1",
			"body": body
		}
		friendSocketStore.send(data)
		// const newMessageData = {
		// 	"id": allmessages.value.length + 1,
		// 	"message": textMessage.value,
		// 	"messageType": hasFiles ? 1 : 0,
		// 	"groupId": null,
		// 	"createTime": '',
		// 	"contentJson": hasFiles ? JSON.stringify(uploadedFiles.value) : '',
		// 	"sender": UserId.value
		// }
		// allmessages.value = [...allmessages.value, newMessageData]
		textMessage.value = ''
		uploadedFiles.value = []
		setTimeout(() => {
			takeGroupMessages()
		}, 1000)
	}

	//修改优先把消息加入列表


	// 发送AI消息
	const sendAIMessage = async () => {
		try {
			let messageContent = textMessage.value
			// 立即添加用户消息到列表（使用完整的 messageContent，包含文件信息）
			allmessages.value = [...allmessages.value, {
				role: 'user',
				content: messageContent
			}]
			await handleConnect();

			if (uploadedFiles.value.length) {
				const fileJson = JSON.stringify(uploadedFiles.value)
				messageContent = messageContent ?
					`${messageContent}\n[文件信息:${fileJson}]` :
					`[文件信息:${fileJson}]`
			}
			const data = {
				"type": "chat",
				"conversation_id": currentSessionId.value,
				"content": messageContent
			}
			socketStore.send(data)
			isThinking.value = true
			isSelfSent.value = true
			// // 立即添加用户消息到列表（使用完整的 messageContent，包含文件信息）
			// allmessages.value = [...allmessages.value, {
			// 	role: 'user',
			// 	content: messageContent
			// }]
			// 添加流式消息气泡（加载中状态）
			streamingMessageId.value = 'streaming-' + Date.now()
			allmessages.value = [...allmessages.value, {
				role: 'assistant',
				content: '',
				_streaming: true,
				_id: streamingMessageId.value
			}]
			textMessage.value = ''
			uploadedFiles.value = []
			scrollToBottom()
		} catch (error) {
			isThinking.value = false
			uni.showToast({
				title: `用户发送消息失败${error}`,
				icon: 'error'
			})
		}
	}

	// 中断 AI 对话
	const stopConversation = async () => {
		console.log('中断对话 - 当前会话ID:', currentSessionId.value)
		try {
			await socketStore.send({
				type: 'stop',
				conversation_id: currentSessionId.value
			})
			console.log('中断指令已发送成功')
		} catch (err) {
			console.error('中断指令发送失败:', err)
		}
		// 移除流式消息占位
		if (streamingMessageId.value) {
			allmessages.value = allmessages.value.filter(m => m._id !== streamingMessageId.value)
			streamingMessageId.value = null
		}
		// 立即关闭弹窗，清除超时
		// clearTimeout(aiResponseTimer)
		isThinking.value = false
		socketStore.isThinking = false
		isSelfSent.value = false
		textMessage.value = ''
		uploadedFiles.value = []
		// takeConversationMessages 由后端中断确认驱动（见 handleBusinessMessage）
		uni.showToast({
			title: '已中断',
			icon: 'none',
			duration: 1500
		})
	}

	// 全局监听 socketStore.isThinking：跨设备同步思考状态，锁/解锁输入框
	watch(() => socketStore.isThinking, async (newVal, oldVal) => {
		console.log("isThinking 变化:", oldVal, "→", newVal, "messageString:", socketStore.messageString);
		// false → true：后端开始回复，锁定输入框
		if (!oldVal && newVal) {
			console.log('AI开始回复（跨设备同步），锁定输入框');
			isThinking.value = true
			// 跨设备场景：先加载最新消息（含 PC 发的用户消息），再追加流式气泡
			if (!isSelfSent.value) {
				await takeConversationMessages()
			}
			if (!streamingMessageId.value) {
				streamingMessageId.value = 'streaming-' + Date.now()
				allmessages.value = [...allmessages.value, {
					role: 'assistant',
					content: '',
					_streaming: true,
					_id: streamingMessageId.value
				}]
			}
			return
		}
		// true → false：AI 回复结束或中断，解锁输入框并刷新消息列表
		if (oldVal && !newVal) {
			console.log('AI回复结束（正常/中断），解锁并刷新消息列表');

			// 先从服务器加载完整消息列表
			await takeConversationMessages()
			// 加载完成后再移除流式消息气泡，避免闪白
			if (streamingMessageId.value) {
				allmessages.value = allmessages.value.filter(m => m._id !== streamingMessageId.value)
				streamingMessageId.value = null
			}
			isThinking.value = false
			isSelfSent.value = false
			textMessage.value = ''
			uploadedFiles.value = []
		}
	})

	// 监听 auth_fail，当 pc 端不在线时弹窗提示并取消思考状态
	watch(() => socketStore.authFailReason, (reason) => {
		if (reason === 'pc offline') {
			isThinking.value = false
			socketStore.authFailReason = ''
			uni.showToast({
				title: 'PC端不在线，请先登录',
				icon: 'none',
				duration: 500
			})
			setTimeout(() => {
				uni.reLaunch({
					url: '/pages/Login/Login'
				})
			}, 500)
		}
	})

	// 监听 pc_offline，PC 端下线时立即强制跳转登录页
	watch(() => socketStore.pcOffline, (offline) => {
		if (offline) {
			isThinking.value = false
			socketStore.isThinking = false
			socketStore.pcOffline = false
			uni.showToast({
				title: 'PC端不在线，请先登录',
				icon: 'none',
				duration: 1500
			})
			setTimeout(() => {
				uni.reLaunch({
					url: '/pages/Login/Login'
				})
			}, 1500)
		}
	})

	// 监听流式消息内容，实时更新 AI 回复（流式输出核心）
	watch(() => socketStore.messageString, (newContent) => {
		if (!streamingMessageId.value || !socketStore.isThinking) return

		const idx = allmessages.value.findIndex(m => m._id === streamingMessageId.value)
		if (idx >= 0) {
			// 使用 splice 保证 Vue 响应式更新
			const updated = {
				...allmessages.value[idx],
				content: newContent
			}
			allmessages.value.splice(idx, 1, updated)
			scrollToBottom()
		}
	})

	const textMessage = ref('')

	// 获取用户历史会话列表
	const UserConversations = ref([])
	// 当前会话id
	const currentSessionId = ref('')
	const userToken = ref('')
	const UserId = ref('')
	const UserAvatar = ref('')
	const UserData = ref(null)

	// 获取当前用户信息（提前加载，用于显示头像等）
	const takeUserInfo = async () => {
		try {
			userToken.value = getToken();
			UserData.value = await getUserInfo(userToken.value)
			UserId.value = UserData.value._id;
			UserAvatar.value = UserData.value.avatar || ''
			console.log("用户信息已加载:", UserId.value, UserAvatar.value);
		} catch (error) {
			console.error("获取用户信息失败:", error);
		}
	}

	// 获取对话列表
	const takeUserConversations = async () => {
		try {
			userToken.value = getToken();
			console.log("token:", userToken.value);
			UserConversations.value = await getUserConversations(userToken.value) || [];
			// console.log("UserConversations:", JSON.stringify(UserConversations.value) );
			// 优先恢复已保存的会话id，避免刷新到列表第一个
			const savedSessionId = getCurrentSessionId();
			if (savedSessionId && UserConversations.value.some(c => c._id === savedSessionId)) {
				currentSessionId.value = savedSessionId;
			} else if (UserConversations.value.length > 0) {
				currentSessionId.value = UserConversations.value[0]._id;
			} else {
				currentSessionId.value = '';
			}
			console.log('保存会话id：', currentSessionId.value);
			uni.setStorageSync('currentSessionId', currentSessionId.value)
		} catch (error) {
			uni.showToast({
				title: `获取会话列表失败${error}`,
				icon: 'error'
			})
		}
	}
	// 好友列表
	// 好友消息列表(不包括头像)
	const FriendInfoList = ref([])
	// 获取用户好友列表
	const takeFriendList = async () => {
		try {
			console.log("开始获取好友列表");
			// UserId 已在 takeUserInfo 中获取
			const friendList = await getChatFriend(UserId.value)
			console.log("friendList:", friendList);
			if (friendList && friendList.length) {
				FriendInfoList.value = await takeUserAvatar(friendList)
			} else {
				FriendInfoList.value = []
				console.log("好友列表为空");
			}
		} catch (error) {
			console.error("获取好友列表失败:", error);
			FriendInfoList.value = []
		} finally {
			UserConversations.value = FriendInfoList.value
		}
	}

	// 从好友列表中获取单个用户头像
	const takeTalkUserAvatar = (userId) => {
		if (!userId) return null
		const user = FriendInfoList.value.find(item => item.receiver === userId)
		return user?.avatar || null
	}
	// 获取好友头像
	const takeUserAvatar = async (friendList) => {
		if (!friendList?.length) return []
		try {
			const friendIds = friendList.map(item => item.receiver)
			const friendAvatarList = await getUserAvatar(userToken.value, friendIds)
			// 创建map,用于快速查找
			const userMap = new Map(friendAvatarList.map(user => [user.user_id, user]) || [])
			// 合并数据
			return friendList.map(friend => {
				const userInfo = userMap.get(friend.receiver)
				return {
					...friend,
					avatar: userInfo?.avatar || null
				}
			})
		} catch (err) {
			console.error('获取好友头像失败', err);
			return friendList
		}
	}

	// 群聊列表
	const GroupList = ref([])
	// 获取用户群聊列表
	const takeGroupList = async () => {
		try {
			console.log("开始获取群聊列表");
			// UserId 已在 takeUserInfo 中获取
			GroupList.value = await getGroup(UserId.value)
			// console.log("GroupList1:", JSON.stringify(GroupList.value));
		} catch (error) {
			console.error("获取群聊列表失败:", error);
			GroupList.value = []
		} finally {
			UserConversations.value = GroupList.value
		}
	}

	// 群成员列表（包含头像）
	const groupMemberList = ref([])
	// 获取群成员头像
	const takeGroupMemberAvatar = async (memberList) => {
		if (!memberList?.length) return []
		try {
			const memberIds = memberList.map(item => item.groupContactId)
			console.log("请求头像的ID列表:", memberIds)
			const memberAvatarList = await getUserAvatar(userToken.value, memberIds)
			console.log("头像接口返回数据:", memberAvatarList)
			// 创建map,用于快速查找
			const userMap = new Map(memberAvatarList.map(user => [user.user_id, user]) || [])
			console.log("userMap的keys:", Array.from(userMap.keys()))
			// 合并数据
			return memberList.map(member => {
				const memberId = member.groupContactId
				const userInfo = userMap.get(memberId)
				console.log(`查找 ${memberId} 的头像:`, userInfo)
				return {
					...member,
					avatar: userInfo?.avatar || null
				}
			})
		} catch (err) {
			console.error('获取群成员头像失败', err);
			return memberList
		}
	}

	// 从群成员列表中获取单个用户头像
	const getGroupMemberAvatarById = (userId) => {
		if (!userId) return null
		const member = groupMemberList.value.find(item => item.groupContactId === userId)
		return member?.avatar || null
	}

	// 当前会话的消息
	// const currentMessages = ref([])
	const allmessages = ref([])
	// const pageInfoNumber = 10;

	const scrollToView = ref('')
	const bottomToggle = ref(false)
	// 加载状态和分页相关变量
	const isLoadingMore = ref(false) // 是否正在加载更多
	const isAILoadingMore = ref(false) // AI 会话是否正在加载更多（滚动到顶部触发）
	const aiPageNumber = ref(0) // AI 会话当前服务端页码（0-based）
	const hasMoreAIMessages = ref(true) // AI 会话是否还有更多消息可加载
	const currentPage = ref(1) // 当前页码（如果后端支持分页）
	const pageSize = 200;

	// 好友消息分页
	const isFriendLoadingMore = ref(false) // 好友会话是否正在加载更多
	const friendPageNumber = ref(1) // 好友消息当前页码（1-based）
	const hasMoreFriendMessages = ref(true) // 好友会话是否还有更多消息

	// 群聊消息分页
	const isGroupLoadingMore = ref(false) // 群聊会话是否正在加载更多
	const groupPageNumber = ref(1) // 群聊消息当前页码（1-based）
	const hasMoreGroupMessages = ref(true) // 群聊会话是否还有更多消息
	const groupSize = 100;
	// 计算属性：自动根据 allmessages 和 currentPage 计算显示消息
	const currentMessages = computed(() => {
		// return allmessages.value.slice(-pageInfoNumber);
		return allmessages.value;
	})


	// 获取对话消息
	const takeConversationMessages = async () => {
		try {
			aiPageNumber.value = 0
			hasMoreAIMessages.value = true
			const rawMessages = await getConversationMessages(userToken.value, currentSessionId.value, pageSize, 0) || [];
			// API 返回的数据已被过滤，无法通过数量判断是否有更多，始终尝试加载更多
			allmessages.value = rawMessages
			console.log("获取到的所有消息：", JSON.stringify(allmessages.value) );
			// console.log("获取到的所有消息：", allmessages.value);
			// currentMessages.value = allmessages.value.slice(-pageInfoNumber);
			// 数据获取后执行滚动
			scrollToBottom();
		} catch (error) {
			uni.showToast({
				title: `获取ai会话内容失败${error}`,
				icon: 'none'
			})
		} finally {
			isLoadingMessages.value = false
		}
	}
	const takeFriendMessages = async () => {
		try {
			friendPageNumber.value = 1
			hasMoreFriendMessages.value = true
			const rawMessages = await getFriendMessages(currentSessionId.value, pageSize, 0) || [];
			allmessages.value = rawMessages
			// console.log("好友消息：", JSON.stringify(allmessages.value));
			// 数据获取后执行滚动
			scrollToBottom();
		} catch (error) {
			uni.showToast({
				title: `获取好友会话消息失败${error}`,
				icon: 'none'
			})
		} finally {
			isLoadingMessages.value = false
		}
	}
	const takeGroupMessages = async () => {
		try {
			groupPageNumber.value = 1
			hasMoreGroupMessages.value = true
			const rawMessages = await getGroupMessages(currentSessionId.value, pageSize, 0) || [];
			allmessages.value = rawMessages
			console.log("群聊消息：", allmessages.value);
			// 获取群成员列表并获取头像
			const memberList = await getGroupMemberList(currentSessionId.value)
			console.log("获取到的群成员列表：", memberList);
			if (memberList && memberList.length) {
				groupMemberList.value = await takeGroupMemberAvatar(memberList)
				console.log("群成员列表（带头像）：", groupMemberList.value);
			} else {
				groupMemberList.value = []
			}
			// 数据获取后执行滚动
			scrollToBottom();
		} catch (error) {
			uni.showToast({
				title: `获取群聊会话失败${error}`,
				icon: 'none'
			})
		} finally {
			isLoadingMessages.value = false
		}
	}
	// // 加载更多消息
	// const loadMoreMessages = async () => {
	// 	console.log('加载更多信息');
	// 	if (allmessages.value.length > pageInfoNumber * currentPage.value) {
	// 		currentPage.value += 1;
	// 		const newMessages = allmessages.value.slice(-pageInfoNumber * currentPage.value);
	// 		const addMessage = newMessages.length - currentMessages.value.length
	// 		currentMessages.value = newMessages
	// 		// 等待DOM更新
	// 		await nextTick()
	// 		scrollToView.value = 'msg-' + (addMessage + 1);

	// 	}
	// 	// currentMessages.value = allmessages.value.slice(-pageInfoNumber);
	// }

	// 滚动到顶部时加载更多消息（根据 ChatType 分发）
	const onScrollToUpper = async () => {
		if (ChatType.value === 0) {
			await loadMoreAIMessages()
		} else if (ChatType.value === 1) {
			await loadMoreFriendMessages()
		} else if (ChatType.value === 2) {
			await loadMoreGroupMessages()
		}
	}

	// 加载更多 AI 历史消息（上一页），并定位到原第一条消息
	const loadMoreAIMessages = async () => {
		if (isAILoadingMore.value || !hasMoreAIMessages.value || !currentSessionId.value) return
		isAILoadingMore.value = true
		try {
			const nextPage = aiPageNumber.value + 1
			const olderMessages = await getConversationMessages(userToken.value, currentSessionId.value, pageSize, nextPage)

			if (olderMessages && olderMessages.length > 0) {
				aiPageNumber.value = nextPage
				allmessages.value = [...olderMessages, ...allmessages.value]

				// 定位滚动：原本第一条消息（old index 0）现在在 index = olderMessages.length 处
				await nextTick()
				scrollToView.value = 'msg-' + olderMessages.length
			} else {
				// 只有 API 返回空时才确认没有更多
				hasMoreAIMessages.value = false
			}
		} catch (e) {
			console.error('加载更多消息失败:', e)
			uni.showToast({ title: '加载更多失败', icon: 'none' })
		} finally {
			isAILoadingMore.value = false
		}
	}

	// 加载更多好友历史消息（上一页），并定位到原第一条消息
	const loadMoreFriendMessages = async () => {
		if (isFriendLoadingMore.value || !hasMoreFriendMessages.value || !currentSessionId.value) return
		isFriendLoadingMore.value = true
		try {
			const nextPage = friendPageNumber.value + 1
			const olderMessages = await getFriendMessages(currentSessionId.value, pageSize, nextPage)

			if (olderMessages && olderMessages.length > 0) {
				friendPageNumber.value = nextPage
				allmessages.value = [...olderMessages, ...allmessages.value]
				// 定位滚动
				await nextTick()
				scrollToView.value = 'msg-' + olderMessages.length
			} else {
				hasMoreFriendMessages.value = false
			}
		} catch (e) {
			console.error('加载更多好友消息失败:', e)
			uni.showToast({ title: '加载更多失败', icon: 'none' })
		} finally {
			isFriendLoadingMore.value = false
		}
	}

	// 加载更多群聊历史消息（上一页），并定位到原第一条消息
	const loadMoreGroupMessages = async () => {
		if (isGroupLoadingMore.value || !hasMoreGroupMessages.value || !currentSessionId.value) return
		isGroupLoadingMore.value = true
		try {
			const nextPage = groupPageNumber.value + 1
			const olderMessages = await getGroupMessages(currentSessionId.value, pageSize, nextPage)

			if (olderMessages && olderMessages.length > 0) {
				groupPageNumber.value = nextPage
				allmessages.value = [...olderMessages, ...allmessages.value]
				// 定位滚动
				await nextTick()
				scrollToView.value = 'msg-' + olderMessages.length
			} else {
				hasMoreGroupMessages.value = false
			}
		} catch (e) {
			console.error('加载更多群聊消息失败:', e)
			uni.showToast({ title: '加载更多失败', icon: 'none' })
		} finally {
			isGroupLoadingMore.value = false
		}
	}

	// 监听滚动
	const onScroll = (e) => {}
	// 滑动到底部
	const scrollToBottom = async () => {
		await nextTick();
		if (currentMessages.value.length > 0) {
			// 两个锚点交替切换，确保每次调用都能触发滚动
			bottomToggle.value = !bottomToggle.value
			scrollToView.value = bottomToggle.value ? 'chat-bottom-a' : 'chat-bottom-b'
		}
	}

	// 监听好友消息
	watch(() => friendSocketStore.MessageReceived, (newId) => {
		console.log("收到了好友消息");
		if (newId) {
			console.log("ChatType:", ChatType.value);
			switch (ChatType.value) {
				case 0:
					takeConversationMessages();
					friendSocketStore.MessageReceived = false
					break;
				case 1:
					takeFriendMessages();
					friendSocketStore.MessageReceived = false
					break;
				case 2:
					takeGroupMessages();
					friendSocketStore.MessageReceived = false
					break;
					// 可选：默认兜底
				default:
					console.log("default 分支");
					friendSocketStore.MessageReceived = false
					break;
			}
		}
	}, {
		immediate: true
	})

	watch(currentSessionId, async (newId, oldId) => {
		if (newId) {
			uni.setStorageSync('currentSessionId', currentSessionId.value)
			// 会话变化时显示加载弹窗并清空消息
			if (oldId && oldId !== newId) {
				isLoadingMessages.value = true
				allmessages.value = []
				hasWyxdFile.value = false
			}
			switch (ChatType.value) {
				case 0:
					takeConversationMessages();
					// 切换 AI 会话时重新连接 socket（绑定新 conversationId）
					socketStore.disconnect()
					handleConnect()
					break;
				case 1:
					await takeFriendMessages();
					break;
				case 2:
					await takeGroupMessages();
					break;
				default:
					takeConversationMessages();
					break;
			}
		}
	}, {
		immediate: true
	})

	// 接收 WYXD 参数（从工作区「AI修改」进入时由 navigateTo 传入）
	onLoad((options) => {
		if (options.wyxdFilePath) {
			wyxdWorkspaceId.value = options.wyxdWorkspaceId ? decodeURIComponent(options.wyxdWorkspaceId) : ''
			wyxdFilePath.value = decodeURIComponent(options.wyxdFilePath)
			wyxdFileName.value = options.wyxdFileName ? decodeURIComponent(options.wyxdFileName) : '未命名文档'
			wyxdUserToken.value = options.wyxdUserToken ? decodeURIComponent(options.wyxdUserToken) : ''
			hasWyxdFile.value = true
		}
	})

	onMounted(() => {
		// currentSessionId.value = getCurrentSessionId()
	})
	onShow(async () => {
		// 优先获取用户信息（用于显示头像等）
		await takeUserInfo();

		const chatType = getChatType()
		ChatType.value = chatType
		const sessionId = getCurrentSessionId()

		// 只有在会话ID存在时才获取消息
		if (sessionId) {
			currentSessionId.value = sessionId
		}

		if (chatType === 0) {
			await takeUserConversations();
			// 只有在会话ID存在时才获取消息
			if (currentSessionId.value) {
				isLoadingMessages.value = true
				takeConversationMessages();
			}
			// AI 对话页面：主动建立 socket 连接，确保消息实时推送
			if (userToken.value && currentSessionId.value && !socketStore.isConnected) {
				handleConnect()
			}
		} else if (chatType === 1) {
			await takeFriendList()
			isLoadingMessages.value = true
			await takeFriendMessages();
			handleFriendConnect()
		} else if (chatType === 2) {
			await takeGroupList()
			await handleFriendConnect()
			isLoadingMessages.value = true
			await takeGroupMessages()
		}
	})

	// 离开 AI 对话页面时断开 socket
	onUnload(() => {
		socketStore.disconnect()
	})
</script>

<style lang="scss" scoped>
	@import "./chat.css";

	
</style>

<!-- v-html 渲染的 markdown 内容无法被 scoped 样式命中，需非 scoped 样式块 -->
<style lang="scss">
	/* 气泡内容字体：确保 v-html 渲染的 markdown 内容使用系统字体栈 */
	.chat-content {
		// font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'HarmonyOS Sans', 'MiSans', 
		// 	'Source Han Sans CN', 'Microsoft YaHei', 'Noto Sans CJK SC', sans-serif;
		// font-weight: 400;

		font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'HarmonyOS Sans', 'MiSans',
			'Source Han Sans CN', 'Microsoft YaHei', 'Noto Sans CJK SC', sans-serif !important;
		font-size: 16px !important;
		font-weight: 600 !important;
		line-height: 1.6;
		margin: 8rpx 0;

		h1 {
			font-size: 42rpx;
			line-height: 1.4;
			margin: 16rpx 0 10rpx;
		}

		h2 {
			font-size: 38rpx;
			line-height: 1.4;
			margin: 14rpx 0 8rpx;
		}

		h3 {
			font-size: 36rpx;
			line-height: 1.4;
			margin: 12rpx 0 8rpx;
		}

		h4,
		h5,
		h6 {
			font-size: 34rpx;
			line-height: 1.5;
			margin: 10rpx 0 6rpx;
		}

		/* 普通正文 */
		p,
		text,
		span {
			font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'HarmonyOS Sans', 'MiSans',
				'Source Han Sans CN', 'Microsoft YaHei', 'Noto Sans CJK SC', sans-serif !important;
			font-size: 38rpx !important;
			font-weight: 400 !important;
			line-height: 1.6;
			margin: 8rpx 0;
		}

		/* 粗体和斜体样式 */
		strong,
		b {
			font-weight: 600 !important;
		}

		em,
		i {
			font-style: italic !important;
		}

		/* 气泡内的内联链接（替代 <a> 标签，点击由 JS 拦截处理） */
		.chat-inline-link {
			color: #3b86ff !important;
			text-decoration: underline;
			word-break: break-all;
		}

		/* 可下载文件链接 → 按钮卡片样式 */
		.chat-download-btn {
			display: inline-flex !important;
			align-items: center;
			gap: 8rpx;
			padding: 8rpx 14rpx;
			margin: 4rpx 0;
			background: #f5f7fb;
			border: 2rpx solid #e4e8f0;
			border-radius: 8rpx;
			text-decoration: none !important;
			color: #333 !important;
			word-break: break-all;
			transition: all 0.15s ease;

			&:active {
				background: #eef1f7;
				border-color: #c8cde0;
				transform: scale(0.98);
			}

			.chat-dl-icon {
				font-size: 26rpx !important;
				line-height: 1;
				flex-shrink: 0;
			}

			.chat-dl-name {
				font-size: 16px !important;
				font-weight: 500;
				color: #333;
				overflow: hidden;
				text-overflow: ellipsis;
				white-space: nowrap;
			}
		}
	}

	/* 流式气泡中的加载动画 */
	.bubble-loading-dots {
		display: flex;
		align-items: center;
		justify-content: flex-start;
		padding: 10rpx 0;
	}

	.bubble-dot {
		width: 12rpx;
		height: 12rpx;
		border-radius: 50%;
		background: #a0a0b8;
		margin-right: 10rpx;
		animation: bubbleDotBlink 1.2s infinite ease-in-out;
	}

	.bubble-dot:nth-child(2) {
		animation-delay: 0.3s;
	}

	.bubble-dot:nth-child(3) {
		animation-delay: 0.6s;
	}

	@keyframes bubbleDotBlink {

		0%,
		100% {
			opacity: 0.25;
			transform: scale(0.7);
		}

		50% {
			opacity: 1;
			transform: scale(1.1);
		}
	}

	/* 消息加载中弹窗（仅会话变化时显示） */
	.messages-loading-overlay {
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		display: flex;
		justify-content: center;
		align-items: center;
		background: rgba(255, 255, 255, 0.9);
		z-index: 100;
	}

	.messages-loading-card {
		display: flex;
		flex-direction: column;
		align-items: center;
		padding: 40rpx 60rpx;
		background: #ffffff;
		border-radius: 24rpx;
		box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
	}

	.messages-loading-text {
		margin-top: 20rpx;
		font-size: 28rpx;
		color: #999;
	}

	/* 消息详情弹窗 — 链接提取区域 */
	.detail-links-section {
		margin-top: 24rpx;
		padding-top: 24rpx;
		border-top: 1rpx solid #eee;
	}

	.detail-links-title {
		font-size: 30rpx;
		font-weight: 600;
		color: #666;
		margin-bottom: 16rpx;
	}

	.detail-link-item {
		padding: 16rpx 20rpx;
		background: #f5f7fa;
		border-radius: 12rpx;
		margin-bottom: 12rpx;
	}

	.link-text {
		font-size: 28rpx;
		color: #007aff;
		word-break: break-all;
	}

	.detail-empty {
		display: flex;
		align-items: center;
		justify-content: center;
		padding: 40rpx 0;
		font-size: 28rpx;
		color: #999;
	}

	/* ========== 自定义下载 Toast（层级高于 ncd-overlay 的 9999）========== */
	.download-toast-overlay {
		position: fixed;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		display: flex;
		align-items: center;
		justify-content: center;
		z-index: 10001;
		pointer-events: none;
	}

	.download-toast-card {
		display: flex;
		align-items: center;
		justify-content: center;
		padding: 24rpx 40rpx;
		border-radius: 24rpx;
		backdrop-filter: blur(12px);
		-webkit-backdrop-filter: blur(12px);
		box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15);
		animation: toastFadeIn 0.25s ease;
		pointer-events: auto;
	}

	@keyframes toastFadeIn {
		from {
			opacity: 0;
			transform: scale(0.85);
		}

		to {
			opacity: 1;
			transform: scale(1);
		}
	}

	.download-toast-loading {
		background: rgba(40, 40, 60, 0.92);
	}

	.download-toast-success {
		background: rgba(40, 60, 40, 0.92);
	}

	.download-toast-error {
		background: rgba(60, 30, 30, 0.92);
	}

	.download-toast-icon {
		font-size: 36rpx;
		color: #fff;
		margin-right: 16rpx;
		display: flex;
		align-items: center;
	}

	.download-toast-card.download-toast-loading .download-toast-icon {
		gap: 8rpx;
	}

	.toast-dot {
		width: 12rpx;
		height: 12rpx;
		border-radius: 50%;
		background: #fff;
		animation: toastDotBlink 1.2s infinite ease-in-out;
	}

	.toast-dot:nth-child(2) {
		animation-delay: 0.3s;
	}

	.toast-dot:nth-child(3) {
		animation-delay: 0.6s;
	}

	@keyframes toastDotBlink {

		0%,
		100% {
			opacity: 0.3;
			transform: scale(0.75);
		}

		50% {
			opacity: 1;
			transform: scale(1.2);
		}
	}

	.download-toast-text {
		font-size: 28rpx;
		color: #fff;
		font-weight: 500;
	}
</style>