<template>
  <view class="chat-container" :style="{ paddingTop: navBarHeight + 'px' }">
    <video
      v-if="isVideoBackground"
      class="background-video"
      :src="backgroundVideo"
      autoplay
      loop
      muted
      :controls="false"
      :show-center-play-btn="false"
      :show-fullscreen-btn="false"
      :enable-progress-gesture="false"
      object-fit="cover"
    ></video>
    <image
      v-else
      class="background-gif"
      :src="backgroundVideo"
      mode="aspectFill"
    />
    <view class="video-overlay"></view>
    <!-- 夜空装饰背景 -->
    <view class="night-sky-decoration">
      <view class="star star-1"></view>
      <view class="star star-2"></view>
      <view class="star star-3"></view>
      <view class="star star-4"></view>
      <view class="star star-5"></view>
      <view class="star star-6"></view>
      <view class="star star-7"></view>
      <view class="star star-8"></view>
    </view>

    <!-- 自定义导航栏 -->
    <view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
      <view class="navbar-content">
        <view class="navbar-left">
          <text class="clear-btn" @tap="clearContext">清空</text>
        </view>
        <view class="navbar-center centered">
          <image class="character-avatar" :src="processedAvatar" mode="aspectFill" />
          <text class="character-name">{{ currentCharacter.name }}</text>
        </view>
        <view class="navbar-right">
        </view>
      </view>
    </view>

    <!-- 🎧 语音对话模式 -->
    <view v-if="isVoiceMode" class="voice-mode-container" :style="{ height: chatMessagesHeight }">
      <view class="voice-mode-content">
        <!-- 角色头像 -->
        <view class="voice-avatar-wrapper">
          <image class="voice-avatar" :src="processedAvatar" mode="aspectFill" />
          <view class="avatar-glow" :class="[voiceState]"></view>
        </view>

        <!-- 状态文本 -->
        <text class="voice-state-text">{{ voiceStateText }}</text>

        <!-- 声波动画 -->
        <view class="sound-wave-container">
          <view class="sound-wave" :key="voiceWaveKey" :class="[voiceState]">
            <view
              class="wave-bar"
              v-for="(style, i) in voiceWaveStyles"
              :key="i"
              :style="style"
            ></view>
          </view>
        </view>

        <!-- 🧪 测试按钮（仅测试模式显示） -->
        <view v-if="isTestMode" class="test-controls">
          <button class="test-btn" @click="sendTestAudio" :disabled="voiceState === 'thinking' || voiceState === 'speaking'">
            {{ testButtonText }}
          </button>
          <text class="test-hint">测试模式：点击发送假数据</text>
        </view>

      </view>
    </view>

    <!-- 聊天消息区域 - 使用 Ant Design X Bubble.List -->
    <scroll-view
      v-else
      class="chat-messages"
      :style="{ height: chatMessagesHeight }"
      scroll-y="true"
      :scroll-top="scrollTop"
      scroll-with-animation="true"
    >
      <view class="message-list">
        <!-- 使用 Bubble 组件展示消息 -->
        <view
          v-for="(message, index) in messages"
          :key="index"
          class="message-wrapper"
        >
          <!-- 系统消息 -->
          <view v-if="message.type === 'system'" class="system-message">
            <text class="system-text">{{ message.content }}</text>
          </view>

          <!-- AI/用户消息 -->
          <view v-else class="bubble-container" :class="[message.type]">
            <view class="bubble-avatar" v-if="message.type === 'ai'">
              <image :src="processedAvatar" mode="aspectFill" />
            </view>

            <view class="bubble-content">
              <view class="bubble" :class="[message.type]">
                <text class="bubble-text">{{ message.content }}</text>

                <!-- AI 消息操作按钮 - 已隐藏播放按钮 -->
                <!-- <view class="bubble-actions" v-if="message.type === 'ai' && isLoggedIn">
                  <view class="action-btn" @click="() => playVoiceResponse(message.content)">
                    <text class="action-icon">🔊</text>
                  </view>
                </view> -->

                <view class="bubble-time">{{ message.time }}</view>
              </view>
            </view>

            <view class="bubble-avatar" v-if="message.type === 'user'">
              <image :src="processedUserAvatar" mode="aspectFill" />
            </view>
          </view>
        </view>

        <!-- 正在输入指示器 -->
        <view class="typing-indicator" v-if="isTyping">
          <view class="bubble-avatar">
            <image :src="processedAvatar" mode="aspectFill" />
          </view>
          <view class="typing-bubble">
            <view class="typing-dots">
              <view class="dot"></view>
              <view class="dot"></view>
              <view class="dot"></view>
            </view>
          </view>
        </view>
      </view>
    </scroll-view>
  </view>

  <!-- 输入区域 - 使用 Ant Design X Sender 风格 -->
  <view class="input-area" :style="{
    bottom: inputAreaBottom + 'px',
    paddingBottom: inputAreaPaddingBottom
  }">
    <!-- 智能体选择按钮 -->
    <!-- <view class="action-bar" v-if="availableTemplates.length > 0">
      <view class="template-selector-btn" @click="showTemplateSelector">
        <text class="template-icon">🤖</text>
        <text class="template-name">{{ currentTemplateName }}</text>
        <text class="arrow-icon">›</text>
      </view>
    </view> -->

    <view class="sender-container">
      <!-- 文本输入模式 -->
      <view v-if="!isRecordingMode" class="text-input-mode">
        <!-- 🎧 语音模式切换按钮 -->
        <view class="voice-mode-toggle" @click="toggleVoiceMode">
          <text class="icon">{{ isVoiceMode ? '💬' : '🎧' }}</text>
        </view>

        <input
          class="text-input"
          v-model="inputText"
          placeholder="输入消息..."
          :disabled="isTyping || isLoading"
          @confirm="sendMessage"
          confirm-type="send"
          @focus="handleInputFocus"
          @blur="handleInputBlur"
          :adjust-position="false"
          :hold-keyboard="false"
        />

        <view class="input-actions">
          <!-- 麦克风按钮已隐藏 -->
          <!-- <view class="action-icon-btn" @click="toggleRecordingMode">
            <text class="icon">🎙️</text>
          </view> -->
          <view
            class="send-btn"
            :class="{'active': inputText.trim()}"
            @click="sendMessage"
          >
            <text class="send-icon">发送</text>
          </view>
        </view>
      </view>

      <!-- 语音输入模式 -->
      <view v-else class="voice-input-mode">
        <view
          class="hold-to-speak"
          :class="{'recording': isActuallyRecording}"
          @touchstart="startActualRecording"
          @touchend="stopActualRecording"
          @touchcancel="cancelActualRecording"
          @touchmove="handleRecordingMove"
        >
          <text class="speak-text">{{ isActuallyRecording ? '松手发送...' : '按住说话' }}</text>
        </view>

        <!-- 键盘按钮已隐藏 -->
        <!-- <view class="action-icon-btn" @click="toggleRecordingMode">
          <text class="icon">⌨️</text>
        </view> -->
      </view>
    </view>

    <!-- 录音状态提示 -->
    <view class="recording-overlay" v-if="isActuallyRecording">
      <view class="recording-modal">
        <text class="recording-tip">{{ recordingTipText }}</text>
        <view class="recording-animation" v-if="!isRecordingCancelled">
          <view class="wave" v-for="i in 5" :key="i"></view>
        </view>
      </view>
    </view>
  </view>

  <!-- 智能体选择弹窗 -->
  <!-- <view class="modal-overlay" v-if="showTemplateModal" @click="hideTemplateSelector">
    <view class="modal-content" @click.stop>
      <view class="modal-header">
        <text class="modal-title">选择智能体</text>
        <view class="close-btn" @click="hideTemplateSelector">
          <text class="close-icon">✕</text>
        </view>
      </view>

      <view class="template-list">
        <view
          class="template-item"
          :class="{'active': currentTemplateId === template.templateId}"
          v-for="template in availableTemplates"
          :key="template.templateId"
          @click="() => selectTemplate(template)"
        >
          <view class="template-info">
            <text class="template-name">{{ template.templateName }}</text>
            <text class="template-desc">{{ template.templateDesc }}</text>
          </view>
          <view class="check-mark" v-if="currentTemplateId === template.templateId">
            <text class="check-icon">✓</text>
          </view>
        </view>
      </view>
    </view>
  </view> -->
</template>

<script setup>
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch, getCurrentInstance } from 'vue';
import { useUserStore } from '@/stores/user.js';
import { getCharacterById, getSmartResponse } from '@/utils/aiCharacters.js';
import { chatAPI, voiceAPI, cleanText, configAPI, getResourceUrl } from '@/utils/api.js';
// 🔴 新增：导入未读消息管理模块
import {
  getUnreadMessages,
  clearUnreadMessages
} from '@/utils/unreadMessages.js';
// 🎙️ 新增：导入WebSocket语音流模块
import VoiceStreamWebSocket from '@/utils/voiceStreamWebSocket.js';
import { getWsUrl, API_CONFIG } from '@/utils/config.js';

// 🧪 测试模式配置
const isTestMode = ref(API_CONFIG.TEST_MODE.enabled);

// 获取组件实例
const instance = getCurrentInstance();

// 定义 Props
const props = defineProps({
  // 角色配置
  characterConfig: {
    type: Object,
    required: true,
    default: () => ({
      id: 'wei-ai',
      roleId: null,
      name: '蔚AI',
      avatar: '/static/avatar/icon_hushi.jpg',
      greeting: '你好！我是蔚AI',
      roleDesc: ''
    })
  },

  // AI 模型配置
  aiConfig: {
    type: Object,
    default: () => ({
      modelId: 10,
      templateId: 9999,
      ttsId: null,
      sttId: null,
      temperature: 0.7,
      topP: 0.9
    })
  }
});

// 状态管理
const userStore = useUserStore();

// 响应式数据
const messages = ref([]);
const inputText = ref('');
const isTyping = ref(false);
const isOnline = ref(true);
const scrollTop = ref(0);
const currentCharacter = ref({});
const userAvatar = ref('/static/default-avatar.png');
const backgroundMediaPath = 'file/background/2025/11/09/grok-video-db7bc117-861f-477e-bef3-e73273a4b55c-1.gif';
const backgroundVideo = computed(() => {
  const normalizedPath = backgroundMediaPath.startsWith('/') ? backgroundMediaPath : `/${backgroundMediaPath}`;
  return getResourceUrl(normalizedPath);
});
const isVideoBackground = computed(() => /\.(mp4|mov|webm)(\?|$)/i.test(backgroundMediaPath));
const conversationId = ref(null);
const isLoading = ref(false);
const isLoggedIn = ref(false);
const keyboardHeight = ref(0); // 键盘高度

// 状态栏高度适配
const statusBarHeight = ref(0);
const navBarHeight = ref(0);
const inputAreaHeight = ref(0); // 输入区域高度，页面加载时获取一次
const tabBarHeight = ref(0); // tabBar 高度

// 输入区域底部 padding：键盘弹出时去掉安全区域
const inputAreaPaddingBottom = computed(() => {
  const value = keyboardHeight.value > 0 ? '16rpx' : 'calc(16rpx + env(safe-area-inset-bottom))';
  return value;
});

// 输入区域的 bottom 值：如果在 tabBar 页面中，需要减去 tabBar 高度
const inputAreaBottom = computed(() => {
  const bottom = keyboardHeight.value > 0 ? keyboardHeight.value - tabBarHeight.value : 0;
  return bottom;
});

