<template>
	<view class="ai-chat-page">
		<!-- 顶部区域 -->
		<view class="header-container">
			<!-- 宣传横幅 -->
			<view class="promo-banner">
				<view class="promo-content">
					<view class="promo-left">
						<text class="promo-title">慢生活守护健康</text>
						<text class="promo-sparkle">✦</text>
					</view>
					<text class="promo-subtitle">营养师专家入驻，在线答疑</text>
					</view>
					<view class="promo-right">
						<view class="tts-toggle-btn" @click="toggleTTS" :class="{ active: ttsEnabled }">
							<text class="tts-toggle-icon">🔊</text>
							<!--<text class="tts-toggle-text">{{ ttsEnabled ? '播报开' : '播报关' }}</text>-->
						</view>
						<view class="clear-btn" @click="clearChat">
							<text class="clear-icon">🗑️</text>
						</view>
						<image class="promo-avatar" src="https://uthink2025.oss-cn-shanghai.aliyuncs.com//crmebimage/public/content/2026/01/11/afcaba68d00b4fccaa49ad2a42c78e7fkk03hqv5vl.png" mode="aspectFit"></image>
					</view>
				</view>
		</view>
		
		<!-- 聊天消息列表 -->
		<scroll-view 
			class="chat-container" 
			scroll-y 
			:scroll-top="scrollTop"
			:scroll-with-animation="true"
		>
			<view class="message-list">
				<!-- AI欢迎消息 -->
				<view class="message-item ai-message">
					<view class="message-avatar">
						<text class="avatar-icon">🤖</text>
					</view>
					<view class="message-content">
						<view class="message-header">
							<text class="message-sender">AI营养师</text>
						</view>
						<view class="message-bubble ai-bubble">
							<text class="message-text">{{ welcomeMessage }}</text>
						</view>
					</view>
					</view>

				<!-- 消息列表 -->
				<view 
					v-for="(msg, index) in messageList" 
					:key="index"
					:class="['message-item', msg.role === 'user' ? 'user-message' : 'ai-message']"
				>
					<!-- AI头像 -->
					<view class="message-avatar" v-if="msg.role === 'ai'">
						<text class="avatar-icon">🤖</text>
					</view>
					
					<view class="message-content">
						<!-- AI发送者名称 -->
						<view class="message-header" v-if="msg.role === 'ai'">
							<text class="message-sender">AI营养师</text>
						</view>
						
					<!-- 消息气泡 -->
					<view :class="['message-bubble', msg.role === 'user' ? 'user-bubble' : 'ai-bubble']">
					<!-- AI 消息 loading 占位（等待回复时显示打字动画）-->
					<view v-if="msg.role === 'ai' && msg.loading" class="typing-indicator">
						<view class="typing-dot"></view>
						<view class="typing-dot"></view>
						<view class="typing-dot"></view>
					</view>
					<text v-else-if="msg.type !== 'image'" class="message-text">{{ msg.content }}<text v-if="msg.streaming" class="streaming-cursor">|</text></text>
						<image
							v-else
							:src="msg.imageUrl"
							mode="widthFix"
							class="message-image"
							@click="previewImage(msg.imageUrl)"
						></image>
					</view>
					<!-- AI 回复操作按钮组 -->
					<view
						v-if="msg.role === 'ai' && !msg.loading && !msg.streaming && msg.content && msg.type !== 'image'"
						class="msg-actions"
					>
						<!-- 复制 -->
						<view class="action-btn" @click="copyMessage(index)">
							<text class="iconfont icon-fuzhi action-icon"></text>
						</view>
						<!-- 重新生成（仅最后一条AI消息显示） -->
						<view class="action-btn" v-if="isLastAiMessage(index)" @click="regenerateMessage(index)">
							<text class="iconfont icon-shuaxin action-icon"></text>
						</view>
						<!-- 语音朗读 -->
						<view class="action-btn" :class="{ 'action-btn-active': ttsPlayingIndex === index }" @click="ttsPlayingIndex === index ? stopTTS() : playTTS(index)">
							<text class="iconfont icon-laba action-icon"></text>
						</view>
						<!-- 删除 -->
						<view class="action-btn" @click="deleteMessage(index)">
							<text class="iconfont icon-shanchu action-icon"></text>
						</view>
					</view>
				</view>
				</view>

				<!-- 加载中提示（仅在没有流式占位消息时显示） -->
				<view v-if="isLoading && !messageList.some(m => m.loading || m.streaming)" class="message-item ai-message">
					<view class="message-avatar">
						<text class="avatar-icon">🤖</text>
					</view>
					<view class="message-content">
						<view class="message-header">
							<text class="message-sender">AI营养师</text>
						</view>
						<view class="message-bubble ai-bubble">
							<view class="typing-indicator">
								<view class="typing-dot"></view>
								<view class="typing-dot"></view>
								<view class="typing-dot"></view>
							</view>
						</view>
					</view>
					</view>
				</view>
		</scroll-view>

		<!-- 快捷问题区域 -->
		<view class="quick-questions">
			<view class="quick-btn" @click="sendQuickQuestion('透析患者可以喝牛奶吗？')">
				<text>透析患者可以喝牛奶吗？</text>
				</view>
			<view class="quick-btn" @click="sendQuickQuestion('什么食物含磷比较低？')">
				<text>什么食物含磷比较低？</text>
				</view>
		</view>

		<!-- 输入区域 -->
		<view class="input-container">
			<!-- 图片预览区域 -->
			<view class="image-preview-area" v-if="pendingImages.length > 0">
				<view class="preview-item" v-for="(img, index) in pendingImages" :key="index">
					<image :src="img.path" mode="aspectFill" class="preview-img"></image>
					<view class="delete-btn" @click="removeImage(index)">
						<text class="delete-icon">×</text>
					</view>
					</view>
				<!-- 继续上传按钮 -->
				<view class="preview-item add-btn" @click="chooseImage" v-if="pendingImages.length < 3">
					<text class="add-icon">+</text>
					</view>
				</view>

			<view class="input-wrapper">
				<!-- 照片上传按钮 -->
				<view class="action-btn" @click="chooseImage">
					<image class="action-icon-svg" src="/static/images/icon-camera.svg" mode="aspectFit"></image>
					</view>
				
				<!-- 语音切换按钮 -->
				<view class="action-btn" @click="toggleVoiceMode">
					<image v-if="!isVoiceMode" class="action-icon-svg mic" src="/static/images/icon-mic.svg" mode="aspectFit"></image>
					<text v-else class="action-icon">⌨️</text>
					</view>
				
				<!-- 文本输入框 -->
				<input 
					v-if="!isVoiceMode"
					class="chat-input" 
					v-model="inputText" 
					placeholder="输入您的问题..."
					placeholder-style="color: #9fa5c0"
					confirm-type="send"
					@confirm="sendMessage"
					:focus="inputFocus"
					@focus="onInputFocus"
					@blur="onInputBlur"
				/>
				
				<!-- 语音按住按钮 -->
				<view 
					v-else 
					class="voice-hold-btn" 
					:class="{ recording: isRecording }"
					@touchstart="startRecord" 
					@touchend="stopRecord"
				>
					<text>{{ isRecording ? '松开 结束' : '按住 说话' }}</text>
					</view>
				
				<!-- 发送按钮 -->
				<view 
					class="send-btn" 
					:class="{ active: inputText.trim().length > 0 || pendingImages.length > 0 }"
					@click="sendMessage"
				>
					<image 
						v-if="inputText.trim().length > 0 || pendingImages.length > 0" 
						class="send-icon-svg" 
						src="/static/images/icon-send-active.svg" 
						mode="aspectFit"
					></image>
					<image 
						v-else 
						class="send-icon-svg" 
						src="/static/images/icon-send.svg" 
						mode="aspectFit"
					></image>
					</view>
				</view>
		</view>
	</view>
