ChatTTS-Forge / playground /ssml.page.mjs
zhzluke96
update
01e655b
import { client } from "./client.mjs";
import { html, create, styled } from "./misc.mjs";
const default_ssml = `
<speak version="0.1">
<voice spk="Bob" seed="-1" style="narration-relaxed">
这里是一个简单的 SSML 示例。
</voice>
</speak>
`.trim();
const useStore = create((set, get) => ({
params: {
ssml: default_ssml,
format: "mp3",
},
setParams: (params) => set({ params }),
loading: false,
/**
* @type {Array<{ id: number, params: { ssml: string; format: string }, url: string }>}
*/
history: [],
setHistory: (history) => set({ history }),
}));
const SSMLFormContainer = styled.div`
display: flex;
flex-direction: column;
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;
}
.ssml-body {
display: flex;
gap: 1rem;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
padding: 0.5rem;
border: 1px solid #333;
}
th {
background-color: #333;
color: white;
}
.btn-danger {
background-color: #dc3545;
color: white;
border: none;
cursor: pointer;
}
.btn-danger:hover {
background-color: #bd2130;
}
`;
const SSMLOptions = () => {
const { params, setParams } = useStore();
return html`
<fieldset style="flex: 2">
<legend>Options</legend>
<label>
Format
<select
value=${params.format}
onChange=${(e) => setParams({ ...params, format: e.target.value })}
>
<option value="mp3">MP3</option>
<option value="wav">WAV</option>
</select>
</label>
</fieldset>
`;
};
const SSMLHistory = () => {
const { history } = useStore();
return html`
<fieldset style="flex: 5">
<legend>History</legend>
<table>
<thead>
<tr>
<th>index</th>
<th>SSML</th>
<th>Audio</th>
</tr>
</thead>
<tbody>
${[...history].reverse().map(
(item) => html`
<tr key=${item.id}>
<td>${item.id}</td>
<td>
<textarea
readonly
style="width: 100%; height: 5rem; resize: none;"
>
${item.params.ssml}
</textarea
>
</td>
<td>
<audio controls>
<source
src=${item.url}
type="audio/${item.params.format}"
/>
</audio>
</td>
</tr>
`
)}
</tbody>
</table>
</fieldset>
`;
};
let generate_index = 0;
const SSMLForm = () => {
const { params, setParams, loading } = useStore();
const request = async () => {
useStore.set({ loading: true });
try {
const blob = await client.synthesizeSSML(params);
const blob_url = URL.createObjectURL(blob);
useStore.set({
history: [
...useStore.get().history,
{
id: generate_index++,
params,
url: blob_url,
},
],
});
} finally {
useStore.set({ loading: false });
}
};
return html`
<${SSMLFormContainer}>
<textarea
placeholder="Enter SSML here..."
value=${params.ssml}
onInput=${(e) => setParams({ ...params, ssml: e.target.value })}
/>
<div>
<button onClick=${request} disabled=${!params.ssml || loading}>
Submit
</button>
<button
class="btn btn-danger"
onClick=${() => {
useStore.set({ history: [] });
}}
disabled=${loading}
>
Clear History
</button>
</div>
<div class="ssml-body">
<${SSMLOptions} />
<${SSMLHistory} />
</div>
<//>
`;
};
const SSMLPageContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
export const SSMLPage = () => {
return html` <${SSMLPageContainer}>
<${SSMLForm} />
<//>`;
};