<template>
  <div class="tts-app">
    <!-- 主容器，采用左右布局 -->
    <div class="main-container">
      <!-- 左侧：参数设置 -->
      <div class="main-left">
        <h1 class="app-title">GPT-SoVITS 语音合成</h1>

        <!-- 角色选择 -->
        <div class="setting-group">
          <label class="setting-label">选择角色</label>
          <el-select
            v-model="selectedCharacterId"
            placeholder="请选择角色"
            @change="switchModelByCharacter"
            :loading="modelSwitching.gpt || modelSwitching.sovits"
            class="wide-select"
          >
            <el-option
              v-for="char in availableCharacters"
              :key="char.id"
              :label="char.name"
              :value="char.id"
            />
          </el-select>
        </div>

        <!-- 合成参数 -->
        <el-form :model="ttsParams" :rules="ttsRules" ref="ttsFormRef" label-width="auto" :disabled="isGenerating || !currentCharacter" class="params-form">
          <el-form-item label="合成文本内容" prop="text">
            <el-input
              v-model="ttsParams.text"
              type="textarea"
              :rows="6"
              placeholder="请输入要合成的文本..."
              class="text-input"
            />
          </el-form-item>
          <el-form-item label="输入文本语言" prop="prompt_lang">
            <el-select v-model="ttsParams.prompt_lang" placeholder="请选择">
              <el-option label="中文" value="zh" />
              <el-option label="英文" value="en" />
              <el-option label="日文" value="ja" />
            </el-select>
          </el-form-item>
          <el-form-item label="生成音频语言" prop="text_lang">
            <el-select v-model="ttsParams.text_lang" placeholder="请选择">
              <el-option label="中文" value="zh" />
              <el-option label="英文" value="en" />
              <el-option label="日文" value="ja" />
            </el-select>
          </el-form-item>
          <el-form-item label="文本分割方式">
            <el-select v-model="ttsParams.text_split_method">
              <el-option label="不切" value="cut0" />
              <el-option label="按。切" value="cut1" />
              <el-option label="按，。切" value="cut2" />
              <el-option label="按？！切" value="cut3" />
              <el-option label="按，。？！切" value="cut4" />
              <el-option label="按每行切" value="cut5" />
              <el-option label="按换行切" value="cut6" />
            </el-select>
          </el-form-item>
          <el-form-item label="音频随机参数">
            <el-slider v-model="ttsParams.temperature" :min="0" :max="2" :step="0.01" class="wide-slider" />
            <span class="slider-value">{{ ttsParams.temperature.toFixed(2) }}</span>
          </el-form-item>
          <el-form-item label="音频语速倍率">
            <el-slider v-model="ttsParams.speed_factor" :min="0.1" :max="3" :step="0.1" class="wide-slider" />
            <span class="slider-value">{{ ttsParams.speed_factor.toFixed(1) }}</span>
          </el-form-item>
          <!-- <el-form-item label="流式传输">
            <el-switch v-model="ttsParams.streaming_mode" />
          </el-form-item> -->
        </el-form>

        <!-- 控制按钮 -->
        <div class="controls-section">
          <el-button type="primary" @click="synthesize" :loading="isGenerating" :disabled="!currentCharacter" class="control-btn">
            {{ isGenerating ? '生成中...' : '合成语音' }}
          </el-button>
          <el-button @click="stopGeneration" :disabled="!isGenerating" class="control-btn secondary-btn">
            停止
          </el-button>
          <el-button @click="clearAudio" class="control-btn secondary-btn">
            清空合成音频
          </el-button>
        </div>
      </div>

      <!-- 右侧：结果与日志 -->
      <div class="main-right">
        <h2 class="section-title">合成结果</h2>
        <div class="result-content">
          <audio :src="audioUrl" controls class="audio-player" v-if="audioUrl" />
          <div v-else class="no-audio-placeholder">
            <!-- 可以放一个音频图标或提示 -->
            <p>暂未生成音频</p>
          </div>
        </div>

        <h2 class="section-title">日志</h2>
        <div class="log-content-wrapper">
          <pre class="log-content">{{ log }}</pre>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, reactive, computed, onUnmounted } from 'vue';
import { ElMessage, FormInstance, FormRules } from 'element-plus';

