From 0198f5031462078c7e7be42c4bb68d4b28dc3a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Wed, 24 Jun 2026 01:43:37 +0800 Subject: [PATCH] Add files via upload --- web/static/i18n/en-US.json | 8 +++- web/static/i18n/zh-CN.json | 8 +++- web/static/js/tasks.js | 77 ++++++++++++++++++++++++++++++++++++++ web/templates/index.html | 5 +++ 4 files changed, 94 insertions(+), 4 deletions(-) diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 30079a96..1f2b4556 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -2580,6 +2580,8 @@ "agentModeSingle": "Single-agent (Eino ADK)", "agentModeMulti": "Multi-agent (Eino)", "agentModeHint": "Same as chat: Eino single-agent (ADK), or Deep / Plan-Execute / Supervisor (last three require multi_agent.enabled).", + "concurrency": "Concurrency", + "concurrencyHint": "Number of subtasks to run in parallel (1-8). Default 1 is serial; use 1-2 for scan-heavy tasks.", "scheduleMode": "Schedule mode", "scheduleModeManual": "Manual", "scheduleModeCron": "Cron expression", @@ -2594,8 +2596,8 @@ "tasksList": "Task list (one task per line)", "tasksListPlaceholder": "Enter task list, one per line", "tasksListPlaceholderExample": "Enter task list, one per line, for example:\nScan open ports of 192.168.1.1\nCheck if https://example.com has SQL injection\nEnumerate subdomains of example.com", - "tasksListHint": "Enter one task command per line; the system will execute them in order. Empty lines are ignored.", - "tasksListHintFull": "Hint: Enter one task command per line; the system will execute these tasks in order. Empty lines are ignored.", + "tasksListHint": "Enter one task command per line; the system runs them via a concurrency pool. Empty lines are ignored.", + "tasksListHintFull": "Hint: Enter one task command per line; the system runs them via a concurrency pool. Empty lines are ignored.", "createQueue": "Create queue" }, "batchQueueDetailModal": { @@ -2629,6 +2631,8 @@ "scheduleToggleFailed": "Failed to update schedule toggle", "completedAt": "Completed at", "taskTotal": "Total tasks", + "concurrency": "Concurrency", + "concurrencyEditHint": "Click to edit. Cannot change while the queue is running.", "taskList": "Task list", "startLabel": "Start", "completeLabel": "Complete", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index 497adab6..f40f8a03 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -2568,6 +2568,8 @@ "agentModeSingle": "单代理(Eino ADK)", "agentModeMulti": "多代理(Eino)", "agentModeHint": "与对话页一致:Eino 单代理(ADK),或 Deep / Plan-Execute / Supervisor(后三种需已启用多代理)。", + "concurrency": "并发数", + "concurrencyHint": "同时执行的子任务数量(1-8)。默认 1 为串行;含扫描类工具时建议 1-2。", "scheduleMode": "调度方式", "scheduleModeManual": "手工执行", "scheduleModeCron": "调度表达式(Cron)", @@ -2582,8 +2584,8 @@ "tasksList": "任务列表(每行一个任务)", "tasksListPlaceholder": "请输入任务列表,每行一个任务", "tasksListPlaceholderExample": "请输入任务列表,每行一个任务,例如:\n扫描 192.168.1.1 的开放端口\n检查 https://example.com 是否存在SQL注入\n枚举 example.com 的子域名", - "tasksListHint": "每行输入一个任务指令,系统将依次执行这些任务。空行会被自动忽略。", - "tasksListHintFull": "提示:每行输入一个任务指令,系统将依次执行这些任务。空行会被自动忽略。", + "tasksListHint": "每行输入一个任务指令,系统将按并发池执行这些任务。空行会被自动忽略。", + "tasksListHintFull": "提示:每行输入一个任务指令,系统将按并发池执行这些任务。空行会被自动忽略。", "createQueue": "创建队列" }, "batchQueueDetailModal": { @@ -2617,6 +2619,8 @@ "scheduleToggleFailed": "更新调度开关失败", "completedAt": "完成时间", "taskTotal": "任务总数", + "concurrency": "并发数", + "concurrencyEditHint": "点击可修改;队列运行中不可改。", "taskList": "任务列表", "startLabel": "开始", "completeLabel": "完成", diff --git a/web/static/js/tasks.js b/web/static/js/tasks.js index a1389a6f..7af962e9 100644 --- a/web/static/js/tasks.js +++ b/web/static/js/tasks.js @@ -990,6 +990,7 @@ async function createBatchQueue() { const roleSelect = document.getElementById('batch-queue-role'); const projectSelect = document.getElementById('batch-queue-project-id'); const agentModeSelect = document.getElementById('batch-queue-agent-mode'); + const concurrencyInput = document.getElementById('batch-queue-concurrency'); const scheduleModeSelect = document.getElementById('batch-queue-schedule-mode'); const cronExprInput = document.getElementById('batch-queue-cron-expr'); const executeNowCheckbox = document.getElementById('batch-queue-execute-now'); @@ -1019,6 +1020,9 @@ async function createBatchQueue() { const scheduleMode = scheduleModeSelect ? (scheduleModeSelect.value === 'cron' ? 'cron' : 'manual') : 'manual'; const cronExpr = cronExprInput ? cronExprInput.value.trim() : ''; const executeNow = executeNowCheckbox ? !!executeNowCheckbox.checked : false; + let concurrency = concurrencyInput ? parseInt(concurrencyInput.value, 10) : 1; + if (!Number.isFinite(concurrency) || concurrency < 1) concurrency = 1; + if (concurrency > 8) concurrency = 8; if (scheduleMode === 'cron' && !cronExpr) { alert(_t('batchImportModal.cronExprRequired')); return; @@ -1043,6 +1047,7 @@ async function createBatchQueue() { cronExpr, executeNow, projectId, + concurrency, }), }); @@ -1489,6 +1494,7 @@ async function showBatchQueueDetail(queueId) {
${escapeHtml(_t('batchQueueDetailModal.role'))}${allowSubtaskMutation ? `${roleLineVal}` : roleLineVal}
${escapeHtml(_t('batchImportModal.agentMode'))}${allowSubtaskMutation ? `${escapeHtml(agentModeText)}` : escapeHtml(agentModeText)}
${escapeHtml(_t('batchImportModal.scheduleMode'))}${allowSubtaskMutation ? `${scheduleDetail}` : scheduleDetail}
+
${escapeHtml(_t('batchQueueDetailModal.concurrency'))}${allowSubtaskMutation ? `${escapeHtml(String(queue.concurrency && queue.concurrency > 0 ? queue.concurrency : 1))}` : escapeHtml(String(queue.concurrency && queue.concurrency > 0 ? queue.concurrency : 1))}
${escapeHtml(_t('batchQueueDetailModal.taskTotal'))}${queue.tasks.length}
${queue.scheduleMode === 'cron' ? `
${escapeHtml(_t('batchQueueDetailModal.scheduleCronAuto'))}
` : ''} @@ -2287,6 +2293,75 @@ async function saveInlineAgentMode() { } } +function normalizeBatchQueueConcurrencyInput(raw) { + let n = parseInt(raw, 10); + if (!Number.isFinite(n) || n < 1) n = 1; + if (n > 8) n = 8; + return n; +} + +// --- 内联编辑:并发数 --- +function startInlineEditConcurrency() { + const container = document.getElementById('bq-concurrency-val'); + if (!container) return; + const queueId = batchQueuesState.currentQueueId; + if (!queueId) return; + apiFetch(`/api/batch-tasks/${queueId}`).then(r => r.json()).then(detail => { + const queue = detail.queue || {}; + const current = normalizeBatchQueueConcurrencyInput(queue.concurrency || 1); + container.innerHTML = ` + + `; + const inp = document.getElementById('bq-edit-concurrency'); + if (!inp) return; + inp.focus(); + inp.select(); + let cancelled = false; + inp.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { e.preventDefault(); inp.blur(); } + if (e.key === 'Escape') { cancelled = true; cancelAllInlineEdits(); } + }); + inp.addEventListener('blur', () => { + if (!cancelled) saveInlineConcurrency(); + }); + }); +} + +async function saveInlineConcurrency() { + if (_bqInlineSaving) return; + _bqInlineSaving = true; + const queueId = batchQueuesState.currentQueueId; + if (!queueId) { _bqInlineSaving = false; return; } + const inp = document.getElementById('bq-edit-concurrency'); + const concurrency = normalizeBatchQueueConcurrencyInput(inp ? inp.value : 1); + try { + const detailResp = await apiFetch(`/api/batch-tasks/${queueId}`); + const detail = await detailResp.json(); + const q = detail.queue || {}; + const response = await apiFetch(`/api/batch-tasks/${queueId}/metadata`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title: q.title || '', + role: q.role || '', + agentMode: q.agentMode || 'eino_single', + concurrency, + }), + }); + if (!response.ok) { + const result = await response.json().catch(() => ({})); + throw new Error(result.error || _t('tasks.updateTaskFailed')); + } + _bqInlineSaving = false; + showBatchQueueDetail(queueId); + refreshBatchQueues(); + } catch (e) { + _bqInlineSaving = false; + console.error(e); + alert(e.message); + } +} + // --- 单条执行 --- async function runSingleBatchTask(queueId, taskId) { if (!queueId || !taskId) return; @@ -2441,6 +2516,8 @@ window.startInlineEditRole = startInlineEditRole; window.saveInlineRole = saveInlineRole; window.startInlineEditAgentMode = startInlineEditAgentMode; window.saveInlineAgentMode = saveInlineAgentMode; +window.startInlineEditConcurrency = startInlineEditConcurrency; +window.saveInlineConcurrency = saveInlineConcurrency; window.runSingleBatchTask = runSingleBatchTask; window.startInlineEditSchedule = startInlineEditSchedule; window.toggleInlineScheduleCron = toggleInlineScheduleCron; diff --git a/web/templates/index.html b/web/templates/index.html index 57c64eb8..0a8ee7cf 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -4010,6 +4010,11 @@
与对话页一致:Eino 单代理(ADK),或 Deep / Plan-Execute / Supervisor(后三种需已启用多代理)。
+
+ + +
同时执行的子任务数量(1-8)。默认 1 为串行;含扫描类工具时建议 1-2。
+