// 聊天区域高度：固定不变，只在初始化时计算一次
const chatMessagesHeight = computed(() => {
  // 100vh - 导航栏高度 - 输入区域高度
  const height = `calc(100vh - ${navBarHeight.value}px - ${inputAreaHeight.value}px)`;
  return height;
});

// 录音相关
const isRecordingMode = ref(false);
const isActuallyRecording = ref(false);
const isRecordingCancelled = ref(false);
const recordingTipText = ref('松手发送，上移取消');
const touchStartY = ref(0);
const recorderManager = ref(null);
const audioContext = ref(null);
const isRecorderActive = ref(false); // 录音器真实启动状态
const isVoiceModeInterrupted = ref(false); // 语音模式是否被中断（用于阻止 onStop 回调继续处理）
const volumeSimulationTimer = ref(null); // 音量模拟定时器

// 🎧 语音对话模式
const isVoiceMode = ref(false);
const voiceState = ref('idle'); // idle, listening, thinking, speaking
const isVoiceRecording = ref(false); // 是否正在录音
const voiceWaveKey = ref(0); // 用于强制刷新声纹动画

// VAD (语音活动检测) 相关
const isAutoVoiceMode = ref(true); // 是否开启自动语音模式
const currentVolume = ref(0);
const lastSpeechTimestamp = ref(0);
const silenceTimer = ref(null);
const recordedBuffers = ref([]);
const vadConfig = ref({
  threshold: 20,          // 说话阈值 (0-100)
  silenceDuration: 1500   // 静音判定时间 (ms)
});

// 🎙️ WebSocket 语音流相关
const voiceStreamWs = ref(null);
const isWebSocketMode = ref(true); // 是否使用WebSocket模式（默认false使用HTTP）
const audioPlayQueue = ref([]); // 音频播放队列
const isPlayingAudio = ref(false); // 是否正在播放音频
const currentSentence = ref(''); // 当前正在显示的句子
const currentChatSessionId = ref(null); // 对话会话ID（用于历史记录）
const currentVoiceTemplateId = ref(null); // 语音模式使用的模板ID

// AI配置
const availableTemplates = ref([]);
const currentModelId = ref(null);
const currentTemplateId = ref(null);
const showTemplateModal = ref(false);

// 🔴 新增：未读消息展示标记
const hasShownUnreadMessages = ref(false);

// 当前模板名称
const currentTemplateName = computed(() => {
  const template = availableTemplates.value.find(t => t.templateId === currentTemplateId.value);
  return template?.templateName || '默认智能体';
});

// 🎧 语音模式状态文本
const voiceStateText = computed(() => {
  const stateMap = {
    idle: '准备就绪',
    listening: '正在倾听...',
    thinking: '思考中...',
    speaking: '回复中...'
  };
  return stateMap[voiceState.value] || '准备就绪';
});

// 🧪 测试按钮文本
const testButtonText = computed(() => {
  const stateMap = {
    idle: '🧪 发送测试音频',
    listening: '⏺️ 录音中...',
    thinking: '⏳ 处理中...',
    speaking: '🔊 回复中...'
  };
  return stateMap[voiceState.value] || '🧪 发送测试音频';
});

// 根据音量动态生成声纹高度
const voiceWaveStyles = computed(() => {
  const barCount = 40;
  const volumeRatio = Math.min(Math.max(currentVolume.value / 100, 0), 1);
  const minHeight = 18; // rpx
  const maxHeight = 140; // rpx

  return Array.from({ length: barCount }, (_, index) => {
    const phase = (index / barCount) * Math.PI; // 0 - π
    const waveFactor = Math.abs(Math.sin(phase)); // 0-1
    const height = minHeight + (maxHeight - minHeight) * waveFactor * volumeRatio;
    const opacity = 0.2 + 0.6 * waveFactor * (0.3 + 0.7 * volumeRatio);

    return {
      height: `${height}rpx`,
      opacity,
      transitionDelay: `${(index % 10) * 0.02}s`
    };
  });
});


// 处理头像URL
const processedAvatar = computed(() => {
  return getResourceUrl(currentCharacter.value.avatar);
});

const processedUserAvatar = computed(() => {
  return getResourceUrl(userAvatar.value);
});

// 监听角色配置变化，触发初始化
watch(() => props.characterConfig, async (newConfig) => {
  if (!newConfig || !newConfig.id) return;

  // 设置当前角色
  currentCharacter.value = {
    id: newConfig.id,
    roleId: newConfig.roleId,
    name: newConfig.name,
    avatar: newConfig.avatar,
    greeting: newConfig.greeting,
    roleDesc: newConfig.roleDesc,
    // 合并 AI 配置
    modelId: props.aiConfig.modelId,
    templateId: props.aiConfig.templateId,
    ttsId: props.aiConfig.ttsId,
    sttId: props.aiConfig.sttId,
    temperature: props.aiConfig.temperature,
    topP: props.aiConfig.topP
  };

  // 加载 AI 配置
  // await loadAIConfigs();

  // 创建/获取会话
  createNewConversation(newConfig.id || newConfig.roleId);

  // 加载历史消息
  await loadHistoryMessages();

  // 如果没有历史消息，显示欢迎消息
  if (messages.value.length === 0) {
    addMessage('ai', currentCharacter.value.greeting);
    if (!isLoggedIn.value) {
      addMessage('system', '您当前处于未登录状态，将使用本地模拟回复。登录后可享受完整的AI对话功能。');
    }
  }

  // 🔴 新增：展示未读消息
  await showUnreadMessages();

  // 确保滚动到底部
  await nextTick();
  setTimeout(() => {
    scrollToBottom();
  }, 300);
}, { immediate: true, deep: true });

// 监听语音状态变化，每次切换都强制刷新声纹DOM，避免残留
watch(voiceState, () => {
  voiceWaveKey.value += 1;
});

// 获取系统状态栏高度
const getSystemInfo = () => {
  const systemInfo = uni.getSystemInfoSync();
  statusBarHeight.value = systemInfo.statusBarHeight || 0;
  // 导航栏总高度 = 状态栏高度 + 导航栏内容高度（44px）
  navBarHeight.value = statusBarHeight.value + 44;

  // 获取当前页面路径，判断是否在 tabBar 中
  const pages = getCurrentPages();
  const currentPage = pages[pages.length - 1];
  const currentRoute = currentPage ? currentPage.route : '';
  // tabBar 页面列表（从 pages.json 中获取）
  const tabBarPages = [
    'pages/drama/index',
    'pages/device/index',
    'pages/chat/chat-box',
    'pages/mine/mine'
  ];
  const isTabBarPage = tabBarPages.includes(currentRoute);
  // 获取 tabBar 高度
  if (systemInfo.screenHeight && systemInfo.windowHeight) {
    const heightDiff = systemInfo.screenHeight - systemInfo.windowHeight;
    // 如果是 tabBar 页面，尝试检测 tabBar 高度
    if (isTabBarPage) {
      if (heightDiff > 40 && heightDiff < 120) {
        // 直接使用检测到的高度差值
        tabBarHeight.value = heightDiff;
      } else if (heightDiff >= 120) {
        // 差值过大，可能包含其他因素，使用保守值
        tabBarHeight.value = 82;
      } else {
        // 差值过小，使用默认值
        tabBarHeight.value = 50;
      }
    } else {
      tabBarHeight.value = 0;
    }
  } else {
    // 如果是 tabBar 页面但无法获取高度，使用默认值
    if (isTabBarPage) {
      tabBarHeight.value = 50;
    }
  }
};

// 获取输入区域实际高度（只在初始化时调用一次）
const getInputAreaHeight = () => {
  // 延迟获取，确保 DOM 已渲染
  setTimeout(() => {
    // 使用 in(instance) 指定在当前组件内查找
    const query = uni.createSelectorQuery().in(instance);
    query.select('.input-area').boundingClientRect();
    query.exec((res) => {
      if (res && res[0] && res[0].height) {
        inputAreaHeight.value = res[0].height;
      } else {
        inputAreaHeight.value = 120; // 降级使用默认值
      }
    });
  }, 300); // 延迟 300ms 确保 DOM 渲染完成
};

onMounted(() => {
  getSystemInfo();     // 获取系统信息
  checkLoginStatus();  // 检查登录状态
  initRecorder();      // 初始化录音器
  getInputAreaHeight(); // 获取输入区域高度
  // 监听键盘高度变化
  // #ifdef MP-WEIXIN
  uni.onKeyboardHeightChange((res) => {
    keyboardHeight.value = res.height;
    // 键盘弹出时，延迟滚动到底部
    if (res.height > 0) {
      setTimeout(() => {
        scrollToBottom();
      }, 300); // 等待输入框移动动画完成
    }
  });
  // #endif
});

// 组件销毁前清理资源
onBeforeUnmount(() => {
  console.log('🧹 组件销毁前清理资源...');

  // 0. 设置中断标志，阻止 onStop 回调继续处理
  isVoiceModeInterrupted.value = true;

  // 1. 停止正在进行的录音（不发送到后端）
  if (isVoiceRecording.value) {
    isVoiceRecording.value = false;
    // #ifdef MP-WEIXIN
    stopRecorderSafely();
    // #endif
    console.log('⏹️ 已停止录音');
  }

  // 2. 清空录音缓冲区，防止 onStop 回调处理
  recordedBuffers.value = [];

  // 3. 停止正在播放的音频
  // #ifdef MP-WEIXIN
  if (audioContext.value) {
    try {
      audioContext.value.stop();
      audioContext.value.destroy();
      audioContext.value = null;
      console.log('⏹️ 已停止音频播放');
    } catch (error) {
      console.warn('停止音频播放失败:', error);
    }
  }
  // #endif

  // 4. 停止音量模拟
  stopVolumeSimulation();

  // 5. 重置所有语音状态
  voiceState.value = 'idle';
  currentVolume.value = 0;

  // 6. 关闭WebSocket连接
  if (voiceStreamWs.value) {
    try {
      voiceStreamWs.value.close();
      voiceStreamWs.value = null;
      console.log('🔌 已关闭WebSocket连接');
    } catch (error) {
      console.warn('关闭WebSocket失败:', error);
    }
  }

  // 7. 清空音频播放队列
  audioPlayQueue.value = [];
  isPlayingAudio.value = false;

  console.log('✅ 资源清理完成');
});

// 加载AI配置
const loadAIConfigs = async () => {
  try {
    const templatesResult = await configAPI.getTemplates();

    if (templatesResult.success) {
      availableTemplates.value = templatesResult.data.data?.list || templatesResult.data.data || templatesResult.data;

      if (availableTemplates.value.length > 0 && !currentTemplateId.value) {
        currentTemplateId.value = availableTemplates.value[0].templateId;
      }
    } else {
      // 模拟数据
      availableTemplates.value = [
        { templateId: 1, templateName: '默认AI模型', templateDesc: '在用户与AI角色对话时，这个智能体会记住用户的言语习惯。' },
        { templateId: 2, templateName: '1号AI模型', templateDesc: '学习用户的表达方式，并逐渐适应个性化的交流风格。' }
      ];
      currentTemplateId.value = 1;
    }
  } catch (error) {
    availableTemplates.value = [
      { templateId: 1, templateName: '默认AI模型', templateDesc: '默认AI模型' }
    ];
    currentTemplateId.value = 1;
  }
};