</template>

<script>
import api from '@/api/models-api.js';

export default {
	data() {
		return {
			scrollTop: 0,
			lastScrollTop: 0, // 用于动态滚动
			inputText: '',
			inputFocus: false,
			isLoading: false,
			isVoiceMode: false,
			isRecording: false,
			recorderManager: null,
			recordDuration: 0,
			recordTimer: null,
			pendingImages: [], // 待发送图片
			welcomeMessage: `👋您好！我是您的AI营养师助手。

我可以帮您：
• 解答饮食疑问
• 评估食物适宜性
• 提供烹饪建议
• 解读检验报告

有什么想问的吗？`,
			messageList: [],
			// TTS
			ttsEnabled: false,
			ttsPlaying: false,
			ttsPlayingIndex: -1,
			innerAudioContext: null
		}
	},
	onLoad() {
		// 页面加载时滚动到底部
		this.$nextTick(() => {
			this.scrollToBottom()
		});
		this.initRecorder();
		this.initAudioContext();
	},
	onUnload() {
		this.stopRecordTimer();
		if (this.isRecording && this.recorderManager) {
			this.recorderManager.stop();
		}
		if (this._streamCtrl) {
			this._streamCtrl.abort();
			this._streamCtrl = null;
		}
		if (this.innerAudioContext) {
			this.innerAudioContext.destroy();
			this.innerAudioContext = null;
		}
	},
	methods: {
		// 初始化录音管理器
		initRecorder() {
			// #ifdef MP-WEIXIN || APP-PLUS
			this.recorderManager = uni.getRecorderManager();
			
			this.recorderManager.onStart(() => {
				console.log('录音开始');
				this.recordDuration = 0;
				this.startRecordTimer();
			});
			
			this.recorderManager.onStop((res) => {
				console.log('录音结束', res);
				this.stopRecordTimer();
				
				if (res.duration < 1000) {
					uni.showToast({
						title: '录音时间太短',
						icon: 'none'
					});
					return;
				}
				
				this.handleRecordResult(res);
			});
			
			this.recorderManager.onError((err) => {
				console.error('录音错误:', err);
				this.isRecording = false;
				this.stopRecordTimer();
				uni.showToast({
					title: '录音失败，请重试',
					icon: 'none'
				});
			});
			// #endif
		},
		
		startRecordTimer() {
			this.recordTimer = setInterval(() => {
				this.recordDuration++;
				if (this.recordDuration >= 60) {
					this.stopRecord();
				}
			}, 1000);
		},
		
		stopRecordTimer() {
			if (this.recordTimer) {
				clearInterval(this.recordTimer);
				this.recordTimer = null;
			}
			this.recordDuration = 0;
		},
		
		toggleVoiceMode() {
			this.isVoiceMode = !this.isVoiceMode;
			if (this.isVoiceMode) {
				// 隐藏键盘
				uni.hideKeyboard();
			} else {
				// 聚焦输入框
				this.$nextTick(() => {
					this.inputFocus = true;
				});
			}
		},
		
		startRecord() {
			// #ifdef H5
			uni.showModal({
				title: '提示',
				content: 'H5环境暂不支持语音输入，请使用小程序或APP',
				showCancel: false
			});
			return;
			// #endif
			
			// #ifdef MP-WEIXIN || APP-PLUS
			if (!this.recorderManager) {
				uni.showToast({
					title: '录音功能初始化失败',
					icon: 'none'
				});
				return;
			}
			
			uni.authorize({
				scope: 'scope.record',
				success: () => {
					this.isRecording = true;
					this.recorderManager.start({
						duration: 60000,
						sampleRate: 16000,
						numberOfChannels: 1,
						encodeBitRate: 96000,
						format: 'mp3'
					});
				},
				fail: () => {
					uni.showModal({
						title: '需要录音权限',
						content: '请在设置中开启录音权限',
						confirmText: '去设置',
						success: (res) => {
							if (res.confirm) {
								uni.openSetting();
							}
						}
					});
				}
			});
			// #endif
		},
		
		stopRecord() {
			if (!this.isRecording) return;
			this.isRecording = false;
			// #ifdef MP-WEIXIN || APP-PLUS
			if (this.recorderManager) {
				this.recorderManager.stop();
			}
			// #endif
			uni.showToast({
				title: '正在识别...',
				icon: 'loading',
				duration: 2000
			});
		},
		
		async handleRecordResult(res) {
			try {
				// 跳过 OSS 上传（图片接口会拒绝 mp3）：直接读取 base64 调用一句话识别（≤60s）
				const base64Data = await this.readFileAsBase64(res.tempFilePath);
				if (!base64Data) {
					throw new Error('读取录音文件失败');
				}
				const dataLen = res.fileSize || Math.floor(base64Data.length * 3 / 4);
				const sentenceRes = await api.sentenceRecognition(base64Data, dataLen, 'mp3');
				if (!sentenceRes || sentenceRes.code !== 200) {
					throw new Error((sentenceRes && (sentenceRes.message || sentenceRes.msg)) || '语音识别失败');
				}
				const recognizedText = sentenceRes.data && sentenceRes.data.result ? String(sentenceRes.data.result).trim() : '';

				if (recognizedText) {
					this.inputText = this.inputText ? (this.inputText + ' ' + recognizedText) : recognizedText;
					if (this.isVoiceMode) {
						this.isVoiceMode = false;
						this.$nextTick(() => { this.inputFocus = true; });
					}
				} else {
					uni.showToast({ title: '未能识别出语音内容', icon: 'none' });
				}
			} catch (error) {
				console.error('语音识别流程失败:', error);
				uni.showToast({
					title: error && error.message ? error.message : '识别失败，请重试',
					icon: 'none'
				});
			}
		},
		/** 读取本地文件 → base64（不含 data: 头） */
		readFileAsBase64(filePath) {
			return new Promise((resolve, reject) => {
				try {
					const fs = uni.getFileSystemManager();
					fs.readFile({
						filePath,
						encoding: 'base64',
						success: (r) => resolve(r.data || ''),
						fail: (e) => reject(e)
					});
				} catch (e) { reject(e); }
			});
		},
		
		async recognizeSpeech(audioUrl) {
			try {
				const createTaskRes = await api.createAsrTask({
					url: audioUrl,
					engineModelType: '16k_zh',
					channelNum: 1,
					resTextFormat: 0,
					sourceType: 0
				});
				
				if (!createTaskRes || createTaskRes.code !== 200) {
					throw new Error('创建识别任务失败');
				}
				
				const taskId = createTaskRes.data.taskId;
				const result = await this.pollAsrResult(taskId);
				return this.parseAsrResult(result);
			} catch (error) {
				console.error('语音识别请求失败:', error);
				throw error;
			}
		},
		
		async pollAsrResult(taskId, maxAttempts = 30, interval = 2000) {
			let attempts = 0;
			while (attempts < maxAttempts) {
				attempts++;
				try {
					const statusRes = await api.queryAsrStatus(taskId);
					if (!statusRes || statusRes.code !== 200) throw new Error('查询识别状态失败');
					
					const status = statusRes.data.status;
					if (status === 2) return statusRes.data;
					else if (status === 3) throw new Error(statusRes.data.errorMsg || '识别失败');
					
					await new Promise(resolve => setTimeout(resolve, interval));
				} catch (error) {
					console.error('查询识别状态出错:', error);
					if (attempts >= maxAttempts) throw error;
					await new Promise(resolve => setTimeout(resolve, interval));
				}
			}
			throw new Error('识别超时，请重试');
		},
		
		parseAsrResult(data) {
			if (!data || !data.result) return '';
			let text = data.result;
			text = text.replace(/\[\d+:\d+\.\d+,\d+:\d+\.\d+\]\s*/g, '');
			text = text.replace(/\n+/g, ' ').trim();
			return text.replace(/\s+/g, ' ');
		},
		
		chooseImage() {
			uni.chooseImage({
				count: 3 - this.pendingImages.length, // 限制最多选择3张
				sizeType: ['compressed'],
				sourceType: ['album', 'camera'],
				success: async (res) => {
					uni.showLoading({ title: '处理中...' });
					try {
						// 遍历处理所有选择的图片
						for (const filePath of res.tempFilePaths) {
							// 使用新的 Coze 上传接口
							const uploadRes = await api.cozeUploadFile(filePath);
							
							if (uploadRes && (uploadRes.code === 0 || uploadRes.code === 200)) {
								const fileInfo = uploadRes.data;
								// 添加到待发送列表
								this.pendingImages.push({
									path: filePath,
									fileInfo: fileInfo
								});
							} else {
								throw new Error('上传失败: ' + (uploadRes?.msg || uploadRes?.message || '服务器返回异常'));
							}
						}
					} catch (error) {
						console.error('上传图片失败:', error);
						uni.showToast({ 
							title: error.message || '上传图片失败', 
							icon: 'none',
							duration: 3000
						});
					} finally {
						uni.hideLoading();
					}
				}
			});
		},
		
		removeImage(index) {
			this.pendingImages.splice(index, 1);
		},
		
		/** 将本地图片转为 data URL，用于 KieAI 多模态合并请求 */
		readFileAsDataUrl(filePath) {
			return new Promise((resolve, reject) => {
				const fs = uni.getFileSystemManager();
				const isJpeg = /\.(jpe?g|jfif)$/i.test(filePath);
				fs.readFile({
					filePath,
					encoding: 'base64',
					success: (res) => {
						const mime = isJpeg ? 'image/jpeg' : 'image/png';
						resolve(`data:${mime};base64,${res.data}`);
					},
					fail: reject
				});
			});
		},
		
		sendImageMessage(imageUrl, fileInfo) {
			// 该方法已废弃，逻辑合并到 sendMessage
		},
		
		previewImage(url) {
			uni.previewImage({
				urls: [url]
			});
		},

		sendQuickQuestion(question) {
			this.inputText = question;
			this.$nextTick(() => {
				this.sendMessage();
			});
		},
		// ---------- 消息操作方法（复制/删除/重新生成） ----------

		copyMessage(index) {
			const msg = this.messageList[index]
			if (!msg || !msg.content) return
			uni.setClipboardData({
				data: msg.content,
				success: () => {
					uni.showToast({ title: '已复制', icon: 'success' })
				}
			})
		},

		deleteMessage(index) {
			uni.showModal({
				title: '提示',
				content: '确定删除这条消息吗？',
				success: (res) => {
					if (res.confirm) {
						this.messageList.splice(index, 1)
						this.messageList = [...this.messageList]
					}
				}
			})
		},

		regenerateMessage(index) {
			// 找到该 AI 消息对应的上一条用户消息
			let userMsgIndex = index - 1
			while (userMsgIndex >= 0 && this.messageList[userMsgIndex].role !== 'user') {
				userMsgIndex--
			}
			if (userMsgIndex < 0) return
			const userMsg = this.messageList[userMsgIndex]
			// 移除当前 AI 回复
			this.messageList.splice(index, 1)
			this.messageList = [...this.messageList]
			// 重新发送
			this.sendToAI(userMsg.content, userMsg.type || 'text')
		},

		isLastAiMessage(index) {
			for (let i = this.messageList.length - 1; i >= 0; i--) {
				if (this.messageList[i].role === 'ai' && !this.messageList[i].loading) {
					return i === index
				}
			}
			return false
		},

		clearChat() {
			uni.showModal({
				title: '提示',
				content: '确定要清空对话吗？',
				success: (res) => {
					if (res.confirm) {
						if (this._streamCtrl) {
							this._streamCtrl.abort();
							this._streamCtrl = null;
						}
						this.isLoading = false;
						this.messageList = [];
					}
				}
			})
		},
		showCommonQuestions() {
			uni.showToast({
				title: '常见问题功能开发中',
				icon: 'none'
			})
		},
		onInputFocus() {
			this.inputFocus = true
			// 延迟滚动到底部，等待键盘弹出
			setTimeout(() => {
				this.scrollToBottom()
			}, 300)
		},
		onInputBlur() {
			this.inputFocus = false
		},
		async sendMessage() {
			if (!this.inputText.trim() && this.pendingImages.length === 0) {
				return
			}
			
			const imagesToSend = [...this.pendingImages];
			const text = this.inputText.trim();
			this.pendingImages = [];
			this.inputText = '';
			
			// 先展示用户消息到界面
			for (const img of imagesToSend) {
				this.messageList.push({
					role: 'user',
					type: 'image',
					imageUrl: img.path,
					content: '[图片]'
				});
			}
			if (text) {
				this.messageList.push({ role: 'user', content: text, type: 'text' });
			}
			this.scrollToBottom();
			
			// 合并为一次多模态请求：图片(base64) + 文字，统一走 KieAI Gemini
			const contentParts = [];
			try {
				for (const img of imagesToSend) {
					const dataUrl = await this.readFileAsDataUrl(img.path);
					contentParts.push({ type: 'image_url', image_url: { url: dataUrl } });
				}
				if (text) {
					contentParts.push({ type: 'text', text });
				} else if (contentParts.length > 0) {
					contentParts.push({ type: 'text', text: '请描述或分析这张图片' });
				}
			} catch (e) {
				console.error('读取图片失败:', e);
				this.messageList.push({ role: 'ai', content: '读取图片失败，请重试。' });
				this.scrollToBottom();
				return;
			}
			
			if (contentParts.length === 0) return;
			// 仅文字时走纯文字接口；否则走多模态（图+文）一次请求
			if (contentParts.length === 1 && contentParts[0].type === 'text') {
				await this.sendToAI(contentParts[0].text, 'text');
			} else {
				await this.sendToAI(contentParts, 'multimodal');
			}
			this.scrollToBottom();
		},
		
		/**
		 * 构建 OpenAI 兼容格式的 messages（KieAI Gemini /api/front/kieai/gemini/chat）
		 * @param {string|Array} content 消息内容
		 * @param {string} type 消息类型 text / image / multimodal
		 * @returns {Array} messages 数组 [{role, content}]
		 */
		buildChatMessages(content, type) {
			// test-0415 反馈3-4：图文问答需明确告诉模型「请基于图片识别食物 / 营养建议」，
			// 否则模型容易回退到通用客套话；纯文字场景仍走旧逻辑（避免影响 3-2 流式回归）
			const SYSTEM_NUTRITIONIST = '你是一名专业的中文营养师助手。当用户消息中包含图片（image_url）时，请先识别图片中的食物或菜品，然后基于其常见营养成分（能量、蛋白质、脂肪、碳水、钾、磷、钠、嘌呤等）给出针对慢性肾病/痛风/糖尿病人群的饮食建议；若图片不是食物，请明确说明「这张图不是食物」并简短回应用户。'

			if (type === 'text') {
				return [{ role: 'user', content: typeof content === 'string' ? content : String(content) }]
			}
			if (type === 'image') {
				let fileInfo = content
				if (typeof fileInfo === 'string') {
					try { fileInfo = JSON.parse(fileInfo) } catch (e) { /* 非JSON */ }
				}
				const imageUrl = (fileInfo && fileInfo.url) || (fileInfo && fileInfo.path) || ''
				if (imageUrl) {
					return [
						{ role: 'system', content: SYSTEM_NUTRITIONIST },
						{
							role: 'user',
							content: [
								{ type: 'image_url', image_url: { url: imageUrl } },
								{ type: 'text', text: '请基于这张图片识别食物，给出营养成分与饮食建议' }
							]
						}
					]
				}
				return [{ role: 'user', content: '我发送了一张图片，请帮我分析' }]
			}
			// multimodal：直接传多模态 parts；含 image_url 时附加 system prompt 引导模型分析图片
			const parts = Array.isArray(content) ? content : [{ type: 'text', text: String(content) }]
			const hasImage = parts.some((p) => p && (p.type === 'image_url' || p.image_url))
			const messages = []
			if (hasImage) {
				messages.push({ role: 'system', content: SYSTEM_NUTRITIONIST })
			}
			messages.push({ role: 'user', content: parts })
			return messages
		},

		async sendToAI(content, type) {
			this.isLoading = true;
			const aiMsg = { role: 'ai', content: '', loading: true, streaming: false };
			this.messageList.push(aiMsg);
			this.scrollToBottom();

			try {
				// BUG-005：本页文本/多模态对话统一走 KieAI Gemini（POST /api/front/kieai/gemini/chat）
				await this._sendViaGemini(content, type, aiMsg)
			} finally {
				aiMsg.loading = false;
				aiMsg.streaming = false;
				this.isLoading = false;
				this.messageList = [...this.messageList];
				this.scrollToBottom();
				if (this.ttsEnabled && aiMsg.content) {
					const aiIdx = this.messageList.indexOf(aiMsg);
					if (aiIdx !== -1) {
						this.$nextTick(() => this.playTTS(aiIdx));
					}
				}
			}
		},

		// ========== KieAI Gemini（流式 SSE，test-0415 反馈3-2）==========
		// 旧版 stream: false 等待全文返回 → 5-15s 才出文字。
		// 现行：调用 kieaiGeminiChatStream，首包到达即停 loading 圈，逐 chunk 渲染 → TTFB <1.5s
		_sendViaGemini(content, type, aiMsg) {
			return new Promise((resolve) => {
				const messages = this.buildChatMessages(content, type)
				let receivedAny = false
				let errorMsg = ''

				const ctrl = api.kieaiGeminiChatStream({ messages })
				this._streamCtrl = ctrl
				ctrl.onMessage((delta) => {
						if (!receivedAny) {
							receivedAny = true
							aiMsg.loading = false
							aiMsg.streaming = true
							this.messageList = [...this.messageList]
						}
						aiMsg.content += delta
						// 每个 chunk 触发响应式刷新（保证 streaming 光标移动）
						this.messageList = [...this.messageList]
						this.scrollToBottom()
					})
					.onError((err) => {
						errorMsg = (err && err.message) || ''
						console.error('Gemini 流式对话失败:', err)
					})
					.onComplete(() => {
						if (!receivedAny) {
							aiMsg.content = errorMsg || '抱歉，处理您的请求时出现错误，请稍后再试。'
						}
						this._streamCtrl = null
						resolve()
					})
			})
		},

		// ---------- TTS 方法 ----------

		initAudioContext() {
			// #ifdef MP-WEIXIN || APP-PLUS
			const ctx = uni.createInnerAudioContext()
			ctx.onEnded(() => {
				// test-0415 反馈3-3：分句队列里还有下一句则继续播；否则结束
				if (this.ttsPlaying && (this._ttsPrefetch || (this._ttsQueue && this._ttsQueue.length > 0))) {
					this._playNextTTSChunk()
					return
				}
				this.ttsPlaying = false
				this.ttsPlayingIndex = -1
			})
			ctx.onError((e) => {
				console.error('[TTS] 播放出错', e)
				this.ttsPlaying = false
				this.ttsPlayingIndex = -1
			})
			this.innerAudioContext = ctx
			// #endif
		},

		toggleTTS() {
			this.ttsEnabled = !this.ttsEnabled
			if (!this.ttsEnabled && this.ttsPlaying) {
				this.stopTTS()
			}
		},

		/**
		 * 分句播放 TTS（test-0415 反馈3-3）
		 * - 旧版：把全文丢给 TTS → 大文本合成耗时长 → 首声延迟 4-8s
		 * - 现行：按中文句号/问号/感叹号/分号切分；首句合成立即播放，
		 *   下一句在前一句播放期间预合成，形成流水线
		 */
		async playTTS(index) {
			const msg = this.messageList[index]
			if (!msg || !msg.content) return
			if (this.ttsPlaying) this.stopTTS()

			const sentences = this.splitTTSSentences(msg.content)
			if (sentences.length === 0) return

			this.ttsPlayingIndex = index
			this.ttsPlaying = true
			this._ttsQueue = sentences.slice()
			this._ttsCurrentIndex = index
			// 预先发起首句合成（首字节最快）
			this._ttsPrefetch = api.cozeTextToSpeech({ input: this._ttsQueue.shift() })
			this._playNextTTSChunk()
		},

		async _playNextTTSChunk() {
			if (!this.ttsPlaying || this.ttsPlayingIndex !== this._ttsCurrentIndex) return
			let prefetch = this._ttsPrefetch
			this._ttsPrefetch = null

			// 提前发起下一句合成（pipeline）
			if (this._ttsQueue && this._ttsQueue.length > 0) {
				const nextText = this._ttsQueue.shift()
				this._ttsPrefetch = api.cozeTextToSpeech({ input: nextText })
			}

			try {
				const tempPath = await prefetch
				if (!this.innerAudioContext || !this.ttsPlaying) return
				this.innerAudioContext.src = tempPath
				this.innerAudioContext.play()
			} catch (e) {
				console.error('[TTS] 合成失败', e)
				if (this._ttsQueue && this._ttsQueue.length > 0) {
					// 当前句失败，跳到下一句
					this._playNextTTSChunk()
				} else {
					uni.showToast({ title: '语音合成失败', icon: 'none' })
					this.ttsPlaying = false
					this.ttsPlayingIndex = -1
				}
			}
		},

		/** 中文句末标点切分；保留标点；空白过滤；超长（>120 字）强切 */
		splitTTSSentences(text) {
			if (!text) return []
			const re = /[^。！？!?；;\n]+[。！？!?；;]?/g
			const arr = (String(text).match(re) || [])
				.map((s) => s.trim())
				.filter(Boolean)
			const result = []
			for (const s of arr) {
				if (s.length <= 120) {
					result.push(s)
				} else {
					// 超长无终点：按 80 字硬切
					for (let i = 0; i < s.length; i += 80) result.push(s.slice(i, i + 80))
				}
			}
			return result
		},

		stopTTS() {
			if (this.innerAudioContext) {
				this.innerAudioContext.stop()
			}
			this._ttsQueue = []
			this._ttsPrefetch = null
			this.ttsPlaying = false
			this.ttsPlayingIndex = -1
		},

		// ---------- 滚动 ----------

		scrollToBottom() {
			this.$nextTick(() => {
				// 动态切换 scrollTop 值以触发滚动更新
				this.lastScrollTop = this.lastScrollTop === 99998 ? 99999 : 99998
				this.scrollTop = this.lastScrollTop
			})
		}
	}
}
</script>

