/* Pipeline Module - Main JavaScript */ (function() { 'use strict'; var API_BASE = ''; // Will be set on init var currentView = 'list'; var currentPipelineId = null; // === Initialization === function init() { // Detect API base from current URL var base = window.location.pathname.replace(/\/pipeline\/index\.ui.*$/, '/pipeline'); API_BASE = base; showListView(); } // === API Helpers === function apiGet(path, callback) { var xhr = new XMLHttpRequest(); xhr.open('GET', API_BASE + path, true); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { try { var data = JSON.parse(xhr.responseText); callback(null, data); } catch(e) { callback('解析响应失败', null); } } }; xhr.onerror = function() { callback('网络请求失败', null); }; xhr.send(); } function apiPost(path, body, callback) { var xhr = new XMLHttpRequest(); xhr.open('POST', API_BASE + path, true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { try { var data = JSON.parse(xhr.responseText); callback(null, data); } catch(e) { callback('解析响应失败', null); } } }; xhr.onerror = function() { callback('网络请求失败', null); }; xhr.send(JSON.stringify(body)); } // === View Rendering === function getContainer() { return document.getElementById('pipeline_content'); } function showListView() { currentView = 'list'; currentPipelineId = null; var c = getContainer(); if (!c) return; c.innerHTML = '
' + '
' + '

产线任务

' + '' + '
' + '
加载中...
' + '
'; loadTaskList(); } function loadTaskList() { apiGet('/api/pipeline_list.dspy', function(err, data) { var el = document.getElementById('pipeline_list_body'); if (!el) return; if (err) { el.innerHTML = '
' + err + '
'; return; } if (data.status === 'error') { el.innerHTML = '
' + (data.message || '加载失败') + '
'; return; } var tasks = data.tasks || data.data || []; if (!tasks.length) { el.innerHTML = '
暂无产线任务

'; return; } var html = ''; el.innerHTML = html; }); } function showCreateView() { currentView = 'create'; var c = getContainer(); if (!c) return; c.innerHTML = '
' + '
' + '' + '新建产线任务
' + '
' + '
' + '' + '
' + '' + '' + '
' + '' + '
' + '
' + '' + '
' + '
' + '' + '
' + '
' + '' + '
' + '
' + '' + '' + '
' + '
'; } function onModeChange() { var mode = document.getElementById('pf_mode').value; var audioG = document.getElementById('pf_audio_group'); var videoG = document.getElementById('pf_video_group'); var lyricsG = document.getElementById('pf_lyrics_group'); if (audioG) audioG.style.display = (mode === 'audio_lyrics') ? 'block' : 'none'; if (videoG) videoG.style.display = (mode === 'video_lyrics') ? 'block' : 'none'; if (lyricsG) lyricsG.style.display = (mode === 'video_lyrics') ? 'none' : 'block'; } function submitTask() { var mode = document.getElementById('pf_mode').value; var title = document.getElementById('pf_title').value; var lyrics = document.getElementById('pf_lyrics') ? document.getElementById('pf_lyrics').value : ''; var lyricist = document.getElementById('pf_lyricist').value; var composer = document.getElementById('pf_composer').value; if (!title) { alert('请输入歌曲名称'); return; } var body = { mode: mode, title: title, lyrics: lyrics, lyricist: lyricist, composer: composer }; if (mode === 'audio_lyrics') { body.input_audio = document.getElementById('pf_audio').value; if (!body.input_audio) { alert('请输入音频文件路径'); return; } } else if (mode === 'video_lyrics') { body.input_video = document.getElementById('pf_video').value; if (!body.input_video) { alert('请输入视频文件路径'); return; } } var btn = document.getElementById('pf_submit_btn'); if (btn) { btn.disabled = true; btn.textContent = '提交中...'; } apiPost('/api/pipeline_submit.dspy', body, function(err, data) { if (btn) { btn.disabled = false; btn.textContent = '提交任务'; } if (err) { alert('提交失败: ' + err); return; } if (data.status === 'error') { alert('提交失败: ' + (data.message || '未知错误')); return; } var pid = data.pipeline_id; if (pid) { showDetailView(pid); } else { showListView(); } }); } // === Task Detail View === function showDetailView(pipelineId) { currentView = 'detail'; currentPipelineId = pipelineId; var c = getContainer(); if (!c) return; c.innerHTML = '
' + '
' + '' + '
加载中...
' + '
' + '
加载任务节点...
' + '
'; loadTaskDetail(pipelineId); } function loadTaskDetail(pid) { apiGet('/api/pipeline_detail.dspy?pipeline_id=' + encodeURIComponent(pid), function(err, data) { if (err) { document.getElementById('pd_nodes').innerHTML = '
' + err + '
'; return; } if (data.status === 'error') { document.getElementById('pd_nodes').innerHTML = '
' + (data.message || '加载失败') + '
'; return; } var titleEl = document.getElementById('pd_title'); var metaEl = document.getElementById('pd_meta'); var nodesEl = document.getElementById('pd_nodes'); if (titleEl) titleEl.textContent = data.title || pid; if (metaEl) { var st = data.state || ''; var stText = {completed:'已完成', running:'运行中', failed:'失败', submitted:'已提交'}[st] || st; metaEl.innerHTML = 'ID: ' + esc(pid) + ' | ' + esc(data.mode || '') + ' | ' + '' + stText + '' + ' | 版本 v' + (data.current_version || 1); } renderNodeTree(nodesEl, data); }); } function renderNodeTree(container, data) { if (!container) return; var steps = data.steps || {}; var artifacts = data.artifacts || {}; var versions = data.versions || {}; // Sort steps by order var stepList = []; for (var name in steps) { if (steps.hasOwnProperty(name)) { var s = steps[name]; stepList.push({name: name, order: s.order || 0, version: s.version || 1, deps: s.deps || [], state: s.state || ''}); } } stepList.sort(function(a, b) { return a.order - b.order; }); if (!stepList.length) { container.innerHTML = '
无步骤数据
'; return; } var html = '
'; for (var i = 0; i < stepList.length; i++) { var step = stepList[i]; var st = step.state || 'pending'; var stText = {completed:'✓', running:'⟳', pending:'○', failed:'✗'}[st] || '?'; var iconClass = 'node-icon-' + st; var nameCN = STEP_NAMES[step.name] || step.name; // Find artifact for this step var artKey = ''; for (var ak in artifacts) { if (artifacts.hasOwnProperty(ak) && artifacts[ak].step === step.name) { artKey = ak; break; } } html += '
' + '
' + '
' + stText + '
' + '' + esc(nameCN) + '' + 'v' + step.version + '' + '
' + '
'; if (st === 'completed') { html += '
' + '
输入 / 输出
' + '
点击加载...
' + '
' + '' + '' + '' + '
'; } else if (st === 'running') { html += '
该节点正在运行中...
'; } else if (st === 'failed') { html += '
该节点执行失败
'; } else { html += '
等待执行
'; } html += '
'; } html += '
'; container.innerHTML = html; // Auto-expand failed or running node for (var j = 0; j < stepList.length; j++) { if (stepList[j].state === 'failed' || stepList[j].state === 'running') { toggleNode(stepList[j].name); } } } // Step name Chinese mapping var STEP_NAMES = { 'audio_preparing': '音频准备', 'video_preparing': '视频准备', 'demucs_separating': '人声分离', 'lyric_generating': '歌词生成', 'lyric_evaluating': '歌词评估', 'music_generating': '音乐生成', 'music_polling': '音乐轮询', 'lyric_calibrating': '歌词校准', 'subtitle_rendering': '字幕渲染', 'subtitle_exporting': '字幕导出', 'character_designing': '角色设计', 'character_image_generating': '角色图生成', 'storyboard_generating': '分镜剧本', 'scene_video_generating': '分镜视频生成', 'scene_video_evaluating': '分镜视频评估', 'scene_video_concatenating': '分镜视频拼接', 'ktv_synthesizing': 'KTV合成' }; function toggleNode(stepName) { var body = document.getElementById('node_body_' + stepName); if (!body) return; var isExpanded = body.classList.contains('expanded'); body.classList.toggle('expanded'); // Load IO data on first expand if (!isExpanded) { var ioEl = document.getElementById('node_io_' + stepName); if (ioEl && ioEl.textContent === '点击加载...') { loadNodeIO(stepName); } } } function loadNodeIO(stepName) { var ioEl = document.getElementById('node_io_' + stepName); if (!ioEl) return; ioEl.textContent = '加载中...'; apiGet('/api/pipeline_node.dspy?pipeline_id=' + encodeURIComponent(currentPipelineId) + '&step=' + encodeURIComponent(stepName), function(err, data) { if (err) { ioEl.textContent = '加载失败: ' + err; return; } if (data.status === 'error') { ioEl.textContent = '错误: ' + (data.message || ''); return; } var text = ''; if (data.input) { text += '【输入】\n' + formatData(data.input) + '\n\n'; } if (data.output) { text += '【输出】\n' + formatData(data.output); } if (!text) text = '(无数据)'; ioEl.textContent = text; }); } // === Node Edit === function showNodeEdit(stepName, modifyType) { var nodeBody = document.getElementById('node_body_' + stepName); if (!nodeBody) return; // Check if edit section already exists var existing = document.getElementById('node_edit_' + stepName); if (existing) { existing.remove(); return; } var typeLabel = modifyType === 'input' ? '修改输入(从该节点重跑)' : '修改输出(从下一节点重跑)'; // Get current content var ioEl = document.getElementById('node_io_' + stepName); var currentContent = ''; if (ioEl) { var text = ioEl.textContent; if (modifyType === 'input' && text.indexOf('【输入】') >= 0) { currentContent = text.substring(text.indexOf('【输入】') + 4, text.indexOf('【输出】') >= 0 ? text.indexOf('【输出】') : text.length).trim(); } else if (modifyType === 'output' && text.indexOf('【输出】') >= 0) { currentContent = text.substring(text.indexOf('【输出】') + 4).trim(); } } var html = '
' + '
' + esc(typeLabel) + '
' + '' + '' + '
' + '' + '' + '
' + '
'; nodeBody.insertAdjacentHTML('beforeend', html); } function submitNodeEdit(stepName) { var content = document.getElementById('node_edit_content_' + stepName).value; var modifyType = document.getElementById('node_edit_type_' + stepName).value; var msgEl = document.getElementById('node_edit_msg_' + stepName); var btn = document.getElementById('node_edit_btn_' + stepName); if (!content.trim()) { alert('内容不能为空'); return; } if (btn) { btn.disabled = true; btn.textContent = '提交中...'; } if (msgEl) msgEl.innerHTML = '
提交修改...
'; apiPost('/api/pipeline_modify.dspy', { pipeline_id: currentPipelineId, step: stepName, modify_type: modifyType, content: content }, function(err, data) { if (btn) { btn.disabled = false; btn.textContent = '确认修改并重跑'; } if (err) { if (msgEl) msgEl.innerHTML = '
' + err + '
'; return; } if (data.status === 'error') { if (msgEl) msgEl.innerHTML = '
' + (data.message || '修改失败') + '
'; return; } if (msgEl) msgEl.innerHTML = '
修改成功!' + (data.new_version ? ' 新版本: v' + data.new_version : '') + (data.rerun_steps ? ' 重跑步骤: ' + data.rerun_steps.join(', ') : '') + '
'; // Reload task detail after 2 seconds setTimeout(function() { loadTaskDetail(currentPipelineId); }, 2000); }); } // === Utilities === function formatData(obj) { if (typeof obj === 'string') return obj; try { return JSON.stringify(obj, null, 2); } catch(e) { return String(obj); } } function esc(s) { if (!s) return ''; var d = document.createElement('div'); d.appendChild(document.createTextNode(s)); return d.innerHTML; } // === Expose Public API === window.PipelineUI = { init: init, showListView: showListView, showCreateView: showCreateView, showDetailView: showDetailView, submitTask: submitTask, onModeChange: onModeChange, toggleNode: toggleNode, loadNodeIO: loadNodeIO, showNodeEdit: showNodeEdit, submitNodeEdit: submitNodeEdit }; // Auto-init on DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();