input-kanban 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/index.html CHANGED
@@ -119,6 +119,7 @@
119
119
  .modal-backdrop.hidden { display: none; }
120
120
  .modal-card { width: min(760px, 100%); border: 1px solid var(--line); border-radius: 14px; background: var(--panel); box-shadow: 0 18px 60px rgba(0,0,0,.38); padding: 16px; }
121
121
  .modal-card textarea { min-height: 220px; }
122
+ .page-footer { padding: 0 18px 18px; color: var(--muted); text-align: center; font-size: 12px; }
122
123
  </style>
123
124
  </head>
124
125
  <body>
@@ -186,6 +187,7 @@
186
187
  </section>
187
188
  </div>
188
189
  </main>
190
+ <footer id="pageFooter" class="page-footer">版本:-</footer>
189
191
  <div id="manualCompleteModal" class="modal-backdrop hidden">
190
192
  <div class="modal-card">
191
193
  <h2>手动标记成功</h2>
@@ -205,12 +207,17 @@ let selectedTask = null;
205
207
  let selectedFileName = null;
206
208
  let manualCompleteTaskId = null;
207
209
  let pendingArchiveRunId = null;
210
+ const autoDispatchingRuns = new Set();
211
+ const autoJudgingRuns = new Set();
212
+ const autoRetryingRuns = new Set();
213
+ const autoRetrySkippedRuns = new Set();
208
214
  let currentState = null;
209
215
  let lastAutoRefreshAt = null;
210
216
  let runListVisibleCount = 10;
211
217
  let latestRuns = [];
212
218
  const statusByRunId = new Map();
213
219
  const AUTO_REFRESH_MS = 3000;
220
+ const AUTO_MAX_RETRIES = 1;
214
221
  const RUN_LIST_PAGE_SIZE = 10;
215
222
 