<style lang="scss" scoped>
.ai-chat-page {
	height: 100vh;
	display: flex;
	flex-direction: column;
	background-color: #f4f5f7;
}

/* 顶部区域 */
.header-container {
	background: #ffffff;
	box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1), 0 2rpx 4rpx rgba(0, 0, 0, 0.06);
	flex-shrink: 0;
}

.nav-bar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	height: 98rpx;
	padding: 0 24rpx;
	border-bottom: 1rpx solid #d0dbea;
}

.nav-back {
	width: 56rpx;
	height: 56rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	
	.back-icon {
		font-size: 40rpx;
		color: #2e3e5c;
		font-weight: 300;
	}
	
	&:active {
		opacity: 0.6;
	}
}

.nav-title {
	font-size: 32rpx;
	color: #2e3e5c;
	font-weight: 400;
	letter-spacing: -0.5rpx;
}

.nav-placeholder {
	width: 56rpx;
}

/* 宣传横幅 */
.promo-banner {
	height: 192rpx;
	background: linear-gradient(135deg, #fdfaff 0%, #fcf5ff 50%, #ffffff 100%);
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 0 32rpx;
	position: relative;
	overflow: hidden;
}

.promo-content {
	display: flex;
	flex-direction: column;
	gap: 8rpx;
	z-index: 1;
}

.promo-left {
	display: flex;
	align-items: center;
	gap: 12rpx;
}

.promo-title {
	font-size: 36rpx;
	font-weight: 600;
	color: #ff6b35;
	letter-spacing: 1rpx;
}

.promo-sparkle {
	font-size: 28rpx;
	color: #ff6b35;
}

.promo-subtitle {
	font-size: 28rpx;
	color: #5c6bc0;
	font-weight: 400;
}

.promo-right {
	display: flex;
	align-items: center;
	gap: 20rpx;
	z-index: 1;
}

.clear-btn {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: 16rpx;
	background: rgba(255, 255, 255, 0.9);
	border-radius: 16rpx;
	box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
	transition: all 0.3s ease;
}

.clear-btn:active {
	transform: scale(0.95);
	background: rgba(255, 255, 255, 1);
	box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
}

.clear-icon {
	font-size: 32rpx;
	margin-bottom: 4rpx;
}

.clear-text {
	font-size: 22rpx;
	color: #ff6b35;
	font-weight: 500;
}

.tts-toggle-btn {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: 16rpx;
	background: rgba(255, 255, 255, 0.9);
	border-radius: 16rpx;
	box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
	transition: all 0.3s ease;
	margin-right: 12rpx;

	&.active {
		background: rgba(76, 175, 80, 0.15);
		box-shadow: 0 2rpx 8rpx rgba(76, 175, 80, 0.3);
	}
}

.tts-toggle-btn:active {
	transform: scale(0.95);
}

.tts-toggle-icon {
	font-size: 32rpx;
	margin-bottom: 4rpx;
}

.tts-toggle-text {
	font-size: 20rpx;
	color: #4caf50;
	font-weight: 500;
}

/* 消息操作按钮组 */
.msg-actions {
	display: flex;
	gap: 20rpx;
	margin-top: 12rpx;
	align-items: center;
}

.action-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 52rpx;
	height: 52rpx;
	background: rgba(0, 0, 0, 0.04);
	border-radius: 12rpx;
	cursor: pointer;
	transition: background 0.2s;
}

