import { client } from "./client.mjs"; import { html, create, styled } from "./misc.mjs"; const sample_texts = [ { text: "天气预报显示,今天会有小雨,请大家出门时记得带伞。降温的天气也提醒我们要适时添衣保暖。", }, { text: "公司的年度总结会议将在下周三举行,请各部门提前准备好相关材料,确保会议顺利进行。", }, { text: "今天的午餐菜单包括烤鸡、沙拉和蔬菜汤,大家可以根据自己的口味选择适合的菜品。", }, { text: "请注意,电梯将在下午两点进行例行维护,预计需要一个小时的时间,请大家在此期间使用楼梯。", }, { text: "图书馆新到了一批书籍,涵盖了文学、科学和历史等多个领域,欢迎大家前来借阅。", }, ]; let history_index = 0; const useStore = create((set, get) => ({ tts: { text: "你好,这里是一段ChatTTS Forge项目的示例文本。", spk: "female2", style: "chat", temperature: 0.3, top_P: 1, top_K: 20, seed: -1, format: "mp3", prompt1: "", prompt2: "", prefix: "", }, styles: [], speakers: [], ui: { loading: false, // 历史生成结果 { audio: Blob, url: string, params: object } history: [], }, async synthesizeTTS() { const params = structuredClone(get().tts); const blob = await client.synthesizeTTS({ ...params, }); const blob_url = URL.createObjectURL(blob); set({ ui: { ...get().ui, history: [ ...get().ui.history, { id: history_index++, audio: blob, url: blob_url, params: params, }, ], }, }); }, setStyles(styles) { set({ styles }); }, setSpeakers(speakers) { set({ speakers }); }, setTTS(tts) { set({ tts: { ...get().tts, ...tts, }, }); }, setUI(ui) { set({ ui: { ...get().ui, ...ui, }, }); }, })); window.addEventListener("load", async () => { const styles = await client.listStyles(); const speakers = await client.listSpeakers(); console.log("styles:", styles); console.log("speakers:", speakers); useStore.get().setStyles(styles.data); useStore.get().setSpeakers(speakers.data); }); const TTSPageContainer = styled.div` h1 { margin-bottom: 1rem; } p { margin-bottom: 1rem; } #app { margin-top: 1rem; } textarea { width: 100%; height: 10rem; margin-bottom: 1rem; min-height: 10rem; resize: vertical; } button { padding: 0.5rem 1rem; background-color: #007bff; color: white; border: none; cursor: pointer; } button:hover { background-color: #0056b3; } button:disabled { background-color: #6c757d; cursor: not-allowed; } fieldset { margin-top: 1rem; padding: 1rem; border: 1px solid #333; } legend { font-weight: bold; } label { display: block; margin-bottom: 0.5rem; } select, input[type="range"], input[type="number"] { width: 100%; margin-top: 0.25rem; } input[type="range"] { width: calc(100% - 2rem); } input[type="number"] { width: calc(100% - 2rem); padding: 0.5rem; } input[type="text"] { width: 100%; padding: 0.5rem; } audio { margin-top: 1rem; } textarea, input, select { background-color: #333; color: white; border: 1px solid #333; border-radius: 0.25rem; padding: 0.5rem; } table { width: 100%; border-collapse: collapse; } th, td { padding: 0.5rem; border: 1px solid #333; } th { background-color: #333; color: white; } th:nth-child(2), td:nth-child(2) { width: 60%; } .content-body { display: flex; gap: 1rem; } .content-left { flex: 1; } .content-right { flex: 4; } h1 small { font-weight: 100; font-size: 0.5em; font-weight: normal; } .btn-synthesize { background-color: #007bff; color: white; border: none; cursor: pointer; padding: 0.5rem 1rem; } .btn-synthesize:hover { background-color: #0056b3; } .btn-synthesize:disabled { background-color: #6c757d; cursor: not-allowed; } .btn-clear { background-color: #dc3545; color: white; border: none; cursor: pointer; padding: 0.5rem 1rem; } .btn-clear:hover { background-color: #bd2130; } .btn-clear:disabled { background-color: #6c757d; cursor: not-allowed; } .btn-random { background-color: #28a745; color: white; border: none; cursor: pointer; padding: 0.5rem 1rem; } .btn-random:hover { background-color: #218838; } pre { white-space: pre-wrap; } .sample-texts { width: unset; display: inline-block; padding: 0.5rem; margin-bottom: 1rem; } `; export const TTSPage = () => { const { tts, setTTS, synthesizeTTS, ui, setUI, speakers, styles } = useStore(); const request = async () => { if (ui.loading) { return; } setUI({ loading: true }); try { await synthesizeTTS(); } catch (error) { console.error("Error synthesizing TTS:", error); } finally { setUI({ loading: false }); } }; return html` <${TTSPageContainer}> <textarea value=${tts.text} onInput=${(e) => setTTS({ text: e.target.value })} ></textarea> <button class="btn-synthesize" disabled=${ui.loading} onClick=${request}> ${ui.loading ? "Synthesizing..." : "Synthesize"} </button> <button class="btn-clear" disabled=${ui.loading} onClick=${() => setUI({ history: [] })} > Clear History </button> <select placeholder="Sample Text" class="sample-texts" value=${tts.text} onChange=${(e) => setTTS({ text: e.target.value })} > ${sample_texts.map( (item, index) => html` <option key=${index} value=${item.text}> Sample ${index + 1}: ${item.text.slice(0, 10) + "..."} </option> ` )} </select> <div class="content-body"> <fieldset class="content-left"> <legend>Options</legend> <label> Speaker: <select value=${tts.spk} onChange=${(e) => setTTS({ spk: e.target.value })} > <option value="-1">*random</option> ${speakers.map( (spk) => html` <option key=${spk.index} value=${spk.name}> ${spk.name} </option> ` )} </select> </label> <label> Style: <select value=${tts.style} onChange=${(e) => setTTS({ style: e.target.value })} > <option value="">*auto</option> ${styles.map( (style) => html` <option key=${style.id} value=${style.name}> ${style.name} </option> ` )} </select> </label> <label> Temperature: <input type="range" min="0.01" max="2" step="0.01" value=${tts.temperature} onInput=${(e) => setTTS({ temperature: e.target.value })} /> ${tts.temperature} </label> <label> Top P: <input type="range" min="0.01" max="1" step="0.01" value=${tts.top_P} onInput=${(e) => setTTS({ top_P: e.target.value })} /> ${tts.top_P} </label> <label> Top K: <input type="range" min="1" max="50" step="1" value=${tts.top_K} onInput=${(e) => setTTS({ top_K: e.target.value })} /> ${tts.top_K} </label> <label> Seed: <input type="number" value=${tts.seed} onInput=${(e) => setTTS({ seed: e.target.value })} /> <button class="btn-random" onClick=${() => setTTS({ seed: Math.floor(Math.random() * 2 ** 32 - 1) })} > Random </button> </label> <label> Format <select value=${tts.format} onChange=${(e) => setTTS({ format: e.target.value })} > <option value="mp3">MP3</option> <option value="wav">WAV</option> </select> </label> <label> Prompt1: <input type="text" value=${tts.prompt1} onInput=${(e) => setTTS({ prompt1: e.target.value })} /> </label> <label> Prompt2: <input type="text" value=${tts.prompt2} onInput=${(e) => setTTS({ prompt2: e.target.value })} /> </label> <label> Prefix: <input type="text" value=${tts.prefix} onInput=${(e) => setTTS({ prefix: e.target.value })} /> </label> </fieldset> <fieldset class="content-right"> <legend>History</legend> <table> <thead> <tr> <th>id</th> <th>Params</th> <th>Audio</th> </tr> </thead> <tbody> ${[...ui.history].reverse().map( (item, index) => html` <tr key=${item.id}> <td>${item.id}</td> <td> <pre>${JSON.stringify(item.params, null, 2)}</pre> </td> <td> <audio controls> <source src=${item.url} type="audio/${item.params.format}" /> </audio> </td> </tr> ` )} </tbody> </table> </fieldset> </div> <//> `; };