216
223
  async function api(path, opts={}) {
@@ -330,6 +337,7 @@ async function loadHealth() {
330
337
  const h = await api('/api/health');
331
338
  document.getElementById('repo').value = h.defaultRepo;
332
339
  document.getElementById('runsDir').value = h.runsDir;
340
+ document.getElementById('pageFooter').textContent = h.version ? `版本:v${h.version}` : '版本:未知(请重启服务)';
333
341
  }
334
342
  function showCreateForm() {
335
343
  selectedRun = null; selectedTask = null; selectedFileName = null; currentState = null;
@@ -357,6 +365,7 @@ async function refreshRuns() {
357
365
  const data = await api('/api/runs');
358
366
  latestRuns = data.runs || [];
359
367
  renderRunList();
368
+ await maybeAutoAdvanceRunSummaries(latestRuns);
360
369
  }
361
370
  function renderRunList() {
362
371
  const visibleRuns = latestRuns.slice(0, runListVisibleCount);
@@ -382,7 +391,7 @@ function showMoreRuns() {
382
391
  renderRunList();
383
392
  }
384
393
  async function selectRun(id) { selectedRun = id; selectedTask = null; selectedFileName = null; clearFileView(); hideCreateForm(); await refreshSelected(); }
385
- async function refreshSelected({auto=false} = {}) {
394
+ async function refreshSelected({auto=false, skipAutoAdvance=false} = {}) {
386
395
  if (!selectedRun) return;
387
396
  currentState = await api(`/api/runs/${selectedRun}/status`);
388
397
  statusByRunId.set(selectedRun, currentState);
@@ -394,6 +403,7 @@ async function refreshSelected({auto=false} = {}) {
394
403
  await loadTaskDescription();
395
404
  renderTasks(); await refreshRuns();
396
405
  if (selectedTask && selectedFileName) await loadFile(selectedFileName, { preserveScroll: true });
406
+ if (!skipAutoAdvance) await maybeAutoAdvanceSelectedRun();
397
407
  }
398
408
  function renderSelectedHeader() {
399
409
  if (!currentState) return '<div class="muted">未选择任务批次</div>';
@@ -463,9 +473,9 @@ function updateAutoRefreshHint() {
463
473
  const el = document.getElementById('autoRefreshHint');
464
474
  if (!el) return;
465
475
  if (!selectedRun || !currentState) { el.textContent = '自动刷新:未启动'; return; }
466
- const active = ['planning','running','judging'].includes(currentState.status) || (currentState.tasks || []).some(t => t.status === 'running') || currentState.planner?.status === 'running' || currentState.judge?.status === 'running';
476
+ const active = ['planning','running','judging','planned','batches_completed','batch_blocked'].includes(currentState.status) || (currentState.tasks || []).some(t => t.status === 'running') || currentState.planner?.status === 'running' || currentState.judge?.status === 'running';
467
477
  const last = lastAutoRefreshAt ? lastAutoRefreshAt.toLocaleTimeString() : '尚未触发';
468
- el.textContent = active ? `自动刷新中:每 ${AUTO_REFRESH_MS / 1000} 秒刷新一次|上次刷新 ${last}` : `自动刷新待命:每 ${AUTO_REFRESH_MS / 1000} 秒检查一次|上次刷新 ${last}`;
478
+ el.textContent = active ? `自动模式中:每 ${AUTO_REFRESH_MS / 1000} 秒刷新并推进一次|上次刷新 ${last}` : `自动模式待命:每 ${AUTO_REFRESH_MS / 1000} 秒检查一次|上次刷新 ${last}`;
469
479
  }
470
480
  function taskStatusCell(t) {
471
481
  if (t?.manualCompletion) {
@@ -802,6 +812,67 @@ async function renameRunLabel(event, runId = selectedRun) {
802
812
  else await refreshRuns();
803
813
  });
804
814
  }
815
+ async function maybeAutoAdvanceRunSummaries(runs) {
816
+ for (const run of runs || []) {
817
+ if (!run || run.runId === selectedRun) continue;
818
+ if (run.status !== 'batch_blocked') autoRetrySkippedRuns.delete(run.runId);
819
+ if (run.status === 'planned') await autoDispatchRun(run.runId, false);
820
+ else if (run.status === 'batches_completed') await autoJudgeRun(run.runId, false);
821
+ else if (run.status === 'batch_blocked') await autoRetryRun(run.runId, false);
822
+ }
823
+ }
824
+ async function autoDispatchRun(runId, refreshSelectedAfter = true) {
825
+ if (!runId || autoDispatchingRuns.has(runId)) return false;
826
+ autoDispatchingRuns.add(runId);
827
+ try {
828
+ await api(`/api/runs/${runId}/dispatch`, {method:'POST'});
829
+ if (refreshSelectedAfter && selectedRun === runId) await refreshSelected({auto:true, skipAutoAdvance:true});
830
+ return true;
831
+ } catch (error) {
832
+ console.error('自动派发失败', error);
833
+ return false;
834
+ } finally {
835
+ autoDispatchingRuns.delete(runId);
836
+ }
837
+ }
838
+ async function autoJudgeRun(runId, refreshSelectedAfter = true) {
839
+ if (!runId || autoJudgingRuns.has(runId)) return false;
840
+ autoJudgingRuns.add(runId);
841
+ try {
842
+ await api(`/api/runs/${runId}/judge`, {method:'POST'});
843
+ if (refreshSelectedAfter && selectedRun === runId) await refreshSelected({auto:true, skipAutoAdvance:true});
844
+ return true;
845
+ } catch (error) {
846
+ console.error('自动验收失败', error);
847
+ return false;
848
+ } finally {
849
+ autoJudgingRuns.delete(runId);
850
+ }
851
+ }
852
+ async function autoRetryRun(runId, refreshSelectedAfter = true) {
853
+ if (!runId || autoRetryingRuns.has(runId) || autoRetrySkippedRuns.has(runId)) return false;
854
+ autoRetryingRuns.add(runId);
855
+ try {
856
+ await api(`/api/runs/${runId}/retry`, {method:'POST', body: JSON.stringify({ reason: 'auto retry from dashboard', maxRetries: AUTO_MAX_RETRIES, auto: true })});
857
+ if (refreshSelectedAfter && selectedRun === runId) await refreshSelected({auto:true, skipAutoAdvance:true});
858
+ return true;
859
+ } catch (error) {
860
+ autoRetrySkippedRuns.add(runId);
861
+ console.error('自动重试失败', error);
862
+ return false;
863
+ } finally {
864
+ autoRetryingRuns.delete(runId);
865
+ }
866
+ }
867
+ async function maybeAutoAdvanceSelectedRun() {
868
+ const runId = selectedRun;
869
+ const state = currentState;
870
+ if (!runId || !state) return;
871
+ if (state.status !== 'batch_blocked') autoRetrySkippedRuns.delete(runId);
872
+ if (state.status === 'planned') await autoDispatchRun(runId);
873
+ else if (state.status === 'batches_completed' && state.judge?.status !== 'running' && state.judge?.status !== 'completed') await autoJudgeRun(runId);
874
+ else if (state.status === 'batch_blocked') await autoRetryRun(runId);
875
+ }
805
876
  async function planRun() { if (selectedRun) await runAction(async () => { await api(`/api/runs/${selectedRun}/plan`, {method:'POST'}); await refreshSelected(); }); }
806
877
  async function dispatchRun() { if (selectedRun) await runAction(async () => { await api(`/api/runs/${selectedRun}/dispatch`, {method:'POST'}); await refreshSelected(); }); }
807
878
  async function judgeRun() { if (selectedRun) await runAction(async () => { await api(`/api/runs/${selectedRun}/judge`, {method:'POST'}); await refreshSelected(); }); }