// 检查登录状态
const checkLoginStatus = () => {
  const customToken = uni.getStorageSync('custom_token');
  const userToken = uni.getStorageSync('user_token');
  const userInfo = uni.getStorageSync('userInfo');

  isLoggedIn.value = !!(userStore.token || customToken || userToken || userInfo);
};

// 初始化录音器
const initRecorder = () => {
  // #ifdef MP-WEIXIN
  recorderManager.value = uni.getRecorderManager();

  recorderManager.value.onStart(() => {
    console.log('录音开始');
    isRecorderActive.value = true;
    if (isVoiceMode.value && isAutoVoiceMode.value) {
      recordedBuffers.value = [];
      lastSpeechTimestamp.value = Date.now();
    }
  });

  recorderManager.value.onStop((res) => {
    isRecorderActive.value = false;

    // 🔴 检查语音模式是否被中断，如果是则不处理任何录音
    if (isVoiceModeInterrupted.value) {
      console.log('⚠️ 语音模式已中断，onStop 不处理录音');
      isVoiceModeInterrupted.value = false; // 重置标志
      return;
    }

    // 判断是语音模式还是普通录音模式
    if (isVoiceMode.value) {
      // 语音模式：处理语音对话
      if (isAutoVoiceMode.value && recordedBuffers.value.length > 0) {
        // 自动模式：使用手动生成的 WAV
        const wavPath = createWavFile();
        if (wavPath) {
          handleVoiceModeMessage(wavPath);
        } else {
          // 生成失败，尝试使用原生文件（虽然是 PCM）
          handleVoiceModeMessage(res.tempFilePath);
        }
      } else if (isVoiceRecording.value) {
        // 手动模式
        handleVoiceModeMessage(res.tempFilePath);
      }
      // 如果 recordedBuffers 为空或 isVoiceRecording 为 false，说明被中断了，不处理
    } else {
      // 普通录音模式：原有逻辑
      if (!isRecordingCancelled.value) {
        handleVoiceMessage(res.tempFilePath);
      }
    }
  });

  recorderManager.value.onFrameRecorded((res) => {
    if (!isVoiceMode.value || !isAutoVoiceMode.value) return;
    
    const { frameBuffer } = res;
    if (frameBuffer && frameBuffer.byteLength > 0) {
      recordedBuffers.value.push(frameBuffer);
      
      // VAD 实时分析
      const vol = calculateVolumeRMS(frameBuffer);
      currentVolume.value = vol;
      
      const now = Date.now();
      
      if (vol >= vadConfig.value.threshold) {
        lastSpeechTimestamp.value = now;
        
        // 如果处于等待说话状态，且检测到声音 -> 转为正在说话(listening)
        // 注意：这里复用 'listening' 状态，表示正在录入有效声音
        // 如果 voiceState 是 'idle'，表示正在等待用户说话
        if (voiceState.value === 'idle') {
          voiceState.value = 'listening';
          console.log('>>> VAD: 检测到说话');
        }
      } else {
        // 低于阈值 (静音)
        if (voiceState.value === 'listening') {
           const silenceTime = now - lastSpeechTimestamp.value;
           
           if (silenceTime > vadConfig.value.silenceDuration) {
             console.log('<<< VAD: 检测到说话结束 (静音超时)');
             // 停止录音并发送
             stopVoiceRecordingAndSend();
           }
        }
      }
    }
  });

  recorderManager.value.onError((err) => {
    isRecorderActive.value = false;
    console.error('录音错误', err);
    uni.showToast({
      title: '录音失败',
      icon: 'none'
    });
    isActuallyRecording.value = false;
    isVoiceRecording.value = false;
    voiceState.value = 'idle';
  });
  // #endif
};

// 统一的录音停止入口，避免重复 stop 触发错误
const stopRecorderSafely = () => {
  // #ifdef MP-WEIXIN
  if (!recorderManager.value || !isRecorderActive.value) {
    return false;
  }
  try {
    recorderManager.value.stop();
    return true;
  } catch (error) {
    console.warn('停止录音失败:', error);
    return false;
  } finally {
    isRecorderActive.value = false;
  }
  // #endif
  return false;
};

// 添加消息
const addMessage = (type, content) => {
  const now = new Date();
  const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;

  messages.value.push({
    type,
    content,
    time
  });

  nextTick(() => {
    scrollToBottom();
  });
};

// 滚动到底部
const scrollToBottom = () => {
  nextTick(() => {
    // 使用 in(instance) 指定在当前组件内查找
    const query = uni.createSelectorQuery().in(instance);
    query.select('.message-list').boundingClientRect();
    query.exec((res) => {
      if (res && res[0] && res[0].height) {
        // 设置一个足够大的值确保滚动到底部（包含底部padding）
        scrollTop.value = res[0].height + 2000;
      }
    });
  });
};

// 延时工具
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// 🔴 新增：展示未读消息的函数
const showUnreadMessages = async () => {
  // 防止重复展示
  if (hasShownUnreadMessages.value) {
    return;
  }

  const roleId = currentCharacter.value.roleId;
  if (!roleId) {
    hasShownUnreadMessages.value = true;
    return;
  }

  // 获取未读消息
  const unreadMessages = getUnreadMessages(roleId);

  if (unreadMessages.length === 0) {
    hasShownUnreadMessages.value = true;
    return;
  }

  console.log(`📬 角色 ${currentCharacter.value.name} (ID: ${roleId}) 有 ${unreadMessages.length} 条未读消息`);

  // 延迟展示，模拟角色正在输入
  await nextTick();
  setTimeout(async () => {
    // 显示"正在输入"状态
    isTyping.value = true;

    // 等待1.5秒（模拟打字延迟）
    await sleep(1500);

    isTyping.value = false;

    // 逐条展示未读消息
    for (let i = 0; i < unreadMessages.length; i++) {
      const msg = unreadMessages[i];

      // 添加消息
      addMessage('ai', msg.content);

      // 滚动到底部
      await nextTick();
      scrollToBottom();

      // 如果不是最后一条，稍作延迟（模拟连续发送的节奏）
      if (i < unreadMessages.length - 1) {
        await sleep(800);
      }
    }

    // 清空该角色的未读消息
    clearUnreadMessages(roleId);
    hasShownUnreadMessages.value = true;

    console.log(`✅ 已展示并清空角色 ${currentCharacter.value.name} 的未读消息`);

    // 最终滚动到底部
    setTimeout(() => {
      scrollToBottom();
    }, 300);
  }, 1000); // 进入聊天后1秒开始展示
};

// 根据 & 分段，逐条展示 AI 回复
const addSegmentedAIResponse = async (text) => {
  if (!text || typeof text !== 'string') return;
  // 使用 & 作为分隔符，按段落逐条展示
  const segments = text
    .split('&')
    .map(s => s.trim())
    .filter(Boolean);

  if (segments.length === 0) {
    addMessage('ai', text.trim());
    return;
  }

  for (let i = 0; i < segments.length; i++) {
    const seg = segments[i];
    addMessage('ai', seg);

    // 每添加一条消息就滚动到底部，确保用户能看到
    await nextTick();
    scrollToBottom();

    // 简单的节奏控制：与长度相关，介于300-1200ms
    const delay = Math.min(1200, Math.max(300, seg.length * 30));
    await sleep(delay);
  }

  // 所有段落添加完成后，再次确保滚动到底部
  setTimeout(() => {
    scrollToBottom();
  }, 100);
};

// 发送消息
const sendMessage = async () => {
  const text = inputText.value.trim();
  if (!text || isTyping.value || isLoading.value) return;

  addMessage('user', text);
  inputText.value = '';
  isTyping.value = true;

  try {
    await getAIResponse(text);
  } catch (error) {
    uni.showToast({
      title: '发送失败',
      icon: 'none'
    });
  } finally {
    isTyping.value = false;
  }
};

// 创建或获取会话ID（基于角色持久化存储）
const createNewConversation = (characterId) => {
  // 生成存储key：根据角色类型区分
  let storageKey = '';
  if (characterId === 'wei-ai' || !currentCharacter.value.roleId) {
    // 蔚AI或默认角色
    storageKey = `session_weiai`;
  } else {
    // 剧情角色
    storageKey = `session_role_${currentCharacter.value.roleId}`;
  }

  // 尝试从本地存储获取已有的sessionId
  let existingSessionId = uni.getStorageSync(storageKey);

  if (existingSessionId) {
    // 已有sessionId，直接使用（保持上下文）
    conversationId.value = existingSessionId;
  } else {
    // 生成新的sessionId
    const userId = userStore.userInfo?.openid || userStore.userInfo?.userId || 'guest';
    const timestamp = Date.now();
    const newSessionId = `session_${characterId}_${userId}_${timestamp}`;

    // 存储到本地
    uni.setStorageSync(storageKey, newSessionId);
    conversationId.value = newSessionId;
  }
};

// 加载历史消息
const loadHistoryMessages = async () => {
  if (!conversationId.value || !isLoggedIn.value) {
    return;
  }

  try {
    const result = await chatAPI.getHistoryMessages(conversationId.value);

    if (result.success && result.data && result.data.length > 0) {
      // 将后端消息格式转换为前端格式，保持与API回复一致的"清理后回复"逻辑
      const historyMessages = [];

      result.data.forEach(msg => {
        // 格式化时间
        let timeStr = '';
        if (msg.createTime) {
          const date = new Date(msg.createTime);
          const hours = String(date.getHours()).padStart(2, '0');
          const minutes = String(date.getMinutes()).padStart(2, '0');
          timeStr = `${hours}:${minutes}`;
        }

        const messageType = msg.sender === 'assistant' ? 'ai' : (msg.sender === 'user' ? 'user' : 'system');
        const rawContent = msg.message || '';

        // 与API一致：先清理文本，再根据 & 分段
        const cleanedContent = cleanText(rawContent);

        if (messageType === 'ai' && cleanedContent.includes('&')) {
          const segments = cleanedContent
            .split('&')
            .map(s => s.trim())
            .filter(Boolean);

          segments.forEach(segment => {
            historyMessages.push({
              type: messageType,
              content: segment,
              time: timeStr
            });
          });
        } else {
          // 非 AI 或无分隔符，直接使用清理后的内容
          const content = cleanedContent.trim();
          if (content) {
            historyMessages.push({
              type: messageType,
              content,
              time: timeStr
            });
          }
        }
      });

      // 按时间排序（从旧到新）
      historyMessages.sort((a, b) => {
        if (!a.time || !b.time) return 0;
        return a.time.localeCompare(b.time);
      });

      // 清空当前消息列表，加载历史消息
      messages.value = historyMessages;
      // 滚动到底部 - 使用延迟确保 DOM 完全渲染
      await nextTick();
      setTimeout(() => {
        scrollToBottom();
      }, 300);
    } else {
    }
  } catch (error) {
  }
};

