jytest/web/video.html

338 lines
15 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.

<!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>