|
@@ -53,10 +53,12 @@
|
|
|
.card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px;
|
|
.card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px;
|
|
|
display:flex;flex-direction:column;gap:10px}
|
|
display:flex;flex-direction:column;gap:10px}
|
|
|
.card.missing{border-style:dashed;background:#241a3d}
|
|
.card.missing{border-style:dashed;background:#241a3d}
|
|
|
|
|
+ .card.running{border-color:var(--accent2);box-shadow:0 0 0 1px rgba(126,232,255,.35),0 0 18px rgba(126,232,255,.18)}
|
|
|
.task-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
|
|
.task-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
|
|
|
.task-status{border-radius:20px;padding:2px 9px;font-size:11px;white-space:nowrap;border:1px solid var(--line)}
|
|
.task-status{border-radius:20px;padding:2px 9px;font-size:11px;white-space:nowrap;border:1px solid var(--line)}
|
|
|
.task-status.done{color:#96ffce;background:#10291f}
|
|
.task-status.done{color:#96ffce;background:#10291f}
|
|
|
.task-status.missing{color:#ffd0df;background:#3d1830}
|
|
.task-status.missing{color:#ffd0df;background:#3d1830}
|
|
|
|
|
+ .task-status.running{color:#7ee8ff;background:#112d3a}
|
|
|
.task-prompt{max-height:94px;overflow:auto;background:#160f29;border:1px solid var(--line);
|
|
.task-prompt{max-height:94px;overflow:auto;background:#160f29;border:1px solid var(--line);
|
|
|
border-radius:8px;padding:8px;color:#cfc4ec;font-size:11px;line-height:1.45;white-space:pre-wrap}
|
|
border-radius:8px;padding:8px;color:#cfc4ec;font-size:11px;line-height:1.45;white-space:pre-wrap}
|
|
|
.placeholder{width:82%;height:82%;border:1px dashed #6b5c92;border-radius:12px;display:flex;
|
|
.placeholder{width:82%;height:82%;border:1px dashed #6b5c92;border-radius:12px;display:flex;
|
|
@@ -211,6 +213,7 @@
|
|
|
<span class="meta">资源库(game):</span>
|
|
<span class="meta">资源库(game):</span>
|
|
|
<select id="gameSel"></select>
|
|
<select id="gameSel"></select>
|
|
|
<button class="ghost" id="reloadBtn">↻ 刷新</button>
|
|
<button class="ghost" id="reloadBtn">↻ 刷新</button>
|
|
|
|
|
+ <button id="retryMissingBtn">批量补生成缺失项</button>
|
|
|
<button class="ghost" id="openFolderBtn">📂 打开素材目录</button>
|
|
<button class="ghost" id="openFolderBtn">📂 打开素材目录</button>
|
|
|
<button id="exportBtn">📦 导出 Cocos 整合包</button>
|
|
<button id="exportBtn">📦 导出 Cocos 整合包</button>
|
|
|
<button class="ghost" id="deleteBtn">🗑 删除该资源库</button>
|
|
<button class="ghost" id="deleteBtn">🗑 删除该资源库</button>
|
|
@@ -231,8 +234,17 @@
|
|
|
const $ = s => document.querySelector(s);
|
|
const $ = s => document.querySelector(s);
|
|
|
let LIB = {characters:[],vfx:[],ui:[]}, ASSET="", TAB="chars";
|
|
let LIB = {characters:[],vfx:[],ui:[]}, ASSET="", TAB="chars";
|
|
|
const animTargets = []; // {el, anim, start}
|
|
const animTargets = []; // {el, anim, start}
|
|
|
|
|
+const ACTIVE_TASKS = new Set();
|
|
|
const esc = s => String(s ?? '').replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));
|
|
const esc = s => String(s ?? '').replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));
|
|
|
const tasksFor = kind => ((LIB.tasks&&LIB.tasks[kind]) || []);
|
|
const tasksFor = kind => ((LIB.tasks&&LIB.tasks[kind]) || []);
|
|
|
|
|
+const taskKey = (kind,id) => `${kind}:${id}`;
|
|
|
|
|
+function missingTasks(){
|
|
|
|
|
+ return ['characters','ui_art','vfx','ui'].flatMap(kind => tasksFor(kind)
|
|
|
|
|
+ .filter(t=>t.status!=='done').map(t=>({kind,id:t.id})));
|
|
|
|
|
+}
|
|
|
|
|
+function markTasksRunning(items, running=true){
|
|
|
|
|
+ items.forEach(t=>running?ACTIVE_TASKS.add(taskKey(t.kind,t.id)):ACTIVE_TASKS.delete(taskKey(t.kind,t.id)));
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
// ---------- 拉取默认 manifest & 资源库 ----------
|
|
// ---------- 拉取默认 manifest & 资源库 ----------
|
|
|
async function loadManifest(){
|
|
async function loadManifest(){
|
|
@@ -253,6 +265,7 @@ async function loadLibrary(game){
|
|
|
if(lib.taskSummary && !pending){
|
|
if(lib.taskSummary && !pending){
|
|
|
opMsg(`任务 ${lib.taskSummary.done}/${lib.taskSummary.total} 已完成,缺失 ${lib.taskSummary.missing} 个。`, lib.taskSummary.missing===0);
|
|
opMsg(`任务 ${lib.taskSummary.done}/${lib.taskSummary.total} 已完成,缺失 ${lib.taskSummary.missing} 个。`, lib.taskSummary.missing===0);
|
|
|
}
|
|
}
|
|
|
|
|
+ $('#retryMissingBtn').disabled = !lib.taskSummary || !lib.taskSummary.missing || ACTIVE_TASKS.size>0;
|
|
|
const boss = lib.slot_config && lib.slot_config.boss;
|
|
const boss = lib.slot_config && lib.slot_config.boss;
|
|
|
if(boss && boss.enabled){
|
|
if(boss && boss.enabled){
|
|
|
const bossId = boss.id || 'boss_demon_lord';
|
|
const bossId = boss.id || 'boss_demon_lord';
|
|
@@ -304,18 +317,19 @@ function renderChars(v){
|
|
|
list.forEach(t=>{
|
|
list.forEach(t=>{
|
|
|
const c=t.asset||{};
|
|
const c=t.asset||{};
|
|
|
const done=t.status==='done' && c.png;
|
|
const done=t.status==='done' && c.png;
|
|
|
|
|
+ const running=ACTIVE_TASKS.has(taskKey('characters',t.id));
|
|
|
const animMap=c.animations||{};
|
|
const animMap=c.animations||{};
|
|
|
const anims=Object.keys(animMap);
|
|
const anims=Object.keys(animMap);
|
|
|
- const card=document.createElement('div'); card.className='card '+(done?'':'missing');
|
|
|
|
|
|
|
+ const card=document.createElement('div'); card.className='card '+(done?'':'missing')+(running?' running':'');
|
|
|
card.innerHTML=`
|
|
card.innerHTML=`
|
|
|
- <div class="stage">${done?`<img src="${ASSET+c.png}" alt="${esc(t.id)}">`:`<div class="placeholder">待生成<br>${esc(t.chineseName)}</div>`}</div>
|
|
|
|
|
|
|
+ <div class="stage">${done?`<img src="${ASSET+c.png}" alt="${esc(t.id)}">`:`<div class="placeholder">${running?'生成中…':'待生成'}<br>${esc(t.chineseName)}</div>`}</div>
|
|
|
<div class="task-head"><div><div class="name">${esc(t.chineseName)}</div><div class="meta">${esc(t.englishName)}</div></div>
|
|
<div class="task-head"><div><div class="name">${esc(t.chineseName)}</div><div class="meta">${esc(t.englishName)}</div></div>
|
|
|
- <span class="task-status ${done?'done':'missing'}">${done?'已生成':'缺失'}</span></div>
|
|
|
|
|
|
|
+ <span class="task-status ${done?'done':(running?'running':'missing')}">${done?'已生成':(running?'生成中':'缺失')}</span></div>
|
|
|
${done&&anims.length?`<div class="row"><select>${anims.map(a=>`<option>${esc(a)}</option>`).join('')}</select></div>`:''}
|
|
${done&&anims.length?`<div class="row"><select>${anims.map(a=>`<option>${esc(a)}</option>`).join('')}</select></div>`:''}
|
|
|
<div class="meta"><span class="pill">${esc(t.assetType||'spine')}</span> ${esc(t.use)}<br>
|
|
<div class="meta"><span class="pill">${esc(t.assetType||'spine')}</span> ${esc(t.use)}<br>
|
|
|
动作: ${esc((done?anims:t.animations||[]).join(', ')||'idle')}</div>
|
|
动作: ${esc((done?anims:t.animations||[]).join(', ')||'idle')}</div>
|
|
|
<div class="task-prompt">${esc(t.prompt||'')}</div>
|
|
<div class="task-prompt">${esc(t.prompt||'')}</div>
|
|
|
- ${done?'':`<button class="ghost retry-btn" data-kind="characters" data-id="${esc(t.id)}">补生成 / 重试</button>`}`;
|
|
|
|
|
|
|
+ ${done?'':`<button class="ghost retry-btn" data-kind="characters" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':'补生成 / 重试'}</button>`}`;
|
|
|
if(done&&anims.length){
|
|
if(done&&anims.length){
|
|
|
const img=card.querySelector('img'), selA=card.querySelector('select');
|
|
const img=card.querySelector('img'), selA=card.querySelector('select');
|
|
|
const tgt={el:img, anim:animMap[anims[0]], start:performance.now()};
|
|
const tgt={el:img, anim:animMap[anims[0]], start:performance.now()};
|
|
@@ -334,15 +348,16 @@ function renderArt(v){
|
|
|
list.forEach(t=>{
|
|
list.forEach(t=>{
|
|
|
const a=t.asset||{};
|
|
const a=t.asset||{};
|
|
|
const done=t.status==='done' && a.file;
|
|
const done=t.status==='done' && a.file;
|
|
|
- const card=document.createElement('div'); card.className='card '+(done?'':'missing');
|
|
|
|
|
|
|
+ const running=ACTIVE_TASKS.has(taskKey('ui_art',t.id));
|
|
|
|
|
+ const card=document.createElement('div'); card.className='card '+(done?'':'missing')+(running?' running':'');
|
|
|
card.innerHTML=`
|
|
card.innerHTML=`
|
|
|
- <div class="stage">${done?`<img class="art-img" src="${ASSET+a.file}" alt="${esc(t.id)}">`:`<div class="placeholder">待生成<br>${esc(t.chineseName)}</div>`}</div>
|
|
|
|
|
|
|
+ <div class="stage">${done?`<img class="art-img" src="${ASSET+a.file}" alt="${esc(t.id)}">`:`<div class="placeholder">${running?'生成中…':'待生成'}<br>${esc(t.chineseName)}</div>`}</div>
|
|
|
<div class="task-head"><div><div class="name">${esc(t.chineseName)}</div><div class="meta">${esc(t.englishName)}</div></div>
|
|
<div class="task-head"><div><div class="name">${esc(t.chineseName)}</div><div class="meta">${esc(t.englishName)}</div></div>
|
|
|
- <span class="task-status ${done?'done':'missing'}">${done?'已生成':'缺失'}</span></div>
|
|
|
|
|
|
|
+ <span class="task-status ${done?'done':(running?'running':'missing')}">${done?'已生成':(running?'生成中':'缺失')}</span></div>
|
|
|
<div class="meta">${done?`${a.w}×${a.h}px · ${a.transparent?'透明素材':'整图背景'}`:`${esc(t.size||'')} · ${t.transparent?'透明素材':'整图背景'}`}<br>
|
|
<div class="meta">${done?`${a.w}×${a.h}px · ${a.transparent?'透明素材':'整图背景'}`:`${esc(t.size||'')} · ${t.transparent?'透明素材':'整图背景'}`}<br>
|
|
|
<span class="pill">ui_art</span> ${esc(t.use)}</div>
|
|
<span class="pill">ui_art</span> ${esc(t.use)}</div>
|
|
|
<div class="task-prompt">${esc(t.prompt||'')}</div>
|
|
<div class="task-prompt">${esc(t.prompt||'')}</div>
|
|
|
- ${done?'':`<button class="ghost retry-btn" data-kind="ui_art" data-id="${esc(t.id)}">补生成 / 重试</button>`}`;
|
|
|
|
|
|
|
+ ${done?'':`<button class="ghost retry-btn" data-kind="ui_art" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':'补生成 / 重试'}</button>`}`;
|
|
|
grid.appendChild(card);
|
|
grid.appendChild(card);
|
|
|
});
|
|
});
|
|
|
v.appendChild(grid);
|
|
v.appendChild(grid);
|
|
@@ -355,14 +370,15 @@ function renderVfx(v){
|
|
|
list.forEach(t=>{
|
|
list.forEach(t=>{
|
|
|
const x=t.asset||{};
|
|
const x=t.asset||{};
|
|
|
const done=t.status==='done' && x.config;
|
|
const done=t.status==='done' && x.config;
|
|
|
- const card=document.createElement('div'); card.className='card '+(done?'':'missing');
|
|
|
|
|
|
|
+ const running=ACTIVE_TASKS.has(taskKey('vfx',t.id));
|
|
|
|
|
+ const card=document.createElement('div'); card.className='card '+(done?'':'missing')+(running?' running':'');
|
|
|
card.innerHTML=`
|
|
card.innerHTML=`
|
|
|
- <div class="stage">${done?'<canvas></canvas>':`<div class="placeholder">待生成<br>${esc(t.chineseName)}</div>`}</div>
|
|
|
|
|
|
|
+ <div class="stage">${done?'<canvas></canvas>':`<div class="placeholder">${running?'生成中…':'待生成'}<br>${esc(t.chineseName)}</div>`}</div>
|
|
|
<div class="task-head"><div><div class="name">${esc(t.chineseName)}</div><div class="meta">${esc(t.englishName)}</div></div>
|
|
<div class="task-head"><div><div class="name">${esc(t.chineseName)}</div><div class="meta">${esc(t.englishName)}</div></div>
|
|
|
- <span class="task-status ${done?'done':'missing'}">${done?'已生成':'缺失'}</span></div>
|
|
|
|
|
|
|
+ <span class="task-status ${done?'done':(running?'running':'missing')}">${done?'已生成':(running?'生成中':'缺失')}</span></div>
|
|
|
<div class="meta"><span class="pill">particle</span> ${done?`模板: ${esc(x.template)} · 发射率 ${x.config.emissionRate}/s · 寿命 ${x.config.life}s`:esc(t.use)}</div>
|
|
<div class="meta"><span class="pill">particle</span> ${done?`模板: ${esc(x.template)} · 发射率 ${x.config.emissionRate}/s · 寿命 ${x.config.life}s`:esc(t.use)}</div>
|
|
|
<div class="task-prompt">${esc(t.prompt||'')}</div>
|
|
<div class="task-prompt">${esc(t.prompt||'')}</div>
|
|
|
- ${done?'':`<button class="ghost retry-btn" data-kind="vfx" data-id="${esc(t.id)}">补生成 / 重试</button>`}`;
|
|
|
|
|
|
|
+ ${done?'':`<button class="ghost retry-btn" data-kind="vfx" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':'补生成 / 重试'}</button>`}`;
|
|
|
grid.appendChild(card);
|
|
grid.appendChild(card);
|
|
|
if(done) startParticle(card.querySelector('canvas'), x.config);
|
|
if(done) startParticle(card.querySelector('canvas'), x.config);
|
|
|
});
|
|
});
|
|
@@ -440,13 +456,14 @@ function renderUi(v){
|
|
|
list.forEach(t=>{
|
|
list.forEach(t=>{
|
|
|
const u=t.asset||{};
|
|
const u=t.asset||{};
|
|
|
const done=t.status==='done';
|
|
const done=t.status==='done';
|
|
|
|
|
+ const running=ACTIVE_TASKS.has(taskKey('ui',t.id));
|
|
|
const preset=done?u.preset:t.prompt;
|
|
const preset=done?u.preset:t.prompt;
|
|
|
- const card=document.createElement('div'); card.className='card '+(done?'':'missing');
|
|
|
|
|
- card.innerHTML=`<div class="stage">${done?`<div class="demo-box">${preset==='number_roll'?'0':'UI'}</div>`:`<div class="placeholder">待生成<br>${esc(t.chineseName)}</div>`}</div>
|
|
|
|
|
|
|
+ const card=document.createElement('div'); card.className='card '+(done?'':'missing')+(running?' running':'');
|
|
|
|
|
+ card.innerHTML=`<div class="stage">${done?`<div class="demo-box">${preset==='number_roll'?'0':'UI'}</div>`:`<div class="placeholder">${running?'生成中…':'待生成'}<br>${esc(t.chineseName)}</div>`}</div>
|
|
|
<div class="task-head"><div><div class="name">${esc(t.chineseName)}</div><div class="meta">${esc(t.englishName)}</div></div>
|
|
<div class="task-head"><div><div class="name">${esc(t.chineseName)}</div><div class="meta">${esc(t.englishName)}</div></div>
|
|
|
- <span class="task-status ${done?'done':'missing'}">${done?'已生成':'缺失'}</span></div>
|
|
|
|
|
|
|
+ <span class="task-status ${done?'done':(running?'running':'missing')}">${done?'已生成':(running?'生成中':'缺失')}</span></div>
|
|
|
<div class="meta"><span class="pill">tween</span> 预设: ${esc(preset)}<br>${esc(t.use)}</div>
|
|
<div class="meta"><span class="pill">tween</span> 预设: ${esc(preset)}<br>${esc(t.use)}</div>
|
|
|
- ${done?'<button class="ghost">▶ 播放</button>':`<button class="ghost retry-btn" data-kind="ui" data-id="${esc(t.id)}">补生成 / 重试</button>`}`;
|
|
|
|
|
|
|
+ ${done?'<button class="ghost">▶ 播放</button>':`<button class="ghost retry-btn" data-kind="ui" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':'补生成 / 重试'}</button>`}`;
|
|
|
if(!done){ grid.appendChild(card); return; }
|
|
if(!done){ grid.appendChild(card); return; }
|
|
|
const box=card.querySelector('.demo-box');
|
|
const box=card.querySelector('.demo-box');
|
|
|
card.querySelector('button').onclick=()=>{
|
|
card.querySelector('button').onclick=()=>{
|
|
@@ -469,7 +486,11 @@ $('#view').addEventListener('click', async e=>{
|
|
|
if(!btn) return;
|
|
if(!btn) return;
|
|
|
const game=$('#gameSel').value;
|
|
const game=$('#gameSel').value;
|
|
|
if(!game||game==='(暂无)'){ opMsg('没有可重试的资源库',false); return; }
|
|
if(!game||game==='(暂无)'){ opMsg('没有可重试的资源库',false); return; }
|
|
|
- btn.disabled=true;
|
|
|
|
|
|
|
+ const item={kind:btn.dataset.kind,id:btn.dataset.id};
|
|
|
|
|
+ markTasksRunning([item], true);
|
|
|
|
|
+ render();
|
|
|
|
|
+ $('#retryMissingBtn').disabled=true;
|
|
|
|
|
+ opMsg(`正在补生成 ${item.kind}/${item.id}…`);
|
|
|
const log=$('#log'); log.style.display='block'; log.textContent=`补生成 ${btn.dataset.kind}/${btn.dataset.id} 任务创建中…`;
|
|
const log=$('#log'); log.style.display='block'; log.textContent=`补生成 ${btn.dataset.kind}/${btn.dataset.id} 任务创建中…`;
|
|
|
try{
|
|
try{
|
|
|
const r=await fetch('/api/retry-task',{method:'POST',headers:{'Content-Type':'application/json'},
|
|
const r=await fetch('/api/retry-task',{method:'POST',headers:{'Content-Type':'application/json'},
|
|
@@ -480,13 +501,15 @@ $('#view').addEventListener('click', async e=>{
|
|
|
const d=await r.json();
|
|
const d=await r.json();
|
|
|
if(!d.ok || !d.jobId){
|
|
if(!d.ok || !d.jobId){
|
|
|
log.textContent='❌ '+(d.error||'补生成失败');
|
|
log.textContent='❌ '+(d.error||'补生成失败');
|
|
|
- btn.disabled=false;
|
|
|
|
|
|
|
+ markTasksRunning([item], false); render();
|
|
|
|
|
+ $('#retryMissingBtn').disabled=false;
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- await pollJob(d.jobId, log, btn);
|
|
|
|
|
|
|
+ await pollJob(d.jobId, log, btn, [item]);
|
|
|
}catch(err){
|
|
}catch(err){
|
|
|
log.textContent='请求失败: '+err;
|
|
log.textContent='请求失败: '+err;
|
|
|
- btn.disabled=false;
|
|
|
|
|
|
|
+ markTasksRunning([item], false); render();
|
|
|
|
|
+ $('#retryMissingBtn').disabled=false;
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
$('#gameSel').onchange=e=>loadLibrary(e.target.value);
|
|
$('#gameSel').onchange=e=>loadLibrary(e.target.value);
|
|
@@ -661,6 +684,36 @@ $('#aiWorkflowBtn').onclick=async()=>{
|
|
|
|
|
|
|
|
$('#openGenBtn').onclick=()=>{ $('#genPanel').open=true; $('#genPanel').scrollIntoView({behavior:'smooth',block:'start'}); };
|
|
$('#openGenBtn').onclick=()=>{ $('#genPanel').open=true; $('#genPanel').scrollIntoView({behavior:'smooth',block:'start'}); };
|
|
|
|
|
|
|
|
|
|
+$('#retryMissingBtn').onclick=async()=>{
|
|
|
|
|
+ const game=$('#gameSel').value;
|
|
|
|
|
+ if(!game||game==='(暂无)'){ opMsg('没有可补生成的资源库',false); return; }
|
|
|
|
|
+ const items=missingTasks();
|
|
|
|
|
+ if(!items.length){ opMsg('当前没有缺失任务'); return; }
|
|
|
|
|
+ markTasksRunning(items, true);
|
|
|
|
|
+ render();
|
|
|
|
|
+ const btn=$('#retryMissingBtn'); btn.disabled=true;
|
|
|
|
|
+ opMsg(`正在批量补生成 ${items.length} 个缺失任务…`);
|
|
|
|
|
+ const log=$('#log'); log.style.display='block'; log.textContent=`批量补生成 ${items.length} 个缺失任务创建中…`;
|
|
|
|
|
+ try{
|
|
|
|
|
+ const r=await fetch('/api/retry-missing',{method:'POST',headers:{'Content-Type':'application/json'},
|
|
|
|
|
+ body:JSON.stringify({
|
|
|
|
|
+ game, provider:$('#provider').value, api_key:$('#apiKey').value,
|
|
|
|
|
+ base_url:$('#baseUrl').value, model:$('#model').value, size:$('#size').value })});
|
|
|
|
|
+ const d=await r.json();
|
|
|
|
|
+ if(!d.ok || !d.jobId){
|
|
|
|
|
+ log.textContent='❌ '+(d.error||'批量补生成失败');
|
|
|
|
|
+ markTasksRunning(items, false); render();
|
|
|
|
|
+ btn.disabled=false;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ await pollJob(d.jobId, log, btn, items);
|
|
|
|
|
+ }catch(e){
|
|
|
|
|
+ log.textContent='请求失败: '+e;
|
|
|
|
|
+ markTasksRunning(items, false); render();
|
|
|
|
|
+ btn.disabled=false;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
$('#exportBtn').onclick=async()=>{
|
|
$('#exportBtn').onclick=async()=>{
|
|
|
const game=$('#gameSel').value;
|
|
const game=$('#gameSel').value;
|
|
|
if(!game||game==='(暂无)'){ opMsg('没有可导出的资源库',false); return; }
|
|
if(!game||game==='(暂无)'){ opMsg('没有可导出的资源库',false); return; }
|
|
@@ -722,7 +775,7 @@ $('#startBtn').onclick=async()=>{
|
|
|
}catch(e){ log.textContent='请求失败: '+e; }
|
|
}catch(e){ log.textContent='请求失败: '+e; }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-async function pollJob(jobId, log, btn){
|
|
|
|
|
|
|
+async function pollJob(jobId, log, btn, runningItems=[]){
|
|
|
let lastText='';
|
|
let lastText='';
|
|
|
while(true){
|
|
while(true){
|
|
|
let d;
|
|
let d;
|
|
@@ -738,13 +791,22 @@ async function pollJob(jobId, log, btn){
|
|
|
lastText=head+'\n'+lines.join('\n')+(d.error?('\n❌ '+d.error):'');
|
|
lastText=head+'\n'+lines.join('\n')+(d.error?('\n❌ '+d.error):'');
|
|
|
log.textContent=lastText;
|
|
log.textContent=lastText;
|
|
|
log.scrollTop=log.scrollHeight;
|
|
log.scrollTop=log.scrollHeight;
|
|
|
|
|
+ if((d.logs||[]).length){
|
|
|
|
|
+ opMsg((d.status==='running'?'生成中:':'任务状态:') + d.logs[d.logs.length-1], d.status!=='error');
|
|
|
|
|
+ }
|
|
|
if(d.status==='done'){
|
|
if(d.status==='done'){
|
|
|
|
|
+ markTasksRunning(runningItems, false);
|
|
|
|
|
+ ACTIVE_TASKS.clear();
|
|
|
await loadLibrary(d.game);
|
|
await loadLibrary(d.game);
|
|
|
- btn.disabled=false;
|
|
|
|
|
|
|
+ if(btn) btn.disabled=false;
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
if(d.status==='error'){
|
|
if(d.status==='error'){
|
|
|
- btn.disabled=false;
|
|
|
|
|
|
|
+ markTasksRunning(runningItems, false);
|
|
|
|
|
+ ACTIVE_TASKS.clear();
|
|
|
|
|
+ render();
|
|
|
|
|
+ if(btn) btn.disabled=false;
|
|
|
|
|
+ $('#retryMissingBtn').disabled=false;
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
await new Promise(r=>setTimeout(r,1200));
|
|
await new Promise(r=>setTimeout(r,1200));
|