// 获取AI回复
const getAIResponse = async (userMessage) => {
  try {
    isLoading.value = true;

    const requestParams = {
      message: userMessage,
      characterId: currentCharacter.value.id,
      conversationId: conversationId.value,
      modelId: 10,
      templateId: 9999
    };

    if (currentCharacter.value.roleId) {
      if (currentCharacter.value.modelId) {
        requestParams.modelId = currentCharacter.value.modelId;
      }
      if (currentCharacter.value.templateId) {
        requestParams.templateId = currentCharacter.value.templateId;
      } else {
        requestParams.templateId = currentCharacter.value.roleId;
      }
    }

    const result = await chatAPI.syncChat(requestParams);

    if (result.success) {
      const aiResponse = result.data;
      await addSegmentedAIResponse(aiResponse);

      if (result.originalResponse && result.originalResponse.conversationId) {
        conversationId.value = result.originalResponse.conversationId;
      }
    } else {
      const fallbackResponse = getSmartResponse(currentCharacter.value, userMessage);
      const cleanedResponse = cleanText(fallbackResponse);
      await addSegmentedAIResponse(cleanedResponse);

      if (!result.isAnonymous) {
        uni.showToast({
          title: 'AI服务暂时不可用，使用本地回复',
          icon: 'none',
          duration: 2000
        });
      }
    }
  } catch (error) {
    const fallbackResponse = getSmartResponse(currentCharacter.value, userMessage);
    const cleanedResponse = cleanText(fallbackResponse);
    await addSegmentedAIResponse(cleanedResponse);

    uni.showToast({
      title: '网络异常，使用本地回复',
      icon: 'none',
      duration: 2000
    });
  } finally {
    isLoading.value = false;
  }
};

// 切换录音模式
const toggleRecordingMode = () => {
  if (isTyping.value) return;
  isRecordingMode.value = !isRecordingMode.value;

  if (!isRecordingMode.value && isActuallyRecording.value) {
    stopActualRecording();
  }
};

// 开始录音
const startActualRecording = (e) => {
  if (isTyping.value || !isRecordingMode.value) return;

  touchStartY.value = e.touches[0].clientY;
  isRecordingCancelled.value = false;
  recordingTipText.value = '松手发送，上移取消';

  // #ifdef MP-WEIXIN
  uni.authorize({
    scope: 'scope.record',
    success: () => {
      isActuallyRecording.value = true;
      recorderManager.value.start({
        duration: 60000,
        sampleRate: 16000,
        numberOfChannels: 1,
        encodeBitRate: 96000,
        format: 'aac'
      });
    },
    fail: () => {
      uni.showModal({
        title: '需要录音权限',
        content: '请在设置中开启录音权限',
        showCancel: false
      });
    }
  });
  // #endif

  // #ifndef MP-WEIXIN
  uni.showToast({
    title: '语音功能仅支持微信小程序',
    icon: 'none'
  });
  // #endif
};

// 停止录音
const stopActualRecording = () => {
  if (!isActuallyRecording.value) return;

  // #ifdef MP-WEIXIN
  isActuallyRecording.value = false;
  isRecordingCancelled.value = false;
  stopRecorderSafely();
  // #endif
};

// 取消录音
const cancelActualRecording = () => {
  if (!isActuallyRecording.value) return;

  // #ifdef MP-WEIXIN
  isActuallyRecording.value = false;
  isRecordingCancelled.value = true;
  stopRecorderSafely();

  uni.showToast({
    title: '录音已取消',
    icon: 'none',
    duration: 1000
  });
  // #endif
};

// 处理录音移动
const handleRecordingMove = (e) => {
  if (isActuallyRecording.value) {
    const currentY = e.touches[0].clientY;
    const deltaY = touchStartY.value - currentY;

    if (deltaY > 50) {
      isRecordingCancelled.value = true;
      recordingTipText.value = '松开取消录音';
    } else {
      isRecordingCancelled.value = false;
      recordingTipText.value = '松手发送，上移取消';
    }
  }
};

// 处理语音消息
const handleVoiceMessage = async (filePath) => {
  if (isRecordingCancelled.value) return;

  addMessage('user', '[语音消息]');
  isTyping.value = true;

  try {
    let effectiveModelId = currentModelId.value;
    let effectiveTemplateId = currentTemplateId.value;

    if (currentCharacter.value.roleId) {
      if (currentCharacter.value.modelId) {
        effectiveModelId = currentCharacter.value.modelId;
      }
      if (currentCharacter.value.templateId) {
        effectiveTemplateId = currentCharacter.value.templateId;
      } else {
        effectiveTemplateId = currentCharacter.value.roleId;
      }
    }

    const voiceChatResult = await voiceAPI.voiceChat(filePath, {
      modelId: effectiveModelId,
      templateId: effectiveTemplateId
    });

    if (voiceChatResult.success) {
      const { userText, aiResponse, audioUrl } = voiceChatResult.data;

      if (messages.value.length > 0) {
        const lastMessage = messages.value[messages.value.length - 1];
        if (lastMessage.type === 'user' && lastMessage.content === '[语音消息]') {
          lastMessage.content = userText || '[语音消息]';
        }
      }

      await addSegmentedAIResponse(aiResponse);

      if (audioUrl) {
        await playVoiceResponse(aiResponse, audioUrl);
      }
    } else {
      const uploadResult = await voiceAPI.uploadVoiceChat(filePath, {
        modelId: effectiveModelId,
        templateId: effectiveTemplateId
      });

      if (uploadResult.success) {
        const { userText, aiResponse, audioUrl } = uploadResult.data;

        if (messages.value.length > 0) {
          const lastMessage = messages.value[messages.value.length - 1];
          if (lastMessage.type === 'user' && lastMessage.content === '[语音消息]') {
            lastMessage.content = userText || '[语音消息]';
          }
        }

        await addSegmentedAIResponse(aiResponse);

        if (audioUrl) {
          await playVoiceResponse(aiResponse, audioUrl);
        }
      } else {
        const fallbackResponse = getSmartResponse(currentCharacter.value, '[语音消息]');
        const cleanedResponse = cleanText(fallbackResponse);
        await addSegmentedAIResponse(cleanedResponse);

        uni.showToast({
          title: '语音处理失败，使用本地回复',
          icon: 'none',
          duration: 2000
        });
      }
    }
  } catch (error) {
    const fallbackResponse = getSmartResponse(currentCharacter.value, '[语音消息]');
    const cleanedResponse = cleanText(fallbackResponse);
    await addSegmentedAIResponse(cleanedResponse);

    uni.showToast({
      title: '语音处理异常，使用本地回复',
      icon: 'none',
      duration: 2000
    });
  } finally {
    isTyping.value = false;
  }
};

// 播放语音
// 开始模拟音量变化（用于 AI 语音播放时的动画）
const startVolumeSimulation = () => {
  // 先清除之前的定时器
  stopVolumeSimulation();

  let baseVolume = 40; // 基础音量
  let phase = 0; // 相位，用于生成波形

  volumeSimulationTimer.value = setInterval(() => {
    // 使用正弦波 + 随机噪声模拟自然的音量变化
    const sineWave = Math.sin(phase) * 20; // 正弦波动 ±20
    const randomNoise = (Math.random() - 0.5) * 15; // 随机噪声 ±7.5
    const variation = (Math.random() - 0.5) * 25; // 额外的随机变化 ±12.5

    // 组合所有因素，确保在合理范围内 (20-80)
    const simulatedVolume = Math.max(20, Math.min(80, baseVolume + sineWave + randomNoise + variation));

    currentVolume.value = simulatedVolume;

    // 更新相位，控制波形频率
    phase += 0.15 + Math.random() * 0.1; // 动态调整频率

    // 偶尔改变基础音量，模拟语调变化
    if (Math.random() < 0.1) {
      baseVolume = 35 + Math.random() * 20; // 35-55 之间
    }
  }, 80); // 每 80ms 更新一次，约 12.5 fps
};

// 停止音量模拟
const stopVolumeSimulation = () => {
  if (volumeSimulationTimer.value) {
    clearInterval(volumeSimulationTimer.value);
    volumeSimulationTimer.value = null;
  }
  currentVolume.value = 0;
};

const playVoiceResponse = async (text, audioUrl = null) => {
  return new Promise(async (resolve, reject) => {
    // #ifdef MP-WEIXIN
    try {
      let finalAudioUrl = audioUrl;

      if (!finalAudioUrl) {
        const ttsResult = await voiceAPI.textToSpeech(text, currentCharacter.value.voiceStyle);

        if (ttsResult.success) {
          finalAudioUrl = ttsResult.data.audioUrl;
        } else {
          uni.showToast({
            title: '语音合成失败',
            icon: 'none'
          });
          resolve(); // 失败也resolve，避免卡住
          return;
        }
      }

      if (audioContext.value) {
        audioContext.value.destroy();
      }

      // 确保静音键/听筒不会拦截播放
      uni.setInnerAudioOption({
        obeyMuteSwitch: false,
        speakerOn: true
      });

      audioContext.value = uni.createInnerAudioContext();
      audioContext.value.autoplay = false;
      audioContext.value.obeyMuteSwitch = false;
      audioContext.value.volume = 1;
      audioContext.value.loop = false;
      audioContext.value.playbackRate = 1;
      audioContext.value.src = finalAudioUrl;

      audioContext.value.onCanplay(() => {
        // 某些机型需要在 canplay 后再触发播放才会有声音
        if (audioContext.value?.paused) {
          audioContext.value.play();
        }
      });

      audioContext.value.onPlay(() => {
        console.log('开始播放语音');
        // 🎵 开始模拟音量变化，驱动声波动画
        startVolumeSimulation();
      });

      audioContext.value.onEnded(() => {
        console.log('语音播放结束');
        // 🎵 停止音量模拟
        stopVolumeSimulation();
        resolve();
      });

      audioContext.value.onError((err) => {
        // 🎵 停止音量模拟
        stopVolumeSimulation();
        uni.showToast({
          title: '语音播放失败',
          icon: 'none'
        });
        resolve(); // 出错也继续
      });

      audioContext.value.play();
    } catch (error) {
      // 🎵 停止音量模拟
      stopVolumeSimulation();
      uni.showToast({
        title: '语音播放异常',
        icon: 'none'
      });
      resolve();
    }
    // #endif

    // #ifndef MP-WEIXIN
    uni.showToast({
      title: '语音功能仅支持微信小程序',
      icon: 'none'
    });
    resolve();
    // #endif
  });
};

// 显示模板选择器
const showTemplateSelector = () => {
  showTemplateModal.value = true;
};

// 隐藏模板选择器
const hideTemplateSelector = () => {
  showTemplateModal.value = false;
};

// 选择模板
const selectTemplate = (template) => {
  currentTemplateId.value = template.templateId;

  uni.showToast({
    title: `已切换到${template.templateName}`,
    icon: 'none',
    duration: 1500
  });

  hideTemplateSelector();
};

// 处理输入框聚焦
const handleInputFocus = (e) => {
  // 在小程序中，通过 detail.height 获取键盘高度
  if (e && e.detail && e.detail.height) {
    keyboardHeight.value = e.detail.height;
  }
};

// 处理输入框失焦
const handleInputBlur = (e) => {
  // 延迟重置键盘高度，避免闪烁
  setTimeout(() => {
    keyboardHeight.value = 0;
  }, 100);
};

// 清空对话上下文
const clearContext = async () => {
  uni.showModal({
    title: '清空对话',
    content: '确定要清空与该角色的所有对话记录吗？此操作不可恢复。',
    confirmText: '确定清空',
    cancelText: '取消',
    success: async (res) => {
      if (res.confirm) {
        try {
          uni.showLoading({ title: '清空中...' });

          // 1. 调用后端清除session（如果用户已登录）
          if (conversationId.value && isLoggedIn.value) {
            try {
              const result = await chatAPI.clearSession(conversationId.value);
            } catch (error) {
            }
          }

          // 2. 删除本地存储的sessionId
          let storageKey = '';
          if (currentCharacter.value.id === 'wei-ai' || !currentCharacter.value.roleId) {
            storageKey = `session_weiai`;
          } else {
            storageKey = `session_role_${currentCharacter.value.roleId}`;
          }
          uni.removeStorageSync(storageKey);
          // 3. 生成新的sessionId
          const characterIdForSession = currentCharacter.value.id || currentCharacter.value.roleId || 'default';
          createNewConversation(characterIdForSession);

          // 4. 清空消息列表（保留欢迎消息）
          messages.value = [];
          addMessage('ai', currentCharacter.value.greeting || '你好！很高兴再次见到你！');

          // 确保滚动到底部
          await nextTick();
          setTimeout(() => {
            scrollToBottom();
          }, 300);

          uni.hideLoading();
          uni.showToast({
            title: '对话已清空',
            icon: 'success'
          });
        } catch (error) {
          uni.hideLoading();
          uni.showToast({
            title: '清空失败',
            icon: 'none'
          });
        }
      }
    }
  });
};