.action-btn:active {
	transform: scale(0.92);
	background: rgba(0, 0, 0, 0.08);
}

.action-btn-active {
	background: rgba(76, 175, 80, 0.15);
}

.action-icon {
	font-size: 28rpx;
	color: #666;
}

.action-btn-active .action-icon {
	color: #4caf50;
}

.tts-play-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	margin-top: 8rpx;
	width: 48rpx;
	height: 48rpx;
	background: rgba(76, 175, 80, 0.12);
	border-radius: 50%;
	cursor: pointer;
}

.tts-play-btn:active {
	transform: scale(0.9);
}

.tts-play-icon {
	font-size: 24rpx;
	color: #4caf50;
}

.promo-avatar {
	width: 180rpx;
	height: 180rpx;
}

/* 聊天容器 */
.chat-container {
	flex: 1;
	overflow-y: auto;
}

.message-list {
	padding: 32rpx;
	display: flex;
	flex-direction: column;
	gap: 32rpx;
}

.message-item {
	display: flex;
	gap: 16rpx;
	
	&.ai-message {
		justify-content: flex-start;
	}
	
	&.user-message {
		justify-content: flex-end;
	}
}

.message-avatar {
	width: 64rpx;
	height: 64rpx;
	border-radius: 50%;
	background: linear-gradient(180deg, #4facfe 0%, #00f2fe 100%);
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	
	.avatar-icon {
		font-size: 28rpx;
	}
}

.message-content {
	flex: 1;
	max-width: 75%;
	display: flex;
	flex-direction: column;
	gap: 8rpx;
}

.message-header {
	display: flex;
	align-items: center;
	padding-left: 16rpx;
	
	.message-sender {
		font-size: 24rpx;
		color: #9fa5c0;
	}
}

.message-bubble {
	border-radius: 32rpx;
	padding: 24rpx 32rpx;
	word-wrap: break-word;
	transition: all 0.3s ease;
	
	&.ai-bubble {
		background: #ffffff;
		border-top-left-radius: 8rpx;
		box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1), 0 2rpx 4rpx rgba(0, 0, 0, 0.06);
		
		.message-text {
			color: #3e5481;
		}
	}
	
	&.user-bubble {
		background: linear-gradient(135deg, #ff8c52 0%, #ff6b35 100%);
		border-top-right-radius: 8rpx;
		box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3);

		.message-text {
			color: #ffffff;
		}
	}
	
	.message-text {
		font-size: 28rpx;
		line-height: 1.65;
		white-space: pre-wrap;
		letter-spacing: -0.2rpx;
	}
}

