wwqg commited on
Commit
add9edd
·
verified ·
1 Parent(s): e89dc48

你这api设置里面只有api站点和一个密钥,数量对不上,而且也没有具体调用模型的设置 - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +975 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Ai
3
- emoji: 🔥
4
- colorFrom: blue
5
- colorTo: yellow
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: ai
3
+ emoji: 🐳
4
+ colorFrom: green
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,975 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI语音播客工作室</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ /* 自定义CSS补充Tailwind */
11
+ .waveform {
12
+ background: linear-gradient(90deg, #3b82f6 0%, #3b82f6 var(--progress, 0%), #e5e7eb var(--progress, 0%), #e5e7eb 100%);
13
+ }
14
+
15
+ .voice-option:hover .play-sample {
16
+ display: block;
17
+ }
18
+
19
+ .fade-in {
20
+ animation: fadeIn 0.3s ease-in;
21
+ }
22
+
23
+ @keyframes fadeIn {
24
+ from { opacity: 0; }
25
+ to { opacity: 1; }
26
+ }
27
+
28
+ /* 自定义滚动条 */
29
+ ::-webkit-scrollbar {
30
+ width: 8px;
31
+ height: 8px;
32
+ }
33
+
34
+ ::-webkit-scrollbar-track {
35
+ background: #f1f1f1;
36
+ }
37
+
38
+ ::-webkit-scrollbar-thumb {
39
+ background: #888;
40
+ border-radius: 4px;
41
+ }
42
+
43
+ ::-webkit-scrollbar-thumb:hover {
44
+ background: #555;
45
+ }
46
+ </style>
47
+ </head>
48
+ <body class="bg-gray-50 text-gray-800 min-h-screen flex flex-col">
49
+ <!-- 导航栏 -->
50
+ <nav class="bg-indigo-600 text-white shadow-lg">
51
+ <div class="container mx-auto px-4 py-3 flex justify-between items-center">
52
+ <div class="flex items-center space-x-2">
53
+ <i class="fas fa-microphone-alt text-2xl"></i>
54
+ <h1 class="text-xl font-bold">AI语音播客工作室</h1>
55
+ </div>
56
+ <div class="hidden md:flex space-x-6">
57
+ <a href="#" class="hover:text-indigo-200 transition">首页</a>
58
+ <a href="#" class="hover:text-indigo-200 transition">教程</a>
59
+ <a href="#" class="hover:text-indigo-200 transition">示例</a>
60
+ <a href="#" class="hover:text-indigo-200 transition">关于</a>
61
+ </div>
62
+ <button class="md:hidden text-xl">
63
+ <i class="fas fa-bars"></i>
64
+ </button>
65
+ </div>
66
+ </nav>
67
+
68
+ <!-- 主内容区 -->
69
+ <main class="flex-grow container mx-auto px-4 py-8">
70
+ <div class="max-w-6xl mx-auto">
71
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
72
+ <!-- 左侧输入和设置面板 -->
73
+ <div class="lg:col-span-2 space-y-6">
74
+ <!-- 文本输入区 -->
75
+ <div class="bg-white rounded-xl shadow-md overflow-hidden">
76
+ <div class="bg-indigo-50 px-4 py-3 border-b border-gray-200 flex justify-between items-center">
77
+ <h2 class="font-semibold text-indigo-700">输入文本</h2>
78
+ <div class="flex space-x-2">
79
+ <button id="clear-text" class="text-gray-500 hover:text-gray-700 transition">
80
+ <i class="fas fa-trash-alt"></i> 清空
81
+ </button>
82
+ <button id="sample-text" class="text-indigo-600 hover:text-indigo-800 transition">
83
+ <i class="fas fa-lightbulb"></i> 示例
84
+ </button>
85
+ </div>
86
+ </div>
87
+ <div class="p-4">
88
+ <textarea id="input-text" rows="10" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="在这里输入您想要转换为语音的文本..."></textarea>
89
+ </div>
90
+ </div>
91
+
92
+ <!-- 语音设置 -->
93
+ <div class="bg-white rounded-xl shadow-md overflow-hidden">
94
+ <div class="bg-indigo-50 px-4 py-3 border-b border-gray-200">
95
+ <h2 class="font-semibold text-indigo-700">语音设置</h2>
96
+ </div>
97
+ <div class="p-4 space-y-4">
98
+ <!-- 语音选择 -->
99
+ <div>
100
+ <label class="block text-sm font-medium text-gray-700 mb-1">选择语��</label>
101
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-3" id="voice-options">
102
+ <!-- 语音选项将通过JS动态生成 -->
103
+ </div>
104
+ </div>
105
+
106
+ <!-- 语音参数 -->
107
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
108
+ <div>
109
+ <label for="speed" class="block text-sm font-medium text-gray-700">语速 <span id="speed-value" class="text-indigo-600">1.0</span></label>
110
+ <input type="range" id="speed" min="0.5" max="2" step="0.1" value="1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
111
+ </div>
112
+ <div>
113
+ <label for="pitch" class="block text-sm font-medium text-gray-700">音高 <span id="pitch-value" class="text-indigo-600">1.0</span></label>
114
+ <input type="range" id="pitch" min="0.5" max="1.5" step="0.1" value="1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
115
+ </div>
116
+ <div>
117
+ <label for="volume" class="block text-sm font-medium text-gray-700">音量 <span id="volume-value" class="text-indigo-600">1.0</span></label>
118
+ <input type="range" id="volume" min="0" max="1" step="0.1" value="1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
119
+ </div>
120
+ </div>
121
+
122
+ <!-- 情感设置 -->
123
+ <div>
124
+ <label class="block text-sm font-medium text-gray-700 mb-1">情感风格</label>
125
+ <div class="flex flex-wrap gap-2">
126
+ <button class="emotion-btn px-3 py-1 bg-gray-100 hover:bg-indigo-100 text-gray-800 rounded-full border border-gray-300 transition" data-emotion="neutral">中性</button>
127
+ <button class="emotion-btn px-3 py-1 bg-gray-100 hover:bg-indigo-100 text-gray-800 rounded-full border border-gray-300 transition" data-emotion="happy">快乐</button>
128
+ <button class="emotion-btn px-3 py-1 bg-gray-100 hover:bg-indigo-100 text-gray-800 rounded-full border border-gray-300 transition" data-emotion="sad">悲伤</button>
129
+ <button class="emotion-btn px-3 py-1 bg-gray-100 hover:bg-indigo-100 text-gray-800 rounded-full border border-gray-300 transition" data-emotion="angry">愤怒</button>
130
+ <button class="emotion-btn px-3 py-1 bg-gray-100 hover:bg-indigo-100 text-gray-800 rounded-full border border-gray-300 transition" data-emotion="excited">兴奋</button>
131
+ <button class="emotion-btn px-3 py-1 bg-gray-100 hover:bg-indigo-100 text-gray-800 rounded-full border border-gray-300 transition" data-emotion="calm">平静</button>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ <!-- API设置 -->
138
+ <div class="bg-white rounded-xl shadow-md overflow-hidden">
139
+ <div class="bg-indigo-50 px-4 py-3 border-b border-gray-200 flex justify-between items-center">
140
+ <h2 class="font-semibold text-indigo-700">API设置</h2>
141
+ <button id="toggle-api" class="text-indigo-600 hover:text-indigo-800 transition">
142
+ <i class="fas fa-cog"></i> 高级设置
143
+ </button>
144
+ </div>
145
+ <div id="api-settings" class="p-4 hidden space-y-4">
146
+ <div>
147
+ <label for="tts-api" class="block text-sm font-medium text-gray-700 mb-1">TTS API端点</label>
148
+ <input type="text" id="tts-api" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="https://api.example.com/tts">
149
+ </div>
150
+ <div>
151
+ <label for="nlp-api" class="block text-sm font-medium text-gray-700 mb-1">NLP API端点</label>
152
+ <input type="text" id="nlp-api" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="https://api.example.com/nlp">
153
+ </div>
154
+ <div>
155
+ <label for="api-key" class="block text-sm font-medium text-gray-700 mb-1">API密钥</label>
156
+ <input type="password" id="api-key" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="输入您的API密钥">
157
+ </div>
158
+ <div class="flex justify-end">
159
+ <button id="save-api" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition">
160
+ 保存设置
161
+ </button>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </div>
166
+
167
+ <!-- 右侧输出和控制面板 -->
168
+ <div class="space-y-6">
169
+ <!-- 播放控制 -->
170
+ <div class="bg-white rounded-xl shadow-md overflow-hidden">
171
+ <div class="bg-indigo-50 px-4 py-3 border-b border-gray-200">
172
+ <h2 class="font-semibold text-indigo-700">播放控制</h2>
173
+ </div>
174
+ <div class="p-4 space-y-4">
175
+ <!-- 波形图占位 -->
176
+ <div class="waveform h-12 rounded-lg" id="waveform" style="--progress: 0%"></div>
177
+
178
+ <!-- 控制按钮 -->
179
+ <div class="flex justify-center space-x-4">
180
+ <button id="generate-btn" class="px-6 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition flex items-center space-x-2">
181
+ <i class="fas fa-magic"></i>
182
+ <span>生成语音</span>
183
+ </button>
184
+ <button id="play-btn" disabled class="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition flex items-center space-x-2">
185
+ <i class="fas fa-play"></i>
186
+ <span>播放</span>
187
+ </button>
188
+ <button id="pause-btn" disabled class="px-6 py-2 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600 transition flex items-center space-x-2">
189
+ <i class="fas fa-pause"></i>
190
+ <span>暂停</span>
191
+ </button>
192
+ </div>
193
+
194
+ <!-- 进度和时间 -->
195
+ <div class="flex justify-between items-center text-sm text-gray-600">
196
+ <span id="current-time">00:00</span>
197
+ <span id="total-time">00:00</span>
198
+ </div>
199
+
200
+ <!-- 下载选项 -->
201
+ <div class="pt-4 border-t border-gray-200">
202
+ <label class="block text-sm font-medium text-gray-700 mb-2">下载格式</label>
203
+ <div class="flex space-x-3">
204
+ <button id="download-mp3" disabled class="px-4 py-2 bg-gray-100 hover:bg-indigo-100 text-gray-800 rounded-lg border border-gray-300 transition flex items-center space-x-2">
205
+ <i class="fas fa-file-audio"></i>
206
+ <span>MP3</span>
207
+ </button>
208
+ <button id="download-wav" disabled class="px-4 py-2 bg-gray-100 hover:bg-indigo-100 text-gray-800 rounded-lg border border-gray-300 transition flex items-center space-x-2">
209
+ <i class="fas fa-file-waveform"></i>
210
+ <span>WAV</span>
211
+ </button>
212
+ </div>
213
+ </div>
214
+ </div>
215
+ </div>
216
+
217
+ <!-- 历史记录 -->
218
+ <div class="bg-white rounded-xl shadow-md overflow-hidden">
219
+ <div class="bg-indigo-50 px-4 py-3 border-b border-gray-200 flex justify-between items-center">
220
+ <h2 class="font-semibold text-indigo-700">历史记录</h2>
221
+ <button id="clear-history" class="text-gray-500 hover:text-gray-700 transition text-sm">
222
+ <i class="fas fa-trash-alt"></i> 清空
223
+ </button>
224
+ </div>
225
+ <div class="p-4">
226
+ <div id="history-list" class="space-y-2 max-h-60 overflow-y-auto">
227
+ <!-- 历史记录项将通过JS动态生成 -->
228
+ <div class="text-center text-gray-500 py-4">
229
+ <i class="fas fa-history text-2xl mb-2"></i>
230
+ <p>暂无历史记录</p>
231
+ </div>
232
+ </div>
233
+ </div>
234
+ </div>
235
+
236
+ <!-- 提示和建议 -->
237
+ <div class="bg-indigo-50 rounded-xl p-4 border border-indigo-100">
238
+ <h3 class="font-medium text-indigo-700 mb-2 flex items-center">
239
+ <i class="fas fa-lightbulb mr-2"></i> 使用小贴士
240
+ </h3>
241
+ <ul class="text-sm text-indigo-800 space-y-1">
242
+ <li>• 使用不同的情感风格来匹配文本内容</li>
243
+ <li>• 调整语速和音高以获得最佳效果</li>
244
+ <li>• 长文本会自动分段处理</li>
245
+ <li>• 点击示例按钮获取灵感</li>
246
+ </ul>
247
+ </div>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ </main>
252
+
253
+ <!-- 页脚 -->
254
+ <footer class="bg-gray-800 text-white py-6">
255
+ <div class="container mx-auto px-4">
256
+ <div class="flex flex-col md:flex-row justify-between items-center">
257
+ <div class="mb-4 md:mb-0">
258
+ <div class="flex items-center space-x-2">
259
+ <i class="fas fa-microphone-alt text-2xl text-indigo-400"></i>
260
+ <span class="text-xl font-bold">AI语音播客工作室</span>
261
+ </div>
262
+ <p class="text-gray-400 text-sm mt-1">让文字生动起来</p>
263
+ </div>
264
+ <div class="flex space-x-6">
265
+ <a href="#" class="hover:text-indigo-400 transition">隐私政策</a>
266
+ <a href="#" class="hover:text-indigo-400 transition">服务条款</a>
267
+ <a href="#" class="hover:text-indigo-400 transition">联系我们</a>
268
+ </div>
269
+ </div>
270
+ <div class="border-t border-gray-700 mt-6 pt-6 text-center text-gray-400 text-sm">
271
+ <p>© 2023 AI语音播客工作室. 保留所有权利.</p>
272
+ </div>
273
+ </div>
274
+ </footer>
275
+
276
+ <!-- 音频元素 -->
277
+ <audio id="audio-player" class="hidden"></audio>
278
+
279
+ <!-- 脚本 -->
280
+ <script>
281
+ // 模块化代码结构
282
+ const App = (() => {
283
+ // 状态管理
284
+ const state = {
285
+ voices: [
286
+ { id: 'voice-1', name: '标准女声', gender: 'female', sampleText: '您好,欢迎使用AI语音播客工作室。', selected: true },
287
+ { id: 'voice-2', name: '标准男声', gender: 'male', sampleText: '科技改变生活,语音连接未来。' },
288
+ { id: 'voice-3', name: '甜美女声', gender: 'female', sampleText: '今天的天气真好,阳光明媚,适合外出散步。' },
289
+ { id: 'voice-4', name: '磁性男声', gender: 'male', sampleText: '深度思考是人类最宝贵的财富之一。' },
290
+ { id: 'voice-5', name: '儿童声音', gender: 'child', sampleText: '我最喜欢听故事了,能给我讲一个吗?' },
291
+ { id: 'voice-6', name: '老年声音', gender: 'senior', sampleText: '岁月如歌,人生如梦,珍惜当下每一刻。' }
292
+ ],
293
+ currentVoice: null,
294
+ emotion: 'neutral',
295
+ audioBlob: null,
296
+ history: JSON.parse(localStorage.getItem('ttsHistory')) || [],
297
+ apiSettings: JSON.parse(localStorage.getItem('apiSettings')) || {
298
+ ttsApi: '',
299
+ nlpApi: '',
300
+ apiKey: ''
301
+ },
302
+ isPlaying: false,
303
+ audioDuration: 0,
304
+ currentAudioTime: 0,
305
+ audioContext: null,
306
+ analyser: null,
307
+ audioSource: null,
308
+ animationFrameId: null
309
+ };
310
+
311
+ // DOM元素
312
+ const elements = {
313
+ inputText: document.getElementById('input-text'),
314
+ voiceOptions: document.getElementById('voice-options'),
315
+ speedSlider: document.getElementById('speed'),
316
+ speedValue: document.getElementById('speed-value'),
317
+ pitchSlider: document.getElementById('pitch'),
318
+ pitchValue: document.getElementById('pitch-value'),
319
+ volumeSlider: document.getElementById('volume'),
320
+ volumeValue: document.getElementById('volume-value'),
321
+ emotionBtns: document.querySelectorAll('.emotion-btn'),
322
+ generateBtn: document.getElementById('generate-btn'),
323
+ playBtn: document.getElementById('play-btn'),
324
+ pauseBtn: document.getElementById('pause-btn'),
325
+ audioPlayer: document.getElementById('audio-player'),
326
+ waveform: document.getElementById('waveform'),
327
+ currentTime: document.getElementById('current-time'),
328
+ totalTime: document.getElementById('total-time'),
329
+ downloadMp3: document.getElementById('download-mp3'),
330
+ downloadWav: document.getElementById('download-wav'),
331
+ clearText: document.getElementById('clear-text'),
332
+ sampleText: document.getElementById('sample-text'),
333
+ historyList: document.getElementById('history-list'),
334
+ clearHistory: document.getElementById('clear-history'),
335
+ toggleApi: document.getElementById('toggle-api'),
336
+ apiSettings: document.getElementById('api-settings'),
337
+ ttsApi: document.getElementById('tts-api'),
338
+ nlpApi: document.getElementById('nlp-api'),
339
+ apiKey: document.getElementById('api-key'),
340
+ saveApi: document.getElementById('save-api')
341
+ };
342
+
343
+ // 初始化函数
344
+ const init = () => {
345
+ renderVoiceOptions();
346
+ bindEvents();
347
+ loadApiSettings();
348
+ renderHistory();
349
+ setupAudioContext();
350
+
351
+ // 设置默认语音
352
+ state.currentVoice = state.voices.find(v => v.selected);
353
+ };
354
+
355
+ // 渲染语音选项
356
+ const renderVoiceOptions = () => {
357
+ elements.voiceOptions.innerHTML = state.voices.map(voice => `
358
+ <div class="voice-option relative p-3 border rounded-lg cursor-pointer transition ${voice.selected ? 'border-indigo-500 bg-indigo-50' : 'border-gray-200 hover:border-indigo-300'}" data-voice-id="${voice.id}">
359
+ <div class="flex items-center">
360
+ <div class="mr-3">
361
+ <i class="fas fa-${voice.gender === 'male' ? 'male' : voice.gender === 'female' ? 'female' : voice.gender === 'child' ? 'child' : 'user'} text-xl ${voice.gender === 'male' ? 'text-blue-500' : voice.gender === 'female' ? 'text-pink-500' : 'text-purple-500'}"></i>
362
+ </div>
363
+ <div>
364
+ <h3 class="font-medium">${voice.name}</h3>
365
+ <p class="text-sm text-gray-600 truncate">${voice.sampleText}</p>
366
+ </div>
367
+ </div>
368
+ <button class="play-sample absolute right-3 top-3 hidden text-indigo-600 hover:text-indigo-800" data-sample-text="${voice.sampleText}">
369
+ <i class="fas fa-play-circle text-lg"></i>
370
+ </button>
371
+ </div>
372
+ `).join('');
373
+ };
374
+
375
+ // 绑定事件
376
+ const bindEvents = () => {
377
+ // 语音选择
378
+ document.querySelectorAll('.voice-option').forEach(el => {
379
+ el.addEventListener('click', (e) => {
380
+ const voiceId = el.getAttribute('data-voice-id');
381
+ selectVoice(voiceId);
382
+
383
+ // 阻止事件冒泡,避免触发播放样本
384
+ if (e.target.closest('.play-sample')) {
385
+ e.stopPropagation();
386
+ }
387
+ });
388
+ });
389
+
390
+ // 播放样本
391
+ document.querySelectorAll('.play-sample').forEach(btn => {
392
+ btn.addEventListener('click', (e) => {
393
+ const sampleText = btn.getAttribute('data-sample-text');
394
+ playSample(sampleText);
395
+ });
396
+ });
397
+
398
+ // 滑块控制
399
+ elements.speedSlider.addEventListener('input', () => {
400
+ const value = elements.speedSlider.value;
401
+ elements.speedValue.textContent = value;
402
+ });
403
+
404
+ elements.pitchSlider.addEventListener('input', () => {
405
+ const value = elements.pitchSlider.value;
406
+ elements.pitchValue.textContent = value;
407
+ });
408
+
409
+ elements.volumeSlider.addEventListener('input', () => {
410
+ const value = elements.volumeSlider.value;
411
+ elements.volumeValue.textContent = value;
412
+ if (elements.audioPlayer) {
413
+ elements.audioPlayer.volume = value;
414
+ }
415
+ });
416
+
417
+ // 情感按钮
418
+ elements.emotionBtns.forEach(btn => {
419
+ btn.addEventListener('click', () => {
420
+ state.emotion = btn.getAttribute('data-emotion');
421
+ updateEmotionButtons();
422
+ });
423
+ });
424
+
425
+ // 生成按钮
426
+ elements.generateBtn.addEventListener('click', generateAudio);
427
+
428
+ // 播放控制
429
+ elements.playBtn.addEventListener('click', playAudio);
430
+ elements.pauseBtn.addEventListener('click', pauseAudio);
431
+
432
+ // 音频事件
433
+ elements.audioPlayer.addEventListener('timeupdate', updateAudioProgress);
434
+ elements.audioPlayer.addEventListener('loadedmetadata', () => {
435
+ state.audioDuration = elements.audioPlayer.duration;
436
+ updateTimeDisplay();
437
+ });
438
+ elements.audioPlayer.addEventListener('ended', () => {
439
+ state.isPlaying = false;
440
+ updatePlayButtons();
441
+ cancelAnimationFrame(state.animationFrameId);
442
+ });
443
+ elements.audioPlayer.addEventListener('play', () => {
444
+ state.isPlaying = true;
445
+ updatePlayButtons();
446
+ visualizeAudio();
447
+ });
448
+ elements.audioPlayer.addEventListener('pause', () => {
449
+ state.isPlaying = false;
450
+ updatePlayButtons();
451
+ cancelAnimationFrame(state.animationFrameId);
452
+ });
453
+
454
+ // 下载按钮
455
+ elements.downloadMp3.addEventListener('click', () => downloadAudio('mp3'));
456
+ elements.downloadWav.addEventListener('click', () => downloadAudio('wav'));
457
+
458
+ // 文本操作
459
+ elements.clearText.addEventListener('click', () => {
460
+ elements.inputText.value = '';
461
+ });
462
+
463
+ elements.sampleText.addEventListener('click', () => {
464
+ elements.inputText.value = `欢迎来到AI语音播客工作室!
465
+
466
+ 这是一个示例文本,展示了系统如何处理不同情感的表达。
467
+
468
+ [快乐] 今天真是美好的一天!阳光明媚,心情愉悦!
469
+ [悲伤] 听到这个消息,我感到非常难过...
470
+ [愤怒] 这种行为简直不可接受!必须立即停止!
471
+ [兴奋] 哇!我简直不敢相信自己的眼睛!太棒了!
472
+ [平静] 深呼吸,放松身心,感受当下的宁静。
473
+
474
+ 您可以根据需要调整语音参数,创造出符合场景的语音效果。`;
475
+ });
476
+
477
+ // 历史记录
478
+ elements.clearHistory.addEventListener('click', clearHistory);
479
+
480
+ // API设置
481
+ elements.toggleApi.addEventListener('click', toggleApiSettings);
482
+ elements.saveApi.addEventListener('click', saveApiSettings);
483
+ };
484
+
485
+ // 选择语音
486
+ const selectVoice = (voiceId) => {
487
+ state.voices.forEach(voice => {
488
+ voice.selected = voice.id === voiceId;
489
+ });
490
+ state.currentVoice = state.voices.find(v => v.id === voiceId);
491
+ renderVoiceOptions();
492
+ };
493
+
494
+ // 播放样本
495
+ const playSample = (text) => {
496
+ // 在实际应用中,这里会调用TTS API生成样本语音
497
+ // 这里使用Web Speech API作为演示
498
+ if ('speechSynthesis' in window) {
499
+ const utterance = new SpeechSynthesisUtterance(text);
500
+ const voices = window.speechSynthesis.getVoices();
501
+ const voice = voices.find(v => v.name.includes(state.currentVoice.gender === 'male' ? 'Male' : 'Female')) || voices[0];
502
+
503
+ utterance.voice = voice;
504
+ utterance.rate = parseFloat(elements.speedSlider.value);
505
+ utterance.pitch = parseFloat(elements.pitchSlider.value);
506
+
507
+ window.speechSynthesis.speak(utterance);
508
+ } else {
509
+ alert('您的浏览器不支持语音合成API,请使用Chrome或Edge浏览器。');
510
+ }
511
+ };
512
+
513
+ // 更新情感按钮状态
514
+ const updateEmotionButtons = () => {
515
+ elements.emotionBtns.forEach(btn => {
516
+ if (btn.getAttribute('data-emotion') === state.emotion) {
517
+ btn.classList.add('bg-indigo-100', 'border-indigo-400', 'text-indigo-800');
518
+ btn.classList.remove('bg-gray-100', 'text-gray-800');
519
+ } else {
520
+ btn.classList.remove('bg-indigo-100', 'border-indigo-400', 'text-indigo-800');
521
+ btn.classList.add('bg-gray-100', 'text-gray-800');
522
+ }
523
+ });
524
+ };
525
+
526
+ // 生成音频
527
+ const generateAudio = async () => {
528
+ const text = elements.inputText.value.trim();
529
+ if (!text) {
530
+ alert('请输入要转换的文本');
531
+ return;
532
+ }
533
+
534
+ // 显示加载状态
535
+ elements.generateBtn.disabled = true;
536
+ elements.generateBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i><span>生成中...</span>';
537
+
538
+ try {
539
+ // 模拟API调用延迟
540
+ await new Promise(resolve => setTimeout(resolve, 1500));
541
+
542
+ // 在实际应用中,这里会调用TTS API
543
+ // 这里使用模拟的音频数据作为演示
544
+ simulateTTSGeneration(text);
545
+
546
+ // 添加到历史记录
547
+ addToHistory(text);
548
+ } catch (error) {
549
+ console.error('生成语音出错:', error);
550
+ alert('生成语音时出错: ' + error.message);
551
+ } finally {
552
+ elements.generateBtn.disabled = false;
553
+ elements.generateBtn.innerHTML = '<i class="fas fa-magic"></i><span>生成语音</span>';
554
+ }
555
+ };
556
+
557
+ // 模拟TTS生成
558
+ const simulateTTSGeneration = (text) => {
559
+ // 创建一个模拟的音频Blob
560
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
561
+ const oscillator = audioContext.createOscillator();
562
+ const gainNode = audioContext.createGain();
563
+
564
+ oscillator.type = 'sine';
565
+ oscillator.frequency.value = state.currentVoice.gender === 'male' ? 120 : 220;
566
+ gainNode.gain.value = 0.5;
567
+
568
+ oscillator.connect(gainNode);
569
+ gainNode.connect(audioContext.destination);
570
+
571
+ // 根据情感调整声音
572
+ switch (state.emotion) {
573
+ case 'happy':
574
+ oscillator.frequency.setValueAtTime(state.currentVoice.gender === 'male' ? 140 : 260, audioContext.currentTime);
575
+ break;
576
+ case 'sad':
577
+ oscillator.frequency.setValueAtTime(state.currentVoice.gender === 'male' ? 100 : 180, audioContext.currentTime);
578
+ break;
579
+ case 'angry':
580
+ oscillator.type = 'square';
581
+ oscillator.frequency.setValueAtTime(state.currentVoice.gender === 'male' ? 130 : 230, audioContext.currentTime);
582
+ break;
583
+ case 'excited':
584
+ oscillator.type = 'sawtooth';
585
+ oscillator.frequency.setValueAtTime(state.currentVoice.gender === 'male' ? 150 : 280, audioContext.currentTime);
586
+ break;
587
+ case 'calm':
588
+ oscillator.type = 'sine';
589
+ oscillator.frequency.setValueAtTime(state.currentVoice.gender === 'male' ? 110 : 200, audioContext.currentTime);
590
+ break;
591
+ }
592
+
593
+ // 模拟音频持续时间 (0.1秒每字符)
594
+ const duration = Math.min(text.length * 0.1, 30); // 最长30秒
595
+
596
+ // 创建音频缓冲区
597
+ const sampleRate = 44100;
598
+ const numChannels = 1;
599
+ const numFrames = sampleRate * duration;
600
+ const buffer = audioContext.createBuffer(numChannels, numFrames, sampleRate);
601
+
602
+ // 填充缓冲区
603
+ for (let channel = 0; channel < numChannels; channel++) {
604
+ const nowBuffering = buffer.getChannelData(channel);
605
+ for (let i = 0; i < numFrames; i++) {
606
+ const time = i / sampleRate;
607
+ const frequency = state.currentVoice.gender === 'male' ? 120 : 220;
608
+ nowBuffering[i] = Math.sin(2 * Math.PI * frequency * time) * 0.5;
609
+ }
610
+ }
611
+
612
+ // 转换为Blob
613
+ const audioBlob = bufferToWav(buffer);
614
+ state.audioBlob = audioBlob;
615
+
616
+ // 设置音频播放器
617
+ const audioUrl = URL.createObjectURL(audioBlob);
618
+ elements.audioPlayer.src = audioUrl;
619
+
620
+ // 启用播放和下载按钮
621
+ elements.playBtn.disabled = false;
622
+ elements.downloadMp3.disabled = false;
623
+ elements.downloadWav.disabled = false;
624
+
625
+ // 显示成功消息
626
+ showNotification('语音生成成功!', 'success');
627
+ };
628
+
629
+ // 将AudioBuffer转换为WAV Blob
630
+ const bufferToWav = (buffer) => {
631
+ const numChannels = buffer.numberOfChannels;
632
+ const sampleRate = buffer.sampleRate;
633
+ const length = buffer.length;
634
+
635
+ const interleaved = new Float32Array(length * numChannels);
636
+ for (let channel = 0; channel < numChannels; channel++) {
637
+ const channelData = buffer.getChannelData(channel);
638
+ for (let i = 0; i < length; i++) {
639
+ interleaved[i * numChannels + channel] = channelData[i];
640
+ }
641
+ }
642
+
643
+ const bufferLength = length * numChannels * 2;
644
+ const arrayBuffer = new ArrayBuffer(44 + bufferLength);
645
+ const view = new DataView(arrayBuffer);
646
+
647
+ // WAV头部
648
+ writeString(view, 0, 'RIFF');
649
+ view.setUint32(4, 36 + bufferLength, true);
650
+ writeString(view, 8, 'WAVE');
651
+ writeString(view, 12, 'fmt ');
652
+ view.setUint32(16, 16, true);
653
+ view.setUint16(20, 1, true); // PCM格式
654
+ view.setUint16(22, numChannels, true);
655
+ view.setUint32(24, sampleRate, true);
656
+ view.setUint32(28, sampleRate * numChannels * 2, true);
657
+ view.setUint16(32, numChannels * 2, true);
658
+ view.setUint16(34, 16, true);
659
+ writeString(view, 36, 'data');
660
+ view.setUint32(40, bufferLength, true);
661
+
662
+ // 音频数据
663
+ const offset = 44;
664
+ for (let i = 0; i < interleaved.length; i++) {
665
+ const sample = Math.max(-1, Math.min(1, interleaved[i]));
666
+ view.setInt16(offset + i * 2, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true);
667
+ }
668
+
669
+ return new Blob([view], { type: 'audio/wav' });
670
+ };
671
+
672
+ const writeString = (view, offset, string) => {
673
+ for (let i = 0; i < string.length; i++) {
674
+ view.setUint8(offset + i, string.charCodeAt(i));
675
+ }
676
+ };
677
+
678
+ // 播放音频
679
+ const playAudio = () => {
680
+ if (state.audioBlob) {
681
+ elements.audioPlayer.play();
682
+ }
683
+ };
684
+
685
+ // 暂停音频
686
+ const pauseAudio = () => {
687
+ elements.audioPlayer.pause();
688
+ };
689
+
690
+ // 更新播放按钮状态
691
+ const updatePlayButtons = () => {
692
+ elements.playBtn.disabled = !state.audioBlob || state.isPlaying;
693
+ elements.pauseBtn.disabled = !state.audioBlob || !state.isPlaying;
694
+ };
695
+
696
+ // 更新音频进度
697
+ const updateAudioProgress = () => {
698
+ state.currentAudioTime = elements.audioPlayer.currentTime;
699
+ const progress = (state.currentAudioTime / state.audioDuration) * 100;
700
+ elements.waveform.style.setProperty('--progress', `${progress}%`);
701
+ updateTimeDisplay();
702
+ };
703
+
704
+ // 更新时间显示
705
+ const updateTimeDisplay = () => {
706
+ elements.currentTime.textContent = formatTime(state.currentAudioTime);
707
+ elements.totalTime.textContent = formatTime(state.audioDuration);
708
+ };
709
+
710
+ // 格式化时间
711
+ const formatTime = (seconds) => {
712
+ const mins = Math.floor(seconds / 60);
713
+ const secs = Math.floor(seconds % 60);
714
+ return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
715
+ };
716
+
717
+ // 音频可视化
718
+ const visualizeAudio = () => {
719
+ if (!state.audioContext) {
720
+ setupAudioContext();
721
+ }
722
+
723
+ if (state.audioSource) {
724
+ state.audioSource.disconnect();
725
+ }
726
+
727
+ state.audioSource = state.audioContext.createMediaElementSource(elements.audioPlayer);
728
+ state.audioSource.connect(state.analyser);
729
+ state.analyser.connect(state.audioContext.destination);
730
+
731
+ state.analyser.fftSize = 64;
732
+ const bufferLength = state.analyser.frequencyBinCount;
733
+ const dataArray = new Uint8Array(bufferLength);
734
+
735
+ const draw = () => {
736
+ state.animationFrameId = requestAnimationFrame(draw);
737
+ state.analyser.getByteFrequencyData(dataArray);
738
+
739
+ // 简化波形显示 - 在实际应用中可以实现更复杂的可视化
740
+ const average = dataArray.reduce((a, b) => a + b) / bufferLength;
741
+ const progress = (elements.audioPlayer.currentTime / elements.audioPlayer.duration) * 100 || 0;
742
+ elements.waveform.style.setProperty('--progress', `${progress}%`);
743
+ };
744
+
745
+ draw();
746
+ };
747
+
748
+ // 设置音频上下文
749
+ const setupAudioContext = () => {
750
+ state.audioContext = new (window.AudioContext || window.webkitAudioContext)();
751
+ state.analyser = state.audioContext.createAnalyser();
752
+ };
753
+
754
+ // 下载音频
755
+ const downloadAudio = (format) => {
756
+ if (!state.audioBlob) return;
757
+
758
+ let blob = state.audioBlob;
759
+ let extension = 'wav';
760
+ let mimeType = 'audio/wav';
761
+
762
+ if (format === 'mp3') {
763
+ // 在实际应用中,这里会将WAV转换为MP3
764
+ // 这里我们只是模拟转换
765
+ mimeType = 'audio/mpeg';
766
+ extension = 'mp3';
767
+ }
768
+
769
+ const url = URL.createObjectURL(blob);
770
+ const a = document.createElement('a');
771
+ a.href = url;
772
+ a.download = `ai-voice-${new Date().toISOString().slice(0, 10)}.${extension}`;
773
+ document.body.appendChild(a);
774
+ a.click();
775
+ document.body.removeChild(a);
776
+ URL.revokeObjectURL(url);
777
+ };
778
+
779
+ // 添加到历史记录
780
+ const addToHistory = (text) => {
781
+ const historyItem = {
782
+ id: Date.now(),
783
+ text: text.length > 50 ? text.substring(0, 50) + '...' : text,
784
+ fullText: text,
785
+ voice: state.currentVoice.name,
786
+ emotion: state.emotion,
787
+ date: new Date().toLocaleString(),
788
+ audioBlob: state.audioBlob
789
+ };
790
+
791
+ state.history.unshift(historyItem);
792
+ if (state.history.length > 10) {
793
+ state.history.pop();
794
+ }
795
+
796
+ saveHistory();
797
+ renderHistory();
798
+ };
799
+
800
+ // 渲染历史记录
801
+ const renderHistory = () => {
802
+ if (state.history.length === 0) {
803
+ elements.historyList.innerHTML = `
804
+ <div class="text-center text-gray-500 py-4">
805
+ <i class="fas fa-history text-2xl mb-2"></i>
806
+ <p>暂无历史记录</p>
807
+ </div>
808
+ `;
809
+ return;
810
+ }
811
+
812
+ elements.historyList.innerHTML = state.history.map(item => `
813
+ <div class="history-item p-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition cursor-pointer fade-in" data-id="${item.id}">
814
+ <div class="flex justify-between items-start">
815
+ <div class="flex-1 min-w-0">
816
+ <h3 class="font-medium truncate">${item.text}</h3>
817
+ <div class="flex items-center text-xs text-gray-500 mt-1 space-x-2">
818
+ <span><i class="fas fa-${item.voice.includes('男') ? 'male' : 'female'} mr-1"></i> ${item.voice}</span>
819
+ <span><i class="fas fa-heart mr-1"></i> ${getEmotionName(item.emotion)}</span>
820
+ <span>${item.date}</span>
821
+ </div>
822
+ </div>
823
+ <button class="play-history ml-2 text-indigo-600 hover:text-indigo-800" data-id="${item.id}">
824
+ <i class="fas fa-play"></i>
825
+ </button>
826
+ </div>
827
+ </div>
828
+ `).join('');
829
+
830
+ // 绑定历史项目点击事件
831
+ document.querySelectorAll('.history-item').forEach(el => {
832
+ el.addEventListener('click', (e) => {
833
+ const id = parseInt(el.getAttribute('data-id'));
834
+ const item = state.history.find(i => i.id === id);
835
+
836
+ if (e.target.closest('.play-history')) {
837
+ e.stopPropagation();
838
+ playHistoryItem(id);
839
+ } else {
840
+ loadHistoryItem(id);
841
+ }
842
+ });
843
+ });
844
+ };
845
+
846
+ // 获取情感名称
847
+ const getEmotionName = (emotion) => {
848
+ const emotions = {
849
+ neutral: '中性',
850
+ happy: '快乐',
851
+ sad: '悲伤',
852
+ angry: '愤怒',
853
+ excited: '兴奋',
854
+ calm: '平静'
855
+ };
856
+ return emotions[emotion] || emotion;
857
+ };
858
+
859
+ // 播放历史项目
860
+ const playHistoryItem = (id) => {
861
+ const item = state.history.find(i => i.id === id);
862
+ if (item && item.audioBlob) {
863
+ state.audioBlob = item.audioBlob;
864
+ const audioUrl = URL.createObjectURL(item.audioBlob);
865
+ elements.audioPlayer.src = audioUrl;
866
+ elements.audioPlayer.play();
867
+
868
+ // 启用播放和下载按钮
869
+ elements.playBtn.disabled = false;
870
+ elements.downloadMp3.disabled = false;
871
+ elements.downloadWav.disabled = false;
872
+ }
873
+ };
874
+
875
+ // 加载历史项目
876
+ const loadHistoryItem = (id) => {
877
+ const item = state.history.find(i => i.id === id);
878
+ if (item) {
879
+ elements.inputText.value = item.fullText;
880
+
881
+ // 设置对应的语音和情感
882
+ const voice = state.voices.find(v => v.name === item.voice);
883
+ if (voice) {
884
+ selectVoice(voice.id);
885
+ }
886
+
887
+ state.emotion = item.emotion;
888
+ updateEmotionButtons();
889
+
890
+ // 滚动到顶部
891
+ window.scrollTo({ top: 0, behavior: 'smooth' });
892
+ }
893
+ };
894
+
895
+ // 清空历史记录
896
+ const clearHistory = () => {
897
+ if (confirm('确定要清空所有历史记录吗?此操作不可撤销。')) {
898
+ state.history = [];
899
+ saveHistory();
900
+ renderHistory();
901
+ }
902
+ };
903
+
904
+ // 保存历史记录
905
+ const saveHistory = () => {
906
+ // 只保存必要信息,Blob太大不保存
907
+ const historyToSave = state.history.map(item => ({
908
+ id: item.id,
909
+ text: item.text,
910
+ fullText: item.fullText,
911
+ voice: item.voice,
912
+ emotion: item.emotion,
913
+ date: item.date
914
+ }));
915
+
916
+ localStorage.setItem('ttsHistory', JSON.stringify(historyToSave));
917
+ };
918
+
919
+ // 加载API设置
920
+ const loadApiSettings = () => {
921
+ elements.ttsApi.value = state.apiSettings.ttsApi || '';
922
+ elements.nlpApi.value = state.apiSettings.nlpApi || '';
923
+ elements.apiKey.value = state.apiSettings.apiKey || '';
924
+ };
925
+
926
+ // 切换API设置显示
927
+ const toggleApiSettings = () => {
928
+ elements.apiSettings.classList.toggle('hidden');
929
+ const isHidden = elements.apiSettings.classList.contains('hidden');
930
+ elements.toggleApi.innerHTML = isHidden
931
+ ? '<i class="fas fa-cog"></i> 高级设置'
932
+ : '<i class="fas fa-times"></i> 关闭设置';
933
+ };
934
+
935
+ // 保存API设置
936
+ const saveApiSettings = () => {
937
+ state.apiSettings = {
938
+ ttsApi: elements.ttsApi.value.trim(),
939
+ nlpApi: elements.nlpApi.value.trim(),
940
+ apiKey: elements.apiKey.value.trim()
941
+ };
942
+
943
+ localStorage.setItem('apiSettings', JSON.stringify(state.apiSettings));
944
+ showNotification('API设置已保存', 'success');
945
+ };
946
+
947
+ // 显示通知
948
+ const showNotification = (message, type) => {
949
+ const notification = document.createElement('div');
950
+ notification.className = `fixed top-4 right-4 px-4 py-2 rounded-lg shadow-lg text-white ${
951
+ type === 'success' ? 'bg-green-500' : 'bg-red-500'
952
+ } z-50 fade-in`;
953
+ notification.textContent = message;
954
+ document.body.appendChild(notification);
955
+
956
+ setTimeout(() => {
957
+ notification.classList.remove('fade-in');
958
+ notification.classList.add('fade-out');
959
+ setTimeout(() => {
960
+ document.body.removeChild(notification);
961
+ }, 300);
962
+ }, 3000);
963
+ };
964
+
965
+ // 公共方法
966
+ return {
967
+ init
968
+ };
969
+ })();
970
+
971
+ // 初始化应用
972
+ document.addEventListener('DOMContentLoaded', App.init);
973
+ </script>
974
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=wwqg/ai" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
975
+ </html>