Files
video-factory/shortdrama/view/modal-config.html
T
2026-07-03 11:38:30 +08:00

288 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<style>
.config-tab-bar { display:flex; gap:4px; border-bottom:2px solid #eee; flex-shrink:0; }
.config-tab { padding:8px 16px; font-size:13px; font-weight:600; color:#888; cursor:pointer; border-bottom:2px solid transparent; margin-bottom:-2px; transition:all .2s; user-select:none; }
.config-tab:hover { color:#4a6cf7; }
.config-tab.active { color:#4a6cf7; border-bottom-color:#4a6cf7; }
.config-tab-content { display:none; }
.config-tab-content.active { display:block; }
</style>
<script>
let curConfigTab = 'chat';
function showConfigModal() {
curConfigTab = 'chat';
document.getElementById('configModal').classList.add('show');
switchConfigTab('chat');
loadConfigForm();
}
function hideConfigModal() {
document.getElementById('configModal').classList.remove('show');
}
function switchConfigTab(tab) {
curConfigTab = tab;
document.querySelectorAll('.config-tab').forEach(function(el) {
el.classList.toggle('active', el.dataset.tab === tab);
});
document.querySelectorAll('.config-tab-content').forEach(function(el) {
el.classList.toggle('active', el.id === 'cfg_tab_' + tab);
});
}
function switchVideoCategory() {
var sel = gid('cfg_videoModelCategory');
var refSections = gid('cfg_video_ref_sections');
if (!sel || !refSections) return;
refSections.style.display = sel.value === 'i2v' ? 'block' : 'none';
}
// ==================== 辅助函数 ====================
function gid(id) { return document.getElementById(id); }
function sv(id, val) { const el = gid(id); if (el) el.value = val; }
// ==================== 加载配置 ====================
async function loadConfigForm() {
try {
const resp = await fetch('/config/model');
const data = await resp.json();
if (data.code !== 0) throw new Error(data.message);
var cfg = data.data || {};
sv('cfg_chatModelName', cfg.chatModelName || '');
sv('cfg_chatApiKey', cfg.chatApiKey || '');
sv('cfg_chat_submit_url', cfg.chatBaseUrl || '');
if (cfg.maxTokens) sv('cfg_maxTokens', cfg.maxTokens);
if (cfg.temperature) sv('cfg_temperature', cfg.temperature);
sv('cfg_videoModelName', cfg.videoModelName || '');
sv('cfg_videoApiKey', cfg.videoApiKey || '');
sv('cfg_video_submit_url', cfg.videoBaseUrl || '');
sv('cfg_video_query_url', cfg.videoQueryUrl || '');
sv('cfg_videoModelCategory', cfg.videoModelCategory || 't2v');
if (cfg.minSingleDuration) sv('cfg_minSingleDuration', cfg.minSingleDuration);
if (cfg.maxSingleDuration) sv('cfg_maxSingleDuration', cfg.maxSingleDuration);
if (cfg.maxReferenceImages) sv('cfg_maxReferenceImages', cfg.maxReferenceImages);
if (cfg.maxRefVideoCount) sv('cfg_maxRefVideoCount', cfg.maxRefVideoCount);
if (cfg.maxRefVideoFileSize) sv('cfg_maxRefVideoFileSize', cfg.maxRefVideoFileSize);
if (cfg.maxRefVideoDuration) sv('cfg_maxRefVideoDuration', cfg.maxRefVideoDuration);
if (cfg.refVideoFormats) sv('cfg_refVideoFormats', cfg.refVideoFormats);
if (cfg.maxRefAudioCount) sv('cfg_maxRefAudioCount', cfg.maxRefAudioCount);
if (cfg.maxRefAudioFileSize) sv('cfg_maxRefAudioFileSize', cfg.maxRefAudioFileSize);
if (cfg.maxRefAudioDuration) sv('cfg_maxRefAudioDuration', cfg.maxRefAudioDuration);
if (cfg.refAudioFormats) sv('cfg_refAudioFormats', cfg.refAudioFormats);
if (cfg.maxRefImageFileSize) sv('cfg_maxRefImageFileSize', cfg.maxRefImageFileSize);
if (cfg.refImageFormats) sv('cfg_refImageFormats', cfg.refImageFormats);
switchVideoCategory();
} catch (err) { console.error('加载配置失败:', err); }
}
// ==================== 保存 ====================
async function saveConfig() {
var chatModelName = gid('cfg_chatModelName').value.trim();
var chatApiUrl = gid('cfg_chat_submit_url')?.value?.trim() || '';
var chatApiKey = gid('cfg_chatApiKey').value.trim();
if (!chatModelName) { alert('请输入对话模型名称'); return; }
if (!chatApiUrl) { alert('请输入对话模型请求地址'); return; }
if (!chatApiKey) { alert('请输入对话模型 API Key'); return; }
var maxTokens = parseInt(gid('cfg_maxTokens')?.value);
var temperature = parseFloat(gid('cfg_temperature')?.value);
if (!maxTokens || maxTokens < 1) { alert('请输入对话模型最大 Token 数'); return; }
if (temperature === undefined || isNaN(temperature) || temperature < 0) { alert('请输入对话模型 Temperature'); return; }
var videoModelName = gid('cfg_videoModelName').value.trim();
var videoApiUrl = gid('cfg_video_submit_url')?.value?.trim() || '';
var videoApiKey = gid('cfg_videoApiKey').value.trim();
var videoQueryUrl = gid('cfg_video_query_url')?.value?.trim() || '';
var videoModelCategory = gid('cfg_videoModelCategory')?.value || 't2v';
if (!videoModelName) { alert('请输入视频生成模型名称'); return; }
if (!videoApiUrl) { alert('请输入视频生成模型请求地址'); return; }
if (!videoApiKey) { alert('请输入视频生成模型 API Key'); return; }
if (!videoQueryUrl) { alert('请输入视频生成结果拉取地址'); return; }
// 图生图模式:校验参考字段
if (videoModelCategory === 'i2v') {
if (!parseInt(gid('cfg_maxRefVideoCount')?.value) > 0) { alert('图生图模式下,参考视频数量必须大于0'); return; }
if (!parseInt(gid('cfg_maxRefVideoFileSize')?.value) > 0) { alert('图生图模式下,单个参考视频最大大小必须填写'); return; }
if (!parseInt(gid('cfg_maxRefVideoDuration')?.value) > 0) { alert('图生图模式下,单个参考视频最大时长必须填写'); return; }
if (!gid('cfg_refVideoFormats')?.value?.trim()) { alert('图生图模式下,允许的视频格式不能为空'); return; }
if (!parseInt(gid('cfg_maxRefAudioCount')?.value) > 0) { alert('图生图模式下,参考音频数量必须大于0'); return; }
if (!parseInt(gid('cfg_maxRefAudioFileSize')?.value) > 0) { alert('图生图模式下,单个参考音频最大大小必须填写'); return; }
if (!parseInt(gid('cfg_maxRefAudioDuration')?.value) > 0) { alert('图生图模式下,单个参考音频最大时长必须填写'); return; }
if (!gid('cfg_refAudioFormats')?.value?.trim()) { alert('图生图模式下,允许的音频格式不能为空'); return; }
if (parseInt(gid('cfg_maxReferenceImages')?.value) < 2) { alert('图生图模式下,单次生成最大参考图数量至少为2'); return; }
if (!parseInt(gid('cfg_maxRefImageFileSize')?.value) > 0) { alert('图生图模式下,单个参考图片最大大小必须填写'); return; }
if (!gid('cfg_refImageFormats')?.value?.trim()) { alert('图生图模式下,允许的图片格式不能为空'); return; }
}
const btn = gid('cfg_saveBtn');
const loading = gid('cfg_loading');
const success = gid('cfg_successMsg');
btn.disabled = true; loading.classList.add('show'); success.classList.remove('show');
try {
const body = {
chatApiKey: chatApiKey,
chatBaseUrl: chatApiUrl,
chatModelName: chatModelName,
maxTokens: maxTokens,
temperature: temperature,
videoApiKey: videoApiKey,
videoBaseUrl: videoApiUrl,
videoModelName: videoModelName,
videoModelCategory: videoModelCategory,
videoQueryUrl: gid('cfg_video_query_url')?.value?.trim() || '',
minSingleDuration: parseInt(gid('cfg_minSingleDuration')?.value) || 2,
maxSingleDuration: parseInt(gid('cfg_maxSingleDuration')?.value) || 15,
maxReferenceImages: parseInt(gid('cfg_maxReferenceImages')?.value) || 9,
maxRefVideoCount: parseInt(gid('cfg_maxRefVideoCount')?.value) || 0,
maxRefVideoFileSize: parseInt(gid('cfg_maxRefVideoFileSize')?.value) || 0,
maxRefVideoDuration: parseInt(gid('cfg_maxRefVideoDuration')?.value) || 0,
refVideoFormats: gid('cfg_refVideoFormats')?.value || '',
maxRefAudioCount: parseInt(gid('cfg_maxRefAudioCount')?.value) || 0,
maxRefAudioFileSize: parseInt(gid('cfg_maxRefAudioFileSize')?.value) || 0,
maxRefAudioDuration: parseInt(gid('cfg_maxRefAudioDuration')?.value) || 0,
refAudioFormats: gid('cfg_refAudioFormats')?.value || '',
maxRefImageFileSize: parseInt(gid('cfg_maxRefImageFileSize')?.value) || 0,
refImageFormats: gid('cfg_refImageFormats')?.value || '',
};
const resp = await fetch('/config/model', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const data = await resp.json();
if (data.code !== 0) throw new Error(data.message);
success.classList.add('show');
setTimeout(function() { success.classList.remove('show'); }, 3000);
} catch (err) {
alert('保存失败: ' + err.message);
} finally {
btn.disabled = false;
loading.classList.remove('show');
}
}
</script>
<!-- 模型管理弹窗 -->
<div class="modal-overlay" id="configModal">
<div class="modal" style="width:80vw;height:80vh;max-width:1100px;padding:24px 28px;display:flex;flex-direction:column;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;flex-shrink:0;">
<h3 style="margin:0;">模型管理</h3>
<button class="btn btn-outline btn-sm" onclick="hideConfigModal()">关闭</button>
</div>
<!-- Tab 栏 -->
<div class="config-tab-bar" style="display:flex;">
<div class="config-tab active" data-tab="chat" onclick="switchConfigTab('chat')">对话模型</div>
<div class="config-tab" data-tab="video" onclick="switchConfigTab('video')">视频生成模型</div>
</div>
<div style="flex:1;overflow-y:auto;padding-right:8px;margin-top:16px;">
<!-- ========== 对话模型 ========== -->
<div class="config-tab-content active" id="cfg_tab_chat">
<div style="margin-bottom:16px;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<span style="font-weight:600;font-size:15px;color:#1a1a2e;">基础配置</span>
<span style="font-size:11px;color:#fff;background:#4a6cf7;border-radius:3px;padding:1px 6px;">必填</span>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="form-group" style="margin-bottom:0;"><label>模型名称 <span style="color:#e44;">*</span></label><input type="text" id="cfg_chatModelName" placeholder="qwen-max" style="width:100%;"></div>
<div class="form-group" style="margin-bottom:0;"><label>API Key <span style="color:#e44;">*</span></label><input type="password" id="cfg_chatApiKey" placeholder="sk-..." style="width:100%;"></div>
<div class="form-group" style="margin-bottom:0;"><label>请求地址 <span style="color:#e44;">*</span></label><input type="text" id="cfg_chat_submit_url" placeholder="https://..." style="width:100%;"></div>
<div class="form-group" style="margin-bottom:0;"><label>请求方式 <span style="color:#e44;">*</span></label><select id="cfg_chat_submit_method"><option value="POST">POST</option><option value="GET">GET</option></select></div>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="form-group" style="margin-bottom:0;"><label>最大 Token 数 <span style="color:#e44;">*</span></label><input type="number" id="cfg_maxTokens" value="4096" min="256" max="131072"><div class="help-text">超过长度自动截断</div></div>
<div class="form-group" style="margin-bottom:0;"><label>Temperature <span style="color:#e44;">*</span></label><input type="number" id="cfg_temperature" value="0.8" min="0" max="2" step="0.1"><div class="help-text">随机值(0-2</div></div>
</div>
</div>
<!-- ========== 视频生成模型 ========== -->
<div class="config-tab-content" id="cfg_tab_video">
<div style="margin-bottom:16px;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<span style="font-weight:600;font-size:15px;color:#1a1a2e;">基础配置</span>
<span style="font-size:11px;color:#fff;background:#4a6cf7;border-radius:3px;padding:1px 6px;">必填</span>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="form-group" style="margin-bottom:0;"><label>模型名称 <span style="color:#e44;">*</span></label><input type="text" id="cfg_videoModelName" placeholder="wanx2.1-t2v-turbo" style="width:100%;"></div>
<div class="form-group" style="margin-bottom:0;"><label>API Key <span style="color:#e44;">*</span></label><input type="password" id="cfg_videoApiKey" placeholder="sk-..." style="width:100%;"></div>
<div class="form-group" style="margin-bottom:0;"><label>请求地址 <span style="color:#e44;">*</span></label><input type="text" id="cfg_video_submit_url" placeholder="https://..." style="width:100%;"></div>
<div class="form-group" style="margin-bottom:0;"><label>请求方式 <span style="color:#e44;">*</span></label><select id="cfg_video_submit_method"><option value="POST">POST</option><option value="GET">GET</option></select></div>
<div class="form-group" style="margin-bottom:0;"><label>结果拉取地址 <span style="color:#e44;">*</span></label><input type="text" id="cfg_video_query_url" placeholder="https://.../api/v1/tasks/{task_id}" style="width:100%;"><div class="help-text">用于查询视频生成任务状态和结果,{task_id} 会被自动替换</div></div>
<div class="form-group" style="margin-bottom:0;"><label>模型类型 <span style="color:#e44;">*</span></label><select id="cfg_videoModelCategory" onchange="switchVideoCategory()" style="width:100%;"><option value="t2v">文生图</option><option value="i2v">图生图</option></select></div>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="form-group" style="margin-bottom:0;"><label>单次生成最小时长(秒)</label><input type="number" id="cfg_minSingleDuration" value="2" min="1" max="30"></div>
<div class="form-group" style="margin-bottom:0;"><label>单次生成最大时长(秒)</label><input type="number" id="cfg_maxSingleDuration" value="15" min="1" max="60"></div>
</div>
<div id="cfg_video_ref_sections" style="display:none;">
<!-- 参考视频约束 -->
<div style="margin-top:20px;padding-top:16px;border-top:1px solid #eee;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<span style="font-weight:600;font-size:15px;color:#1a1a2e;">参考视频</span>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="form-group" style="margin-bottom:0;"><label>参考视频数量 <span style="color:#e44;">*</span></label><input type="number" id="cfg_maxRefVideoCount" value="0" min="0" max="50"><div class="help-text">0=不限制</div></div>
<div class="form-group" style="margin-bottom:0;"><label>单个视频最大大小(MB) <span style="color:#e44;">*</span></label><input type="number" id="cfg_maxRefVideoFileSize" value="50" min="0" max="1024"></div>
<div class="form-group" style="margin-bottom:0;"><label>单个视频最大时长(秒) <span style="color:#e44;">*</span></label><input type="number" id="cfg_maxRefVideoDuration" value="30" min="0" max="3600"></div>
<div class="form-group" style="margin-bottom:0;"><label>允许的视频格式 <span style="color:#e44;">*</span></label><input type="text" id="cfg_refVideoFormats" value="mp4,avi,mov,mkv,webm" placeholder="逗号分隔"><div class="help-text">逗号分隔,如 mp4,avi,mov</div></div>
</div>
</div>
<!-- 参考音频约束 -->
<div style="margin-top:20px;padding-top:16px;border-top:1px solid #eee;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<span style="font-weight:600;font-size:15px;color:#1a1a2e;">参考音频</span>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="form-group" style="margin-bottom:0;"><label>参考音频数量 <span style="color:#e44;">*</span></label><input type="number" id="cfg_maxRefAudioCount" value="0" min="0" max="50"><div class="help-text">0=不限制</div></div>
<div class="form-group" style="margin-bottom:0;"><label>单个音频最大大小(MB) <span style="color:#e44;">*</span></label><input type="number" id="cfg_maxRefAudioFileSize" value="10" min="0" max="1024"></div>
<div class="form-group" style="margin-bottom:0;"><label>单个音频最大时长(秒) <span style="color:#e44;">*</span></label><input type="number" id="cfg_maxRefAudioDuration" value="30" min="0" max="3600"></div>
<div class="form-group" style="margin-bottom:0;"><label>允许的音频格式 <span style="color:#e44;">*</span></label><input type="text" id="cfg_refAudioFormats" value="wav,mp3,aac,flac,ogg" placeholder="逗号分隔"><div class="help-text">逗号分隔,如 wav,mp3,aac</div></div>
</div>
</div>
<!-- 参考图片约束 -->
<div style="margin-top:20px;padding-top:16px;border-top:1px solid #eee;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<span style="font-weight:600;font-size:15px;color:#1a1a2e;">参考图片</span>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="form-group" style="margin-bottom:0;grid-column:1/-1;">
<label>单次生成最大参考图数量 <span style="color:#e44;">*</span></label>
<input type="number" id="cfg_maxReferenceImages" value="9" min="1" max="50">
<div class="help-text">模型API限制的单次最大参考图片数(含首帧)。实际使用时首帧占1个,演员+场景+道具参考图共占用除此以外的数量。例如填写9,则每次生成最多使用8张参考图</div>
</div>
<div class="form-group" style="margin-bottom:0;"><label>单个图片最大大小(MB) <span style="color:#e44;">*</span></label><input type="number" id="cfg_maxRefImageFileSize" value="10" min="0" max="100"></div>
<div class="form-group" style="margin-bottom:0;"><label>允许的图片格式 <span style="color:#e44;">*</span></label><input type="text" id="cfg_refImageFormats" value="png,jpg,jpeg,webp,bmp" placeholder="逗号分隔"><div class="help-text">逗号分隔,如 png,jpg,jpeg,webp</div></div>
</div>
</div>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div style="flex-shrink:0;display:flex;gap:12px;justify-content:center;align-items:center;margin-top:16px;padding-top:14px;border-top:1px solid #eee;">
<div class="loading" id="cfg_loading" style="display:none;align-items:center;gap:6px;color:#888;font-size:13px;"><div class="spinner"></div><span>保存中...</span></div>
<button class="btn btn-primary" id="cfg_saveBtn" onclick="saveConfig()">保存配置</button>
<button class="btn btn-outline" onclick="hideConfigModal()">取消</button>
</div>
<div class="success-msg" id="cfg_successMsg">配置已保存</div>
</div>
</div>