/* 流式输出闪烁光标 */
.streaming-cursor {
	animation: blink 0.8s step-end infinite;
	color: #4facfe;
	font-weight: 600;
}

@keyframes blink {
	0%, 100% { opacity: 1; }
	50% { opacity: 0; }
}

/* 打字指示器 */
.typing-indicator {
	display: flex;
	gap: 10rpx;
	align-items: center;
	padding: 8rpx 0;
}

.typing-dot {
	width: 14rpx;
	height: 14rpx;
	border-radius: 50%;
	background: #4facfe;
	animation: typing 1.4s infinite;
	
	&:nth-child(2) {
		animation-delay: 0.2s;
	}
	
	&:nth-child(3) {
		animation-delay: 0.4s;
	}
}

@keyframes typing {
	0%, 60%, 100% {
		transform: translateY(0);
		opacity: 0.4;
	}
	30% {
		transform: translateY(-10rpx);
		opacity: 1;
	}
}

/* 消息图片 */
.message-image {
	max-width: 400rpx;
	border-radius: 16rpx;
}

/* 快捷问题区域 */
.quick-questions {
	display: flex;
	gap: 16rpx;
	padding: 20rpx 32rpx;
	flex-shrink: 0;
}

.quick-btn {
	flex: 1;
	height: 84rpx;
	background: #ffffff;
	border: 1rpx solid #e5e7eb;
	border-radius: 24rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1), 0 2rpx 4rpx rgba(0, 0, 0, 0.06);
	
	text {
		font-size: 24rpx;
		color: #3e5481;
		letter-spacing: -0.2rpx;
	}
	
	&:active {
		background: #f4f5f7;
		transform: scale(0.98);
	}
}

