diff --git a/README.md b/README.md
deleted file mode 100644
index c0f1fbe..0000000
--- a/README.md
+++ /dev/null
@@ -1,156 +0,0 @@
-# OpenAITTS配音台
-
-一个简洁高效的文本转语音工具,支持自定义OpenAI API地址,基于Go后端和浏览器前端实现。
-
-## 功能特点
-
-- ✨ 简洁大方的用户界面
-- 🔧 支持自定义API地址
-- 🎤 多种声音模型选择
-- 📱 响应式设计,适配各种设备
-- 💾 本地存储API配置
-- ⚡ 快速生成和播放语音
-- 📥 支持音频下载
-
-## 技术栈
-
-- **后端**: Go 1.18+
-- **前端**: HTML5 + CSS3 + JavaScript (ES6+)
-- **API**: OpenAI TTS API
-
-## 配置说明
-
-### 1. 配置文件方式
-
-复制 `config.example.json` 为 `config.json`,然后修改其中的配置:
-
-```json
-{
- "api_base_url": "https://your-api-address.com",
- "port": "8080"
-}
-```
-
-### 2. 环境变量方式
-
-你也可以通过环境变量来配置:
-
-```bash
-export OPENAI_API_BASE_URL="https://your-api-address.com"
-export PORT="8080"
-```
-
-### 3. 前端直接配置
-
-在浏览器界面中,你可以直接输入API Key和API Base URL,这些配置会自动保存到浏览器本地存储中。
-
-## 快速开始
-
-### 前提条件
-
-- 已安装Go 1.18或更高版本
-- 有可用的OpenAI API Key
-- 自定义API地址(如果需要)
-
-### 启动服务
-
-在项目根目录下执行:
-
-```bash
-go run main.go
-```
-
-或者使用配置文件:
-
-```bash
-go run main.go -config=config.json
-```
-
-服务启动后,会在控制台输出:
-
-```
-2024/01/01 12:00:00 Server starting on port 8080...
-2024/01/01 12:00:00 Using API base URL: https://api.openai.com
-2024/01/01 12:00:00 Open http://localhost:8080 in your browser
-```
-
-### 使用方法
-
-1. 在浏览器中打开 `http://localhost:8080`
-2. 在API配置区域输入你的API Key和自定义API地址
-3. 在文本输入区域输入要转换为语音的文本
-4. 选择模型和声音参数
-5. 点击"生成语音"按钮
-6. 生成完成后,可以播放或下载音频
-
-## 支持的声音模型
-
-- **tts-1**: 标准模型,速度快
-- **tts-1-hd**: 高清模型,质量更高
-
-## 支持的声音类型
-
-- **alloy**: 清晰自然的声音
-- **echo**: 沉稳有力的声音
-- **fable**: 温暖亲切的声音
-- **onyx**: 成熟稳重的声音
-- **nova**: 活泼开朗的声音
-- **shimmer**: 柔美动听的声音
-
-## 项目结构
-
-```
-.
-├── main.go # Go后端主程序
-├── index.html # 前端HTML页面
-├── styles.css # 前端CSS样式
-├── app.js # 前端JavaScript
-├── config.example.json # 配置文件示例
-└── README.md # 项目说明文档
-```
-
-## API端点
-
-### POST /api/tts
-
-生成语音的API端点,请求体格式:
-
-```json
-{
- "api_key": "sk-...",
- "text": "要转换的文本",
- "voice": "alloy",
- "model": "tts-1-hd",
- "api_base": "https://your-api-address.com"
-}
-```
-
-响应:
-- 成功:返回MP3音频文件(Content-Type: audio/mpeg)
-- 失败:返回JSON格式的错误信息
-
-## 注意事项
-
-1. 请确保你的API Key有效且有足够的配额
-2. 自定义API地址需要兼容OpenAI TTS API的接口格式
-3. 文本长度建议不超过4096个字符
-4. 生成的音频文件会自动下载到你的默认下载目录
-
-## 浏览器支持
-
-- Chrome 60+
-- Firefox 55+
-- Safari 12+
-- Edge 79+
-
-## 许可证
-
-MIT License
-
-## 贡献
-
-欢迎提交Issue和Pull Request!
-
-## 联系方式
-
-如有问题或建议,请通过GitHub Issues反馈。
diff --git a/app.js b/app.js
deleted file mode 100644
index 1cafae0..0000000
--- a/app.js
+++ /dev/null
@@ -1,221 +0,0 @@
-// DOM元素获取
-const apiKeyInput = document.getElementById('apiKey');
-const apiBaseInput = document.getElementById('apiBase');
-const textInput = document.getElementById('textInput');
-const charCount = document.getElementById('charCount');
-const modelSelect = document.getElementById('model');
-const voiceSelect = document.getElementById('voice');
-const generateBtn = document.getElementById('generateBtn');
-const playBtn = document.getElementById('playBtn');
-const downloadBtn = document.getElementById('downloadBtn');
-const statusDiv = document.getElementById('status');
-const audioPlayer = document.getElementById('audioPlayer');
-
-// 当前生成的音频URL
-let currentAudioURL = null;
-
-// 初始化函数
-function init() {
- // 加载本地存储的配置
- loadConfig();
-
- // 绑定事件监听器
- bindEventListeners();
-
- // 初始状态更新
- updateCharCount();
-}
-
-// 加载本地存储的配置
-function loadConfig() {
- const savedAPIKey = localStorage.getItem('openai_api_key');
- const savedAPIBase = localStorage.getItem('openai_api_base');
-
- if (savedAPIKey) {
- apiKeyInput.value = savedAPIKey;
- }
-
- if (savedAPIBase) {
- apiBaseInput.value = savedAPIBase;
- }
-}
-
-// 保存配置到本地存储
-function saveConfig() {
- localStorage.setItem('openai_api_key', apiKeyInput.value);
- localStorage.setItem('openai_api_base', apiBaseInput.value);
-}
-
-// 绑定事件监听器
-function bindEventListeners() {
- // 文本输入事件 - 更新字符计数
- textInput.addEventListener('input', updateCharCount);
-
- // API配置输入事件 - 保存到本地存储
- apiKeyInput.addEventListener('input', saveConfig);
- apiBaseInput.addEventListener('input', saveConfig);
-
- // 生成语音按钮点击事件
- generateBtn.addEventListener('click', generateSpeech);
-
- // 播放按钮点击事件
- playBtn.addEventListener('click', togglePlay);
-
- // 下载按钮点击事件
- downloadBtn.addEventListener('click', downloadAudio);
-
- // 音频播放结束事件
- audioPlayer.addEventListener('ended', onAudioEnded);
-}
-
-// 更新字符计数
-function updateCharCount() {
- const count = textInput.value.length;
- charCount.textContent = count;
-}
-
-// 生成语音
-async function generateSpeech() {
- // 验证输入
- if (!validateInput()) {
- return;
- }
-
- // 保存配置
- saveConfig();
-
- // 更新状态
- setStatus('loading', '正在生成语音...');
- disableControls();
-
- try {
- // 构建请求数据
- const requestData = {
- api_key: apiKeyInput.value,
- text: textInput.value,
- voice: voiceSelect.value,
- model: modelSelect.value,
- api_base: apiBaseInput.value
- };
-
- // 发送请求到后端API
- const response = await fetch('/api/tts', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(requestData)
- });
-
- if (!response.ok) {
- const errorData = await response.json().catch(() => ({
- error: `HTTP错误: ${response.status} ${response.statusText}`
- }));
- throw new Error(errorData.error || '生成语音失败');
- }
-
- // 获取音频Blob
- const audioBlob = await response.blob();
-
- // 创建音频URL
- if (currentAudioURL) {
- URL.revokeObjectURL(currentAudioURL);
- }
- currentAudioURL = URL.createObjectURL(audioBlob);
-
- // 更新音频播放器
- audioPlayer.src = currentAudioURL;
-
- // 更新状态
- setStatus('success', '语音生成成功!');
- enableControls();
-
- } catch (error) {
- // 处理错误
- setStatus('error', `生成失败: ${error.message}`);
- disableControls();
- }
-}
-
-// 验证输入
-function validateInput() {
- if (!apiKeyInput.value.trim()) {
- setStatus('error', '请输入API Key');
- apiKeyInput.focus();
- return false;
- }
-
- if (!textInput.value.trim()) {
- setStatus('error', '请输入要转换的文本');
- textInput.focus();
- return false;
- }
-
- return true;
-}
-
-// 切换播放状态
-function togglePlay() {
- if (audioPlayer.paused) {
- audioPlayer.play();
- playBtn.textContent = '暂停';
- } else {
- audioPlayer.pause();
- playBtn.textContent = '播放';
- }
-}
-
-// 音频播放结束
-function onAudioEnded() {
- playBtn.textContent = '播放';
-}
-
-// 下载音频
-function downloadAudio() {
- if (!currentAudioURL) {
- return;
- }
-
- // 创建下载链接
- const link = document.createElement('a');
- link.href = currentAudioURL;
- link.download = 'speech.mp3';
-
- // 触发下载
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
-}
-
-// 设置状态
-function setStatus(type, message) {
- statusDiv.className = `status ${type}`;
- statusDiv.textContent = message;
-
- // 清除成功状态
- if (type === 'success') {
- setTimeout(() => {
- statusDiv.className = 'status';
- statusDiv.textContent = '';
- }, 3000);
- }
-}
-
-// 禁用控件
-function disableControls() {
- generateBtn.disabled = true;
- playBtn.disabled = true;
- downloadBtn.disabled = true;
- generateBtn.classList.add('loading');
-}
-
-// 启用控件
-function enableControls() {
- generateBtn.disabled = false;
- playBtn.disabled = false;
- downloadBtn.disabled = false;
- generateBtn.classList.remove('loading');
-}
-
-// 页面加载完成后初始化
-document.addEventListener('DOMContentLoaded', init);
diff --git a/config.example.json b/config.example.json
deleted file mode 100644
index a03bb7a..0000000
--- a/config.example.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "api_base_url": "https://api.openai.com",
- "port": "8080"
-}
diff --git a/index.html b/index.html
deleted file mode 100644
index aae6241..0000000
--- a/index.html
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
-
-
- OpenAITTS配音台
-
-
-
-
-
- OpenAITTS配音台
- 简洁高效的文本转语音工具
-
-
-
-
-
-
-
-
-
-
-
- 语音参数
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/main.go b/main.go
deleted file mode 100644
index 3117508..0000000
--- a/main.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package main
-
-import (
- "bytes"
- "encoding/json"
- "flag"
- "io"
- "log"
- "net/http"
- "os"
- "strings"
-)
-
-// 配置结构体
-type Config struct {
- APIBaseURL string `json:"api_base_url"`
- Port string `json:"port"`
-}
-
-// 从配置文件加载配置
-func loadConfig() Config {
- config := Config{
- APIBaseURL: "https://api.openai.com", // 默认API地址
- Port: "8080", // 默认端口
- }
-
- // 尝试读取配置文件
- if _, err := os.Stat("config.json"); err == nil {
- data, err := os.ReadFile("config.json")
- if err == nil {
- json.Unmarshal(data, &config)
- }
- }
-
- // 环境变量覆盖配置文件
- if envURL := os.Getenv("OPENAI_API_BASE_URL"); envURL != "" {
- config.APIBaseURL = envURL
- }
- if envPort := os.Getenv("PORT"); envPort != "" {
- config.Port = envPort
- }
-
- return config
-}
-
-// TTS请求结构体
-type TTSRequest struct {
- APIKey string `json:"api_key"`
- Text string `json:"text"`
- Voice string `json:"voice"`
- Model string `json:"model"`
- APIBase string `json:"api_base"`
-}
-
-// OpenAI TTS API请求体
-type openAITTSRequest struct {
- Model string `json:"model"`
- Input string `json:"input"`
- Voice string `json:"voice"`
-}
-
-func main() {
- // 命令行参数
- var configPath string
- flag.StringVar(&configPath, "config", "config.json", "配置文件路径")
- flag.Parse()
-
- config := loadConfig()
-
- // 静态文件服务
- http.Handle("/", http.FileServer(http.Dir(".")))
-
- // TTS API端点
- http.HandleFunc("/api/tts", func(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodPost {
- http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
- return
- }
-
- // 解析请求体
- var req TTSRequest
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, "Invalid request body", http.StatusBadRequest)
- return
- }
-
- // 使用用户提供的API地址,如果没有则使用配置中的
- apiBaseURL := config.APIBaseURL
- if req.APIBase != "" {
- apiBaseURL = req.APIBase
- }
-
- // 构建OpenAI TTS API请求
- openAIReq := openAITTSRequest{
- Model: req.Model,
- Input: req.Text,
- Voice: req.Voice,
- }
-
- reqBody, err := json.Marshal(openAIReq)
- if err != nil {
- http.Error(w, "Failed to marshal request", http.StatusInternalServerError)
- return
- }
-
- // 创建HTTP客户端
- client := &http.Client{}
- openAIReq, err := http.NewRequest("POST", apiBaseURL+"/v1/audio/speech", bytes.NewBuffer(reqBody))
- if err != nil {
- http.Error(w, "Failed to create request", http.StatusInternalServerError)
- return
- }
-
- // 设置请求头
- openAIReq.Header.Set("Content-Type", "application/json")
- openAIReq.Header.Set("Authorization", "Bearer "+req.APIKey)
-
- // 发送请求
- resp, err := client.Do(openAIReq)
- if err != nil {
- http.Error(w, "Failed to call OpenAI API: "+err.Error(), http.StatusInternalServerError)
- return
- }
- defer resp.Body.Close()
-
- // 检查响应状态
- if resp.StatusCode != http.StatusOK {
- errBody, _ := io.ReadAll(resp.Body)
- http.Error(w, "OpenAI API error: "+string(errBody), resp.StatusCode)
- return
- }
-
- // 设置响应头
- w.Header().Set("Content-Type", "audio/mpeg")
- w.Header().Set("Content-Disposition", "attachment; filename=speech.mp3")
-
- // 将响应体写入客户端
- io.Copy(w, resp.Body)
- })
-
- log.Printf("Server starting on port %s...", config.Port)
- log.Printf("Using API base URL: %s", config.APIBaseURL)
- log.Printf("Open http://localhost:%s in your browser", config.Port)
-
- // 启动服务器
- if err := http.ListenAndServe(":"+config.Port, nil); err != nil {
- log.Fatal("Server failed to start: ", err)
- }
-}
diff --git a/styles.css b/styles.css
deleted file mode 100644
index a59b4f8..0000000
--- a/styles.css
+++ /dev/null
@@ -1,344 +0,0 @@
-/* 基础样式重置 */
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-
-/* 全局样式 */
-body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
- line-height: 1.6;
- color: #333;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- min-height: 100vh;
-}
-
-/* 容器样式 */
-.container {
- max-width: 800px;
- margin: 0 auto;
- padding: 20px;
- background: rgba(255, 255, 255, 0.95);
- border-radius: 16px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- margin-top: 20px;
- margin-bottom: 20px;
-}
-
-/* 头部样式 */
-header {
- text-align: center;
- margin-bottom: 30px;
- padding-bottom: 20px;
- border-bottom: 2px solid #e0e0e0;
-}
-
-header h1 {
- color: #2d3748;
- font-size: 2.5rem;
- font-weight: 700;
- margin-bottom: 10px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
-}
-
-.subtitle {
- color: #718096;
- font-size: 1.1rem;
- font-weight: 400;
-}
-
-/* 主要内容区域 */
-main {
- display: flex;
- flex-direction: column;
- gap: 25px;
-}
-
-/* 各部分通用样式 */
-section {
- background: #f8fafc;
- padding: 25px;
- border-radius: 12px;
- border: 1px solid #e2e8f0;
- transition: all 0.3s ease;
-}
-
-section:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
-}
-
-section h2 {
- color: #2d3748;
- font-size: 1.5rem;
- font-weight: 600;
- margin-bottom: 20px;
- display: flex;
- align-items: center;
- gap: 10px;
-}
-
-section h2::before {
- content: '';
- width: 4px;
- height: 24px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- border-radius: 2px;
-}
-
-/* 配置网格布局 */
-.config-grid,
-.params-grid,
-.controls-grid {
- display: grid;
- grid-template-columns: 1fr;
- gap: 20px;
-}
-
-@media (min-width: 600px) {
- .config-grid,
- .params-grid,
- .controls-grid {
- grid-template-columns: repeat(2, 1fr);
- }
-}
-
-/* 表单组样式 */
-.form-group {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.form-group label {
- font-weight: 500;
- color: #4a5568;
- font-size: 0.95rem;
-}
-
-/* 表单输入样式 */
-.form-input,
-.form-select {
- padding: 12px 16px;
- border: 2px solid #e2e8f0;
- border-radius: 8px;
- font-size: 1rem;
- transition: all 0.3s ease;
- background: white;
- width: 100%;
-}
-
-.form-input:focus,
-.form-select:focus {
- outline: none;
- border-color: #667eea;
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
-}
-
-.form-input::placeholder {
- color: #a0aec0;
-}
-
-/* 文本输入区域 */
-.text-input-container {
- display: flex;
- flex-direction: column;
- gap: 10px;
-}
-
-#textInput {
- padding: 16px;
- border: 2px solid #e2e8f0;
- border-radius: 8px;
- font-size: 1rem;
- font-family: inherit;
- resize: vertical;
- min-height: 150px;
- transition: all 0.3s ease;
- background: white;
-}
-
-#textInput:focus {
- outline: none;
- border-color: #667eea;
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
-}
-
-.text-info {
- text-align: right;
- color: #718096;
- font-size: 0.9rem;
-}
-
-/* 按钮样式 */
-.btn {
- padding: 12px 24px;
- border: none;
- border-radius: 8px;
- font-size: 1rem;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.3s ease;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- text-decoration: none;
-}
-
-.btn-primary {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
-}
-
-.btn-primary:hover:not(:disabled) {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
-}
-
-.btn-secondary {
- background: #e2e8f0;
- color: #4a5568;
-}
-
-.btn-secondary:hover:not(:disabled) {
- background: #cbd5e0;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
-}
-
-.btn:disabled {
- opacity: 0.6;
- cursor: not-allowed;
- transform: none;
-}
-
-/* 控制区域 */
-.controls-section {
- display: flex;
- flex-direction: column;
- gap: 15px;
-}
-
-.status {
- padding: 12px 16px;
- border-radius: 8px;
- font-size: 0.95rem;
- font-weight: 500;
- text-align: center;
- min-height: 44px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.status.success {
- background: #c6f6d5;
- color: #22543d;
- border: 1px solid #9ae6b4;
-}
-
-.status.error {
- background: #fed7d7;
- color: #742a2a;
- border: 1px solid #feb2b2;
-}
-
-.status.loading {
- background: #ebf8ff;
- color: #2a4365;
- border: 1px solid #bee3f8;
-}
-
-/* 音频播放区域 */
-.audio-player-container {
- display: flex;
- justify-content: center;
- padding: 20px 0;
-}
-
-.audio-player {
- width: 100%;
- max-width: 100%;
- height: 50px;
- background: white;
- border-radius: 25px;
- border: 2px solid #e2e8f0;
- transition: all 0.3s ease;
-}
-
-.audio-player:focus {
- outline: none;
- border-color: #667eea;
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
-}
-
-/* 页脚样式 */
-footer {
- text-align: center;
- margin-top: 30px;
- padding-top: 20px;
- border-top: 2px solid #e0e0e0;
- color: #718096;
- font-size: 0.9rem;
-}
-
-/* 响应式设计 */
-@media (max-width: 600px) {
- .container {
- margin: 10px;
- padding: 15px;
- }
-
- header h1 {
- font-size: 2rem;
- }
-
- section {
- padding: 20px;
- }
-
- .btn {
- padding: 10px 20px;
- font-size: 0.95rem;
- }
-}
-
-/* 动画效果 */
-@keyframes fadeIn {
- from {
- opacity: 0;
- transform: translateY(10px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-section {
- animation: fadeIn 0.5s ease-out;
-}
-
-/* 加载动画 */
-.loading::after {
- content: '';
- display: inline-block;
- width: 16px;
- height: 16px;
- border: 2px solid rgba(255, 255, 255, 0.3);
- border-radius: 50%;
- border-top-color: #fff;
- animation: spin 1s ease-in-out infinite;
- margin-left: 10px;
-}
-
-@keyframes spin {
- to { transform: rotate(360deg); }
-}