// API 配置
// const apiConfig = reactive({
//   baseUrl: 'http://152.136.153.72:27007',
// });

const apiConfig = reactive({
  baseUrl: 'http://127.0.0.1:9880',
});


// 可用角色列表
const availableCharacters = [
    { 
        id: 'yier', 
        name: '一二(yier)', 
        gptPath: 'D:\\AI\\aimods\\GPT_weights_v2\\yier-e20.ckpt', 
        sovitsPath: 'D:\\AI\\aimods\\SoVITS_weights_v2\\yier_e8_s144.pth', 
        refAudioPath: 'D:\\AI\\aimods\\voice\\yier001.mp3',
        // promptText: 'This is a prompt for 一二.' // 示例：可选的提示文本
    },
    { 
        id: 'bubu', 
        name: '布布(bubu)', 
        gptPath: 'D:\\AI\\aimods\\GPT_weights_v2\\bubu-e20.ckpt', 
        sovitsPath: 'D:\\AI\\aimods\\SoVITS_weights_v2\\bubu_e8_s136.pth', 
        refAudioPath: 'D:\\AI\\aimods\\voice\\bubu001.wav'
    },
];

// 当前选中的角色 ID，初始化为 null
const selectedCharacterId = ref<string | null>(null); // 默认不选择任何角色

// 计算属性：根据 ID 获取当前选中的角色信息
const currentCharacter = computed(() => {
    if (!selectedCharacterId.value) return null; // 如果没有选中ID，直接返回null
    return availableCharacters.find(char => char.id === selectedCharacterId.value) || null;
});

// TTS 参数
const ttsParams = reactive({
  text: '欢迎来到后台配音系统，这是后台语音合成示例。',
  text_lang: 'zh',
  ref_audio_path: computed(() => currentCharacter.value?.refAudioPath || '').value, // 初始化时取默认角色的路径，若无则为空字符串
  aux_ref_audio_paths: [],
  prompt_text: computed(() => currentCharacter.value?.promptText || '').value, // 初始化时取默认角色的提示文本，若无则为空字符串
  prompt_lang: 'zh',
  top_k: 5,
  top_p: 1.0,
  temperature: 1.0,
  text_split_method: 'cut5',
  batch_size: 1,
  batch_threshold: 0.75,
  split_bucket: true,
  speed_factor: 1.0,
  streaming_mode: true,
  seed: -1,
  parallel_infer: true,
  repetition_penalty: 1.35,
  sample_steps: 32,
  super_sampling: false,
  media_type: 'wav',
});

// 表单验证规则
const ttsRules: FormRules = {
  text: [
    { required: true, message: '请输入待合成的文本', trigger: 'blur' },
  ],
  text_lang: [
    { required: true, message: '请选择文本语言', trigger: 'change' },
  ],
  prompt_lang: [
    { required: true, message: '请选择提示文本语言', trigger: 'change' },
  ],
};

// 模型切换状态
const modelSwitching = reactive({
  gpt: false,
  sovits: false,
});

const ttsFormRef = ref<FormInstance>();
const isGenerating = ref(false);
const audioUrl = ref<string | null>(null); // 初始值设为 null
const log = ref('');

// 切换模型辅助函数
const switchGPTModel = async (path: string) => {
    try {
        const response = await fetch(`${apiConfig.baseUrl}/set_gpt_weights?weights_path=${encodeURIComponent(path)}`, {
            method: 'GET',
        });
        const responseText = await response.text(); // 读取响应文本
        if(response.status === 200) {
            log.value += 'GPT 模型切换成功！\n';
            ElMessage.success('GPT 模型切换成功');
            return true;
        } else {
            log.value += `GPT 模型切换失败: ${responseText}\n`;
            ElMessage.error(`GPT 模型切换失败: ${responseText}`);
            return false;
        }
    } catch (error: any) {
        log.value += `GPT 模型切换网络错误: ${error.message}\n`;
        ElMessage.error(`GPT 模型切换网络错误: ${error.message}`);
        return false;
    }
};