/* 图片预览区域 */
.image-preview-area {
	display: flex;
	gap: 20rpx;
	padding-bottom: 20rpx;
	overflow-x: auto;
	white-space: nowrap;
	
	.preview-item {
		position: relative;
		width: 120rpx;
		height: 120rpx;
		flex-shrink: 0;
		
		.preview-img {
			width: 100%;
			height: 100%;
			border-radius: 16rpx;
			background: #f5f5f5;
		}
		
		.delete-btn {
			position: absolute;
			top: -10rpx;
			right: -10rpx;
			width: 36rpx;
			height: 36rpx;
			background: rgba(0, 0, 0, 0.5);
			border-radius: 50%;
			display: flex;
			align-items: center;
			justify-content: center;
			z-index: 10;
			
			.delete-icon {
				color: #ffffff;
				font-size: 24rpx;
				line-height: 1;
				margin-top: -2rpx;
			}
		}
		
		&.add-btn {
			background: #f5f5f5;
			border-radius: 16rpx;
			display: flex;
			align-items: center;
			justify-content: center;
			border: 2rpx dashed #d0dbea;
			
			.add-icon {
				font-size: 48rpx;
				color: #9fa5c0;
				line-height: 1;
				margin-top: -4rpx;
			}
			
			&:active {
				background: #e0e0e0;
			}
		}
	}
}

