From dd4c22a1bdd52eef8e0abfd72ad8eee376a3e858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Tue, 7 Jul 2026 17:17:19 +0800 Subject: [PATCH] Add files via upload --- web/static/css/style.css | 252 ++++++++++++++++++++++++++++++++++--- web/static/js/tasks.js | 264 ++++++++++++++++++++++++++++++++++----- web/templates/index.html | 8 +- 3 files changed, 477 insertions(+), 47 deletions(-) diff --git a/web/static/css/style.css b/web/static/css/style.css index 21eebb64..41b555ba 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -15842,17 +15842,17 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible { .batch-queue-detail-kv { display: grid; - grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); - gap: 10px 20px; - margin-bottom: 14px; + grid-template-columns: repeat(2, minmax(280px, 1fr)); + gap: 12px 32px; + margin-bottom: 18px; } .bq-kv { display: grid; - grid-template-columns: minmax(100px, 34%) 1fr; - gap: 8px 12px; - align-items: start; - padding: 8px 0; + grid-template-columns: minmax(88px, 112px) 1fr; + gap: 10px 16px; + align-items: center; + padding: 10px 0; border-bottom: 1px solid var(--border-color); font-size: 0.875rem; } @@ -15917,22 +15917,34 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible { /* Inline edit controls (replaces value in-place) */ .bq-inline-edit-controls { - display: inline-flex; + display: flex; align-items: center; - gap: 6px; + gap: 10px; flex-wrap: wrap; + width: 100%; + min-width: 0; } .bq-inline-edit-controls input[type="text"], .bq-inline-edit-controls select { - padding: 4px 8px; - border-radius: 6px; + padding: 8px 12px; + border-radius: 8px; border: 1.5px solid var(--accent-color); - font-size: 13px; + font-size: 0.875rem; outline: none; background: var(--bg-primary); color: var(--text-primary); transition: border-color 0.15s, box-shadow 0.15s; } +.bq-inline-edit-controls input[type="text"] { + flex: 1; + min-width: 200px; +} + +.bq-inline-edit-controls .bq-edit-cron-expr { + flex: 1 1 280px; + min-width: 240px; + max-width: 100%; +} .bq-inline-edit-controls input[type="text"]:focus, .bq-inline-edit-controls select:focus { box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.15); @@ -15960,11 +15972,18 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible { .batch-task-inline-edit textarea:focus { box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.15); } +@media (max-width: 720px) { + .batch-queue-detail-kv { + grid-template-columns: 1fr; + gap: 8px; + } +} + @media (max-width: 520px) { .bq-kv, .bq-kv--block { grid-template-columns: 1fr; - gap: 4px; + gap: 6px; } } @@ -29318,7 +29337,8 @@ html[data-theme="dark"] .workflow-form-select-option:hover, html[data-theme="dark"] .projects-filter-select-option:hover, html[data-theme="dark"] .chat-files-filter-select-option:hover, html[data-theme="dark"] #role-modal .role-form-select-option:hover, -html[data-theme="dark"] .monitor-filter-select-option:hover { +html[data-theme="dark"] .monitor-filter-select-option:hover, +html[data-theme="dark"] .batch-form-select-option:hover { background: rgba(96, 165, 250, 0.12); } @@ -29334,7 +29354,8 @@ html[data-theme="dark"] .workflow-form-select-option.is-selected, html[data-theme="dark"] .projects-filter-select-option.is-selected, html[data-theme="dark"] .chat-files-filter-select-option.is-selected, html[data-theme="dark"] #role-modal .role-form-select-option.is-selected, -html[data-theme="dark"] .monitor-filter-select-option.is-selected { +html[data-theme="dark"] .monitor-filter-select-option.is-selected, +html[data-theme="dark"] .batch-form-select-option.is-selected { background: rgba(96, 165, 250, 0.18); color: var(--accent-hover); } @@ -29355,6 +29376,7 @@ html[data-theme="dark"] .workflow-tool-select-trigger, html[data-theme="dark"] .projects-filter-select-trigger, html[data-theme="dark"] .chat-files-filter-select-trigger, html[data-theme="dark"] .role-form-select-trigger, +html[data-theme="dark"] .batch-form-select-trigger, html[data-theme="dark"] .monitor-filter-select-trigger, html[data-theme="dark"] .role-selector-btn, html[data-theme="dark"] .conversation-project-filter-trigger { @@ -29383,6 +29405,7 @@ html[data-theme="dark"] .workflow-form-select-ui.open .workflow-form-select-trig html[data-theme="dark"] .projects-filter-select-ui.open .projects-filter-select-trigger, html[data-theme="dark"] .chat-files-filter-select-ui.open .chat-files-filter-select-trigger, html[data-theme="dark"] #role-modal .role-form-select-ui.open .role-form-select-trigger, +html[data-theme="dark"] .batch-form-select-ui.open .batch-form-select-trigger, html[data-theme="dark"] .monitor-filter-select-ui.open .monitor-filter-select-trigger { border-color: var(--accent-color); box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.16); @@ -31119,6 +31142,205 @@ html[data-theme="dark"] #agent-md-modal .modal-footer, white-space: nowrap; } +/* 批量任务:新建队列 / 详情内联编辑 — 自定义下拉 */ +.batch-form-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +#batch-import-modal .batch-form-select-ui, +#batch-queue-detail-modal .batch-form-select-ui { + position: relative; + width: 100%; + min-width: 0; +} + +.batch-form-select-ui--inline { + display: block; + width: 100%; + min-width: 220px; + max-width: none; +} + +.batch-form-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + min-height: 40px; + padding: 10px 12px; + border: 1px solid var(--border-color); + border-radius: 8px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 0.875rem; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.batch-form-select-ui--inline .batch-form-select-trigger { + min-height: 36px; + padding: 8px 12px; + font-size: 0.875rem; + border-radius: 8px; + border-width: 1.5px; + border-color: var(--accent-color); +} + +.batch-form-select-trigger:hover:not(:disabled) { + border-color: rgba(59, 130, 246, 0.45); +} + +.batch-form-select-ui.open .batch-form-select-trigger { + border-color: var(--accent-color); + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12); +} + +.batch-form-select-ui.open { + z-index: 120; +} + +.batch-form-select-ui.is-disabled .batch-form-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + +.batch-form-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.batch-form-select-caret { + flex-shrink: 0; + color: var(--text-secondary); + transition: transform 0.15s ease; +} + +.batch-form-select-ui.open .batch-form-select-caret { + transform: rotate(180deg); +} + +.batch-form-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: auto; + min-width: 100%; + width: max-content; + max-width: min(420px, calc(100vw - 48px)); + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: var(--shadow-lg); +} + +.batch-form-select-ui.open .batch-form-select-dropdown { + display: block; +} + +.batch-form-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 10px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-primary); + font-size: 0.8125rem; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: background 0.12s ease, color 0.12s ease; +} + +.batch-form-select-option:hover { + background: var(--bg-secondary); +} + +.batch-form-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +.batch-form-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +.batch-form-select-option.is-selected .batch-form-select-check { + visibility: visible; +} + +.batch-form-select-label { + flex: 1; + min-width: 0; + white-space: nowrap; +} + +.batch-form-select-ui--inline .batch-form-select-value { + white-space: nowrap; + overflow: visible; + text-overflow: unset; +} + +.batch-form-select-option .batch-form-select-label { + overflow: visible; + text-overflow: unset; + white-space: nowrap; +} + +#batch-import-modal .modal-content:has(.batch-form-select-ui.open), +#batch-import-modal .modal-body:has(.batch-form-select-ui.open), +#batch-queue-detail-modal .modal-content:has(.batch-form-select-ui.open), +#batch-queue-detail-modal .modal-body:has(.batch-form-select-ui.open) { + overflow: visible; +} + +.bq-inline-edit-controls .batch-form-native-select { + position: absolute; +} + +.bq-inline-edit-controls .batch-form-select-ui--inline { + margin: 0; + flex: 1 1 240px; +} + +#batch-queue-detail-modal .bq-kv__v:has(.bq-inline-edit-controls) { + overflow: visible; +} + +#batch-queue-detail-modal .batch-queue-detail-tech__sum { + white-space: normal; + line-height: 1.5; +} + html[data-theme="dark"] #role-modal .modal-content, html[data-theme="dark"] #role-modal .modal-header, html[data-theme="dark"] #role-modal .modal-body, diff --git a/web/static/js/tasks.js b/web/static/js/tasks.js index 397023d7..57b4ea89 100644 --- a/web/static/js/tasks.js +++ b/web/static/js/tasks.js @@ -765,6 +765,7 @@ function toggleTasksAutoRefresh(enabled) { // 初始化任务管理页面 function initTasksPage() { initBatchQueuesFilterSelects(); + initBatchFormSelects(); // 恢复自动刷新设置 const autoRefreshCheckbox = document.getElementById('tasks-auto-refresh'); if (autoRefreshCheckbox) { @@ -930,6 +931,7 @@ async function showBatchImportModal() { } } await refreshBatchProjectSelectOptions(); + refreshBatchFormSelects(); openAppModal('batch-import-modal', { focusEl: input }); } @@ -1299,6 +1301,207 @@ function initBatchQueuesFilterSelects() { syncAllBatchQueuesFilterSelects(); } +const BATCH_IMPORT_FORM_SELECT_IDS = [ + 'batch-queue-role', + 'batch-queue-project-id', + 'batch-queue-agent-mode', + 'batch-queue-schedule-mode', +]; +const batchFormSelectMap = {}; +let batchFormSelectDocBound = false; +const BATCH_FORM_SELECT_CARET = ''; + +function closeAllBatchFormSelects() { + Object.keys(batchFormSelectMap).forEach(function (id) { + const reg = batchFormSelectMap[id]; + if (!reg || !reg.wrapper) return; + reg.wrapper.classList.remove('open'); + if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false'); + }); +} + +function syncBatchFormSelect(selectId) { + const reg = batchFormSelectMap[selectId]; + if (!reg) return; + const select = reg.select; + const dropdown = reg.dropdown; + const trigger = reg.trigger; + const valueSpan = trigger.querySelector('.batch-form-select-value'); + + dropdown.innerHTML = ''; + Array.prototype.forEach.call(select.options, function (opt) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'batch-form-select-option'; + item.setAttribute('role', 'option'); + item.setAttribute('data-value', opt.value); + if (opt.value === select.value) { + item.classList.add('is-selected'); + item.setAttribute('aria-selected', 'true'); + } else { + item.setAttribute('aria-selected', 'false'); + } + const check = document.createElement('span'); + check.className = 'batch-form-select-check'; + check.setAttribute('aria-hidden', 'true'); + check.textContent = '✓'; + const label = document.createElement('span'); + label.className = 'batch-form-select-label'; + label.textContent = opt.textContent; + item.appendChild(check); + item.appendChild(label); + dropdown.appendChild(item); + }); + + const selectedOpt = select.options[select.selectedIndex]; + if (valueSpan) { + valueSpan.textContent = selectedOpt ? selectedOpt.textContent : ''; + } + trigger.disabled = !!select.disabled; + reg.wrapper.classList.toggle('is-disabled', !!select.disabled); +} + +function syncAllBatchImportFormSelects() { + BATCH_IMPORT_FORM_SELECT_IDS.forEach(syncBatchFormSelect); +} + +function isBatchFormSelectFocused(selectId) { + const active = document.activeElement; + if (!active) return false; + if (active.id === selectId) return true; + const reg = batchFormSelectMap[selectId]; + return !!(reg && reg.wrapper && reg.wrapper.contains(active)); +} + +function focusBatchFormSelect(selectId) { + const reg = batchFormSelectMap[selectId]; + if (reg && reg.trigger) { + reg.trigger.focus(); + return; + } + const select = document.getElementById(selectId); + if (select) select.focus(); +} + +function bindBatchInlineEditFocusOut(container, saveFn, isCancelledFn) { + if (!container) return; + container.addEventListener('focusout', function () { + setTimeout(function () { + if (isCancelledFn && isCancelledFn()) return; + if (container.contains(document.activeElement)) return; + saveFn(); + }, 100); + }); +} + +function enhanceBatchFormSelect(selectId, options) { + options = options || {}; + const select = document.getElementById(selectId); + if (!select) return; + const existing = batchFormSelectMap[selectId]; + if (existing && existing.select !== select) { + delete batchFormSelectMap[selectId]; + } + if (select.dataset.batchFormCustom === '1') { + syncBatchFormSelect(selectId); + return; + } + select.dataset.batchFormCustom = '1'; + select.classList.add('batch-form-native-select'); + select.tabIndex = -1; + select.setAttribute('aria-hidden', 'true'); + + const wrapper = document.createElement('div'); + wrapper.className = 'batch-form-select-ui' + (options.inline ? ' batch-form-select-ui--inline' : ''); + + const trigger = document.createElement('button'); + trigger.type = 'button'; + trigger.className = 'batch-form-select-trigger'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); + const valueSpan = document.createElement('span'); + valueSpan.className = 'batch-form-select-value'; + trigger.appendChild(valueSpan); + trigger.insertAdjacentHTML('beforeend', BATCH_FORM_SELECT_CARET); + + const dropdown = document.createElement('div'); + dropdown.className = 'batch-form-select-dropdown'; + dropdown.setAttribute('role', 'listbox'); + + const parent = select.parentNode; + parent.insertBefore(wrapper, select); + wrapper.appendChild(trigger); + wrapper.appendChild(dropdown); + wrapper.appendChild(select); + + batchFormSelectMap[selectId] = { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select }; + + trigger.addEventListener('click', function (e) { + e.stopPropagation(); + if (select.disabled) return; + const open = wrapper.classList.contains('open'); + closeAllBatchFormSelects(); + if (!open) { + wrapper.classList.add('open'); + trigger.setAttribute('aria-expanded', 'true'); + } + }); + + dropdown.addEventListener('click', function (e) { + const opt = e.target.closest('.batch-form-select-option'); + if (!opt) return; + e.stopPropagation(); + const val = opt.getAttribute('data-value'); + if (val === null) return; + if (select.value !== val) { + select.value = val; + select.dispatchEvent(new Event('change', { bubbles: true })); + } + wrapper.classList.remove('open'); + trigger.setAttribute('aria-expanded', 'false'); + syncBatchFormSelect(selectId); + }); + + select.addEventListener('change', function () { + syncBatchFormSelect(selectId); + }); + + syncBatchFormSelect(selectId); +} + +function refreshBatchFormSelect(selectId, options) { + const select = document.getElementById(selectId); + if (!select) { + delete batchFormSelectMap[selectId]; + return; + } + enhanceBatchFormSelect(selectId, options); +} + +function refreshBatchFormSelects() { + Object.keys(batchFormSelectMap).forEach(function (id) { + if (!document.getElementById(id)) delete batchFormSelectMap[id]; + }); + BATCH_IMPORT_FORM_SELECT_IDS.forEach(function (id) { + enhanceBatchFormSelect(id, { inline: false }); + }); + if (!batchFormSelectDocBound) { + batchFormSelectDocBound = true; + document.addEventListener('click', function (e) { + if (e.target.closest('.batch-form-select-ui')) return; + closeAllBatchFormSelects(); + }); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeAllBatchFormSelects(); + }); + } + syncAllBatchImportFormSelects(); +} + +function initBatchFormSelects() { + refreshBatchFormSelects(); +} + // 筛选批量任务队列 function filterBatchQueues() { const statusFilter = document.getElementById('batch-queues-status-filter'); @@ -2337,15 +2540,20 @@ function startInlineEditRole() { ${orphanOpt}${opts} `; + refreshBatchFormSelect('bq-edit-role', { inline: true }); const sel = document.getElementById('bq-edit-role'); + const controls = container.querySelector('.bq-inline-edit-controls'); + const roleReg = batchFormSelectMap['bq-edit-role']; if (sel) { - sel.focus(); + focusBatchFormSelect('bq-edit-role'); let cancelled = false; - sel.addEventListener('keydown', (e) => { + const onEscape = (e) => { if (e.key === 'Escape') { cancelled = true; cancelAllInlineEdits(); } - }); + }; + sel.addEventListener('keydown', onEscape); + if (roleReg && roleReg.trigger) roleReg.trigger.addEventListener('keydown', onEscape); sel.addEventListener('change', () => { if (!cancelled) saveInlineRole(); }); - sel.addEventListener('blur', () => { if (!cancelled) saveInlineRole(); }); + bindBatchInlineEditFocusOut(controls, () => { if (!cancelled) saveInlineRole(); }, () => cancelled); } }); } @@ -2397,15 +2605,20 @@ function startInlineEditAgentMode() { `; + refreshBatchFormSelect('bq-edit-agentmode', { inline: true }); const sel = document.getElementById('bq-edit-agentmode'); + const controls = container.querySelector('.bq-inline-edit-controls'); + const modeReg = batchFormSelectMap['bq-edit-agentmode']; if (sel) { - sel.focus(); + focusBatchFormSelect('bq-edit-agentmode'); let cancelled = false; - sel.addEventListener('keydown', (e) => { + const onEscape = (e) => { if (e.key === 'Escape') { cancelled = true; cancelAllInlineEdits(); } - }); + }; + sel.addEventListener('keydown', onEscape); + if (modeReg && modeReg.trigger) modeReg.trigger.addEventListener('keydown', onEscape); sel.addEventListener('change', () => { if (!cancelled) saveInlineAgentMode(); }); - sel.addEventListener('blur', () => { if (!cancelled) saveInlineAgentMode(); }); + bindBatchInlineEditFocusOut(controls, () => { if (!cancelled) saveInlineAgentMode(); }, () => cancelled); } }); } @@ -2543,45 +2756,38 @@ function startInlineEditSchedule() { const queue = detail.queue; const isCron = queue.scheduleMode === 'cron'; container.innerHTML = ` - `; + refreshBatchFormSelect('bq-edit-schedule-mode', { inline: true }); let schedCancelled = false; const sel = document.getElementById('bq-edit-schedule-mode'); const cronInp = document.getElementById('bq-edit-cron-expr'); + const controls = container.querySelector('.bq-inline-edit-controls'); + const schedReg = batchFormSelectMap['bq-edit-schedule-mode']; + const onSchedEscape = (e) => { if (e.key === 'Escape') { schedCancelled = true; cancelAllInlineEdits(); } }; if (sel) { - sel.focus(); - sel.addEventListener('keydown', (e) => { if (e.key === 'Escape') { schedCancelled = true; cancelAllInlineEdits(); } }); + focusBatchFormSelect('bq-edit-schedule-mode'); + sel.addEventListener('keydown', onSchedEscape); + if (schedReg && schedReg.trigger) schedReg.trigger.addEventListener('keydown', onSchedEscape); sel.addEventListener('change', () => { toggleInlineScheduleCron(); - // 切到 manual 时直接保存;切到 cron 时等用户输入表达式后 blur 保存 if (sel.value !== 'cron' && !schedCancelled) saveInlineSchedule(); }); - sel.addEventListener('blur', (e) => { - // 如果焦点移到了 cron 输入框,不触发保存 - setTimeout(() => { - const active = document.activeElement; - if (active && (active.id === 'bq-edit-cron-expr' || active.id === 'bq-edit-schedule-mode')) return; - if (!schedCancelled) saveInlineSchedule(); - }, 100); - }); } if (cronInp) { cronInp.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); cronInp.blur(); } if (e.key === 'Escape') { schedCancelled = true; cancelAllInlineEdits(); } }); - cronInp.addEventListener('blur', () => { - setTimeout(() => { - const active = document.activeElement; - if (active && (active.id === 'bq-edit-cron-expr' || active.id === 'bq-edit-schedule-mode')) return; - if (!schedCancelled) saveInlineSchedule(); - }, 100); - }); } + bindBatchInlineEditFocusOut(controls, () => { + if (schedCancelled) return; + saveInlineSchedule(); + }, () => schedCancelled); }); } function toggleInlineScheduleCron() { @@ -2675,6 +2881,7 @@ window.saveInlineSchedule = saveInlineSchedule; document.addEventListener('languagechange', function () { try { syncAllBatchQueuesFilterSelects(); + syncAllBatchImportFormSelects(); const tasksPage = document.getElementById('page-tasks'); if (!tasksPage || !tasksPage.classList.contains('active')) { return; @@ -2697,4 +2904,5 @@ document.addEventListener('languagechange', function () { document.addEventListener('DOMContentLoaded', function () { initBatchQueuesFilterSelects(); + initBatchFormSelects(); }); diff --git a/web/templates/index.html b/web/templates/index.html index 21ae2bde..795ca8e1 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -4753,21 +4753,21 @@