338 lines
15 KiB
HTML
338 lines
15 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>视频生成</title>
|
||
<link rel="stylesheet" href="css/style.css">
|
||
</head>
|
||
<body>
|
||
<div class="header">
|
||
<h1>视频生成</h1>
|
||
</div>
|
||
|
||
<div class="container">
|
||
<div class="tabs">
|
||
<div class="tab active" onclick="switchTab('marketing')">带货营销</div>
|
||
<div class="tab" onclick="switchTab('story')">剧情营销</div>
|
||
<div class="tab" onclick="switchTab('short')">短片创作</div>
|
||
</div>
|
||
|
||
<!-- 带货营销 -->
|
||
<div class="card" id="tab-marketing">
|
||
<div class="card-title">带货营销视频</div>
|
||
<div class="form-group">
|
||
<label class="form-label">商品名称 *</label>
|
||
<input type="text" class="form-input" id="m_product_name" placeholder="输入商品名称">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">商品图片URL(1-3张,逗号分隔)*</label>
|
||
<textarea class="form-textarea" id="m_product_images" placeholder="https://example.com/img1.jpg,https://example.com/img2.jpg"></textarea>
|
||
<button type="button" class="btn-testdata" onclick="showTestData('product','m_product_images')">📦 使用测试数据</button>
|
||
<div id="testdata-m_product_images" class="testdata-picker" style="display:none"></div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">模特图片URL(最多1张)</label>
|
||
<input type="text" class="form-input" id="m_model_image" placeholder="可选">
|
||
<button type="button" class="btn-testdata" onclick="showTestData('model','m_model_image')">👤 使用测试数据</button>
|
||
<div id="testdata-m_model_image" class="testdata-picker" style="display:none"></div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">营销Prompt</label>
|
||
<textarea class="form-textarea" id="m_prompt" placeholder="自定义营销prompt(可选)"></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label style="display:flex;align-items:center;gap:8px">
|
||
<input type="checkbox" id="m_watermark"> 添加水印
|
||
</label>
|
||
</div>
|
||
<button class="btn btn-primary btn-block" onclick="submitMarketing()">提交带货营销视频</button>
|
||
<div id="result-marketing"></div>
|
||
</div>
|
||
|
||
<!-- 剧情营销 -->
|
||
<div class="card" id="tab-story" style="display:none">
|
||
<div class="card-title">剧情营销视频</div>
|
||
<div class="form-group">
|
||
<label class="form-label">商品名称 *</label>
|
||
<input type="text" class="form-input" id="s_product_name" placeholder="输入商品名称">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">商品图片URL(1-9张,逗号分隔)*</label>
|
||
<textarea class="form-textarea" id="s_product_images" placeholder="https://example.com/img1.jpg"></textarea>
|
||
<button type="button" class="btn-testdata" onclick="showTestData('product','s_product_images')">📦 使用测试数据</button>
|
||
<div id="testdata-s_product_images" class="testdata-picker" style="display:none"></div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">模特图片URL(最多1张)</label>
|
||
<input type="text" class="form-input" id="s_model_image" placeholder="可选">
|
||
<button type="button" class="btn-testdata" onclick="showTestData('model','s_model_image')">👤 使用测试数据</button>
|
||
<div id="testdata-s_model_image" class="testdata-picker" style="display:none"></div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">模型</label>
|
||
<select class="form-select" id="s_model">
|
||
<option value="seedance20_fast">Seedance 2.0 Fast</option>
|
||
<option value="seedance25">Seedance 2.5</option>
|
||
<option value="seedance20">Seedance 2.0</option>
|
||
<option value="seedance20_mini">Seedance 2.0 Mini</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">时长(秒)</label>
|
||
<input type="number" class="form-input" id="s_duration" value="15" min="5" max="30">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">剧情Prompt</label>
|
||
<textarea class="form-textarea" id="s_prompt" placeholder="自定义剧情prompt(可选)"></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label style="display:flex;align-items:center;gap:8px">
|
||
<input type="checkbox" id="s_watermark"> 添加水印
|
||
</label>
|
||
</div>
|
||
<button class="btn btn-primary btn-block" onclick="submitStory()">提交剧情营销视频</button>
|
||
<div id="result-story"></div>
|
||
</div>
|
||
|
||
<!-- 短片创作 -->
|
||
<div class="card" id="tab-short" style="display:none">
|
||
<div class="card-title">短片创作</div>
|
||
<div class="form-group">
|
||
<label class="form-label">提示词 *</label>
|
||
<textarea class="form-textarea" id="sh_prompt" placeholder="描述你想创作的视频内容"></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">参考图片(最多9张,逗号分隔)</label>
|
||
<textarea class="form-textarea" id="sh_images" placeholder="https://example.com/ref1.jpg"></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">模型</label>
|
||
<select class="form-select" id="sh_model">
|
||
<option value="seedance20_fast">Seedance 2.0 Fast</option>
|
||
<option value="seedance25">Seedance 2.5</option>
|
||
<option value="seedance20">Seedance 2.0</option>
|
||
<option value="seedance20_mini">Seedance 2.0 Mini</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">时长(秒)</label>
|
||
<input type="number" class="form-input" id="sh_duration" value="15" min="5" max="30">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">画面比例</label>
|
||
<select class="form-select" id="sh_ratio">
|
||
<option value="16:9">16:9</option>
|
||
<option value="9:16">9:16</option>
|
||
<option value="1:1">1:1</option>
|
||
<option value="4:3">4:3</option>
|
||
<option value="3:4">3:4</option>
|
||
<option value="21:9">21:9</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">分辨率</label>
|
||
<select class="form-select" id="sh_resolution">
|
||
<option value="720P">720P</option>
|
||
<option value="480P">480P</option>
|
||
<option value="1080P">1080P</option>
|
||
<option value="4K">4K</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label style="display:flex;align-items:center;gap:8px">
|
||
<input type="checkbox" id="sh_watermark"> 添加水印
|
||
</label>
|
||
</div>
|
||
<button class="btn btn-primary btn-block" onclick="submitShort()">提交短片创作</button>
|
||
<div id="result-short"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="nav">
|
||
<a href="index.html" class="nav-item">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||
首页
|
||
</a>
|
||
<a href="novel.html" class="nav-item">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
||
短剧
|
||
</a>
|
||
<a href="video.html" class="nav-item active">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>
|
||
视频
|
||
</a>
|
||
<a href="tasks.html" class="nav-item">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
||
任务
|
||
</a>
|
||
</nav>
|
||
|
||
<script src="js/app.js"></script>
|
||
<script>
|
||
let testDataCache = null;
|
||
|
||
async function loadTestData() {
|
||
if (testDataCache) return testDataCache;
|
||
testDataCache = await API.get('/api/testdata');
|
||
return testDataCache;
|
||
}
|
||
|
||
async function showTestData(type, targetId) {
|
||
const picker = document.getElementById('testdata-' + targetId);
|
||
// 切换显示
|
||
if (picker.style.display !== 'none') {
|
||
picker.style.display = 'none';
|
||
return;
|
||
}
|
||
const data = await loadTestData();
|
||
const items = type === 'product' ? (data.test_product || []) : (data.test_model || []);
|
||
if (items.length === 0) {
|
||
picker.innerHTML = '<div class="testdata-empty">暂无测试数据</div>';
|
||
picker.style.display = 'block';
|
||
return;
|
||
}
|
||
picker.innerHTML = items.map(item => `
|
||
<div class="testdata-item" onclick="pickTestData('${targetId}','${item.url}','${item.name}',this)">
|
||
<img src="${item.url}" class="testdata-thumb" onerror="this.style.display='none'">
|
||
<div class="testdata-info">
|
||
<span class="testdata-name">${item.name}</span>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
picker.style.display = 'block';
|
||
}
|
||
|
||
function pickTestData(targetId, url, name, el) {
|
||
const target = document.getElementById(targetId);
|
||
if (target.tagName === 'TEXTAREA') {
|
||
// 商品图片: 追加到现有内容
|
||
const cur = target.value.trim();
|
||
if (cur) {
|
||
target.value = cur + ',' + url;
|
||
} else {
|
||
target.value = url;
|
||
}
|
||
} else {
|
||
// 模特图片: 直接替换
|
||
target.value = url;
|
||
}
|
||
// 高亮选中
|
||
el.parentElement.querySelectorAll('.testdata-item').forEach(i => i.classList.remove('selected'));
|
||
el.classList.add('selected');
|
||
}
|
||
|
||
function switchTab(tab) {
|
||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||
document.querySelectorAll('[id^="tab-"]').forEach(c => c.style.display = 'none');
|
||
event.target.classList.add('active');
|
||
document.getElementById('tab-' + tab).style.display = 'block';
|
||
}
|
||
|
||
async function submitMarketing() {
|
||
const input = {
|
||
product_name: document.getElementById('m_product_name').value,
|
||
product_image_url_list: document.getElementById('m_product_images').value.split(',').map(s => s.trim()).filter(Boolean),
|
||
enable_watermark: document.getElementById('m_watermark').checked
|
||
};
|
||
const modelImg = document.getElementById('m_model_image').value.trim();
|
||
if (modelImg) input.model_image_url_list = [modelImg];
|
||
const prompt = document.getElementById('m_prompt').value.trim();
|
||
if (prompt) input.prompt = prompt;
|
||
|
||
try {
|
||
const result = await API.post('/api/video/submit', {
|
||
capability_key: 'pippit_avatar_marketing_agent',
|
||
input: input
|
||
});
|
||
handleVideoResult(result, 'marketing');
|
||
} catch(err) {
|
||
showAlert('提交失败: ' + err.message, 'error');
|
||
}
|
||
}
|
||
|
||
async function submitStory() {
|
||
const input = {
|
||
product_name: document.getElementById('s_product_name').value,
|
||
product_image_url_list: document.getElementById('s_product_images').value.split(',').map(s => s.trim()).filter(Boolean),
|
||
model: document.getElementById('s_model').value,
|
||
duration: parseInt(document.getElementById('s_duration').value),
|
||
enable_watermark: document.getElementById('s_watermark').checked
|
||
};
|
||
const modelImg = document.getElementById('s_model_image').value.trim();
|
||
if (modelImg) input.model_image_url_list = [modelImg];
|
||
const prompt = document.getElementById('s_prompt').value.trim();
|
||
if (prompt) input.prompt = prompt;
|
||
|
||
try {
|
||
const result = await API.post('/api/video/submit', {
|
||
capability_key: 'pippit_story_marketing_agent',
|
||
input: input
|
||
});
|
||
handleVideoResult(result, 'story');
|
||
} catch(err) {
|
||
showAlert('提交失败: ' + err.message, 'error');
|
||
}
|
||
}
|
||
|
||
async function submitShort() {
|
||
const input = {
|
||
prompt: document.getElementById('sh_prompt').value,
|
||
model: document.getElementById('sh_model').value,
|
||
duration: parseInt(document.getElementById('sh_duration').value),
|
||
ratio: document.getElementById('sh_ratio').value,
|
||
resolution: document.getElementById('sh_resolution').value,
|
||
enable_watermark: document.getElementById('sh_watermark').checked
|
||
};
|
||
const images = document.getElementById('sh_images').value.trim();
|
||
if (images) input.image_url_list = images.split(',').map(s => s.trim()).filter(Boolean);
|
||
|
||
try {
|
||
const result = await API.post('/api/video/submit', {
|
||
capability_key: 'pippit_video_part_agent',
|
||
input: input
|
||
});
|
||
handleVideoResult(result, 'short');
|
||
} catch(err) {
|
||
showAlert('提交失败: ' + err.message, 'error');
|
||
}
|
||
}
|
||
|
||
function handleVideoResult(result, tab) {
|
||
if (result.data) {
|
||
showAlert('视频任务已提交!', 'success');
|
||
const taskID = result.data.task_id || '';
|
||
const runID = result.data.run_id || '';
|
||
pollTask(taskID || runID,
|
||
(id) => API.post('/api/video/query', {task_id: taskID, run_id: runID}),
|
||
(res) => {
|
||
const status = res.data?.status;
|
||
const container = document.getElementById('result-' + tab);
|
||
container.innerHTML = `<div class="result"><div class="result-title">状态: ${getStatusBadge(status)}</div></div>`;
|
||
if (status === 'SUCCESS') {
|
||
const artifact = res.data.video_artifact;
|
||
if (artifact && artifact.url) {
|
||
container.innerHTML = `
|
||
<div class="result">
|
||
<div class="result-title">视频生成成功!</div>
|
||
<video class="result-video" controls src="${artifact.url}"></video>
|
||
<p style="margin-top:8px;font-size:12px;color:var(--text-light)">
|
||
时长: ${artifact.duration}s | 分辨率: ${artifact.width}x${artifact.height}
|
||
</p>
|
||
<a href="${artifact.url}" download class="btn btn-success" style="margin-top:8px;display:inline-block">下载视频</a>
|
||
</div>`;
|
||
}
|
||
} else if (status === 'FAILED') {
|
||
container.innerHTML = `<div class="result" style="background:#fee2e2;border-color:#fca5a5">
|
||
<div class="result-title" style="color:#991b1b">生成失败</div>
|
||
<p style="font-size:13px">错误码: ${res.data.err_code || '-'}<br>错误信息: ${res.data.err_msg || '-'}</p>
|
||
</div>`;
|
||
}
|
||
}
|
||
);
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|