<!DOCTYPE html>
<html>
<head>
    <title>OpenAI TTS</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        textarea { width: 90%; height: 200px; margin-bottom: 10px; padding: 10px; }
        button { padding: 10px 20px; background-color: #007BFF; color: white; border: none; border-radius: 5px; cursor: pointer; }
        button:disabled { background-color: #cccccc; cursor: not-allowed; }
        #audioPlayer { margin-top: 10px; width: 90%; }
        #message { color: red; margin-top: 10px; }
        .config-panel {
            border: 1px solid #ccc;
            padding: 10px;
            margin-bottom: 20px;
            border-radius: 5px;
        }
        .config-panel label {
            display: block;
            margin-bottom: 5px;
        }
        .config-panel input, .config-panel select {
            width: 90%;
            padding: 8px;
            margin-bottom: 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }

    </style>
</head>
<body>
    <h1>OpenAI TTS</h1>

    <div class="config-panel">
        <h2>配置</h2>
        <label for="baseUrl">API基础URL:</label>
        <input type="text" id="baseUrl" value="https://api.siliconflow.cn">

        <label for="apiKey">API密钥:</label>
        <input type="text" id="apiKey">

        <label for="model">模型:</label>
        <select id="model">
            <option value="FunAudioLLM/CosyVoice2-0.5B">FunAudioLLM/CosyVoice2-0.5B</option>
            <option value="RVC-Boss/GPT-SoVITS">RVC-Boss/GPT-SoVITS</option>
            <option value="LoRA/RVC-Boss/GPT-SoVITS">LoRA/RVC-Boss/GPT-SoVITS</option>
        </select>

        <label for="voice">语音:</label>
        <select id="voice">
            <option value="speech:onyx:cm151jhbw0007srjw2455dz6y:pxpgizwsmaxttynlcqye">Onyx</option>
        </select>
    </div>

    <textarea id="textInput" placeholder="在此输入文本..."></textarea><br>
    <button id="generateButton">生成语音</button>
    <audio id="audioPlayer" controls></audio>
    <div id="message"></div>

    <script>
        const textInput = document.getElementById('textInput');
        const generateButton = document.getElementById('generateButton');
        const audioPlayer = document.getElementById('audioPlayer');
        const messageDiv = document.getElementById('message');
        let currentAudioUrl = null;

        const baseUrlInput = document.getElementById('baseUrl');
        const apiKeyInput = document.getElementById('apiKey');
        const modelSelect = document.getElementById('model');
        const voiceSelect = document.getElementById('voice');


        generateButton.addEventListener('click', () => {
            const text = textInput.value.trim();
            if (!text) {
                messageDiv.textContent = '请输入一些文本。';
                return;
            }

            messageDiv.textContent = '';
            generateButton.disabled = true;
            generateButton.textContent = '生成中...';

            const baseUrl = baseUrlInput.value;
            const apiKey = apiKeyInput.value;
            const voice = voiceSelect.value;
            const model = modelSelect.value;

            if (!apiKey || !baseUrl) {
                messageDiv.textContent = 'API密钥或基础URL未配置。';
                generateButton.disabled = false;
                generateButton.textContent = '生成语音';
                return;
            }

            callTTSApi(text, baseUrl, apiKey, voice, model, (error, audioUrl) => {
                generateButton.disabled = false;
                generateButton.textContent = '生成语音';
                if (error) {
                    messageDiv.textContent = error;
                } else {
                    if (currentAudioUrl) {
                        URL.revokeObjectURL(currentAudioUrl);
                    }
                    audioPlayer.src = audioUrl;
                    currentAudioUrl = audioUrl;
                    audioPlayer.play();
                }
            });
        });


        function callTTSApi(text, baseUrl, apiKey, voice, model, callback) {
            const url = `${baseUrl}/v1/audio/speech`;

            fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${apiKey}`
                },
                body: JSON.stringify({
                    model: model,
                    input: text,
                    voice: voice
                })
            })
            .then(response => {
                if (!response.ok) {
                    return response.text().then(text => {
                        throw new Error(`HTTP 错误!状态: ${response.status}, 响应: ${text}`);
                    });
                }
                return response.arrayBuffer();
            })
            .then(buffer => {
                const audioBlob = new Blob([buffer], { type: 'audio/mpeg' });
                const audioUrl = URL.createObjectURL(audioBlob);
                callback(null, audioUrl);
            })
            .catch(error => {
                console.error('Fetch 错误:', error);
                callback(`TTS API请求失败:${error.message}`);
            });
        }


        window.addEventListener('beforeunload', () => {
            if (currentAudioUrl) {
                URL.revokeObjectURL(currentAudioUrl);
            }
        });

    </script>
</body>
</html>

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.