From 201241571bbcad66077f6f244c539b0aebc25e35 Mon Sep 17 00:00:00 2001 From: HuangHai <10402852@qq.com> Date: Tue, 2 Sep 2025 07:36:42 +0800 Subject: [PATCH] 'commit' --- .../__pycache__/ttsRoute.cpython-310.pyc | Bin 3809 -> 3687 bytes dsLightRag/Routes/ttsRoute.py | 26 +- dsLightRag/Util/GengerateAudio.py | 82 ++++-- .../GengerateAudio.cpython-310.pyc | Bin 7614 -> 9454 bytes dsLightRag/static/text-to-speech.html | 238 +++++++++--------- 5 files changed, 189 insertions(+), 157 deletions(-) diff --git a/dsLightRag/Routes/__pycache__/ttsRoute.cpython-310.pyc b/dsLightRag/Routes/__pycache__/ttsRoute.cpython-310.pyc index 3a0e2f68f88d9f4e855e284c6408096d1da6c489..2a8df3543cf38fa727c5ee7ce30a78e64e2a5733 100644 GIT binary patch delta 1210 zcmaJ=-D@0G6u)O?XLe?GX0yB5Y(ARRge{e2*`!}ug(AjUTVK*@BFKP*aqrE#GRcHH zv#l-ID4Pcn3YP1OPiYE*J_p2ip^ts>ZNvwWTH-&T4}zX^18ETHoxMNKIrp4^D-5FAJ5|aHoIV>vXp_ieV8LXS@(Q8*b=o*^0f5Qdj~e zEN=5+AGmYZ2z|ls2co}jiTkMgTCWNJ{x}czfS38$ z4jVyO*Otm2h$63!U>*2&O-!sCe4N*JEOcmShY1X6@JSsqDg2M7_>`#e>AMD>5mod) z!Dn|0=snG0g|(Xp)ZKC$a_8DsHb^$3xNYX{()G9ANrst_d2n@g_4?(HdqSV)*4slN zZ=t*UdX(;8g0G?L;|0Jx-!bmdP+iP(}ET$!U_Om1|xAPc4~E#5MEp?HZZK z3F-tB2zlXyB#GrD=?#MC2y}+$Ntz;@U+q4NaGYT4=9lm|0> z<_y)J&iPk#22-9!YiLuMB9#l|^0Zgo=2^78j5|GrpmTAzb&{;N3yV=4_fR@oG(dXX zKjNl#*I=k3Yi>Vxn{fKDr_jTowPz$ehq2dm{6o71=k~j$i?C8cgVe8p9>9N?UmX&| z2fOJojY5%c(j=fdeTNU9Dg?( z#0yu%W?x8TduhlAi7cw6^5o&e?Vyuv6VPOJF}c|nnOr25hAN2-Nps5!TS=Np%6^PT x%_xzXI~O++K8(eyfSP91VGj6EH+=Aovgw(&SurY}<9XmzyeV)R9{BY-{0kmW^pXGo delta 1344 zcmaJ=&2Jk;6rb5$uXoqG?%J^vJGEO2M^sjD!$;LZ)kBN;IDpzibLmP@Z8KXF z-6}<;krF{tnx@r6AQgmDP(^znlt$u12=RBY2P6cxlM@H7J@DSR2^KSd)N1BNUcmek=O@$Lv42~sS#qOg460)3S21v>R| zjGiWy!ZucPvO*){)NFG)obl@wMI5A~o2TeHttP<86$Bd~ik+{Aev>=6kwkC-?h~Fv zIVTG0k^Lr!L$DfXvWP|v>OTHuC^rRHA6TIA2>pP&QXZvI06aAic_%hRM(gGD#KmxWN(~wl;xDaC{}w+RXX&IUnkCk~aksPj zrB#A&>zB38r=NG1=ewURcUHgYt=;S0UfgbPZY@9TEPc@X_NUI$CtG*!N$z9|Q`x%l zeP{LKK}vV&X6IV_zRLH4IntQl^m#$FV{g%u;!*4sniVJXY~tTC^1|1PkkB=&JT`%; z?vtrPrsw9WQ+_k3g?w(}y&dU_c%a`s;9ODOS1!>@q>^l1heI->?)s12m5qL){k+6+ zbL8z4yw*4>30UHepQEM0q&qiPdS5*l?7{U9I~%wAzW)5-BWOdvD?5jF+aUaSRmW1H z$hCM&R4r4ajAvfLtE{N}Ir#H*?0wMS2az^VIjS!J9D~((8bFy>3~|vYJGgr}@wgG9 zZ4@>B6d{{A^JRKidR9Ekuh-8$!SNw*aRwHD7U9t5hINdd#%ImtXya7>XPhTnIGIr4 z_7}H$Yi+p5{#{mL@-BG+kI3oV9mm&NX)$kS75==KwEeLsx|f56^ltN-iC z5wOmPvh!*RrAj`E*TY(EdZxx-6mw45j*Qtk=Fc>zr+7?!?c@U;XpzptRx{v{SqqwP z0zcqfKDTjATtJu_5lB%WZ Us;jt`VOiA3xYQ_E)Xrq+KLu7)?*IS* diff --git a/dsLightRag/Routes/ttsRoute.py b/dsLightRag/Routes/ttsRoute.py index 17a2bc46..f1f842f3 100644 --- a/dsLightRag/Routes/ttsRoute.py +++ b/dsLightRag/Routes/ttsRoute.py @@ -2,7 +2,7 @@ import logging import uuid from typing import Optional -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, HTTPException, Query # 添加Query导入 from pydantic import BaseModel from Util.GengerateAudio import ByteDanceTTS @@ -36,17 +36,13 @@ class TextToSpeechResponse(BaseModel): request_id: Optional[str] = None -@router.get("/voices/categories") +@router.get("/voice-categories") async def get_voice_categories(): - """ - 获取所有音色分类接口 - 返回所有可用的音色分类列表 - """ try: categories = tts_instance.get_all_categories() return { "success": True, - "data": categories, + "data": categories, # 恢复为原始的 data 字段 "message": "获取音色分类成功" } except Exception as e: @@ -56,15 +52,9 @@ async def get_voice_categories(): detail=f"获取音色分类失败: {str(e)}" ) - -@router.get("/voices/by-category/{category}") -async def get_voices_by_category(category: str): - """ - 根据分类获取音色列表接口 - Args: - category: 音色分类名称 - 返回指定分类下的所有音色列表 - """ +# 恢复原始的音色列表接口路由 +@router.get("/voices") +async def get_voices_by_category(category: str = Query(...)): # 现在Query已定义 try: voices = tts_instance.get_voices_by_category(category) if not voices: @@ -72,7 +62,7 @@ async def get_voices_by_category(category: str): "success": False, "message": f"未找到分类 '{category}' 下的音色" } - + return { "success": True, "data": voices, @@ -86,7 +76,7 @@ async def get_voices_by_category(category: str): ) -@router.get("/voices/all") +@router.get("/all") async def get_all_voices(): """ 获取所有音色分类和音色列表接口 diff --git a/dsLightRag/Util/GengerateAudio.py b/dsLightRag/Util/GengerateAudio.py index 0fe73504..3998d364 100644 --- a/dsLightRag/Util/GengerateAudio.py +++ b/dsLightRag/Util/GengerateAudio.py @@ -26,44 +26,78 @@ class ByteDanceTTS: # 音色分类字典 TTS_VOICES = { "通用场景": { - "zh_female_xiaoxue_moon_bigtts": "小雪(女声,温柔亲切)", - "zh_male_xiaofeng_common": "小峰(男声,沉稳大气)", - "zh_female_xiaoxin_common": "小新(女声,自然流畅)", - "zh_male_xiaoyu_common": "小鱼(男声,年轻活力)" + "BV700_V2_streaming": "灿灿 2.0", + "BV705_streaming": "炀炀", + "BV701_V2_streaming": "擎苍 2.0", + "BV001_V2_streaming": "通用女声 2.0", + "BV700_streaming": "灿灿", + "BV406_V2_streaming": "超自然音色-梓梓2.0", + "BV406_streaming": "超自然音色-梓梓", + "BV407_V2_streaming": "超自然音色-燃燃2.0", + "BV407_streaming": "超自然音色-燃燃", + "BV001_streaming": "通用女声(12种情感)", + "BV002_streaming": "通用男声" }, "有声阅读": { - "zh_female_xiaoxue_moon_bigtts": "小雪(女声,温柔亲切)", - "zh_female_xiaoxin_common": "小新(女声,自然流畅)", - "zh_female_xiaomei_moon_bigtts": "小美(女声,甜美温柔)", - "zh_female_xiaoli_moon_bigtts": "小丽(女声,清晰标准)" + "BV701_streaming": "擎苍", + "BV123_streaming": "阳光青年", + "BV120_streaming": "反卷青年", + "BV119_streaming": "通用赘婿", + "BV115_streaming": "古风少御", + "BV107_streaming": "霸气青叔", + "BV100_streaming": "质朴青年", + "BV104_streaming": "温柔淑女", + "BV004_streaming": "开朗青年", + "BV113_streaming": "甜宠少御", + "BV102_streaming": "儒雅青年" }, "智能助手": { - "zh_female_xiaoxue_moon_bigtts": "小雪(女声,温柔亲切)", - "zh_male_xiaofeng_common": "小峰(男声,沉稳大气)", - "zh_female_xiaoxin_common": "小新(女声,自然流畅)", - "zh_male_xiaoyu_common": "小鱼(男声,年轻活力)" + "BV405_streaming": "甜美小源", + "BV007_streaming": "亲切女声", + "BV009_streaming": "知性女声", + "BV419_streaming": "诚诚", + "BV415_streaming": "童童", + "BV008_streaming": "亲切男声" }, "视频配音": { - "zh_male_xiaofeng_common": "小峰(男声,沉稳大气)", - "zh_female_xiaomei_moon_bigtts": "小美(女声,甜美温柔)", - "zh_female_xiaoli_moon_bigtts": "小丽(女声,清晰标准)", - "zh_male_xiaoyu_common": "小鱼(男声,年轻活力)" + "BV408_streaming": "译制片男声", + "BV426_streaming": "懒小羊", + "BV428_streaming": "清新文艺女声", + "BV403_streaming": "鸡汤女声", + "BV158_streaming": "智慧老者", + "BV157_streaming": "慈爱姥姥", + "BR001_streaming": "说唱小哥", + "BV410_streaming": "活力解说男", + "BV411_streaming": "影视解说小帅", + "BV437_streaming": "解说小帅-多情感", + "BV412_streaming": "影视解说小美", + "BV159_streaming": "纨绔青年", + "BV418_streaming": "直播一姐", + "BV142_streaming": "沉稳解说男", + "BV143_streaming": "潇洒青年", + "BV056_streaming": "阳光男声", + "BV005_streaming": "活泼女声", + "BV064_streaming": "小萝莉" }, "特色音色": { - "zh_female_xiaoxue_moon_bigtts": "小雪(女声,温柔亲切)", - "zh_female_xiaomei_moon_bigtts": "小美(女声,甜美温柔)" + "BV051_streaming": "奶气萌娃", + "BV063_streaming": "动漫海绵", + "BV417_streaming": "动漫海星", + "BV050_streaming": "动漫小新", + "BV061_streaming": "天才童声" }, "广告配音": { - "zh_male_xiaofeng_common": "小峰(男声,沉稳大气)", - "zh_female_xiaoli_moon_bigtts": "小丽(女声,清晰标准)" + "BV401_streaming": "促销男声", + "BV402_streaming": "促销女声", + "BV006_streaming": "磁性男声" }, "新闻播报": { - "zh_female_xiaoli_moon_bigtts": "小丽(女声,清晰标准)", - "zh_male_xiaofeng_common": "小峰(男声,沉稳大气)" + "BV011_streaming": "新闻女声", + "BV012_streaming": "新闻男声" }, "教育场景": { - "zh_female_xiaoxin_common": "小新(女声,自然流畅)", - "zh_male_xiaoyu_common": "小鱼(男声,年轻活力)" + "BV034_streaming": "知性姐姐-双语", + "BV033_streaming": "温柔小哥" } } diff --git a/dsLightRag/Util/__pycache__/GengerateAudio.cpython-310.pyc b/dsLightRag/Util/__pycache__/GengerateAudio.cpython-310.pyc index cb7f31b028d9d64b3551b79896302b54b003271a..eeff3114d31b3adfc5e8ad18b2c7909a94bbb836 100644 GIT binary patch delta 3737 zcmZ`+T~Hg>71pkFV#$^FW!}y>BRj@n|kOEGS!3hjp z;D$W#Kt2>eArygkyby|^1XjUnD1|a8hYDB&m9Q4pp=Uij09CL7Ho_*@JnjxVU<+(D zB-jQIB7Oz7Bkq8Q5Ub&4;q>Q=>Cb@p40+%O@KUcUkjFz_&i^5Q1@nId-O!@C z;YcN!o>e$p={cVqn>Huo z=EbWSI**R6Lw{<%FV+8>WbgdSL_9Tkt;TxCz=w3#e2G_o$N0j?WdA9AzARFBV;gdo zba#2cSDTy~vQG6`y%Tqr2V+I|8OgC5xajJ_KiRiz<~C>O=J2JJi)YNMXU$u03z6{! z>*^TA43BX`YHr#ZpRSw1G}l>dJY1!~wfJnY=v7X}SnZcc+_j z@Y@@a3`%ZNijq?Ye{Ny?xrI@bUG}tT7H&w_KUTt5c-9=4PY(8r)$p~{+S)djk0h&q z6w^-LIxXBb)mSOH7y7I>u3B&Orv?{A@**W$MS0#U3vXMqm&{3>t`eg7>g`fgW9yfT zR^L>r=VYpt7G7e>(7S2drnKQ>`alxiRjAp;j)((`k?y%;T z%+nWAQx}noi3^tP)bX^;_hwU5KNBQUTi`45*V$LZuQ)Cv7&Ydl^Wr8Fn)ZWBrQ(`; zMid*0h`xH;=JF(xi_^&^ej;zq38~ytB`?fbqwn5X=rN~8xQ5?88SSlgZ7?|<&&ty0 z$4$}i-ASUL@AX@AqvDv7j#6*metwzqD<;z*ioIEd#@vz>U(W2k(qKDYyoInlsgbu* zXXtv?u?~~-c;Tc*UN@&taRu}Aw6%QMx_%?MbUoW(otv;3PzQTW7%t6NgF|?z=#F=Z zrQ7z98yajqNCv+Tckh1wBF5GoBE}}dC%SX{)XG?o$lqqLAN-3a@!$(awUbYjdx`rq z_lYk4_$c;24_p^BTX}QIrYj6-U74@WXIq^x*tfk5w>^CeO^sk<)R@Dsr>?v!)cF;T z1>AuLqgtrd+4hO6)!7Ob-|JeDH&F*@c?4bp4?!^j-3(fZ@q}kvCFgKA_fYfAIJ62w z_cTdIb_dZxb9&IDRCW6fbTZ~R`RbdtVx|Lv?@eA)%6PF~lgcI-b1dGMO zkZ=+DHBw1sGK_x=M z6>5*PbR}Fz@H=W$dz?({NF>tEOQku9#|WMwc#hzCg2Mza5@b^0ztN^?^i7sZ6)nO= zdK^rWJ+dV05<=FCeIOTSTf`{a&OVYoDDfGSjx?ls{la^1ql@{wFJz|G`S+zq3{H-`HyT?nA7Uy)ReXn~;6XS0%AeWVbA_ zPh}UO`=d9)BaCUV?MJV-T zf>96IO2u27b+m*#D<$JkRa>{W63Y_|k|`LhF4JBn*{()b@s1yEOf= zkW!LH2tGDNsu~HTzDOmcv@Rvkw4eO(hxphEA)(rCghaj&LLlJYaUQk>#M(1=?mhS1 zxihnyUtYNTgxBzR>LmC(5x6z}+u(KYteiV8OA?bJGL<35eH9@zI9_86(cM$PgWdNFzuz zB^0`oJ`A&Fm?3AAhbjtvgzkc93vGN^3h%XM9r4^&*!#lbM=KYutuD_LF6Rpu7gv`* zC@w7&KbgD#^^L;J^y>22>>8_(hu}?PcR#FLcxi4PJmsyZ zSor;=JC&^B(wXA<#p0*ah3WUjx_LcpDfa->c93FvRR=w%$B$Qbn5+ti32#6pA3`I7 z7a**Z_0bEqU|yMVu0gUhm}}8%4ciEX{Z1b+@E)oT18_&(2&*N~vK+lX?!Z+3W{ot; zl~)2OCk=g+nLkTbL*FicFEab8A%N2@*3rH_IiaTD{85A+09#EYjabH3$yzX6Zcyab?0L{>CKRu0OFO#W0{VE(T~ z{!&z6R-yBrGL&BoTygYZdvLTh_osZPEgl?@U$V{zho8ny%I}F-`60|AwgWyvC zHo;S@=s;X9l8Wi+UnR>G>K_d>^fHc%&%zyAT1sRnHT diff --git a/dsLightRag/static/text-to-speech.html b/dsLightRag/static/text-to-speech.html index fa97a7d0..b003c697 100644 --- a/dsLightRag/static/text-to-speech.html +++ b/dsLightRag/static/text-to-speech.html @@ -158,6 +158,12 @@ min-height: 100px; } + .voice-options p { + color: #666; + text-align: center; + padding: 20px; + } + .voice-card { border: 1px solid #ddd; border-radius: 8px; @@ -165,12 +171,6 @@ cursor: pointer; transition: all 0.3s; } - - .voice-options p { - color: #666; - text-align: center; - padding: 20px; - } .voice-card:hover { border-color: #3498db; @@ -242,7 +242,7 @@
- +
@@ -321,105 +321,121 @@ // 当前选中的音色 let selectedVoiceType = null; - // API基础URL - const apiBaseUrl = '/api/VideoRetalk'; + // API基础URL - 使用完整URL + const apiBaseUrl = window.location.origin + '/api/tts'; + + // 显示错误信息 + function showError(message) { + errorMessage.textContent = message; + errorMessage.style.display = 'block'; + successMessage.style.display = 'none'; + } + + // 显示成功信息 + function showSuccess(message) { + successMessage.textContent = message; + successMessage.style.display = 'block'; + errorMessage.style.display = 'none'; + } + + // 隐藏所有消息 + function hideMessages() { + errorMessage.style.display = 'none'; + successMessage.style.display = 'none'; + } // 获取所有音色分类 async function loadVoiceCategories() { try { - const response = await fetch(`${apiBaseUrl}/voices/categories`); - const data = await response.json(); + console.log('正在加载音色分类,API地址:', `${apiBaseUrl}/voice-categories`); + const response = await fetch(`${apiBaseUrl}/voice-categories`); - if (data.success) { - // 清空现有选项 - categorySelect.innerHTML = ''; - - // 添加分类选项 + if (!response.ok) { + throw new Error(`API请求失败: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + console.log('音色分类数据:', data); + + // 清空下拉列表 + categorySelect.innerHTML = ''; + + // 检查返回数据结构 + if (data.success && data.data && Array.isArray(data.data) && data.data.length > 0) { data.data.forEach(category => { const option = document.createElement('option'); - option.value = category; + option.value = category; // 直接使用分类名称作为值 option.textContent = category; categorySelect.appendChild(option); }); } else { - showError('获取音色分类失败: ' + data.message); + showError('未能加载音色分类列表'); + console.error('音色分类数据格式不正确:', data); + } + + // 绑定分类变化事件(确保只绑定一次) + if (!categorySelect.dataset.eventBound) { + categorySelect.addEventListener('change', function() { + if (this.value) { + loadVoicesByCategory(this.value); + } else { + voiceOptions.innerHTML = '

请先选择音色分类

'; + selectedVoiceType = null; + } + }); + categorySelect.dataset.eventBound = 'true'; } } catch (error) { - showError('获取音色分类失败: ' + error.message); + console.error('加载音色分类失败:', error); + showError(`加载音色分类失败: ${error.message}`); } } - // 根据分类获取音色列表 - async function loadVoicesByCategory(category) { + // 加载指定分类的音色 + async function loadVoicesByCategory(categoryId) { try { - const response = await fetch(`${apiBaseUrl}/voices/by-category/${category}`); - const data = await response.json(); + console.log('正在加载音色列表,分类:', categoryId); + const response = await fetch(`${apiBaseUrl}/voices?category=${encodeURIComponent(categoryId)}`); - if (data.success) { - // 清空现有音色选项 - voiceOptions.innerHTML = ''; - - // 添加数据类型检查 - if (typeof data.data !== 'object' || data.data === null) { - showError('获取的音色列表格式不正确'); - return; - } - - // 将对象转换为数组格式 [{voice_type, name, description}, ...] - const voicesArray = Object.entries(data.data).map(([voiceType, description]) => { - // 从描述中提取名称和说明(假设格式为 "名称(说明)") - const match = description.match(/^(.*?)\((.*?)\)$/); - return { - voice_type: voiceType, - name: match ? match[1] : description, - description: match ? match[2] : '无描述' - }; - }); - - // 检查数组是否为空 - if (voicesArray.length === 0) { - voiceOptions.innerHTML = '

该分类下没有可用音色

'; - return; - } - - // 添加音色卡片 - voicesArray.forEach(voice => { + if (!response.ok) { + throw new Error(`API请求失败: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + console.log('音色列表数据:', data); + voiceOptions.innerHTML = ''; + + // 检查返回数据结构 + if (data.success && data.data && typeof data.data === 'object' && Object.keys(data.data).length > 0) { + Object.entries(data.data).forEach(([voiceId, voiceName]) => { const voiceCard = document.createElement('div'); voiceCard.className = 'voice-card'; - voiceCard.dataset.voiceType = voice.voice_type; + voiceCard.dataset.voiceType = voiceId; - const voiceName = document.createElement('div'); - voiceName.className = 'voice-name'; - voiceName.textContent = voice.name; + voiceCard.innerHTML = ` +
${voiceName}
+ `; - const voiceDescription = document.createElement('div'); - voiceDescription.className = 'voice-description'; - voiceDescription.textContent = voice.description || '暂无描述'; - - voiceCard.appendChild(voiceName); - voiceCard.appendChild(voiceDescription); - - // 添加点击事件 voiceCard.addEventListener('click', function() { - // 移除其他卡片的选中状态 + // 移除其他选中状态 document.querySelectorAll('.voice-card').forEach(card => { card.classList.remove('selected'); }); - - // 添加当前卡片的选中状态 + // 设置当前选中状态 this.classList.add('selected'); - - // 保存选中的音色类型 selectedVoiceType = this.dataset.voiceType; + console.log('已选择音色:', selectedVoiceType); }); voiceOptions.appendChild(voiceCard); }); } else { - voiceOptions.innerHTML = '

获取音色列表失败: ' + data.message + '

'; + voiceOptions.innerHTML = '

该分类下没有可用音色

'; + console.error('音色列表数据格式不正确:', data); } } catch (error) { - voiceOptions.innerHTML = '

获取音色列表失败: ' + error.message + '

'; + console.error('加载音色列表失败:', error); + showError('加载音色列表失败,请重试'); } } @@ -456,8 +472,10 @@ encoding: 'mp3' }; + console.log('正在生成音频,请求数据:', requestData); + // 发送请求 - const response = await fetch(`${apiBaseUrl}/tts`, { + const response = await fetch(`${apiBaseUrl}/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -466,6 +484,7 @@ }); const data = await response.json(); + console.log('音频生成结果:', data); if (data.success) { // 显示成功消息 @@ -477,61 +496,50 @@ // 设置下载按钮 downloadBtn.onclick = function() { - const a = document.createElement('a'); - a.href = data.audio_url; - a.download = 'tts_audio.mp3'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); + if (!data.audio_url) { + showError('没有找到音频文件'); + return; + } + + // 创建有意义的文件名 (使用文本前10个字符 + 时间戳) + const textPreview = textInput.value.trim().substring(0, 10).replace(/[^a-zA-Z0-9]/g, '_'); + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const fileName = `tts_${textPreview}_${timestamp}.mp3`; + + try { + const a = document.createElement('a'); + a.href = data.audio_url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } catch (error) { + console.error('下载失败:', error); + showError('下载失败,请重试'); + } }; } else { - showError('语音生成失败: ' + data.message); - emptyResult.style.display = 'block'; + showError(data.message || '语音生成失败'); } } catch (error) { - showError('语音生成失败: ' + error.message); - emptyResult.style.display = 'block'; + console.error('生成语音失败:', error); + showError('生成语音失败,请重试'); } finally { - // 隐藏加载状态 + // 恢复按钮状态 loading.classList.remove('active'); generateBtn.disabled = false; + + // 如果没有音频结果,显示空状态 + if (audioResult.style.display === 'none') { + emptyResult.style.display = 'block'; + } } } - // 显示错误消息 - function showError(message) { - errorMessage.textContent = message; - errorMessage.style.display = 'block'; - successMessage.style.display = 'none'; - } - - // 显示成功消息 - function showSuccess(message) { - successMessage.textContent = message; - successMessage.style.display = 'block'; - errorMessage.style.display = 'none'; - } - - // 隐藏所有消息 - function hideMessages() { - errorMessage.style.display = 'none'; - successMessage.style.display = 'none'; - } - - // 事件监听器 - categorySelect.addEventListener('change', function() { - const category = this.value; - if (category) { - loadVoicesByCategory(category); - } else { - voiceOptions.innerHTML = '

请先选择音色分类

'; - selectedVoiceType = null; - } - }); - + // 绑定生成按钮点击事件 generateBtn.addEventListener('click', generateAudio); - // 初始化 + // 初始化加载音色分类 loadVoiceCategories(); });