const switchSoVITSModel = async (path: string) => {
    try {
        const response = await fetch(`${apiConfig.baseUrl}/set_sovits_weights?weights_path=${encodeURIComponent(path)}`, {
            method: 'GET',
        });
        const responseText = await response.text(); // 读取响应文本
        if(response.status === 200) {
            log.value += 'SoVITS 模型切换成功！\n';
            ElMessage.success('SoVITS 模型切换成功');
            return true;
        } else {
            log.value += `SoVITS 模型切换失败: ${responseText}\n`;
            ElMessage.error(`SoVITS 模型切换失败: ${responseText}`);
            return false;
        }
    } catch (error: any) {
        log.value += `SoVITS 模型切换网络错误: ${error.message}\n`;
        ElMessage.error(`SoVITS 模型切换网络错误: ${error.message}`);
        return false;
    }
};

// 根据选中的角色切换模型
const switchModelByCharacter = async (characterId: string) => {
    const char = currentCharacter.value; // 使用计算属性 currentCharacter
    if (!char) { // 检查是否为 null
        ElMessage.error(`找不到角色 ID "${characterId}" 的配置。`);
        log.value += `错误: 找不到角色 ID "${characterId}" 的配置。\n`;
        return;
    }

    log.value += `开始切换模型为角色 "${char.name}"...\n`;

    // 1. 切换 GPT 模型
    modelSwitching.gpt = true;
    log.value += `正在切换 GPT 模型到: ${char.gptPath}\n`;
    const gptSuccess = await switchGPTModel(char.gptPath);
    modelSwitching.gpt = false;

    // 2. 切换 SoVITS 模型
    modelSwitching.sovits = true;
    log.value += `正在切换 SoVITS 模型到: ${char.sovitsPath}\n`;
    const sovitsSuccess = await switchSoVITSModel(char.sovitsPath);
    modelSwitching.sovits = false;

    if (gptSuccess && sovitsSuccess) {
        log.value += `角色 "${char.name}" 的模型切换完成！\n`;
        // 更新 ttsParams 中由角色决定的字段
        ttsParams.ref_audio_path = char.refAudioPath;
        ttsParams.prompt_text = char.promptText || '';
    } else {
        log.value += `角色 "${char.name}" 的模型切换部分失败或全部失败。\n`;
    }
};

// 合成语音
const synthesize = async () => {
    if (!currentCharacter.value) {
        ElMessage.error('请先选择一个角色。');
        return;
    }

    const validate = await ttsFormRef.value?.validate().catch(() => false);
    if (!validate) {
        ElMessage.error('请检查输入参数');
        return;
    }

    isGenerating.value = true;
    log.value += '开始合成语音...\n';
    clearAudio(); // 清空之前的音频

    try {
        // 构建 POST 请求体
        const requestBody = { ...ttsParams };
        // 如果 prompt_text 为空，则从 payload 中移除
        if (!requestBody.prompt_text) {
            delete requestBody.prompt_text;
        }

        const response = await fetch(`${apiConfig.baseUrl}/tts`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(requestBody),
        });

        if (!response.ok) {
            const errorData = await response.json().catch(() => ({ message: `HTTP Error ${response.status}` }));
            throw new Error(errorData.message || `HTTP Error ${response.status}`);
        }

        // 检查返回的是否为音频流
        const contentType = response.headers.get('content-type');
        if (!contentType || !contentType.includes('audio')) {
            const errorText = await response.text();
            throw new Error(`API returned non-audio content: ${errorText}`);
        }

        const audioBlob = await response.blob();
        // 创建临时 URL 用于播放和下载
        const newUrl = URL.createObjectURL(audioBlob);
        audioUrl.value = newUrl;
        log.value += '语音合成成功！\n';
        ElMessage.success('语音合成成功');
    } catch (error: any) {
        console.error('Synthesis failed:', error);
        log.value += `语音合成失败: ${error.message}\n`;
        ElMessage.error(`语音合成失败: ${error.message}`);
    } finally {
        isGenerating.value = false;
    }
};

// 停止生成 (当前 fetch 无法真正中断，主要重置 UI 状态)
const stopGeneration = () => {
    isGenerating.value = false;
    log.value += '用户已请求停止生成。\n';
    ElMessage.info('已请求停止生成');
};

// 清空音频
const clearAudio = () => {
    if (audioUrl.value) {
        URL.revokeObjectURL(audioUrl.value);
        audioUrl.value = null;
    }
};

// 组件卸载时清理 URL 对象
onUnmounted(() => {
    clearAudio();
});
</script>