// 🎧 切换语音模式
const toggleVoiceMode = async () => {
  isVoiceMode.value = !isVoiceMode.value;

  if (isVoiceMode.value) {
    // 进入语音模式
    voiceState.value = 'idle';
    isVoiceModeInterrupted.value = false; // 重置中断标志

    // 🎙️ 建立WebSocket连接（如果启用WebSocket模式）
    if (isWebSocketMode.value) {
      try {
        const wsUrl = getWsUrl(API_CONFIG.WS_ENDPOINTS.VOICE_STREAM);
        voiceStreamWs.value = new VoiceStreamWebSocket(wsUrl);
        
        // 设置事件监听器
        voiceStreamWs.value.on('connected', () => {
          console.log('[VoiceMode] WebSocket已连接');
        });
        
        voiceStreamWs.value.on('sttResult', (text) => {
          console.log('[VoiceMode] STT结果:', text);
          addMessage('user', text);
          currentSentence.value = '';
        });
        
        voiceStreamWs.value.on('llmToken', (token) => {
          // 可选：显示LLM输出
          console.log('[VoiceMode] LLM Token:', token);
        });
        
        voiceStreamWs.value.on('sentence', (sentence) => {
          console.log('[VoiceMode] 完整句子:', sentence);
          currentSentence.value = sentence;
          addMessage('ai', sentence);
        });
        
        voiceStreamWs.value.on('audioChunk', (audioData) => {
          // 将音频数据加入播放队列
          audioPlayQueue.value.push(audioData);
          processAudioQueue();
        });
        
        voiceStreamWs.value.on('complete', () => {
          console.log('[VoiceMode] 对话完成，等待播放队列清空');
          
          // 等待播放队列清空后再设置idle
          const checkQueueEmpty = () => {
            if (audioPlayQueue.value.length === 0 && !isPlayingAudio.value) {
              console.log('[VoiceMode] 播放队列已清空，切换到idle状态');
              voiceState.value = 'idle';
              
              // 如果是自动模式，重新开始录音
              if (isAutoVoiceMode.value && isVoiceMode.value) {
                console.log('↺ 自动模式：重新开始监听');
                startVoiceRecording();
              }
            } else {
              console.log('[VoiceMode] 队列还有数据或正在播放，100ms后再检查');
              setTimeout(checkQueueEmpty, 100);
            }
          };
          
          checkQueueEmpty();
        });
        
        voiceStreamWs.value.on('error', (error) => {
          console.error('[VoiceMode] 错误:', error);
          uni.showToast({
            title: '语音处理失败: ' + error,
            icon: 'none',
            duration: 2000
          });
          voiceState.value = 'idle';
        });
        
        voiceStreamWs.value.on('disconnected', () => {
          console.log('[VoiceMode] WebSocket已断开');
        });

        // 复用文字对话的 conversationId，保持历史记录一致
        // 如果还没有 conversationId，会在initializeConversation中生成
        if (!conversationId.value) {
          await initializeConversation();
        }

        // 使用当前角色的templateId（如果有的话）
        currentVoiceTemplateId.value = currentCharacter.value.templateId || currentCharacter.value.roleId || null;

        console.log('[VoiceMode] 连接参数 - SessionId:', conversationId.value, 'TemplateId:', currentVoiceTemplateId.value);

        // 连接WebSocket（传递与文字对话相同的sessionId和templateId）
        await voiceStreamWs.value.connect(
          conversationId.value,
          currentVoiceTemplateId.value,
          userStore.token,
          userStore.userId
        );
        
      } catch (error) {
        console.error('[VoiceMode] WebSocket连接失败:', error);
        uni.showToast({
          title: 'WebSocket连接失败',
          icon: 'none',
          duration: 2000
        });
        isVoiceMode.value = false;
        return;
      }
    }

    uni.showToast({
      title: '已切换到语音模式' + (isWebSocketMode.value ? '(实时流式)' : '') + (isTestMode.value ? '(测试)' : ''),
      icon: 'none',
      duration: 1500
    });

    // 如果是自动模式且非测试模式，直接开始监听
    if (isAutoVoiceMode.value && !isTestMode.value) {
      startVoiceRecording();
    }
  } else {
    // 退出语音模式 - 中断所有正在进行的语音操作
    console.log('🔄 退出语音模式，中断所有语音操作...');

    // 0. 设置中断标志，阻止 onStop 回调继续处理
    isVoiceModeInterrupted.value = true;

    // 1. 停止正在进行的录音（不发送到后端）
    if (isVoiceRecording.value) {
      isVoiceRecording.value = false;
      // #ifdef MP-WEIXIN
      stopRecorderSafely();
      // #endif
      console.log('⏹️ 已停止录音');
    }

    // 2. 清空录音缓冲区，防止 onStop 回调处理
    recordedBuffers.value = [];

    // 3. 停止正在播放的音频
    // #ifdef MP-WEIXIN
    if (audioContext.value) {
      try {
        audioContext.value.stop();
        audioContext.value.destroy();
        audioContext.value = null;
        console.log('⏹️ 已停止音频播放');
      } catch (error) {
        console.warn('停止音频播放失败:', error);
      }
    }
    // #endif

    // 4. 停止音量模拟
    stopVolumeSimulation();

    // 5. 重置所有语音状态
    voiceState.value = 'idle';
    currentVolume.value = 0;

    // 6. 关闭WebSocket连接
    if (voiceStreamWs.value) {
      try {
        voiceStreamWs.value.close();
        voiceStreamWs.value = null;
        console.log('🔌 已关闭WebSocket连接');
      } catch (error) {
        console.warn('关闭WebSocket失败:', error);
      }
    }

    // 7. 清空音频播放队列
    audioPlayQueue.value = [];
    isPlayingAudio.value = false;
    currentSentence.value = '';

    uni.showToast({
      title: '已切换到文本模式',
      icon: 'none',
      duration: 1500
    });
  }
};

// 计算音量 RMS
const calculateVolumeRMS = (buffer) => {
  const dataView = new DataView(buffer);
  let sumSquares = 0;
  const length = buffer.byteLength / 2;
  
  for (let i = 0; i < length; i++) {
    const sample = dataView.getInt16(i * 2, true);
    sumSquares += sample * sample;
  }
  
  const meanSquare = sumSquares / length;
  const rms = Math.sqrt(meanSquare);
  
  // 映射到 0-100
  let vol = Math.round((rms / 12000) * 100); 
  if (vol > 100) vol = 100;
  if (vol < 0) vol = 0;
  
  return vol;
};

// 写入字符串到 DataView
const writeString = (view, offset, string) => {
  for (let i = 0; i < string.length; i++) {
    view.setUint8(offset + i, string.charCodeAt(i));
  }
};

// 创建 WAV 头部
const createWavHeader = (dataLength, sampleRate, channelCount, bitDepth) => {
  const buffer = new ArrayBuffer(44);
  const view = new DataView(buffer);
  
  // RIFF identifier
  writeString(view, 0, 'RIFF');
  // file length
  view.setUint32(4, 36 + dataLength, true);
  // RIFF type
  writeString(view, 8, 'WAVE');
  // format chunk identifier
  writeString(view, 12, 'fmt ');
  // format chunk length
  view.setUint32(16, 16, true);
  // sample format (raw)
  view.setUint16(20, 1, true);
  // channel count
  view.setUint16(22, channelCount, true);
  // sample rate
  view.setUint32(24, sampleRate, true);
  // byte rate (sample rate * block align)
  view.setUint32(28, sampleRate * channelCount * (bitDepth / 8), true);
  // block align (channel count * bytes per sample)
  view.setUint16(32, channelCount * (bitDepth / 8), true);
  // bits per sample
  view.setUint16(34, bitDepth, true);
  // data chunk identifier
  writeString(view, 36, 'data');
  // data chunk length
  view.setUint32(40, dataLength, true);
  
  return buffer;
};

// 生成 WAV 文件
const createWavFile = () => {
  if (recordedBuffers.value.length === 0) return null;
  
  // 1. 合并所有 Buffer
  const totalLength = recordedBuffers.value.reduce((acc, buf) => acc + buf.byteLength, 0);
  const allData = new Uint8Array(totalLength);
  let offset = 0;
  for (const buf of recordedBuffers.value) {
    allData.set(new Uint8Array(buf), offset);
    offset += buf.byteLength;
  }

  // 2. 创建 WAV 头部 (44字节)
  const wavHead = createWavHeader(totalLength, 16000, 1, 16);
  
  // 3. 拼接头部和数据
  const wavFile = new Uint8Array(wavHead.byteLength + allData.byteLength);
  wavFile.set(new Uint8Array(wavHead), 0);
  wavFile.set(allData, wavHead.byteLength);
  
  // #ifdef MP-WEIXIN
  const fs = uni.getFileSystemManager();
  const filePath = `${wx.env.USER_DATA_PATH}/auto_record_${Date.now()}.wav`;
  
  try {
    fs.writeFileSync(filePath, wavFile.buffer, 'binary');
    return filePath;
  } catch (e) {
    console.error('文件保存失败', e);
    return null;
  }
  // #endif
  
  return null;
};

// 🎧 切换录音状态（开始/停止录音）
const toggleVoiceRecording = async () => {
  if (voiceState.value === 'thinking' || voiceState.value === 'speaking') {
    uni.showToast({
      title: 'AI正在处理中，请稍候',
      icon: 'none'
    });
    return;
  }

  if (!isVoiceRecording.value) {
    // 开始录音
    startVoiceRecording();
  } else {
    // 停止录音并发送
    await stopVoiceRecordingAndSend();
  }
};

// 🎧 开始录音
const startVoiceRecording = () => {
  // #ifdef MP-WEIXIN
  uni.authorize({
    scope: 'scope.record',
    success: () => {
      isVoiceRecording.value = true;
      isVoiceModeInterrupted.value = false; // 重置中断标志
      // 自动模式下初始状态为 idle (等待说话)，手动模式直接为 listening
      voiceState.value = isAutoVoiceMode.value ? 'idle' : 'listening';
      
      const options = {
        duration: 600000, // 自动模式下时长设长一点
        sampleRate: 16000,
        numberOfChannels: 1,
        encodeBitRate: 96000,
        format: isAutoVoiceMode.value ? 'pcm' : 'aac',
        frameSize: isAutoVoiceMode.value ? 2 : undefined // VAD 需要分片回调
      };
      
      recorderManager.value.start(options);

      if (!isAutoVoiceMode.value) {
        uni.showToast({
          title: '开始录音',
          icon: 'none',
          duration: 1000
        });
      } else {
        console.log('自动语音模式已启动，等待说话...');
      }
    },
    fail: () => {
      uni.showModal({
        title: '需要录音权限',
        content: '请在设置中开启录音权限',
        showCancel: false
      });
    }
  });
  // #endif

  // #ifndef MP-WEIXIN
  uni.showToast({
    title: '语音功能仅支持微信小程序',
    icon: 'none'
  });
  // #endif
};

