@bolloon/bolloon-agent 0.1.30 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/pi-sdk.js +10 -1
- package/dist/index.js +13 -1
- package/dist/llm/audio-config-store.js +199 -0
- package/dist/llm/config-store.js +80 -6
- package/dist/llm/pi-ai.js +30 -2
- package/dist/llm/video-config-store.js +201 -0
- package/dist/pi-ecosystem/index.js +1 -1
- package/dist/web/api-config.html +308 -53
- package/dist/web/client.js +375 -8
- package/dist/web/server.js +324 -5
- package/dist/web/style.css +83 -0
- package/package.json +1 -1
- package/src/agents/pi-sdk.ts +9 -1
- package/src/index.ts +13 -1
- package/src/llm/audio-config-store.ts +246 -0
- package/src/llm/config-store.ts +21 -11
- package/src/llm/pi-ai.ts +22 -2
- package/src/llm/video-config-store.ts +257 -0
- package/src/web/api-config.html +308 -53
- package/src/web/client.js +375 -8
- package/src/web/server.ts +354 -5
- package/src/web/style.css +83 -0
package/dist/web/api-config.html
CHANGED
|
@@ -20,9 +20,47 @@
|
|
|
20
20
|
<a href="/" class="back-link">← 返回主页</a>
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
|
-
<!--
|
|
24
|
-
<div class="
|
|
25
|
-
<
|
|
23
|
+
<!-- 标签切换 -->
|
|
24
|
+
<div class="api-tabs">
|
|
25
|
+
<button class="api-tab active" data-tab="llm" onclick="switchTab('llm')">
|
|
26
|
+
<span class="api-tab-icon">💬</span>
|
|
27
|
+
<span>LLM 对话</span>
|
|
28
|
+
</button>
|
|
29
|
+
<button class="api-tab" data-tab="video" onclick="switchTab('video')">
|
|
30
|
+
<span class="api-tab-icon">🎬</span>
|
|
31
|
+
<span>视频生成</span>
|
|
32
|
+
</button>
|
|
33
|
+
<button class="api-tab" data-tab="audio" onclick="switchTab('audio')">
|
|
34
|
+
<span class="api-tab-icon">🎵</span>
|
|
35
|
+
<span>音频生成</span>
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<!-- LLM 面板 -->
|
|
40
|
+
<div class="api-panel" id="panel-llm" data-panel="llm">
|
|
41
|
+
<div class="provider-list" id="providerList">
|
|
42
|
+
<div class="loading-state">加载中...</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- 视频生成面板 -->
|
|
47
|
+
<div class="api-panel" id="panel-video" data-panel="video" style="display: none;">
|
|
48
|
+
<div class="video-intro">
|
|
49
|
+
<p>视频生成走异步任务流(提交任务 → 轮询结果),与 LLM 实时对话不同。Seedance 由字节火山方舟 (ARK) 提供,支持文生视频 (t2v) 与图生视频 (i2v);MiniMax Video 为同 tab 下的另一选项。</p>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="provider-list" id="videoProviderList">
|
|
52
|
+
<div class="loading-state">加载中...</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<!-- 音频生成面板 -->
|
|
57
|
+
<div class="api-panel" id="panel-audio" data-panel="audio" style="display: none;">
|
|
58
|
+
<div class="video-intro">
|
|
59
|
+
<p>音频生成:TTS(文生语音)/ Music(文生音乐)。MiniMax 提供 speech-01 与 music-01 模型,TTS 默认走 OpenAI 兼容 <code>/audio/speech</code> 端点,Music 走 MiniMax 自有 <code>/music_generation</code> 端点。</p>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="provider-list" id="audioProviderList">
|
|
62
|
+
<div class="loading-state">加载中...</div>
|
|
63
|
+
</div>
|
|
26
64
|
</div>
|
|
27
65
|
|
|
28
66
|
<!-- 配置弹窗 -->
|
|
@@ -43,7 +81,7 @@
|
|
|
43
81
|
<div class="form-group">
|
|
44
82
|
<label>API Key</label>
|
|
45
83
|
<input type="password" id="apiKeyInput" placeholder="输入 API Key">
|
|
46
|
-
<p class="form-hint">已有 Key 会保留,输入新值以更新</p>
|
|
84
|
+
<p class="form-hint" id="apiKeyHint">已有 Key 会保留,输入新值以更新</p>
|
|
47
85
|
</div>
|
|
48
86
|
|
|
49
87
|
<div class="form-group">
|
|
@@ -52,11 +90,80 @@
|
|
|
52
90
|
<p class="form-hint">留空使用默认地址</p>
|
|
53
91
|
</div>
|
|
54
92
|
|
|
55
|
-
<div class="form-
|
|
93
|
+
<div class="form-group">
|
|
94
|
+
<label>模型</label>
|
|
95
|
+
<input type="text" id="modelInput" placeholder="如 gpt-4" list="modelSuggestList">
|
|
96
|
+
<datalist id="modelSuggestList"></datalist>
|
|
97
|
+
<p class="form-hint" id="modelHint"></p>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<!-- 视频模型专用字段 -->
|
|
101
|
+
<div class="video-only-fields" style="display: none;">
|
|
102
|
+
<div class="form-row">
|
|
103
|
+
<div class="form-group">
|
|
104
|
+
<label>分辨率</label>
|
|
105
|
+
<select id="resolutionInput">
|
|
106
|
+
<option value="480p">480p</option>
|
|
107
|
+
<option value="720p">720p</option>
|
|
108
|
+
<option value="1080p">1080p</option>
|
|
109
|
+
</select>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="form-group">
|
|
112
|
+
<label>宽高比</label>
|
|
113
|
+
<select id="ratioInput">
|
|
114
|
+
<option value="16:9">16:9</option>
|
|
115
|
+
<option value="9:16">9:16</option>
|
|
116
|
+
<option value="1:1">1:1</option>
|
|
117
|
+
<option value="4:3">4:3</option>
|
|
118
|
+
<option value="3:4">3:4</option>
|
|
119
|
+
<option value="21:9">21:9</option>
|
|
120
|
+
</select>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
56
123
|
<div class="form-group">
|
|
57
|
-
<label
|
|
58
|
-
<input type="
|
|
124
|
+
<label>默认时长(秒)</label>
|
|
125
|
+
<input type="number" id="durationInput" min="3" max="12" step="1" value="5">
|
|
126
|
+
<p class="form-hint">Seedance 支持 3-12 秒</p>
|
|
59
127
|
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<!-- 音频模型专用字段 -->
|
|
131
|
+
<div class="audio-only-fields" style="display: none;">
|
|
132
|
+
<div class="form-group">
|
|
133
|
+
<label>音色 / 风格</label>
|
|
134
|
+
<input type="text" id="voiceInput" placeholder="male-qn-jingying / female-shaonv ...">
|
|
135
|
+
<p class="form-hint" id="voiceHint"></p>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="form-row">
|
|
138
|
+
<div class="form-group">
|
|
139
|
+
<label>语速</label>
|
|
140
|
+
<input type="number" id="speedInput" min="0.5" max="2.0" step="0.1" value="1.0">
|
|
141
|
+
</div>
|
|
142
|
+
<div class="form-group">
|
|
143
|
+
<label>输出格式</label>
|
|
144
|
+
<select id="formatInput">
|
|
145
|
+
<option value="mp3">mp3</option>
|
|
146
|
+
<option value="pcm">pcm</option>
|
|
147
|
+
<option value="wav">wav</option>
|
|
148
|
+
</select>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="form-group" id="audioModeGroup" style="display: none;">
|
|
152
|
+
<label>模式</label>
|
|
153
|
+
<select id="modeInput">
|
|
154
|
+
<option value="instrumental">纯音乐</option>
|
|
155
|
+
<option value="lyrics">带歌词</option>
|
|
156
|
+
</select>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="form-group" id="audioDurationGroup" style="display: none;">
|
|
159
|
+
<label>默认时长(秒)</label>
|
|
160
|
+
<input type="number" id="audioDurationInput" min="5" max="120" step="1" value="30">
|
|
161
|
+
<p class="form-hint">music-01 支持 5-120 秒</p>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<!-- LLM 专用字段 -->
|
|
166
|
+
<div class="llm-only-fields">
|
|
60
167
|
<div class="form-group form-group-small">
|
|
61
168
|
<label>温度</label>
|
|
62
169
|
<input type="number" id="temperatureInput" value="0.7" min="0" max="2" step="0.1">
|
|
@@ -80,49 +187,115 @@
|
|
|
80
187
|
</div>
|
|
81
188
|
|
|
82
189
|
<script>
|
|
83
|
-
//
|
|
84
|
-
let
|
|
190
|
+
// 当前 tab
|
|
191
|
+
let currentTab = 'llm';
|
|
192
|
+
|
|
193
|
+
// 缓存三份配置
|
|
194
|
+
let llmConfigData = null;
|
|
195
|
+
let videoConfigData = null;
|
|
196
|
+
let audioConfigData = null;
|
|
85
197
|
let currentProvider = null;
|
|
198
|
+
let currentProviderType = 'llm'; // 'llm' | 'video' | 'audio'
|
|
199
|
+
|
|
200
|
+
// ==================== Tab 切换 ====================
|
|
201
|
+
function switchTab(tab) {
|
|
202
|
+
currentTab = tab;
|
|
203
|
+
document.querySelectorAll('.api-tab').forEach(btn => {
|
|
204
|
+
btn.classList.toggle('active', btn.dataset.tab === tab);
|
|
205
|
+
});
|
|
206
|
+
document.querySelectorAll('.api-panel').forEach(panel => {
|
|
207
|
+
panel.style.display = panel.dataset.panel === tab ? 'block' : 'none';
|
|
208
|
+
});
|
|
209
|
+
// 切换 tab 时更新计数 badge
|
|
210
|
+
updateCountBadge();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function updateCountBadge() {
|
|
214
|
+
const data = currentTab === 'llm' ? llmConfigData :
|
|
215
|
+
currentTab === 'video' ? videoConfigData : audioConfigData;
|
|
216
|
+
if (!data) {
|
|
217
|
+
document.getElementById('configCount').textContent = '加载中...';
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
let configured = 0, total = 0;
|
|
221
|
+
for (const key in data.providers) {
|
|
222
|
+
total++;
|
|
223
|
+
const p = data.providers[key];
|
|
224
|
+
if (currentTab === 'llm') {
|
|
225
|
+
if (p.enabled && p.apiKey) configured++;
|
|
226
|
+
} else {
|
|
227
|
+
const info = data.providerInfo[key] || {};
|
|
228
|
+
if (p.enabled && (!info.requiresApiKey || p.apiKey)) configured++;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const labelMap = { llm: '', video: '视频', audio: '音频' };
|
|
232
|
+
const label = labelMap[currentTab] || '';
|
|
233
|
+
document.getElementById('configCount').textContent = configured + '/' + total + ' ' + label + '已配置';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ==================== 加载 ====================
|
|
237
|
+
async function loadAll() {
|
|
238
|
+
await Promise.all([loadLLMConfig(), loadVideoConfig(), loadAudioConfig()]);
|
|
239
|
+
}
|
|
86
240
|
|
|
87
|
-
|
|
88
|
-
async function loadConfig() {
|
|
241
|
+
async function loadLLMConfig() {
|
|
89
242
|
try {
|
|
90
243
|
const resp = await fetch('/api/llm-config');
|
|
91
|
-
|
|
92
|
-
renderProviders();
|
|
244
|
+
llmConfigData = await resp.json();
|
|
245
|
+
renderProviders('llm');
|
|
246
|
+
if (currentTab === 'llm') updateCountBadge();
|
|
93
247
|
} catch (err) {
|
|
94
248
|
document.getElementById('providerList').innerHTML =
|
|
95
249
|
'<div class="error-state">加载失败: ' + err.message + '</div>';
|
|
96
250
|
}
|
|
97
251
|
}
|
|
98
252
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
253
|
+
async function loadVideoConfig() {
|
|
254
|
+
try {
|
|
255
|
+
const resp = await fetch('/api/video-config');
|
|
256
|
+
videoConfigData = await resp.json();
|
|
257
|
+
renderProviders('video');
|
|
258
|
+
if (currentTab === 'video') updateCountBadge();
|
|
259
|
+
} catch (err) {
|
|
260
|
+
document.getElementById('videoProviderList').innerHTML =
|
|
261
|
+
'<div class="error-state">加载失败: ' + err.message + '</div>';
|
|
262
|
+
}
|
|
263
|
+
}
|
|
104
264
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
265
|
+
async function loadAudioConfig() {
|
|
266
|
+
try {
|
|
267
|
+
const resp = await fetch('/api/audio-config');
|
|
268
|
+
audioConfigData = await resp.json();
|
|
269
|
+
renderProviders('audio');
|
|
270
|
+
if (currentTab === 'audio') updateCountBadge();
|
|
271
|
+
} catch (err) {
|
|
272
|
+
document.getElementById('audioProviderList').innerHTML =
|
|
273
|
+
'<div class="error-state">加载失败: ' + err.message + '</div>';
|
|
113
274
|
}
|
|
114
|
-
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ==================== 渲染 ====================
|
|
278
|
+
function renderProviders(type) {
|
|
279
|
+
const data = type === 'llm' ? llmConfigData :
|
|
280
|
+
type === 'video' ? videoConfigData : audioConfigData;
|
|
281
|
+
const listEl = document.getElementById(
|
|
282
|
+
type === 'llm' ? 'providerList' :
|
|
283
|
+
type === 'video' ? 'videoProviderList' : 'audioProviderList'
|
|
284
|
+
);
|
|
285
|
+
if (!data) return;
|
|
286
|
+
|
|
287
|
+
const providers = data.providers;
|
|
288
|
+
const info = data.providerInfo;
|
|
289
|
+
const isActive = (key) => data.activeProvider === key;
|
|
115
290
|
|
|
116
|
-
// 生成卡片
|
|
117
291
|
let html = '';
|
|
118
292
|
for (const key in providers) {
|
|
119
293
|
const p = providers[key];
|
|
120
294
|
const i = info[key] || { name: key, description: '供应商', requiresApiKey: true };
|
|
121
|
-
const
|
|
295
|
+
const active = isActive(key);
|
|
122
296
|
|
|
123
|
-
// 状态判断
|
|
124
297
|
let statusText, statusClass;
|
|
125
|
-
if (
|
|
298
|
+
if (active) {
|
|
126
299
|
statusText = '使用中';
|
|
127
300
|
statusClass = 'status-accent';
|
|
128
301
|
} else if (p.enabled && p.apiKey) {
|
|
@@ -136,16 +309,16 @@
|
|
|
136
309
|
statusClass = 'status-muted';
|
|
137
310
|
}
|
|
138
311
|
|
|
139
|
-
// 配置状态
|
|
140
312
|
const isConfigured = p.enabled && p.apiKey;
|
|
141
313
|
|
|
142
314
|
html += `
|
|
143
|
-
<div class="provider-card ${
|
|
315
|
+
<div class="provider-card ${active ? 'active' : ''}" onclick="openModal('${type}', '${key}')">
|
|
144
316
|
<div class="provider-header">
|
|
145
317
|
<div class="provider-icon">${i.name.charAt(0).toUpperCase()}</div>
|
|
146
318
|
<div class="provider-info">
|
|
147
319
|
<h3 class="provider-name">${i.name}</h3>
|
|
148
320
|
<p class="provider-desc">${i.description}</p>
|
|
321
|
+
${i.docs ? '<a class="provider-docs" href="' + i.docs + '" target="_blank" onclick="event.stopPropagation()">📖 文档</a>' : ''}
|
|
149
322
|
</div>
|
|
150
323
|
<div class="status-badge ${statusClass}">
|
|
151
324
|
<span class="status-dot"></span>
|
|
@@ -163,35 +336,86 @@
|
|
|
163
336
|
</div>
|
|
164
337
|
`;
|
|
165
338
|
}
|
|
166
|
-
|
|
339
|
+
listEl.innerHTML = html;
|
|
167
340
|
}
|
|
168
341
|
|
|
169
|
-
//
|
|
170
|
-
function openModal(providerKey) {
|
|
342
|
+
// ==================== 弹窗 ====================
|
|
343
|
+
function openModal(type, providerKey) {
|
|
344
|
+
currentProviderType = type;
|
|
171
345
|
currentProvider = providerKey;
|
|
172
|
-
const
|
|
173
|
-
|
|
346
|
+
const data = type === 'llm' ? llmConfigData :
|
|
347
|
+
type === 'video' ? videoConfigData : audioConfigData;
|
|
348
|
+
const p = data.providers[providerKey];
|
|
349
|
+
const i = data.providerInfo[providerKey] || { name: providerKey, description: '供应商' };
|
|
174
350
|
|
|
175
351
|
document.getElementById('modalIcon').textContent = i.name.charAt(0).toUpperCase();
|
|
176
352
|
document.getElementById('modalTitle').textContent = '配置 ' + i.name;
|
|
177
353
|
document.getElementById('modalSubtitle').textContent = i.description;
|
|
178
354
|
|
|
179
355
|
document.getElementById('apiKeyInput').value = '';
|
|
356
|
+
document.getElementById('apiKeyHint').textContent = p.apiKey
|
|
357
|
+
? '当前已配置 (***' + (p.apiKey.slice(-4) || '') + '),输入新值以更新'
|
|
358
|
+
: '输入 API Key';
|
|
180
359
|
document.getElementById('baseUrlInput').value = p.baseUrl || '';
|
|
181
360
|
document.getElementById('modelInput').value = p.model || '';
|
|
182
|
-
|
|
361
|
+
|
|
362
|
+
// 填充模型下拉建议(仅 LLM 类型有 providerInfo.models)
|
|
363
|
+
const datalist = document.getElementById('modelSuggestList');
|
|
364
|
+
datalist.innerHTML = '';
|
|
365
|
+
if (type === 'llm' && i.models && Array.isArray(i.models)) {
|
|
366
|
+
for (const m of i.models) {
|
|
367
|
+
const opt = document.createElement('option');
|
|
368
|
+
opt.value = m;
|
|
369
|
+
datalist.appendChild(opt);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 视频/音频/LLM 专用字段显隐
|
|
374
|
+
const isVideo = type === 'video';
|
|
375
|
+
const isAudio = type === 'audio';
|
|
376
|
+
document.querySelector('.video-only-fields').style.display = isVideo ? 'block' : 'none';
|
|
377
|
+
document.querySelector('.audio-only-fields').style.display = isAudio ? 'block' : 'none';
|
|
378
|
+
document.querySelector('.llm-only-fields').style.display = (!isVideo && !isAudio) ? 'block' : 'none';
|
|
379
|
+
|
|
380
|
+
if (isVideo) {
|
|
381
|
+
document.getElementById('resolutionInput').value = p.resolution || '720p';
|
|
382
|
+
document.getElementById('ratioInput').value = p.ratio || '16:9';
|
|
383
|
+
document.getElementById('durationInput').value = p.duration || 5;
|
|
384
|
+
document.getElementById('modelHint').textContent = '示例: doubao-seedance-1-0-lite-t2v-250428 (文生视频) 或 ...-i2v-... (图生视频)';
|
|
385
|
+
} else if (isAudio) {
|
|
386
|
+
const isMusic = (i.kind === 'music');
|
|
387
|
+
// TTS 字段
|
|
388
|
+
document.getElementById('voiceInput').parentElement.style.display = isMusic ? 'none' : 'block';
|
|
389
|
+
document.getElementById('speedInput').parentElement.parentElement.style.display = isMusic ? 'none' : 'flex';
|
|
390
|
+
document.getElementById('modeInput').parentElement.style.display = isMusic ? 'block' : 'none';
|
|
391
|
+
document.getElementById('audioDurationInput').parentElement.style.display = isMusic ? 'block' : 'none';
|
|
392
|
+
|
|
393
|
+
if (!isMusic) {
|
|
394
|
+
document.getElementById('voiceInput').value = p.voice || 'male-qn-jingying';
|
|
395
|
+
document.getElementById('voiceHint').textContent = '常用音色: male-qn-jingying, female-shaonv, female-yujie, presenter_male, presenter_female';
|
|
396
|
+
document.getElementById('speedInput').value = p.speed || 1.0;
|
|
397
|
+
document.getElementById('formatInput').value = p.format || 'mp3';
|
|
398
|
+
} else {
|
|
399
|
+
document.getElementById('modeInput').value = p.mode || 'instrumental';
|
|
400
|
+
document.getElementById('audioDurationInput').value = p.duration || 30;
|
|
401
|
+
}
|
|
402
|
+
document.getElementById('modelHint').textContent = isMusic
|
|
403
|
+
? '示例: music-01'
|
|
404
|
+
: '示例: speech-01 (TTS) / asr-01 (语音转写)';
|
|
405
|
+
} else {
|
|
406
|
+
document.getElementById('temperatureInput').value = p.temperature || 0.7;
|
|
407
|
+
}
|
|
183
408
|
|
|
184
409
|
document.getElementById('testResult').style.display = 'none';
|
|
185
410
|
document.getElementById('configModal').style.display = 'flex';
|
|
186
411
|
}
|
|
187
412
|
|
|
188
|
-
// 关闭弹窗
|
|
189
413
|
function closeModal() {
|
|
190
414
|
document.getElementById('configModal').style.display = 'none';
|
|
191
415
|
currentProvider = null;
|
|
192
416
|
}
|
|
193
417
|
|
|
194
|
-
// 测试连接
|
|
418
|
+
// ==================== 测试连接 ====================
|
|
195
419
|
async function testConnection() {
|
|
196
420
|
const btn = document.getElementById('testBtn');
|
|
197
421
|
const result = document.getElementById('testResult');
|
|
@@ -200,8 +424,12 @@
|
|
|
200
424
|
btn.innerHTML = '<span class="spinner"></span> 测试中...';
|
|
201
425
|
result.style.display = 'none';
|
|
202
426
|
|
|
427
|
+
const endpoint = currentProviderType === 'llm' ? '/api/llm-test' :
|
|
428
|
+
currentProviderType === 'video' ? '/api/video-test' :
|
|
429
|
+
'/api/audio-test';
|
|
430
|
+
|
|
203
431
|
try {
|
|
204
|
-
const resp = await fetch(
|
|
432
|
+
const resp = await fetch(endpoint, {
|
|
205
433
|
method: 'POST',
|
|
206
434
|
headers: { 'Content-Type': 'application/json' },
|
|
207
435
|
body: JSON.stringify({ provider: currentProvider })
|
|
@@ -222,44 +450,71 @@
|
|
|
222
450
|
btn.innerHTML = '⚡ 测试连接';
|
|
223
451
|
}
|
|
224
452
|
|
|
225
|
-
//
|
|
453
|
+
// ==================== 保存 ====================
|
|
226
454
|
document.getElementById('configForm').onsubmit = async function(e) {
|
|
227
455
|
e.preventDefault();
|
|
228
456
|
|
|
457
|
+
const data = currentProviderType === 'llm' ? llmConfigData : videoConfigData;
|
|
458
|
+
const existing = data.providers[currentProvider];
|
|
459
|
+
const apiKeyVal = document.getElementById('apiKeyInput').value;
|
|
460
|
+
|
|
229
461
|
const updateData = {
|
|
230
462
|
enabled: true,
|
|
231
|
-
apiKey:
|
|
232
|
-
baseUrl: document.getElementById('baseUrlInput').value ||
|
|
233
|
-
model: document.getElementById('modelInput').value ||
|
|
234
|
-
temperature: parseFloat(document.getElementById('temperatureInput').value) || 0.7
|
|
463
|
+
apiKey: apiKeyVal || existing.apiKey || '',
|
|
464
|
+
baseUrl: document.getElementById('baseUrlInput').value || existing.baseUrl || '',
|
|
465
|
+
model: document.getElementById('modelInput').value || existing.model || ''
|
|
235
466
|
};
|
|
236
467
|
|
|
468
|
+
if (currentProviderType === 'video') {
|
|
469
|
+
updateData.resolution = document.getElementById('resolutionInput').value;
|
|
470
|
+
updateData.ratio = document.getElementById('ratioInput').value;
|
|
471
|
+
updateData.duration = parseInt(document.getElementById('durationInput').value) || 5;
|
|
472
|
+
} else if (currentProviderType === 'audio') {
|
|
473
|
+
const info = audioConfigData.providerInfo[currentProvider] || {};
|
|
474
|
+
const isMusic = info.kind === 'music';
|
|
475
|
+
if (isMusic) {
|
|
476
|
+
updateData.mode = document.getElementById('modeInput').value;
|
|
477
|
+
updateData.duration = parseInt(document.getElementById('audioDurationInput').value) || 30;
|
|
478
|
+
} else {
|
|
479
|
+
updateData.voice = document.getElementById('voiceInput').value;
|
|
480
|
+
updateData.speed = parseFloat(document.getElementById('speedInput').value) || 1.0;
|
|
481
|
+
updateData.format = document.getElementById('formatInput').value;
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
updateData.temperature = parseFloat(document.getElementById('temperatureInput').value) || 0.7;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const endpoint = currentProviderType === 'llm' ? '/api/llm-config' :
|
|
488
|
+
currentProviderType === 'video' ? '/api/video-config' :
|
|
489
|
+
'/api/audio-config';
|
|
490
|
+
|
|
237
491
|
try {
|
|
238
|
-
await fetch(
|
|
492
|
+
await fetch(endpoint, {
|
|
239
493
|
method: 'POST',
|
|
240
494
|
headers: { 'Content-Type': 'application/json' },
|
|
241
495
|
body: JSON.stringify({ provider: currentProvider, config: updateData })
|
|
242
496
|
});
|
|
243
497
|
|
|
244
|
-
// 显示保存成功
|
|
245
498
|
const saveBtn = this.querySelector('.btn-save');
|
|
246
499
|
saveBtn.textContent = '✓ 已保存';
|
|
247
500
|
setTimeout(() => {
|
|
248
501
|
closeModal();
|
|
249
|
-
|
|
502
|
+
if (currentProviderType === 'llm') loadLLMConfig();
|
|
503
|
+
else if (currentProviderType === 'video') loadVideoConfig();
|
|
504
|
+
else loadAudioConfig();
|
|
250
505
|
}, 500);
|
|
251
506
|
} catch (err) {
|
|
252
507
|
alert('保存失败: ' + err.message);
|
|
253
508
|
}
|
|
254
509
|
};
|
|
255
510
|
|
|
256
|
-
//
|
|
511
|
+
// ==================== 关闭弹窗 ====================
|
|
257
512
|
document.getElementById('configModal').onclick = function(e) {
|
|
258
513
|
if (e.target === this) closeModal();
|
|
259
514
|
};
|
|
260
515
|
|
|
261
516
|
// 启动
|
|
262
|
-
|
|
517
|
+
loadAll();
|
|
263
518
|
</script>
|
|
264
519
|
</body>
|
|
265
|
-
</html>
|
|
520
|
+
</html>
|