<style lang="less" scoped>
// Typecho风格的简洁变量
@primary-color: #467b96;
@primary-hover-color: #3a6a80;
@text-color: #444;
@text-color-light: #777;
@border-color: #e4e4e4;
@background-color: #f9f9f9;
@background-light-color: #fff;
@border-radius: 4px;
@font-size-base: 14px;
@font-size-large: 16px;
@font-size-title: 20px;
@font-size-section: 18px;
@padding-base: 15px;
@padding-section: 20px;

.tts-app {
  padding: 20px;
  margin: 0 auto;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
  color: @text-color;
//   background-color: @background-color;
//   min-height: 100vh;
}

.main-container {
  display: flex;
  gap: 30px;
  align-items: stretch; // 让左右两栏高度保持一致
}

.main-left,
.main-right {
  flex: 1; // 使左右两栏等宽
  background: @background-light-color;
  border-radius: @border-radius;
  padding: @padding-section;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  display: flex;
  flex-direction: column;
}

.main-left {
  min-width: 0; // 防止flex item溢出
}

.main-right {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.app-title {
  font-size: @font-size-title;
  font-weight: 500;
  margin: 0 0 @padding-section 0;
  padding-bottom: 10px;
  border-bottom: 1px solid @border-color;
  color: @text-color;
}

.section-title {
  font-size: @font-size-section;
  font-weight: 500;
  margin: 0 0 @padding-base 0;
  padding-bottom: 8px;
  border-bottom: 1px solid @border-color;
  color: @text-color;
}

.setting-group {
  margin-bottom: @padding-section;
}

.setting-label {
  display: block;
  margin-bottom: 8px;
  font-weight: 500;
  color: @text-color;
}

.wide-select {
  width: 100%;
}

.params-form {
  flex: 1; // 让表单占据剩余空间
  display: flex;
  flex-direction: column;
  justify-content: space-between; // 让表单内容和按钮分开

  :deep(.el-form-item) {
    margin-bottom: 16px;

    .el-form-item__label {
      padding-bottom: 4px; // label与控件更紧凑
      color: @text-color-light;
      font-size: @font-size-base;
      font-weight: 400;
    }
  }
}

.text-input {
  :deep(.el-textarea__inner) {
    min-height: 120px !important;
    font-family: inherit;
    resize: vertical; // 允许垂直调整大小
  }
}

.wide-slider {
  width: calc(100% - 80px); // 预留出数字显示的空间
  margin-right: 10px;
}

.slider-value {
  display: inline-block;
  width: 50px;
  text-align: center;
  font-size: @font-size-base;
  color: @text-color-light;
}

.controls-section {
  margin-top: auto; // 将按钮推到底部
  padding-top: @padding-section;
  border-top: 1px solid @border-color;
  display: flex;
  gap: 10px;
  flex-wrap: wrap; // 按钮过多时换行
}

.control-btn {
  flex: 1; // 按钮平均分配空间
  min-width: 120px; // 设置最小宽度
  margin: 0; // 重置可能的默认margin
  font-size: @font-size-base;

  &.secondary-btn {
    color: @text-color;
    border-color: @border-color;
    background-color: @background-color;

    &:hover {
      background-color: #f0f0f0;
      border-color: #ccc;
    }
  }
}

.result-content {
  flex: 1; // 占据剩余空间
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100px; // 设置最小高度
  border: 1px dashed @border-color;
  border-radius: @border-radius;
  margin-bottom: @padding-section;
  background-color: #fafafa;

  .audio-player {
    width: 100%;
    max-width: 500px; // 限制播放器最大宽度
    border-radius: @border-radius;
    outline: none;
  }
}

.no-audio-placeholder {
  color: @text-color-light;
  text-align: center;
}

.log-content-wrapper {
  flex: 1; // 日志区域占据剩余空间
  display: flex;
  flex-direction: column;
  min-height: 200px; // 设置最小高度
}

.log-content {
  flex: 1; // 让日志内容区域自适应
  background-color: #2d2d2d;
  color: #f8f8f2;
  border: 1px solid @border-color;
  border-radius: @border-radius;
  padding: @padding-base;
  overflow-y: auto;
  white-space: pre-wrap;
  word-wrap: break-word;
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
  font-size: 12px;
  line-height: 1.4;
  margin: 0;
}
</style>