/* 输入区域 */
.input-container {
	background: #f4f5f7;
	padding: 16rpx 32rpx;
	padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
	flex-shrink: 0;
}

.input-wrapper {
	display: flex;
	align-items: center;
	gap: 16rpx;
}

/* 操作按钮 */
.action-btn {
	width: 80rpx;
	height: 80rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	
	.action-icon {
		font-size: 44rpx;
	}
	
	.action-icon-svg {
		width: 48rpx;
		height: 48rpx;
		
		&.mic {
			width: 40rpx;
			height: 40rpx;
		}
	}
	
	&:active {
		opacity: 0.7;
	}
}

.nav-back-hover {
	opacity: 0.6;
}

/* 语音按住按钮 */
.voice-hold-btn {
	flex: 1;
	height: 88rpx;
	background: #ffffff;
	border: 1rpx solid #e5e7eb;
	border-radius: 100rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 30rpx;
	color: #3e5481;
	font-weight: 500;
	transition: all 0.2s;
	box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1), 0 2rpx 4rpx rgba(0, 0, 0, 0.06);
	
	&.recording {
		background: linear-gradient(135deg, #ff8c52 0%, #ff6b35 100%);
		color: #ffffff;
		border-color: transparent;
		transform: scale(0.98);
	}
	
	&:active {
		background: #f4f5f7;
	}
}

.chat-input {
	flex: 1;
	height: 88rpx;
	background: #ffffff;
	border: 1rpx solid #e5e7eb;
	border-radius: 100rpx;
	padding: 0 96rpx 0 32rpx;
	font-size: 32rpx;
	color: #3e5481;
	box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1), 0 2rpx 4rpx rgba(0, 0, 0, 0.06);
}

.send-btn {
	width: 80rpx;
	height: 80rpx;
	border-radius: 50%;
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
	background: transparent;
	
	&.active {
		background: rgba(255, 107, 53, 0.1);
		
		.send-icon-svg {
			transform: scale(1.1);
		}
		
		&:active {
			transform: scale(0.9);
			background: rgba(255, 107, 53, 0.2);
		}
	}
	
	.send-icon-svg {
		width: 40rpx;
		height: 40rpx;
		transition: all 0.3s;
	}
}
</style>
