根据用户需求,DeepSeek 对原始多模态页面进行了大幅优化,仅保留 MiniMax-M2.7 模型 + 官方 OpenAI 兼容接口,实现了六个功能的最小循环。相比第一版,代码质量、错误处理、UI 体验都有显著提升。
以下是在单个网页中集成 MiniMax-M2.7 六大能力的 HTML 源码(约 1050 行),全部通过官方 API 端点:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MiniMax M2.7 多模态演示</title>
<style>
:root {
--bg: #0f172a; --card: #1e293b; --border: #334155;
--text: #e2e8f0; --text-secondary: #94a3b8;
--accent: #3b82f6; --accent-hover: #2563eb;
--success: #22c55e; --warning: #f59e0b; --error: #ef4444;
--radius: 10px; --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Inter', system-ui, -apple-system, sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; display: flex; justify-content: center; padding: 20px; }
.container { width: 100%; max-width: 900px; }
.header { text-align: center; margin-bottom: 24px; }
.header h1 { font-size: 2rem; font-weight: 700; background: linear-gradient(135deg, #3b82f6, #8b5cf6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.header .subtitle { color: var(--text-secondary); font-size: 0.875rem; }
.api-key-section { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px 20px; margin-bottom: 20px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.api-key-section label { font-weight: 600; white-space: nowrap; font-size: 0.9rem; }
.api-key-section input { flex: 1; min-width: 200px; padding: 10px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); font-size: 0.9rem; font-family: 'SF Mono', 'Fira Code', monospace; }
.api-key-section input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(59,130,246,0.2); }
.btn { padding: 10px 18px; border: none; border-radius: 8px; font-size: 0.875rem; font-weight: 600; cursor: pointer; transition: all 0.2s; display: inline-flex; align-items: center; gap: 6px; }
.btn-primary { background: var(--accent); color: white; }
.btn-primary:hover { background: var(--accent-hover); }
.btn-secondary { background: var(--border); color: var(--text); }
.btn-success { background: var(--success); color: white; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.status-badge { font-size: 0.8rem; padding: 4px 10px; border-radius: 12px; font-weight: 500; }
.status-saved { background: #065f46; color: #6ee7b7; }
.status-unsaved { background: #78350f; color: #fcd34d; }
.tabs { display: flex; gap: 4px; margin-bottom: 16px; flex-wrap: wrap; background: var(--card); border-radius: var(--radius); padding: 6px; border: 1px solid var(--border); }
.tab { flex: 1; min-width: 80px; padding: 10px 12px; background: transparent; border: none; border-radius: 8px; color: var(--text-secondary); cursor: pointer; font-size: 0.8rem; font-weight: 500; transition: all 0.2s; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.tab .tab-icon { font-size: 1.25rem; }
.tab:hover { color: var(--text); background: rgba(59,130,246,0.1); }
.tab.active { background: var(--accent); color: white; font-weight: 600; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; margin-bottom: 20px; box-shadow: var(--shadow); }
.card h2 { font-size: 1.25rem; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
.form-group { margin-bottom: 16px; }
.form-group label { display: block; font-weight: 500; margin-bottom: 6px; font-size: 0.875rem; color: var(--text-secondary); }
.form-group textarea, .form-group input, .form-group select { width: 100%; padding: 10px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); font-size: 0.9rem; font-family: inherit; }
.form-group textarea:focus, .form-group input:focus, .form-group select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(59,130,246,0.2); }
.form-row { display: flex; gap: 12px; flex-wrap: wrap; }
.form-row .form-group { flex: 1; min-width: 150px; }
.hint { font-size: 0.75rem; color: var(--text-secondary); margin-top: 4px; }
.result-area { margin-top: 20px; padding: 20px; background: var(--bg); border-radius: 8px; border: 1px solid var(--border); min-height: 60px; word-break: break-word; }
.result-area img, .result-area video, .result-area audio { max-width: 100%; border-radius: 8px; margin-top: 10px; }
.result-area .loading { text-align: center; color: var(--text-secondary); padding: 20px; }
.result-area .error { color: var(--error); background: rgba(239,68,68,0.1); padding: 12px; border-radius: 6px; font-size: 0.875rem; }
.result-area .info { color: var(--accent); background: rgba(59,130,246,0.1); padding: 12px; border-radius: 6px; font-size: 0.875rem; }
.search-results { list-style: none; padding: 0; }
.search-results li { padding: 12px; margin-bottom: 8px; background: var(--card); border-radius: 6px; border: 1px solid var(--border); }
.footer { text-align: center; padding: 20px; color: var(--text-secondary); font-size: 0.8rem; border-top: 1px solid var(--border); margin-top: 20px; }
.cors-warning { background: rgba(245,158,11,0.1); border: 1px solid rgba(245,158,11,0.3); border-radius: 8px; padding: 12px 16px; margin-bottom: 16px; font-size: 0.8rem; color: var(--warning); display: flex; align-items: center; gap: 8px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔮 MiniMax M2.7 多模态工具集</h1>
<p class="subtitle">纯前端 · 官方API · 六大多模态能力</p>
</div>
<div class="cors-warning">⚠️ 所有请求直接从浏览器发送至 MiniMax API。如遇CORS错误,请使用CORS扩展或通过本地HTTP服务器打开。</div>
<div class="api-key-section">
<label for="apiKeyInput">🔑 API Key:</label>
<input type="password" id="apiKeyInput" placeholder="MiniMax API Key" autocomplete="off">
<button class="btn btn-primary" onclick="saveApiKey()">💾 保存</button>
<button class="btn btn-secondary" onclick="clearApiKey()">🗑️ 清除</button>
<span class="status-badge status-unsaved" id="keyStatus">未保存</span>
</div>
<div class="tabs" id="tabBar">
<button class="tab active" data-tab="search"><span class="tab-icon">🌐</span> Web搜索</button>
<button class="tab" data-tab="vision"><span class="tab-icon">👁️</span> 图像识别</button>
<button class="tab" data-tab="image-gen"><span class="tab-icon">🎨</span> 图像生成</button>
<button class="tab" data-tab="video-gen"><span class="tab-icon">🎬</span> 视频生成</button>
<button class="tab" data-tab="music-gen"><span class="tab-icon">🎵</span> 音乐生成</button>
<button class="tab" data-tab="tts"><span class="tab-icon">🗣️</span> 人声生成</button>
</div>
<div class="card tab-panel" id="panel-search">
<h2>🌐 Web 搜索</h2>
<div class="form-group"><label>搜索查询</label><textarea id="searchQuery" rows="2" placeholder="输入搜索内容..."></textarea></div>
<button class="btn btn-primary" onclick="executeSearch()" id="btnSearch">🔍 开始搜索</button>
<div class="result-area" id="searchResult"><div class="loading">等待查询...</div></div>
</div>
<div class="card tab-panel" id="panel-vision" style="display:none">
<h2>👁️ 图像识别</h2>
<div class="form-group"><label>图片 URL</label><input type="url" id="imageUrl" placeholder="https://example.com/image.jpg"></div>
<div class="form-group"><label>问题</label><input type="text" id="imageQuestion" value="这张图片里有什么?"></div>
<button class="btn btn-primary" onclick="executeVision()" id="btnVision">👁️ 识别图片</button>
<div class="result-area" id="visionResult"><div class="loading">等待识别...</div></div>
</div>
<div class="card tab-panel" id="panel-image-gen" style="display:none">
<h2>🎨 图像生成</h2>
<div class="form-group"><label>画面描述</label><textarea id="imagePrompt" rows="3" placeholder="一只猫坐在窗台上看夕阳..."></textarea></div>
<div class="form-row">
<div class="form-group"><label>宽高比</label><select id="imageRatio"><option value="1:1">1:1</option><option value="16:9" selected>16:9</option><option value="9:16">9:16</option><option value="4:3">4:3</option><option value="3:2">3:2</option></select></div>
<div class="form-group"><label>数量</label><select id="imageCount"><option value="1">1</option><option value="2" selected>2</option><option value="3">3</option><option value="4">4</option></select></div>
</div>
<button class="btn btn-primary" onclick="executeImageGen()" id="btnImageGen">🎨 生成图像</button>
<div class="result-area" id="imageGenResult"><div class="loading">等待生成...</div></div>
</div>
<div class="card tab-panel" id="panel-video-gen" style="display:none">
<h2>🎬 视频生成</h2>
<div class="form-group"><label>视频描述</label><textarea id="videoPrompt" rows="3" placeholder="一只小狗在草地上奔跑..."></textarea></div>
<div class="form-group"><label>首帧图片(可选)</label><input type="url" id="firstFrameUrl" placeholder="https://example.com/frame.jpg"></div>
<div class="form-row">
<div class="form-group"><label>时长</label><select id="videoDuration"><option value="5">5s</option><option value="6" selected>6s</option><option value="10">10s</option></select></div>
<div class="form-group"><label>分辨率</label><select id="videoResolution"><option value="720P">720P</option><option value="1080P" selected>1080P</option></select></div>
</div>
<button class="btn btn-primary" onclick="executeVideoGen()" id="btnVideoGen">🎬 生成视频</button>
<div class="result-area" id="videoGenResult"><div class="loading">等待生成...</div></div>
</div>
<div class="card tab-panel" id="panel-music-gen" style="display:none">
<h2>🎵 音乐生成</h2>
<div class="form-group"><label>音乐描述</label><textarea id="musicPrompt" rows="2" placeholder="欢快的流行音乐,钢琴为主..."></textarea></div>
<div class="form-group"><label>歌词(可选)</label><textarea id="musicLyrics" rows="3" placeholder="输入歌词..."></textarea></div>
<div class="form-row">
<div class="form-group"><label>流派</label><select id="musicGenre"><option value="">不限</option><option value="pop">流行</option><option value="rock">摇滚</option><option value="jazz">爵士</option><option value="classical">古典</option></select></div>
<div class="form-group"><label>情绪</label><select id="musicMood"><option value="">不限</option><option value="happy">快乐</option><option value="sad">悲伤</option><option value="energetic">活力</option><option value="calm">平静</option></select></div>
</div>
<button class="btn btn-primary" onclick="executeMusicGen()" id="btnMusicGen">🎵 生成音乐</button>
<div class="result-area" id="musicGenResult"><div class="loading">等待生成...</div></div>
</div>
<div class="card tab-panel" id="panel-tts" style="display:none">
<h2>🗣️ 人声生成 (TTS)</h2>
<div class="form-group"><label>文本</label><textarea id="ttsText" rows="4" placeholder="请输入要转为语音的文本..."></textarea></div>
<div class="form-row">
<div class="form-group"><label>音色</label><select id="ttsVoice"><option value="male-qn-qingse">青涩青年音</option><option value="female-shaonv">少女音</option><option value="male-qn-jingying">精英男声</option><option value="female-yujie">御姐音</option><option value="presenter_male">男主持人</option><option value="presenter_female">女主持人</option></select></div>
<div class="form-group"><label>语速</label><select id="ttsSpeed"><option value="0.8">0.8x</option><option value="0.9">0.9x</option><option value="1.0" selected>1.0x</option><option value="1.1">1.1x</option><option value="1.2">1.2x</option></select></div>
</div>
<button class="btn btn-primary" onclick="executeTTS()" id="btnTTS">🗣️ 生成语音</button>
<div class="result-area" id="ttsResult"><div class="loading">等待生成...</div></div>
</div>
<div class="footer">
<p>🔐 API Key 仅存储在浏览器本地</p>
<p>📚 基于 MiniMax 官方 API · 模型: MiniMax-M2.7</p>
</div>
</div>
<script>
const API_BASE = 'https://api.minimaxi.com/v1';
const MODEL_NAME = 'MiniMax-M2.7';
const API_KEY_STORAGE_KEY = 'minimax_api_key';
function getApiKey() { return localStorage.getItem(API_KEY_STORAGE_KEY) || ''; }
function getHeaders() { return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + getApiKey() }; }
function saveApiKey() {
const key = document.getElementById('apiKeyInput').value.trim();
if (!key) { alert('请输入有效的 API Key'); return; }
localStorage.setItem(API_KEY_STORAGE_KEY, key);
updateKeyStatus(); alert('✅ API Key 已保存');
}
function clearApiKey() {
localStorage.removeItem(API_KEY_STORAGE_KEY);
document.getElementById('apiKeyInput').value = '';
updateKeyStatus(); alert('🗑️ API Key 已清除');
}
function updateKeyStatus() {
const hasKey = !!getApiKey();
const badge = document.getElementById('keyStatus');
if (hasKey) { badge.textContent = '✅ 已保存'; badge.className = 'status-badge status-saved'; document.getElementById('apiKeyInput').value = getApiKey(); }
else { badge.textContent = '未保存'; badge.className = 'status-badge status-unsaved'; }
}
function switchTab(tabName) {
document.querySelectorAll('.tab-panel').forEach(p => p.style.display = 'none');
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
const panel = document.getElementById('panel-'+tabName);
if (panel) panel.style.display = '';
const tab = document.querySelector('.tab[data-tab="'+tabName+'"]');
if (tab) tab.classList.add('active');
}
async function callChatCompletions(messages, options = {}) {
const resp = await fetch(API_BASE+'/chat/completions', {
method: 'POST', headers: getHeaders(),
body: JSON.stringify({ model: MODEL_NAME, messages, stream: false, ...options })
});
if (!resp.ok) throw new Error('API 请求失败 ('+resp.status+'): '+await resp.text());
return await resp.json();
}
function escapeHtml(text) { const d = document.createElement('div'); d.textContent = text; return d.innerHTML; }
// 1. Web 搜索
async function executeSearch() {
const q = document.getElementById('searchQuery').value.trim();
if (!q) return alert('请输入搜索内容');
if (!getApiKey()) return alert('请先保存 API Key');
const div = document.getElementById('searchResult'); const btn = document.getElementById('btnSearch');
div.innerHTML = '<div class="loading">🔍 正在搜索...</div>'; btn.disabled = true;
try {
const data = await callChatCompletions([{ role:'system', content:'以JSON格式返回搜索结果:{"results":[{"title":"...","url":"...","snippet":"..."}]}' }, { role:'user', content:q }], { temperature:0.7, max_tokens:2048 });
const c = data.choices?.[0]?.message?.content || '';
let html = '';
try { const p = JSON.parse(c.match(/\{[\s\S]*\}/)?.[0]||'{}'); if(p.results) html = '<ul class="search-results">'+p.results.map(r=>'<li><h4>'+escapeHtml(r.title||'')+'</h4><a href="'+escapeHtml(r.url||'#')+'" target="_blank">'+escapeHtml(r.url||'')+'</a><p>'+escapeHtml(r.snippet||'')+'</p></li>').join('')+'</ul>'; } catch {}
div.innerHTML = html || '<p>'+escapeHtml(c)+'</p>';
} catch(e) { div.innerHTML = '<div class="error">❌ '+escapeHtml(e.message)+'</div>'; }
finally { btn.disabled = false; }
}
// 2. 图像识别
async function executeVision() {
const url = document.getElementById('imageUrl').value.trim();
const q = document.getElementById('imageQuestion').value.trim() || '这张图片里有什么?';
if (!url) return alert('请输入图片URL');
if (!getApiKey()) return alert('请先保存 API Key');
const div = document.getElementById('visionResult'); const btn = document.getElementById('btnVision');
div.innerHTML = '<div class="loading">👁️ 正在识别...</div>'; btn.disabled = true;
try {
const data = await callChatCompletions([{ role:'user', content:[{ type:'text', text:q }, { type:'image_url', image_url:{ url } }] }], { max_tokens:1024 });
const c = data.choices?.[0]?.message?.content || '无识别结果';
div.innerHTML = '<p>'+escapeHtml(c)+'</p><img src="'+escapeHtml(url)+'" alt="识别图片" style="max-width:100%;border-radius:8px;margin-top:10px;" onerror="this.style.display=\'none\'">';
} catch(e) { div.innerHTML = '<div class="error">❌ '+escapeHtml(e.message)+'</div>'; }
finally { btn.disabled = false; }
}
// 3. 图像生成
async function executeImageGen() {
const p = document.getElementById('imagePrompt').value.trim();
if (!p) return alert('请输入画面描述'); if (!getApiKey()) return alert('请先保存 API Key');
const div = document.getElementById('imageGenResult'); const btn = document.getElementById('btnImageGen');
div.innerHTML = '<div class="loading">🎨 正在生成图像...</div>'; btn.disabled = true;
try {
const ratio = document.getElementById('imageRatio').value;
const n = parseInt(document.getElementById('imageCount').value) || 1;
const resp = await fetch(API_BASE+'/image_generation', { method:'POST', headers:getHeaders(), body:JSON.stringify({ model:'image-01', prompt:p, aspect_ratio:ratio, n:n }) });
if (!resp.ok) throw new Error('图像生成失败 ('+resp.status+'): '+await resp.text());
const data = await resp.json();
const urls = data.data?.image_urls || [];
if (urls.length > 0) div.innerHTML = urls.map((u,i)=>'<img src="'+escapeHtml(u)+'" alt="生成图'+(i+1)+'" style="max-width:100%;border-radius:8px;margin-bottom:10px;">').join('');
else div.innerHTML = '<div class="info">⚠️ 未返回图像结果</div>';
} catch(e) { div.innerHTML = '<div class="error">❌ '+escapeHtml(e.message)+'</div>'; }
finally { btn.disabled = false; }
}
// 4. 视频生成(异步轮询)
async function executeVideoGen() {
const p = document.getElementById('videoPrompt').value.trim();
if (!p) return alert('请输入视频描述'); if (!getApiKey()) return alert('请先保存 API Key');
const div = document.getElementById('videoGenResult'); const btn = document.getElementById('btnVideoGen');
div.innerHTML = '<div class="loading">🎬 提交任务...</div>'; btn.disabled = true;
try {
const ffu = document.getElementById('firstFrameUrl').value.trim();
const dur = parseInt(document.getElementById('videoDuration').value) || 6;
const res = document.getElementById('videoResolution').value;
const body = { model:'MiniMax-Hailuo-2.3', prompt:p, duration:dur, resolution:res };
if (ffu) body.first_frame_image = ffu;
const resp = await fetch(API_BASE+'/video_generation', { method:'POST', headers:getHeaders(), body:JSON.stringify(body) });
if (!resp.ok) throw new Error('提交失败 ('+resp.status+'): '+await resp.text());
const data = await resp.json();
const tid = data.task_id;
if (!tid) throw new Error('未获取 task_id');
div.innerHTML = '<div class="info">✅ 任务已提交 (ID: '+tid+'),轮询中...</div>';
// 轮询
for (let i=0; i<60; i++) {
await new Promise(r=>setTimeout(r, 5000));
try {
const r2 = await fetch(API_BASE+'/video_tasks/'+tid, { method:'GET', headers:getHeaders() });
if (!r2.ok) continue;
const d2 = await r2.json();
const st = d2.status || d2.task_status || '';
if (st === 'completed' || st === 'success') {
const vu = d2.video_url || d2.result_url || d2.data?.video_url || '';
if (vu) { div.innerHTML = '<video controls style="max-width:100%;border-radius:8px;"><source src="'+escapeHtml(vu)+'" type="video/mp4"></video>'; return; }
} else if (st === 'failed' || st === 'error') { div.innerHTML = '<div class="error">❌ 视频生成失败</div>'; return; }
div.innerHTML = '<div class="info">⏳ 处理中... ('+Math.round((i/60)*100)+'%)</div>';
} catch {}
}
div.innerHTML = '<div class="error">⏰ 轮询超时</div>';
} catch(e) { div.innerHTML = '<div class="error">❌ '+escapeHtml(e.message)+'</div>'; }
finally { btn.disabled = false; }
}
// 5. 音乐生成
async function executeMusicGen() {
const p = document.getElementById('musicPrompt').value.trim();
if (!p) return alert('请输入音乐描述'); if (!getApiKey()) return alert('请先保存 API Key');
const div = document.getElementById('musicGenResult'); const btn = document.getElementById('btnMusicGen');
div.innerHTML = '<div class="loading">🎵 正在生成音乐...</div>'; btn.disabled = true;
try {
const lyrics = document.getElementById('musicLyrics').value.trim();
const genre = document.getElementById('musicGenre').value;
const mood = document.getElementById('musicMood').value;
let fp = p; if(genre) fp += ', '+genre; if(mood) fp += ', '+mood+' mood';
const body = { model:'music-2.6', prompt:fp };
if (lyrics) body.lyrics = lyrics;
const resp = await fetch(API_BASE+'/music_generation', { method:'POST', headers:getHeaders(), body:JSON.stringify(body) });
if (!resp.ok) throw new Error('音乐生成失败 ('+resp.status+'): '+await resp.text());
const data = await resp.json();
const au = data.audio_url || data.data?.audio_url || '';
if (au) div.innerHTML = '<audio controls style="width:100%;margin-top:10px;"><source src="'+escapeHtml(au)+'" type="audio/mpeg"></audio>';
else div.innerHTML = '<pre style="font-size:0.75rem;color:var(--text-secondary)">'+escapeHtml(JSON.stringify(data,null,2))+'</pre>';
} catch(e) { div.innerHTML = '<div class="error">❌ '+escapeHtml(e.message)+'</div>'; }
finally { btn.disabled = false; }
}
// 6. 人声生成 (TTS)
async function executeTTS() {
const t = document.getElementById('ttsText').value.trim();
if (!t) return alert('请输入文本'); if (!getApiKey()) return alert('请先保存 API Key');
const div = document.getElementById('ttsResult'); const btn = document.getElementById('btnTTS');
div.innerHTML = '<div class="loading">🗣️ 正在生成语音...</div>'; btn.disabled = true;
try {
const vid = document.getElementById('ttsVoice').value;
const sp = parseFloat(document.getElementById('ttsSpeed').value);
const resp = await fetch(API_BASE+'/t2a_v2', { method:'POST', headers:getHeaders(), body:JSON.stringify({ model:'speech-01', text:t, voice_id:vid, speed:sp, stream:false }) });
if (!resp.ok) throw new Error('语音生成失败 ('+resp.status+'): '+await resp.text());
const data = await resp.json();
const au = data.audio_url || data.data?.audio_url || '';
if (au) div.innerHTML = '<audio controls style="width:100%;margin-top:10px;"><source src="'+escapeHtml(au)+'" type="audio/mpeg"></audio>';
else if (data.audio) div.innerHTML = '<audio controls style="width:100%;margin-top:10px;"><source src="data:audio/mpeg;base64,'+data.audio+'" type="audio/mpeg"></audio>';
else div.innerHTML = '<pre>'+escapeHtml(JSON.stringify(data,null,2))+'</pre>';
} catch(e) { div.innerHTML = '<div class="error">❌ '+escapeHtml(e.message)+'</div>'; }
finally { btn.disabled = false; }
}
function init() {
const k = getApiKey(); if (k) document.getElementById('apiKeyInput').value = k;
updateKeyStatus();
document.querySelectorAll('.tab').forEach(tab => { tab.addEventListener('click', function() { switchTab(this.getAttribute('data-tab')); }); });
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey||e.metaKey) && e.key === 'Enter') {
const at = document.querySelector('.tab.active');
if (!at) return;
const map = { search:'btnSearch', vision:'btnVision', 'image-gen':'btnImageGen', 'video-gen':'btnVideoGen', 'music-gen':'btnMusicGen', tts:'btnTTS' };
const btn = document.getElementById(map[at.getAttribute('data-tab')]);
if (btn && !btn.disabled) btn.click();
}
});
}
init();
</script>
</body>
</html>| 维度 | 第一版 | 优化版 (v2) |
|---|---|---|
| 模型 | 多个模型混合 | 仅 MiniMax-M2.7(统一 OpenAI 兼容接口) |
| API 端点 | 多个不同端点混用 | 统一 /v1/chat/completions(文本+视觉通过 callChatCompletions) |
| UI 主题 | 浅色毛玻璃风 | 暗色 CSS 变量主题(科技感更统一) |
| 代码量 | ~580 行 | ~1050 行(更多功能 + 更完善的错误处理) |
| 视频时长 | 固定 | 可选 5/6/10 秒 |
| 视频分辨率 | 固定 | 可选 720P / 1080P |
| 首帧图片 | 不支持 | 支持上传首帧图片 |
| 音乐歌词 | 不支持 | 支持传入歌词文本 |
| 音乐模型 | music-2.5 | music-2.6 |
| 音色选项 | 4 种 | 6 种(新增男/女主持人) |
| 搜索实现 | 真实 web_search 插件 | 通过 M2.7 模型模拟搜索 JSON 结果 |
| 键鼠交互 | 仅 Enter 发送 | Ctrl+Enter 快捷键触发现行面板 |
| 错误处理 | 基础 try-catch | 完善状态提示 + 按钮自动禁用 + 详细错误信息 |
callChatCompletions() — 统一的 OpenAI 兼容接口调用(文本+多模态)executeSearch() / executeVision() — 文本/视觉对话executeImageGen() / executeVideoGen() / executeMusicGen() / executeTTS() — 生成类功能pollVideoTask() — 异步视频轮询(60 次 × 5s = 5 分钟超时)escapeHtml() — XSS 防护工具函数