// 🎧 停止录音并发送
const stopVoiceRecordingAndSend = async () => {
  if (!isVoiceRecording.value) return;

  isVoiceRecording.value = false;

  // #ifdef MP-WEIXIN
  const stopped = stopRecorderSafely();
  if (!stopped) {
    voiceState.value = 'idle';
  }
  // 停止后会触发 onStop 回调，在那里处理录音文件
  // #endif

  // #ifndef MP-WEIXIN
  uni.showToast({
    title: '语音功能仅支持微信小程序',
    icon: 'none'
  });
  voiceState.value = 'idle';
  // #endif
};

// 🎧 处理语音模式的录音消息
const handleVoiceModeMessage = async (filePath) => {
  // 检查是否还在语音模式，如果已切换则不处理
  if (!isVoiceMode.value) {
    console.log('⚠️ 已切换到文本模式，取消语音处理');
    return;
  }

  isVoiceRecording.value = false;
  voiceState.value = 'thinking';

  try {
    console.log('🎧 语音模式：开始处理录音文件', filePath);

    // 🎙️ WebSocket模式：通过WebSocket发送音频
    if (isWebSocketMode.value && voiceStreamWs.value && voiceStreamWs.value.isConnected) {
      // 读取音频文件并通过WebSocket发送
      const fs = uni.getFileSystemManager();
      console.log(wx.env.USER_DATA_PATH)
      fs.readFile({
        filePath: filePath,
        success: (res) => {
          console.log('[VoiceMode] 读取音频文件成功，大小:', res.data.byteLength);
          
          // 发送音频数据
          const success = voiceStreamWs.value.sendAudio(res.data);
          if (!success) {
            throw new Error('发送音频数据失败');
          }
          
          console.log('[VoiceMode] 音频已通过WebSocket发送');
        },
        fail: (err) => {
          console.error('[VoiceMode] 读取音频文件失败:', err);
          uni.showToast({
            title: '读取音频文件失败',
            icon: 'none',
            duration: 2000
          });
          voiceState.value = 'idle';
        }
      });
      return;
    }

    // HTTP模式：调用后端voice-chat接口
    const result = await voiceAPI.voiceChat(filePath, {
      sessionId: conversationId.value, // 使用当前会话ID保持上下文
      modelId: currentCharacter.value.modelId || 10,
      templateId: currentCharacter.value.templateId || currentCharacter.value.roleId || 6,
      ttsConfigId: currentCharacter.value.ttsId || null,
      sttConfigId: currentCharacter.value.sttId || null,
      useFunctionCall: false
    });

    if (result.success && result.data) {
      const { sttResult, llmResult, ttsResult } = result.data;

      console.log('🎧 STT识别结果:', sttResult?.text);
      console.log('🎧 AI回复文本:', llmResult?.response);
      console.log('🎧 TTS音频:', ttsResult?.audioBase64 ? '已获取' : '未获取');

      // 再次检查是否还在语音模式
      if (!isVoiceMode.value) {
        console.log('⚠️ API返回时已切换到文本模式，取消后续处理');
        return;
      }

      // 可选：将对话记录到聊天列表
      if (sttResult?.text) {
        addMessage('user', sttResult.text);
      }
      if (llmResult?.response) {
        const cleanedResponse = cleanText(llmResult.response);
        await addSegmentedAIResponse(cleanedResponse);
      }

      // 再次检查是否还在语音模式（因为 addSegmentedAIResponse 是异步的）
      if (!isVoiceMode.value) {
        console.log('⚠️ 消息添加后已切换到文本模式，取消音频播放');
        return;
      }

      // 切换到说话状态
      voiceState.value = 'speaking';

      // 播放AI返回的语音
      if (ttsResult && ttsResult.audioBase64) {
        // 如果有audioBase64，播放Base64音频
        await playVoiceFromBase64(ttsResult.audioBase64, llmResult?.response);
      } else if (ttsResult && ttsResult.audioPath) {
        // 如果有audioPath，播放路径音频
        const audioUrl = getResourceUrl(ttsResult.audioPath);
        await playVoiceResponse(llmResult?.response, audioUrl);
      } else {
        // 没有音频，只显示文本
        uni.showToast({
          title: llmResult?.response || 'AI已回复',
          icon: 'none',
          duration: 2000
        });
        await sleep(2000);
      }

      // 循环对话：播放结束后，如果是自动模式且仍在语音模式，重新开始录音
      if (isAutoVoiceMode.value && isVoiceMode.value) {
        console.log('↺ 自动模式：重新开始监听');
        startVoiceRecording();
      } else if (isVoiceMode.value) {
        // 如果还在语音模式但不是自动模式，回到待机状态
        voiceState.value = 'idle';
      }
      // 如果已经不在语音模式，不做任何操作（已被中断）

    } else {
      // API调用失败
      throw new Error(result.error?.message || '语音处理失败');
    }

  } catch (error) {
    console.error('🎧 语音处理失败:', error);
    voiceState.value = 'idle';

    uni.showToast({
      title: '语音处理失败: ' + (error.message || '未知错误'),
      icon: 'none',
      duration: 2000
    });
    
    // 出错后是否重启？为了安全起见，出错暂不自动重启，让用户手动点击重试
  }
};

// 🧪 加载测试PCM音频数据
const generateTestPCMAudio = () => {
  return new Promise((resolve, reject) => {
    const config = API_CONFIG.TEST_MODE;
    
    // 方式1: 优先使用base64数据
    if (config.testAudioBase64 && config.testAudioBase64.trim() !== '') {
      try {
        console.log('[TestMode] 从base64加载测试音频数据');
        const arrayBuffer = uni.base64ToArrayBuffer(config.testAudioBase64);
        console.log('[TestMode] 测试音频数据已加载:', {
          dataSize: arrayBuffer.byteLength + ' bytes',
          expectedFormat: '16000Hz, 16bit, 单声道, Little Endian'
        });
        resolve(arrayBuffer);
      } catch (error) {
        console.error('[TestMode] base64解码失败:', error);
        reject(new Error('base64数据解码失败'));
      }
      return;
    }
    
    // 方式2: 从文件路径读取
    if (config.testAudioPath && config.testAudioPath.trim() !== '') {
      // #ifdef MP-WEIXIN
      const fs = uni.getFileSystemManager();
     const testAudioPath = wx.env.USER_DATA_PATH + "/output.pcm";
      fs.readFile({
        filePath: testAudioPath,
        success: (res) => {
          console.log('[TestMode] 从文件加载测试音频数据:', config.testAudioPath);
          console.log('[TestMode] 文件大小:', res.data.byteLength + ' bytes');
          resolve(res.data);
        },
        fail: (err) => {
          console.error('[TestMode] 读取音频文件失败:', err);
          reject(new Error('读取音频文件失败: ' + JSON.stringify(err)));
        }
      });
      // #endif
      
      // #ifndef MP-WEIXIN
      reject(new Error('文件读取仅支持微信小程序'));
      // #endif
      return;
    }
    
    // 如果两个都没有配置，提示错误
    reject(new Error('请在config.js中配置 testAudioBase64 或 testAudioPath'));
  });
};

// 🧪 发送测试音频数据
const sendTestAudio = async () => {
  if (!isVoiceMode.value) {
    uni.showToast({
      title: '请先进入语音模式',
      icon: 'none',
      duration: 2000
    });
    return;
  }
  
  if (voiceState.value === 'thinking' || voiceState.value === 'speaking') {
    uni.showToast({
      title: '正在处理中，请稍候',
      icon: 'none',
      duration: 1500
    });
    return;
  }
  
  try {
    console.log('[TestMode] 开始发送测试音频数据');
    voiceState.value = 'listening';
    
    // 加载测试PCM数据
    const testAudioData = await generateTestPCMAudio();
    
    // 模拟VAD检测（等待一段时间模拟说话）
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // 切换到思考状态
    voiceState.value = 'thinking';
    
    // WebSocket模式：直接发送ArrayBuffer
    if (isWebSocketMode.value && voiceStreamWs.value && voiceStreamWs.value.isConnected) {
      const success = voiceStreamWs.value.sendAudio(testAudioData);
      if (!success) {
        throw new Error('发送音频数据失败');
      }
      console.log('[TestMode] 测试音频已通过WebSocket发送');
    } else {
      // HTTP模式：需要先保存为文件，然后调用接口
      // #ifdef MP-WEIXIN
      const fs = uni.getFileSystemManager();
      const filePath = `${wx.env.USER_DATA_PATH}/test_audio_${Date.now()}.pcm`;
      
      // 将ArrayBuffer转为base64
      const base64 = uni.arrayBufferToBase64(testAudioData);
      
      await new Promise((resolve, reject) => {
        fs.writeFile({
          filePath: filePath,
          data: base64,
          encoding: 'base64',
          success: () => {
            console.log('[TestMode] 测试音频文件已保存:', filePath);
            // 调用现有的处理逻辑
            handleVoiceModeMessage(filePath);
            resolve();
          },
          fail: (err) => {
            console.error('[TestMode] 保存测试音频文件失败:', err);
            reject(err);
          }
        });
      });
      // #endif
      
      // #ifndef MP-WEIXIN
      uni.showToast({
        title: 'HTTP模式仅支持微信小程序',
        icon: 'none',
        duration: 2000
      });
      voiceState.value = 'idle';
      // #endif
    }
    
  } catch (error) {
    console.error('[TestMode] 发送测试音频失败:', error);
    uni.showToast({
      title: '发送测试音频失败',
      icon: 'none',
      duration: 2000
    });
    voiceState.value = 'idle';
  }
};

// 🎙️ 处理音频播放队列（WebSocket流式音频）
const processAudioQueue = async () => {
  if (isPlayingAudio.value || audioPlayQueue.value.length === 0) {
    return;
  }

  isPlayingAudio.value = true;
  voiceState.value = 'speaking';

  // #ifdef MP-WEIXIN
  try {
    // 开始播放队列中的音频
    while (audioPlayQueue.value.length > 0 && isVoiceMode.value) {
      const audioData = audioPlayQueue.value.shift();
      
      // 将ArrayBuffer转换为临时文件（MP3格式）
      const fs = uni.getFileSystemManager();
      const filePath = `${wx.env.USER_DATA_PATH}/stream_audio_${Date.now()}.mp3`;
      
      await new Promise((resolve, reject) => {
        // 将ArrayBuffer转为base64
        const base64 = uni.arrayBufferToBase64(audioData);
        
        fs.writeFile({
          filePath: filePath,
          data: base64,
          encoding: 'base64',
          success: () => {
            console.log('[AudioQueue] MP3文件写入成功:', filePath);
            
            // 播放音频
            if (audioContext.value) {
              audioContext.value.destroy();
            }

            uni.setInnerAudioOption({
              obeyMuteSwitch: false,
              speakerOn: true
            });

            audioContext.value = uni.createInnerAudioContext();
            audioContext.value.autoplay = false;
            audioContext.value.obeyMuteSwitch = false;
            audioContext.value.volume = 1;
            audioContext.value.loop = false;
            audioContext.value.src = filePath;

            audioContext.value.onCanplay(() => {
              console.log('[AudioQueue] 音频可以播放，开始播放');
              if (audioContext.value?.paused) {
                audioContext.value.play();
              }
            });

            audioContext.value.onPlay(() => {
              console.log('[AudioQueue] 开始播放音频块');
              startVolumeSimulation();
            });

            audioContext.value.onEnded(() => {
              console.log('[AudioQueue] 音频块播放完成');
              stopVolumeSimulation();
              audioContext.value?.destroy();
              audioContext.value = null;
              resolve();
            });

            audioContext.value.onError((err) => {
              console.error('[AudioQueue] 音频播放错误:', err);
              stopVolumeSimulation();
              audioContext.value?.destroy();
              audioContext.value = null;
              resolve(); // 继续播放下一个
            });
            
            // 尝试立即播放（某些情况下不会触发onCanplay）
            audioContext.value.play();
          },
          fail: (err) => {
            console.error('[AudioQueue] 写入文件失败:', err);
            reject(err);
          }
        });
      });
    }
  } catch (error) {
    console.error('[AudioQueue] 播放队列处理失败:', error);
  } finally {
    isPlayingAudio.value = false;
    
    // 检查队列是否还有数据
    if (audioPlayQueue.value.length > 0 && isVoiceMode.value) {
      processAudioQueue();
    }
    // 不要在队列为空时设置idle，应该由complete事件来控制状态
    // 因为队列为空不代表对话完成，后端可能还在合成下一句音频
  }
  // #endif
};

// 🎧 播放Base64编码的音频
const playVoiceFromBase64 = async (audioBase64, text = '') => {
  return new Promise((resolve, reject) => {
    // #ifdef MP-WEIXIN
    try {
      // 将Base64转换为临时文件
      const fs = uni.getFileSystemManager();
      const filePath = `${wx.env.USER_DATA_PATH}/voice_temp_${Date.now()}.wav`;
      
      // 写入文件
      fs.writeFile({
        filePath: filePath,
        data: audioBase64,
        encoding: 'base64',
        success: () => {
          console.log('🎧 Base64音频已转换为文件:', filePath);
          
          // 播放音频
          if (audioContext.value) {
            audioContext.value.destroy();
          }

          uni.setInnerAudioOption({
            obeyMuteSwitch: false,
            speakerOn: true
          });

          audioContext.value = uni.createInnerAudioContext();
          audioContext.value.autoplay = false;
          audioContext.value.obeyMuteSwitch = false;
          audioContext.value.volume = 1;
          audioContext.value.loop = false;
          audioContext.value.playbackRate = 1;
          audioContext.value.src = filePath;

          audioContext.value.onCanplay(() => {
            if (audioContext.value?.paused) {
              audioContext.value.play();
            }
          });

          audioContext.value.onPlay(() => {
            console.log('🎧 开始播放AI语音');
            // 🎵 开始模拟音量变化，驱动声波动画
            startVolumeSimulation();
          });

          audioContext.value.onEnded(() => {
            console.log('🎧 AI语音播放完成');
            // 🎵 停止音量模拟
            stopVolumeSimulation();
            resolve();
          });

          audioContext.value.onError((err) => {
            console.error('🎧 语音播放失败:', err);
            // 🎵 停止音量模拟
            stopVolumeSimulation();
            reject(err);
          });

          audioContext.value.play();
        },
        fail: (err) => {
          console.error('🎧 Base64转文件失败:', err);
          reject(err);
        }
      });
    } catch (error) {
      console.error('🎧 播放Base64音频异常:', error);
      reject(error);
    }
    // #endif

    // #ifndef MP-WEIXIN
    uni.showToast({
      title: '语音播放仅支持微信小程序',
      icon: 'none'
    });
    resolve();
    // #endif
  });
};

// 🎧 取消录音
const cancelVoiceRecording = () => {
  if (!isVoiceRecording.value) return;

  isVoiceRecording.value = false;
  voiceState.value = 'idle';

  // #ifdef MP-WEIXIN
  stopRecorderSafely();
  // #endif

  uni.showToast({
    title: '已取消录音',
    icon: 'none',
    duration: 1000
  });
};
</script>

<!-- 非 scoped 样式：影响页面根元素，防止键盘弹出时页面被推上去 -->
<style>
page {
  height: 100vh;
  overflow: hidden;
  background: linear-gradient(180deg, #1a0b2e 0%, #2d1b4e 50%, #1a0b2e 100%);
}
</style>

<style scoped>
/* 页面容器 */

.chat-container {
  width: 100vw;
  height: 100vh;
  background: linear-gradient(180deg, #1a0b2e 0%, #2d1b4e 50%, #1a0b2e 100%);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  overflow: hidden;
}

.background-video {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
}

.background-gif {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
  pointer-events: none;
}

.video-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(180deg, rgba(26, 11, 46, 0.9) 0%, rgba(45, 27, 78, 0.75) 50%, rgba(26, 11, 46, 0.9) 100%);
  z-index: -1;
  pointer-events: none;
}

/* 夜空装饰 */
.night-sky-decoration {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

.star {
  position: absolute;
  width: 6rpx;
  height: 6rpx;
  background: #f9e076;
  border-radius: 50%;
  animation: twinkle 2s infinite;
  box-shadow: 0 0 10rpx #f9e076;
}

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

.star-1 { top: 10%; left: 15%; animation-delay: 0s; }
.star-2 { top: 20%; left: 80%; animation-delay: 0.3s; }
.star-3 { top: 30%; left: 45%; animation-delay: 0.6s; }
.star-4 { top: 50%; left: 25%; animation-delay: 0.9s; }
.star-5 { top: 60%; left: 70%; animation-delay: 1.2s; }
.star-6 { top: 70%; left: 40%; animation-delay: 1.5s; }
.star-7 { top: 15%; left: 60%; animation-delay: 0.4s; }
.star-8 { top: 85%; left: 55%; animation-delay: 1.8s; }

/* 自定义导航栏 */
.custom-navbar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  background: rgba(26, 11, 46, 0.95);
  backdrop-filter: blur(20rpx);
  box-shadow: 0 2rpx 20rpx rgba(0, 0, 0, 0.3);
  border-bottom: 1rpx solid rgba(249, 224, 118, 0.1);
  z-index: 1000;
}

.navbar-content {
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 32rpx;
  position: relative;
}

.navbar-left {
  position: absolute;
  left: 24rpx;
  display: flex;
  align-items: center;
  gap: 16rpx;
  color: #f9e076;
  font-size: 28rpx;
  font-weight: 500;
  text-shadow: 0 0 10rpx rgba(249, 224, 118, 0.3);
}

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

.navbar-center.centered {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}

.character-avatar {
  width: 64rpx;
  height: 64rpx;
  border-radius: 50%;
  border: 3rpx solid rgba(249, 224, 118, 0.3);
  box-shadow: 0 4rpx 12rpx rgba(249, 224, 118, 0.2);
}

.character-name {
  font-size: 32rpx;
  font-weight: 600;
  color: #f9e076;
  text-shadow: 0 0 15rpx rgba(249, 224, 118, 0.4);
}

.navbar-right {
  position: absolute;
  right: 32rpx;
  display: flex;
  align-items: center;
}

.clear-btn {
  font-size: 28rpx;
  color: #f9e076;
  font-weight: 500;
  text-shadow: 0 0 8rpx rgba(249, 224, 118, 0.3);
  padding: 8rpx 16rpx;
  transition: all 0.3s;
}

.clear-btn:active {
  opacity: 0.7;
  transform: scale(0.95);
}

.status-dot.online {
  background: #4caf50;
  box-shadow: 0 0 12rpx rgba(76, 175, 80, 0.8);
  animation: pulse 2s infinite;
}

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

/* 聊天消息区域 */
.chat-messages {
  padding: 24rpx;
  overflow-y: auto;
}

.message-list {
  display: flex;
  flex-direction: column;
  gap: 24rpx;
  padding-bottom: 120rpx; /* 增加底部padding，确保最后一条消息不被输入框遮挡 */
}

.message-wrapper {
  width: 100%;
}

/* 系统消息 */
.system-message {
  display: flex;
  justify-content: center;
  margin: 16rpx 0;
}

.system-text {
  background: rgba(249, 224, 118, 0.1);
  backdrop-filter: blur(10px);
  border: 1rpx solid rgba(249, 224, 118, 0.2);
  color: rgba(249, 224, 118, 0.8);
  padding: 12rpx 24rpx;
  border-radius: 20rpx;
  font-size: 24rpx;
  text-align: center;
  max-width: 80%;
  line-height: 1.5;
}

/* Bubble 样式 */
.bubble-container {
  display: flex;
  align-items: flex-end;
  gap: 16rpx;
  width: 100%;
}

.bubble-container.user {
  flex-direction: row-reverse;
}

.bubble-avatar {
  width: 72rpx;
  height: 72rpx;
  border-radius: 50%;
  overflow: hidden;
  flex-shrink: 0;
  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}

.bubble-avatar image {
  width: 100%;
  height: 100%;
}

.bubble-content {
  flex: 1;
  max-width: 70%;
}

.bubble {
  padding: 20rpx 24rpx;
  border-radius: 24rpx;
  position: relative;
  word-wrap: break-word;
  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3);
}

.bubble.ai {
  background: linear-gradient(135deg, rgba(249, 224, 118, 0.15) 0%, rgba(249, 224, 118, 0.08) 100%);
  border: 1rpx solid rgba(249, 224, 118, 0.3);
  color: #f9e076;
  border-bottom-left-radius: 8rpx;
}

.bubble.user {
  background: linear-gradient(135deg, #f9e076 0%, #f5d042 100%);
  color: #1a0b2e;
  border-bottom-right-radius: 8rpx;
  box-shadow: 0 8rpx 24rpx rgba(249, 224, 118, 0.3);
}

.bubble-text {
  font-size: 28rpx;
  line-height: 1.6;
  display: block;
  margin-bottom: 8rpx;
}

.bubble-actions {
  display: flex;
  gap: 12rpx;
  margin: 12rpx 0 8rpx 0;
}

.action-btn {
  width: 48rpx;
  height: 48rpx;
  border-radius: 50%;
  background: rgba(249, 224, 118, 0.2);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
}

.action-btn:active {
  background: rgba(249, 224, 118, 0.4);
  transform: scale(0.95);
}

.action-icon {
  font-size: 24rpx;
}

.bubble-time {
  font-size: 20rpx;
  opacity: 0.6;
  text-align: right;
}

.bubble.ai .bubble-time {
  text-align: left;
}

/* 正在输入 */
.typing-indicator {
  display: flex;
  align-items: flex-end;
  gap: 16rpx;
}

.typing-bubble {
  background: linear-gradient(135deg, rgba(249, 224, 118, 0.15) 0%, rgba(249, 224, 118, 0.08) 100%);
  border: 1rpx solid rgba(249, 224, 118, 0.3);
  padding: 20rpx 24rpx;
  border-radius: 24rpx;
  border-bottom-left-radius: 8rpx;
  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3);
}

.typing-dots {
  display: flex;
  gap: 8rpx;
  align-items: center;
}

.dot {
  width: 12rpx;
  height: 12rpx;
  border-radius: 50%;
  background: #f9e076;
  box-shadow: 0 0 8rpx rgba(249, 224, 118, 0.5);
  animation: typing 1.4s infinite ease-in-out;
}

.dot:nth-child(1) { animation-delay: -0.32s; }
.dot:nth-child(2) { animation-delay: -0.16s; }

@keyframes typing {
  0%, 80%, 100% {
    transform: scale(0.8);
    opacity: 0.5;
  }
  40% {
    transform: scale(1);
    opacity: 1;
  }
}

/* 输入区域 */
.input-area {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: rgba(26, 11, 46, 0.95);
  backdrop-filter: blur(20rpx);
  border-top: 1rpx solid rgba(249, 224, 118, 0.1);
  padding: 16rpx 24rpx;
  /* padding-bottom 通过动态样式绑定控制 */
  box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.3);
  z-index: 1000;
  transition: bottom 0.3s ease, padding-bottom 0.3s ease;
}

.action-bar {
  margin-bottom: 12rpx;
}

.template-selector-btn {
  background: linear-gradient(135deg, rgba(249, 224, 118, 0.1) 0%, rgba(249, 224, 118, 0.05) 100%);
  border: 2rpx solid rgba(249, 224, 118, 0.3);
  border-radius: 16rpx;
  padding: 12rpx 20rpx;
  display: flex;
  align-items: center;
  gap: 12rpx;
  transition: all 0.3s;
}

.template-selector-btn:active {
  background: linear-gradient(135deg, rgba(249, 224, 118, 0.2) 0%, rgba(249, 224, 118, 0.1) 100%);
  transform: scale(0.98);
}

.template-icon {
  font-size: 28rpx;
}

.template-name {
  flex: 1;
  font-size: 26rpx;
  color: #f9e076;
  font-weight: 500;
}

.arrow-icon {
  font-size: 32rpx;
  color: #f9e076;
}

/* Sender 样式 */
.sender-container {
  background: rgba(249, 224, 118, 0.08);
  border: 1rpx solid rgba(249, 224, 118, 0.2);
  border-radius: 24rpx;
  overflow: hidden;
}

.text-input-mode {
  display: flex;
  align-items: center;
  gap: 12rpx;
  padding: 8rpx 16rpx;
}

.text-input {
  flex: 1;
  font-size: 28rpx;
  color: #f9e076;
  background: transparent;
  border: none;
  outline: none;
  padding: 12rpx 0;
  min-height: 48rpx;
}

.text-input::placeholder {
  color: rgba(249, 224, 118, 0.5);
}

.input-actions {
  display: flex;
  align-items: center;
  gap: 12rpx;
}

.action-icon-btn {
  width: 56rpx;
  height: 56rpx;
  border-radius: 50%;
  background: rgba(249, 224, 118, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
}

.action-icon-btn:active {
  background: rgba(249, 224, 118, 0.3);
  transform: scale(0.95);
}

.icon {
  font-size: 28rpx;
}

.send-btn {
  padding: 12rpx 24rpx;
  border-radius: 20rpx;
  background: rgba(249, 224, 118, 0.2);
  font-size: 26rpx;
  color: rgba(249, 224, 118, 0.6);
  font-weight: 500;
  transition: all 0.3s;
}

.send-btn.active {
  background: linear-gradient(135deg, #f9e076 0%, #f5d042 100%);
  color: #1a0b2e;
  box-shadow: 0 4rpx 12rpx rgba(249, 224, 118, 0.4);
}

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

.send-icon {
  display: block;
}

/* 语音输入模式 */
.voice-input-mode {
  display: flex;
  align-items: center;
  gap: 12rpx;
  padding: 12rpx 16rpx;
}

.hold-to-speak {
  flex: 1;
  background: rgba(249, 224, 118, 0.1);
  border: 2rpx dashed rgba(249, 224, 118, 0.3);
  border-radius: 16rpx;
  padding: 20rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
}

.hold-to-speak.recording {
  background: linear-gradient(135deg, #f9e076 0%, #f5d042 100%);
  border-color: transparent;
  animation: pulse-recording 1s infinite;
}

.speak-text {
  font-size: 28rpx;
  color: #f9e076;
  font-weight: 500;
}

.hold-to-speak.recording .speak-text {
  color: #1a0b2e;
}

@keyframes pulse-recording {
  0%, 100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.02);
  }
}

/* 录音提示 */
.recording-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
  backdrop-filter: blur(4rpx);
}

.recording-modal {
  background: rgba(26, 11, 46, 0.95);
  backdrop-filter: blur(20rpx);
  border: 2rpx solid rgba(249, 224, 118, 0.3);
  padding: 48rpx;
  border-radius: 32rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24rpx;
  box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.5);
}

.recording-tip {
  font-size: 28rpx;
  color: #f9e076;
  font-weight: 500;
  text-shadow: 0 0 10rpx rgba(249, 224, 118, 0.5);
}

.recording-animation {
  display: flex;
  gap: 8rpx;
  align-items: center;
}

.wave {
  width: 6rpx;
  height: 40rpx;
  background: linear-gradient(135deg, #f9e076 0%, #f5d042 100%);
  border-radius: 3rpx;
  animation: wave 1.2s infinite ease-in-out;
  box-shadow: 0 0 8rpx rgba(249, 224, 118, 0.5);
}

.wave:nth-child(1) { animation-delay: 0s; }
.wave:nth-child(2) { animation-delay: 0.1s; }
.wave:nth-child(3) { animation-delay: 0.2s; }
.wave:nth-child(4) { animation-delay: 0.3s; }
.wave:nth-child(5) { animation-delay: 0.4s; }

@keyframes wave {
  0%, 100% {
    height: 40rpx;
  }
  50% {
    height: 80rpx;
  }
}

/* 模态框 */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
  backdrop-filter: blur(8rpx);
}

.modal-content {
  width: 86%;
  max-width: 640rpx;
  max-height: 80vh;
  background: linear-gradient(135deg, rgba(26, 11, 46, 0.98) 0%, rgba(45, 27, 78, 0.98) 100%);
  border: 2rpx solid rgba(249, 224, 118, 0.3);
  border-radius: 32rpx;
  overflow: hidden;
  box-shadow: 0 12rpx 48rpx rgba(0, 0, 0, 0.5);
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 32rpx;
  border-bottom: 1rpx solid rgba(249, 224, 118, 0.1);
  background: linear-gradient(135deg, rgba(249, 224, 118, 0.08) 0%, rgba(249, 224, 118, 0.03) 100%);
}

.modal-title {
  font-size: 32rpx;
  font-weight: 600;
  color: #f9e076;
  text-shadow: 0 0 15rpx rgba(249, 224, 118, 0.5);
}

.close-btn {
  width: 56rpx;
  height: 56rpx;
  border-radius: 50%;
  background: rgba(249, 224, 118, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
}

.close-btn:active {
  background: rgba(249, 224, 118, 0.2);
  transform: scale(0.95);
}

.close-icon {
  font-size: 28rpx;
  color: #f9e076;
}

.template-list {
  max-height: 60vh;
  overflow-y: auto;
}

.template-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 32rpx;
  border-bottom: 1rpx solid rgba(249, 224, 118, 0.1);
  transition: all 0.3s;
}

.template-item:active {
  background: rgba(249, 224, 118, 0.05);
}

.template-item.active {
  background: linear-gradient(135deg, rgba(249, 224, 118, 0.15) 0%, rgba(249, 224, 118, 0.08) 100%);
}

.template-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 8rpx;
  padding-right: 16rpx;
}

.template-item .template-name {
  font-size: 28rpx;
  font-weight: 600;
  color: #f9e076;
  margin-bottom: 4rpx;
}

.template-desc {
  font-size: 24rpx;
  color: rgba(249, 224, 118, 0.7);
  line-height: 1.5;
}

.check-mark {
  width: 48rpx;
  height: 48rpx;
  border-radius: 50%;
  background: linear-gradient(135deg, #f9e076 0%, #f5d042 100%);
  box-shadow: 0 4rpx 12rpx rgba(249, 224, 118, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.check-icon {
  font-size: 24rpx;
  color: #1a0b2e;
  font-weight: bold;
}

/* 🎧 语音模式切换按钮 */
.voice-mode-toggle {
  width: 56rpx;
  height: 56rpx;
  border-radius: 50%;
  background: rgba(249, 224, 118, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
  flex-shrink: 0;
}

.voice-mode-toggle:active {
  background: rgba(249, 224, 118, 0.3);
  transform: scale(0.95);
}

.voice-mode-toggle .icon {
  font-size: 28rpx;
}

/* 🎧 语音对话模式容器 */
.voice-mode-container {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48rpx 32rpx;
}

.voice-mode-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 48rpx;
  width: 100%;
}

/* 🎧 语音模式头像 */
.voice-avatar-wrapper {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.voice-avatar {
  width: 240rpx;
  height: 240rpx;
  border-radius: 50%;
  border: 6rpx solid rgba(249, 224, 118, 0.3);
  box-shadow: 0 8rpx 32rpx rgba(249, 224, 118, 0.3);
  position: relative;
  z-index: 2;
}

.avatar-glow {
  position: absolute;
  width: 280rpx;
  height: 280rpx;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(249, 224, 118, 0.3) 0%, transparent 70%);
  z-index: 1;
}

.avatar-glow.listening {
  animation: pulse-glow 2s infinite;
  background: radial-gradient(circle, rgba(76, 175, 80, 0.4) 0%, transparent 70%);
}

.avatar-glow.thinking {
  animation: pulse-glow 1.5s infinite;
  background: radial-gradient(circle, rgba(33, 150, 243, 0.4) 0%, transparent 70%);
}

.avatar-glow.speaking {
  animation: pulse-glow 1s infinite;
  background: radial-gradient(circle, rgba(249, 224, 118, 0.5) 0%, transparent 70%);
}

@keyframes pulse-glow {
  0%, 100% {
    transform: scale(1);
    opacity: 0.6;
  }
  50% {
    transform: scale(1.1);
    opacity: 1;
  }
}

/* 🎧 状态文本 */
.voice-state-text {
  font-size: 36rpx;
  font-weight: 600;
  color: #f9e076;
  text-shadow: 0 0 20rpx rgba(249, 224, 118, 0.5);
  letter-spacing: 2rpx;
}

/* 🎧 声波动画容器 */
.sound-wave-container {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 160rpx;
}

.sound-wave {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6rpx;
  height: 100%;
}

.wave-bar {
  width: 6rpx;
  background: linear-gradient(180deg, #f9e076 0%, #f5d042 100%);
  border-radius: 3rpx;
  height: 20rpx;
  opacity: 0.3;
  transition: height 0.15s linear, opacity 0.15s linear;
}

.sound-wave.listening .wave-bar {
  background: linear-gradient(180deg, #4caf50 0%, #45a049 100%);
}

.sound-wave.thinking .wave-bar {
  background: linear-gradient(180deg, #2196f3 0%, #1976d2 100%);
}

.sound-wave.speaking .wave-bar {
  background: linear-gradient(180deg, #f9e076 0%, #f5d042 100%);
}

/* 🧪 测试模式样式 */
.test-controls {
  margin-top: 80rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20rpx;
}

.test-btn {
  padding: 24rpx 60rpx;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border-radius: 50rpx;
  font-size: 32rpx;
  font-weight: 500;
  border: none;
  box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.4);
  transition: all 0.3s ease;
}

.test-btn:active {
  transform: scale(0.95);
  box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.3);
}

.test-btn[disabled] {
  background: linear-gradient(135deg, #bbb 0%, #999 100%);
  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
  opacity: 0.6;
}

.test-hint {
  font-size: 24rpx;
  color: rgba(255, 255, 255, 0.6);
  text-align: center;
}

</style>
