// 任务管理页面功能
// HTML转义函数(如果未定义)
if (typeof escapeHtml === 'undefined') {
function escapeHtml(text) {
if (text == null) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// 任务管理状态
const tasksState = {
allTasks: [],
filteredTasks: [],
selectedTasks: new Set(),
autoRefresh: true,
refreshInterval: null,
durationUpdateInterval: null,
completedTasksHistory: [], // 保存最近完成的任务历史
showHistory: true // 是否显示历史记录
};
// 从localStorage加载已完成任务历史
function loadCompletedTasksHistory() {
try {
const saved = localStorage.getItem('tasks-completed-history');
if (saved) {
const history = JSON.parse(saved);
// 只保留最近24小时内完成的任务
const now = Date.now();
const oneDayAgo = now - 24 * 60 * 60 * 1000;
tasksState.completedTasksHistory = history.filter(task => {
const completedTime = task.completedAt || task.startedAt;
return completedTime && new Date(completedTime).getTime() > oneDayAgo;
});
// 保存清理后的历史
saveCompletedTasksHistory();
}
} catch (error) {
console.error('加载已完成任务历史失败:', error);
tasksState.completedTasksHistory = [];
}
}
// 保存已完成任务历史到localStorage
function saveCompletedTasksHistory() {
try {
localStorage.setItem('tasks-completed-history', JSON.stringify(tasksState.completedTasksHistory));
} catch (error) {
console.error('保存已完成任务历史失败:', error);
}
}
// 更新已完成任务历史
function updateCompletedTasksHistory(currentTasks) {
// 保存当前所有任务作为快照(用于下次比较)
const currentTaskIds = new Set(currentTasks.map(t => t.conversationId));
// 如果是首次加载,只需要保存当前任务快照
if (tasksState.allTasks.length === 0) {
return;
}
const previousTaskIds = new Set(tasksState.allTasks.map(t => t.conversationId));
// 找出刚完成的任务(之前存在但现在不存在的)
// 只要任务从列表中消失了,就认为它已完成
const justCompleted = tasksState.allTasks.filter(task => {
return previousTaskIds.has(task.conversationId) && !currentTaskIds.has(task.conversationId);
});
// 将刚完成的任务添加到历史中
justCompleted.forEach(task => {
// 检查是否已存在(避免重复添加)
const exists = tasksState.completedTasksHistory.some(t => t.conversationId === task.conversationId);
if (!exists) {
// 如果任务状态不是最终状态,标记为completed
const finalStatus = ['completed', 'failed', 'timeout', 'cancelled'].includes(task.status)
? task.status
: 'completed';
tasksState.completedTasksHistory.push({
conversationId: task.conversationId,
message: task.message || '未命名任务',
startedAt: task.startedAt,
status: finalStatus,
completedAt: new Date().toISOString()
});
}
});
// 限制历史记录数量(最多保留50条)
if (tasksState.completedTasksHistory.length > 50) {
tasksState.completedTasksHistory = tasksState.completedTasksHistory
.sort((a, b) => new Date(b.completedAt || b.startedAt) - new Date(a.completedAt || a.startedAt))
.slice(0, 50);
}
saveCompletedTasksHistory();
}
// 加载任务列表
async function loadTasks() {
const listContainer = document.getElementById('tasks-list');
if (!listContainer) return;
listContainer.innerHTML = '
加载中...
';
try {
// 并行加载运行中的任务和已完成的任务历史
const [activeResponse, completedResponse] = await Promise.allSettled([
apiFetch('/api/agent-loop/tasks'),
apiFetch('/api/agent-loop/tasks/completed').catch(() => null) // 如果API不存在,返回null
]);
// 处理运行中的任务
if (activeResponse.status === 'rejected' || !activeResponse.value || !activeResponse.value.ok) {
throw new Error('获取任务列表失败');
}
const activeResult = await activeResponse.value.json();
const activeTasks = activeResult.tasks || [];
// 加载已完成任务历史(如果API可用)
let completedTasks = [];
if (completedResponse.status === 'fulfilled' && completedResponse.value && completedResponse.value.ok) {
try {
const completedResult = await completedResponse.value.json();
completedTasks = completedResult.tasks || [];
} catch (e) {
console.warn('解析已完成任务历史失败:', e);
}
}
// 保存所有任务
tasksState.allTasks = activeTasks;
// 更新已完成任务历史(从后端API获取)
if (completedTasks.length > 0) {
// 合并后端历史记录和本地历史记录(去重)
const backendTaskIds = new Set(completedTasks.map(t => t.conversationId));
const localHistory = tasksState.completedTasksHistory.filter(t =>
!backendTaskIds.has(t.conversationId)
);
// 后端的历史记录优先,然后添加本地独有的
tasksState.completedTasksHistory = [
...completedTasks.map(t => ({
conversationId: t.conversationId,
message: t.message || '未命名任务',
startedAt: t.startedAt,
status: t.status || 'completed',
completedAt: t.completedAt || new Date().toISOString()
})),
...localHistory
];
// 限制历史记录数量
if (tasksState.completedTasksHistory.length > 50) {
tasksState.completedTasksHistory = tasksState.completedTasksHistory
.sort((a, b) => new Date(b.completedAt || b.startedAt) - new Date(a.completedAt || a.startedAt))
.slice(0, 50);
}
saveCompletedTasksHistory();
} else {
// 如果后端API不可用,仍然使用前端逻辑更新历史
updateCompletedTasksHistory(activeTasks);
}
updateTaskStats(activeTasks);
filterAndSortTasks();
startDurationUpdates();
} catch (error) {
console.error('加载任务失败:', error);
listContainer.innerHTML = `
加载失败: ${escapeHtml(error.message)}
`;
}
}
// 更新任务统计
function updateTaskStats(tasks) {
const stats = {
running: 0,
cancelling: 0,
completed: 0,
failed: 0,
timeout: 0,
cancelled: 0,
total: tasks.length
};
tasks.forEach(task => {
if (task.status === 'running') {
stats.running++;
} else if (task.status === 'cancelling') {
stats.cancelling++;
} else if (task.status === 'completed') {
stats.completed++;
} else if (task.status === 'failed') {
stats.failed++;
} else if (task.status === 'timeout') {
stats.timeout++;
} else if (task.status === 'cancelled') {
stats.cancelled++;
}
});
const statRunning = document.getElementById('stat-running');
const statCancelling = document.getElementById('stat-cancelling');
const statCompleted = document.getElementById('stat-completed');
const statTotal = document.getElementById('stat-total');
if (statRunning) statRunning.textContent = stats.running;
if (statCancelling) statCancelling.textContent = stats.cancelling;
if (statCompleted) statCompleted.textContent = stats.completed;
if (statTotal) statTotal.textContent = stats.total;
}
// 筛选任务
function filterTasks() {
filterAndSortTasks();
}
// 排序任务
function sortTasks() {
filterAndSortTasks();
}
// 筛选和排序任务
function filterAndSortTasks() {
const statusFilter = document.getElementById('tasks-status-filter')?.value || 'all';
const sortBy = document.getElementById('tasks-sort-by')?.value || 'time-desc';
// 合并当前任务和历史任务
let allTasks = [...tasksState.allTasks];
// 如果显示历史记录,添加历史任务
if (tasksState.showHistory) {
const historyTasks = tasksState.completedTasksHistory
.filter(ht => !tasksState.allTasks.some(t => t.conversationId === ht.conversationId))
.map(ht => ({ ...ht, isHistory: true }));
allTasks = [...allTasks, ...historyTasks];
}
// 筛选
let filtered = allTasks;
if (statusFilter === 'active') {
// 仅运行中的任务(不包括历史)
filtered = tasksState.allTasks.filter(task =>
task.status === 'running' || task.status === 'cancelling'
);
} else if (statusFilter === 'history') {
// 仅历史记录
filtered = allTasks.filter(task => task.isHistory);
} else if (statusFilter !== 'all') {
filtered = allTasks.filter(task => task.status === statusFilter);
}
// 排序
filtered.sort((a, b) => {
const aTime = new Date(a.completedAt || a.startedAt);
const bTime = new Date(b.completedAt || b.startedAt);
switch (sortBy) {
case 'time-asc':
return aTime - bTime;
case 'time-desc':
return bTime - aTime;
case 'status':
return (a.status || '').localeCompare(b.status || '');
case 'message':
return (a.message || '').localeCompare(b.message || '');
default:
return 0;
}
});
tasksState.filteredTasks = filtered;
renderTasks(filtered);
updateBatchActions();
}
// 切换显示历史记录
function toggleShowHistory(show) {
tasksState.showHistory = show;
localStorage.setItem('tasks-show-history', show ? 'true' : 'false');
filterAndSortTasks();
}
// 计算执行时长
function calculateDuration(startedAt) {
if (!startedAt) return '未知';
const start = new Date(startedAt);
const now = new Date();
const diff = Math.floor((now - start) / 1000); // 秒
if (diff < 60) {
return `${diff}秒`;
} else if (diff < 3600) {
const minutes = Math.floor(diff / 60);
const seconds = diff % 60;
return `${minutes}分${seconds}秒`;
} else {
const hours = Math.floor(diff / 3600);
const minutes = Math.floor((diff % 3600) / 60);
return `${hours}小时${minutes}分`;
}
}
// 开始时长更新
function startDurationUpdates() {
// 清除旧的定时器
if (tasksState.durationUpdateInterval) {
clearInterval(tasksState.durationUpdateInterval);
}
// 每秒更新一次执行时长
tasksState.durationUpdateInterval = setInterval(() => {
updateTaskDurations();
}, 1000);
}
// 更新任务执行时长显示
function updateTaskDurations() {
const taskItems = document.querySelectorAll('.task-item[data-task-id]');
taskItems.forEach(item => {
const startedAt = item.dataset.startedAt;
const status = item.dataset.status;
const durationEl = item.querySelector('.task-duration');
if (durationEl && startedAt && (status === 'running' || status === 'cancelling')) {
durationEl.textContent = calculateDuration(startedAt);
}
});
}
// 渲染任务列表
function renderTasks(tasks) {
const listContainer = document.getElementById('tasks-list');
if (!listContainer) return;
if (tasks.length === 0) {
listContainer.innerHTML = `
当前没有符合条件的任务
${tasksState.allTasks.length === 0 && tasksState.completedTasksHistory.length > 0 ?
'
提示:有已完成的任务历史,请勾选"显示历史记录"查看
' : ''}
`;
return;
}
// 状态映射
const statusMap = {
'running': { text: '执行中', class: 'task-status-running' },
'cancelling': { text: '取消中', class: 'task-status-cancelling' },
'failed': { text: '执行失败', class: 'task-status-failed' },
'timeout': { text: '执行超时', class: 'task-status-timeout' },
'cancelled': { text: '已取消', class: 'task-status-cancelled' },
'completed': { text: '已完成', class: 'task-status-completed' }
};
// 分离当前任务和历史任务
const activeTasks = tasks.filter(t => !t.isHistory);
const historyTasks = tasks.filter(t => t.isHistory);
let html = '';
// 渲染当前任务
if (activeTasks.length > 0) {
html += activeTasks.map(task => renderTaskItem(task, statusMap)).join('');
}
// 渲染历史任务
if (historyTasks.length > 0) {
html += `
${historyTasks.map(task => renderTaskItem(task, statusMap, true)).join('')}
`;
}
listContainer.innerHTML = html;
}
// 渲染单个任务项
function renderTaskItem(task, statusMap, isHistory = false) {
const startedTime = task.startedAt ? new Date(task.startedAt) : null;
const completedTime = task.completedAt ? new Date(task.completedAt) : null;
const timeText = startedTime && !isNaN(startedTime.getTime())
? startedTime.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
: '未知时间';
const completedText = completedTime && !isNaN(completedTime.getTime())
? completedTime.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
: '';
const status = statusMap[task.status] || { text: task.status, class: 'task-status-unknown' };
const isFinalStatus = ['failed', 'timeout', 'cancelled', 'completed'].includes(task.status);
const canCancel = !isFinalStatus && task.status !== 'cancelling' && !isHistory;
const isSelected = tasksState.selectedTasks.has(task.conversationId);
const duration = (task.status === 'running' || task.status === 'cancelling')
? calculateDuration(task.startedAt)
: '';
return `
${task.conversationId ? `
对话ID:
${escapeHtml(task.conversationId)}
` : ''}
`;
}
// 清空任务历史
function clearTasksHistory() {
if (!confirm('确定要清空所有任务历史记录吗?')) {
return;
}
tasksState.completedTasksHistory = [];
saveCompletedTasksHistory();
filterAndSortTasks();
}
// 切换任务选择
function toggleTaskSelection(conversationId, selected) {
if (selected) {
tasksState.selectedTasks.add(conversationId);
} else {
tasksState.selectedTasks.delete(conversationId);
}
updateBatchActions();
}
// 更新批量操作UI
function updateBatchActions() {
const batchActions = document.getElementById('tasks-batch-actions');
const selectedCount = document.getElementById('tasks-selected-count');
if (!batchActions || !selectedCount) return;
const count = tasksState.selectedTasks.size;
if (count > 0) {
batchActions.style.display = 'flex';
selectedCount.textContent = `已选择 ${count} 项`;
} else {
batchActions.style.display = 'none';
}
}
// 清除任务选择
function clearTaskSelection() {
tasksState.selectedTasks.clear();
updateBatchActions();
// 重新渲染以更新复选框状态
filterAndSortTasks();
}
// 批量取消任务
async function batchCancelTasks() {
const selected = Array.from(tasksState.selectedTasks);
if (selected.length === 0) return;
if (!confirm(`确定要取消 ${selected.length} 个任务吗?`)) {
return;
}
let successCount = 0;
let failCount = 0;
for (const conversationId of selected) {
try {
const response = await apiFetch('/api/agent-loop/cancel', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ conversationId }),
});
if (response.ok) {
successCount++;
} else {
failCount++;
}
} catch (error) {
console.error('取消任务失败:', conversationId, error);
failCount++;
}
}
// 清除选择
clearTaskSelection();
// 刷新任务列表
await loadTasks();
// 显示结果
if (failCount > 0) {
alert(`批量取消完成:成功 ${successCount} 个,失败 ${failCount} 个`);
} else {
alert(`成功取消 ${successCount} 个任务`);
}
}
// 复制任务ID
function copyTaskId(conversationId) {
navigator.clipboard.writeText(conversationId).then(() => {
// 显示复制成功提示
const tooltip = document.createElement('div');
tooltip.textContent = '已复制!';
tooltip.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.8); color: white; padding: 8px 16px; border-radius: 4px; z-index: 10000;';
document.body.appendChild(tooltip);
setTimeout(() => tooltip.remove(), 1000);
}).catch(err => {
console.error('复制失败:', err);
});
}
// 取消任务
async function cancelTask(conversationId, button) {
if (!conversationId) return;
const originalText = button.textContent;
button.disabled = true;
button.textContent = '取消中...';
try {
const response = await apiFetch('/api/agent-loop/cancel', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ conversationId }),
});
if (!response.ok) {
const result = await response.json().catch(() => ({}));
throw new Error(result.error || '取消任务失败');
}
// 从选择中移除
tasksState.selectedTasks.delete(conversationId);
updateBatchActions();
// 重新加载任务列表
await loadTasks();
} catch (error) {
console.error('取消任务失败:', error);
alert('取消任务失败: ' + error.message);
button.disabled = false;
button.textContent = originalText;
}
}
// 查看对话
function viewConversation(conversationId) {
if (!conversationId) return;
// 切换到对话页面
if (typeof switchPage === 'function') {
switchPage('chat');
// 加载并选中该对话 - 使用全局函数
setTimeout(() => {
// 尝试多种方式加载对话
if (typeof loadConversation === 'function') {
loadConversation(conversationId);
} else if (typeof window.loadConversation === 'function') {
window.loadConversation(conversationId);
} else {
// 如果函数不存在,尝试通过URL跳转
window.location.hash = `chat?conversation=${conversationId}`;
console.log('切换到对话页面,对话ID:', conversationId);
}
}, 500);
}
}
// 刷新任务列表
async function refreshTasks() {
await loadTasks();
}
// 切换自动刷新
function toggleTasksAutoRefresh(enabled) {
tasksState.autoRefresh = enabled;
// 保存到localStorage
localStorage.setItem('tasks-auto-refresh', enabled ? 'true' : 'false');
if (enabled) {
// 启动自动刷新
if (!tasksState.refreshInterval) {
tasksState.refreshInterval = setInterval(() => {
loadBatchQueues();
}, 5000);
}
} else {
// 停止自动刷新
if (tasksState.refreshInterval) {
clearInterval(tasksState.refreshInterval);
tasksState.refreshInterval = null;
}
}
}
// 初始化任务管理页面
function initTasksPage() {
// 恢复自动刷新设置
const autoRefreshCheckbox = document.getElementById('tasks-auto-refresh');
if (autoRefreshCheckbox) {
const saved = localStorage.getItem('tasks-auto-refresh');
const enabled = saved !== null ? saved === 'true' : true;
autoRefreshCheckbox.checked = enabled;
toggleTasksAutoRefresh(enabled);
} else {
toggleTasksAutoRefresh(true);
}
// 只加载批量任务队列
loadBatchQueues();
}
// 清理定时器(页面切换时调用)
function cleanupTasksPage() {
if (tasksState.refreshInterval) {
clearInterval(tasksState.refreshInterval);
tasksState.refreshInterval = null;
}
if (tasksState.durationUpdateInterval) {
clearInterval(tasksState.durationUpdateInterval);
tasksState.durationUpdateInterval = null;
}
tasksState.selectedTasks.clear();
stopBatchQueueRefresh();
}
// 导出函数供全局使用
window.loadTasks = loadTasks;
window.cancelTask = cancelTask;
window.viewConversation = viewConversation;
window.refreshTasks = refreshTasks;
window.initTasksPage = initTasksPage;
window.cleanupTasksPage = cleanupTasksPage;
window.filterTasks = filterTasks;
window.sortTasks = sortTasks;
window.toggleTaskSelection = toggleTaskSelection;
window.clearTaskSelection = clearTaskSelection;
window.batchCancelTasks = batchCancelTasks;
window.copyTaskId = copyTaskId;
window.toggleTasksAutoRefresh = toggleTasksAutoRefresh;
window.toggleShowHistory = toggleShowHistory;
window.clearTasksHistory = clearTasksHistory;
// ==================== 批量任务功能 ====================
// 批量任务状态
const batchQueuesState = {
queues: [],
currentQueueId: null,
refreshInterval: null,
// 筛选和分页状态
filterStatus: 'all', // 'all', 'pending', 'running', 'paused', 'completed', 'cancelled'
searchKeyword: '',
currentPage: 1,
pageSize: 10,
total: 0,
totalPages: 1
};
// 显示新建任务模态框
async function showBatchImportModal() {
const modal = document.getElementById('batch-import-modal');
const input = document.getElementById('batch-tasks-input');
const titleInput = document.getElementById('batch-queue-title');
const roleSelect = document.getElementById('batch-queue-role');
if (modal && input) {
input.value = '';
if (titleInput) {
titleInput.value = '';
}
// 重置角色选择为默认
if (roleSelect) {
roleSelect.value = '';
}
updateBatchImportStats('');
// 加载并填充角色列表
if (roleSelect && typeof loadRoles === 'function') {
try {
const loadedRoles = await loadRoles();
// 清空现有选项(除了默认选项)
roleSelect.innerHTML = '';
// 添加已启用的角色
const sortedRoles = loadedRoles.sort((a, b) => {
if (a.name === '默认') return -1;
if (b.name === '默认') return 1;
return (a.name || '').localeCompare(b.name || '', 'zh-CN');
});
sortedRoles.forEach(role => {
if (role.name !== '默认' && role.enabled !== false) {
const option = document.createElement('option');
option.value = role.name;
option.textContent = role.name;
roleSelect.appendChild(option);
}
});
} catch (error) {
console.error('加载角色列表失败:', error);
}
}
modal.style.display = 'block';
input.focus();
}
}
// 关闭新建任务模态框
function closeBatchImportModal() {
const modal = document.getElementById('batch-import-modal');
if (modal) {
modal.style.display = 'none';
}
}
// 更新新建任务统计
function updateBatchImportStats(text) {
const statsEl = document.getElementById('batch-import-stats');
if (!statsEl) return;
const lines = text.split('\n').filter(line => line.trim() !== '');
const count = lines.length;
if (count > 0) {
statsEl.innerHTML = `共 ${count} 个任务
`;
statsEl.style.display = 'block';
} else {
statsEl.style.display = 'none';
}
}
// 监听批量任务输入
document.addEventListener('DOMContentLoaded', function() {
const input = document.getElementById('batch-tasks-input');
if (input) {
input.addEventListener('input', function() {
updateBatchImportStats(this.value);
});
}
});
// 创建批量任务队列
async function createBatchQueue() {
const input = document.getElementById('batch-tasks-input');
const titleInput = document.getElementById('batch-queue-title');
const roleSelect = document.getElementById('batch-queue-role');
if (!input) return;
const text = input.value.trim();
if (!text) {
alert('请输入至少一个任务');
return;
}
// 按行分割任务
const tasks = text.split('\n').map(line => line.trim()).filter(line => line !== '');
if (tasks.length === 0) {
alert('没有有效的任务');
return;
}
// 获取标题(可选)
const title = titleInput ? titleInput.value.trim() : '';
// 获取角色(可选,空字符串表示默认角色)
const role = roleSelect ? roleSelect.value || '' : '';
try {
const response = await apiFetch('/api/batch-tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ title, tasks, role }),
});
if (!response.ok) {
const result = await response.json().catch(() => ({}));
throw new Error(result.error || '创建批量任务队列失败');
}
const result = await response.json();
closeBatchImportModal();
// 显示队列详情
showBatchQueueDetail(result.queueId);
// 刷新批量队列列表
refreshBatchQueues();
} catch (error) {
console.error('创建批量任务队列失败:', error);
alert('创建批量任务队列失败: ' + error.message);
}
}
// 获取角色图标(辅助函数)
function getRoleIconForDisplay(roleName, rolesList) {
if (!roleName || roleName === '') {
return '🔵'; // 默认角色图标
}
if (Array.isArray(rolesList) && rolesList.length > 0) {
const role = rolesList.find(r => r.name === roleName);
if (role && role.icon) {
let icon = role.icon;
// 检查是否是 Unicode 转义格式(可能包含引号)
const unicodeMatch = icon.match(/^"?\\U([0-9A-F]{8})"?$/i);
if (unicodeMatch) {
try {
const codePoint = parseInt(unicodeMatch[1], 16);
icon = String.fromCodePoint(codePoint);
} catch (e) {
// 转换失败,使用默认图标
console.warn('转换 icon Unicode 转义失败:', icon, e);
return '👤';
}
}
return icon;
}
}
return '👤'; // 默认图标
}
// 加载批量任务队列列表
async function loadBatchQueues(page) {
const section = document.getElementById('batch-queues-section');
if (!section) return;
// 如果指定了page,使用它;否则使用当前页
if (page !== undefined) {
batchQueuesState.currentPage = page;
}
// 加载角色列表(用于显示正确的角色图标)
let loadedRoles = [];
if (typeof loadRoles === 'function') {
try {
loadedRoles = await loadRoles();
} catch (error) {
console.warn('加载角色列表失败,将使用默认图标:', error);
}
}
batchQueuesState.loadedRoles = loadedRoles; // 保存到状态中供渲染使用
// 构建查询参数
const params = new URLSearchParams();
params.append('page', batchQueuesState.currentPage.toString());
params.append('limit', batchQueuesState.pageSize.toString());
if (batchQueuesState.filterStatus && batchQueuesState.filterStatus !== 'all') {
params.append('status', batchQueuesState.filterStatus);
}
if (batchQueuesState.searchKeyword) {
params.append('keyword', batchQueuesState.searchKeyword);
}
try {
const response = await apiFetch(`/api/batch-tasks?${params.toString()}`);
if (!response.ok) {
throw new Error('获取批量任务队列失败');
}
const result = await response.json();
batchQueuesState.queues = result.queues || [];
batchQueuesState.total = result.total || 0;
batchQueuesState.totalPages = result.total_pages || 1;
renderBatchQueues();
} catch (error) {
console.error('加载批量任务队列失败:', error);
section.style.display = 'block';
const list = document.getElementById('batch-queues-list');
if (list) {
list.innerHTML = '加载失败: ' + escapeHtml(error.message) + '
';
}
}
}
// 筛选批量任务队列
function filterBatchQueues() {
const statusFilter = document.getElementById('batch-queues-status-filter');
const searchInput = document.getElementById('batch-queues-search');
if (statusFilter) {
batchQueuesState.filterStatus = statusFilter.value;
}
if (searchInput) {
batchQueuesState.searchKeyword = searchInput.value.trim();
}
// 重置到第一页并重新加载
batchQueuesState.currentPage = 1;
loadBatchQueues(1);
}
// 渲染批量任务队列列表
function renderBatchQueues() {
const section = document.getElementById('batch-queues-section');
const list = document.getElementById('batch-queues-list');
const pagination = document.getElementById('batch-queues-pagination');
if (!section || !list) return;
section.style.display = 'block';
const queues = batchQueuesState.queues;
if (queues.length === 0) {
list.innerHTML = '';
if (pagination) pagination.style.display = 'none';
return;
}
// 确保分页控件可见(重置之前可能设置的 display: none)
if (pagination) {
pagination.style.display = '';
}
list.innerHTML = queues.map(queue => {
const statusMap = {
'pending': { text: '待执行', class: 'batch-queue-status-pending' },
'running': { text: '执行中', class: 'batch-queue-status-running' },
'paused': { text: '已暂停', class: 'batch-queue-status-paused' },
'completed': { text: '已完成', class: 'batch-queue-status-completed' },
'cancelled': { text: '已取消', class: 'batch-queue-status-cancelled' }
};
const status = statusMap[queue.status] || { text: queue.status, class: 'batch-queue-status-unknown' };
// 统计任务状态
const stats = {
total: queue.tasks.length,
pending: 0,
running: 0,
completed: 0,
failed: 0,
cancelled: 0
};
queue.tasks.forEach(task => {
if (task.status === 'pending') stats.pending++;
else if (task.status === 'running') stats.running++;
else if (task.status === 'completed') stats.completed++;
else if (task.status === 'failed') stats.failed++;
else if (task.status === 'cancelled') stats.cancelled++;
});
const progress = stats.total > 0 ? Math.round((stats.completed + stats.failed + stats.cancelled) / stats.total * 100) : 0;
// 允许删除待执行、已完成或已取消状态的队列
const canDelete = queue.status === 'pending' || queue.status === 'completed' || queue.status === 'cancelled';
const titleDisplay = queue.title ? `${escapeHtml(queue.title)}` : '';
// 显示角色信息(使用正确的角色图标)
const loadedRoles = batchQueuesState.loadedRoles || [];
const roleIcon = getRoleIconForDisplay(queue.role, loadedRoles);
const roleName = queue.role && queue.role !== '' ? queue.role : '默认';
const roleDisplay = `${roleIcon} ${escapeHtml(roleName)}`;
return `
总计: ${stats.total}
待执行: ${stats.pending}
执行中: ${stats.running}
已完成: ${stats.completed}
失败: ${stats.failed}
${stats.cancelled > 0 ? `已取消: ${stats.cancelled}` : ''}
`;
}).join('');
// 渲染分页控件
renderBatchQueuesPagination();
}
// 渲染批量任务队列分页控件
function renderBatchQueuesPagination() {
const paginationContainer = document.getElementById('batch-queues-pagination');
if (!paginationContainer) return;
const { currentPage, pageSize, total, totalPages } = batchQueuesState;
// 如果没有数据,不显示分页控件
if (total === 0) {
paginationContainer.innerHTML = '';
paginationContainer.style.display = 'none';
return;
}
// 确保分页控件可见
paginationContainer.style.display = '';
// 即使只有一页,也显示分页信息(总数和每页条数选择器)
// 计算显示的页码范围
let startPage = Math.max(1, currentPage - 2);
let endPage = Math.min(totalPages, currentPage + 2);
// 确保显示5个页码(如果可能)
if (endPage - startPage < 4) {
if (startPage === 1) {
endPage = Math.min(totalPages, startPage + 4);
} else if (endPage === totalPages) {
startPage = Math.max(1, endPage - 4);
}
}
let paginationHTML = '';
paginationContainer.innerHTML = paginationHTML;
}
// 跳转到指定页面
function goBatchQueuesPage(page) {
const { totalPages } = batchQueuesState;
if (page < 1 || page > totalPages) return;
loadBatchQueues(page);
// 滚动到列表顶部
const list = document.getElementById('batch-queues-list');
if (list) {
list.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
// 改变每页显示数量
function changeBatchQueuesPageSize() {
const pageSizeSelect = document.getElementById('batch-queues-page-size-pagination');
if (!pageSizeSelect) return;
const newPageSize = parseInt(pageSizeSelect.value, 10);
if (newPageSize && newPageSize > 0) {
batchQueuesState.pageSize = newPageSize;
batchQueuesState.currentPage = 1; // 重置到第一页
loadBatchQueues(1);
}
}
// 显示批量任务队列详情
async function showBatchQueueDetail(queueId) {
const modal = document.getElementById('batch-queue-detail-modal');
const title = document.getElementById('batch-queue-detail-title');
const content = document.getElementById('batch-queue-detail-content');
const startBtn = document.getElementById('batch-queue-start-btn');
const cancelBtn = document.getElementById('batch-queue-cancel-btn');
const deleteBtn = document.getElementById('batch-queue-delete-btn');
const addTaskBtn = document.getElementById('batch-queue-add-task-btn');
if (!modal || !content) return;
try {
// 加载角色列表(如果还未加载)
let loadedRoles = [];
if (typeof loadRoles === 'function') {
try {
loadedRoles = await loadRoles();
} catch (error) {
console.warn('加载角色列表失败,将使用默认图标:', error);
}
}
const response = await apiFetch(`/api/batch-tasks/${queueId}`);
if (!response.ok) {
throw new Error('获取队列详情失败');
}
const result = await response.json();
const queue = result.queue;
batchQueuesState.currentQueueId = queueId;
if (title) {
title.textContent = queue.title ? `批量任务队列 - ${escapeHtml(queue.title)}` : '批量任务队列';
}
// 更新按钮显示
const pauseBtn = document.getElementById('batch-queue-pause-btn');
if (addTaskBtn) {
addTaskBtn.style.display = queue.status === 'pending' ? 'inline-block' : 'none';
}
if (startBtn) {
// pending状态显示"开始执行",paused状态显示"继续执行"
startBtn.style.display = (queue.status === 'pending' || queue.status === 'paused') ? 'inline-block' : 'none';
if (startBtn && queue.status === 'paused') {
startBtn.textContent = '继续执行';
} else if (startBtn && queue.status === 'pending') {
startBtn.textContent = '开始执行';
}
}
if (pauseBtn) {
// running状态显示"暂停队列"
pauseBtn.style.display = queue.status === 'running' ? 'inline-block' : 'none';
}
if (deleteBtn) {
// 允许删除待执行、已完成或已取消状态的队列
deleteBtn.style.display = (queue.status === 'pending' || queue.status === 'completed' || queue.status === 'cancelled' || queue.status === 'paused') ? 'inline-block' : 'none';
}
// 队列状态映射
const queueStatusMap = {
'pending': { text: '待执行', class: 'batch-queue-status-pending' },
'running': { text: '执行中', class: 'batch-queue-status-running' },
'paused': { text: '已暂停', class: 'batch-queue-status-paused' },
'completed': { text: '已完成', class: 'batch-queue-status-completed' },
'cancelled': { text: '已取消', class: 'batch-queue-status-cancelled' }
};
// 任务状态映射
const taskStatusMap = {
'pending': { text: '待执行', class: 'batch-task-status-pending' },
'running': { text: '执行中', class: 'batch-task-status-running' },
'completed': { text: '已完成', class: 'batch-task-status-completed' },
'failed': { text: '失败', class: 'batch-task-status-failed' },
'cancelled': { text: '已取消', class: 'batch-task-status-cancelled' }
};
// 获取角色信息(如果队列有角色配置)
let roleDisplay = '';
if (queue.role && queue.role !== '') {
// 如果有角色配置,尝试获取角色详细信息
let roleName = queue.role;
let roleIcon = '👤';
// 从已加载的角色列表中查找角色图标
if (Array.isArray(loadedRoles) && loadedRoles.length > 0) {
const role = loadedRoles.find(r => r.name === roleName);
if (role && role.icon) {
let icon = role.icon;
const unicodeMatch = icon.match(/^"?\\U([0-9A-F]{8})"?$/i);
if (unicodeMatch) {
try {
const codePoint = parseInt(unicodeMatch[1], 16);
icon = String.fromCodePoint(codePoint);
} catch (e) {
// 转换失败,使用默认图标
}
}
roleIcon = icon;
}
}
roleDisplay = `
角色
${roleIcon} ${escapeHtml(roleName)}
`;
} else {
// 默认角色
roleDisplay = `
角色
🔵 默认
`;
}
content.innerHTML = `
${queue.title ? `
任务标题
${escapeHtml(queue.title)}
` : ''}
${roleDisplay}
队列ID
${escapeHtml(queue.id)}
状态
${queueStatusMap[queue.status]?.text || queue.status}
创建时间
${new Date(queue.createdAt).toLocaleString('zh-CN')}
${queue.startedAt ? `
开始时间
${new Date(queue.startedAt).toLocaleString('zh-CN')}
` : ''}
${queue.completedAt ? `
完成时间
${new Date(queue.completedAt).toLocaleString('zh-CN')}
` : ''}
任务总数
${queue.tasks.length}
任务列表
${queue.tasks.map((task, index) => {
const taskStatus = taskStatusMap[task.status] || { text: task.status, class: 'batch-task-status-unknown' };
const canEdit = queue.status === 'pending' && task.status === 'pending';
const taskMessageEscaped = escapeHtml(task.message).replace(/'/g, "'").replace(/"/g, """).replace(/\n/g, "\\n");
return `
${task.startedAt ? `
开始: ${new Date(task.startedAt).toLocaleString('zh-CN')}
` : ''}
${task.completedAt ? `
完成: ${new Date(task.completedAt).toLocaleString('zh-CN')}
` : ''}
${task.error ? `
错误: ${escapeHtml(task.error)}
` : ''}
${task.result ? `
结果: ${escapeHtml(task.result.substring(0, 200))}${task.result.length > 200 ? '...' : ''}
` : ''}
`;
}).join('')}
`;
modal.style.display = 'block';
// 如果队列正在运行,自动刷新
if (queue.status === 'running') {
startBatchQueueRefresh(queueId);
}
} catch (error) {
console.error('获取队列详情失败:', error);
alert('获取队列详情失败: ' + error.message);
}
}
// 开始批量任务队列
async function startBatchQueue() {
const queueId = batchQueuesState.currentQueueId;
if (!queueId) return;
try {
const response = await apiFetch(`/api/batch-tasks/${queueId}/start`, {
method: 'POST',
});
if (!response.ok) {
const result = await response.json().catch(() => ({}));
throw new Error(result.error || '启动批量任务失败');
}
// 刷新详情
showBatchQueueDetail(queueId);
refreshBatchQueues();
} catch (error) {
console.error('启动批量任务失败:', error);
alert('启动批量任务失败: ' + error.message);
}
}
// 暂停批量任务队列
async function pauseBatchQueue() {
const queueId = batchQueuesState.currentQueueId;
if (!queueId) return;
if (!confirm('确定要暂停这个批量任务队列吗?当前正在执行的任务将被停止,后续任务将保留待执行状态。')) {
return;
}
try {
const response = await apiFetch(`/api/batch-tasks/${queueId}/pause`, {
method: 'POST',
});
if (!response.ok) {
const result = await response.json().catch(() => ({}));
throw new Error(result.error || '暂停批量任务失败');
}
// 刷新详情
showBatchQueueDetail(queueId);
refreshBatchQueues();
} catch (error) {
console.error('暂停批量任务失败:', error);
alert('暂停批量任务失败: ' + error.message);
}
}
// 删除批量任务队列(从详情模态框)
async function deleteBatchQueue() {
const queueId = batchQueuesState.currentQueueId;
if (!queueId) return;
if (!confirm('确定要删除这个批量任务队列吗?此操作不可恢复。')) {
return;
}
try {
const response = await apiFetch(`/api/batch-tasks/${queueId}`, {
method: 'DELETE',
});
if (!response.ok) {
const result = await response.json().catch(() => ({}));
throw new Error(result.error || '删除批量任务队列失败');
}
closeBatchQueueDetailModal();
refreshBatchQueues();
} catch (error) {
console.error('删除批量任务队列失败:', error);
alert('删除批量任务队列失败: ' + error.message);
}
}
// 从列表删除批量任务队列
async function deleteBatchQueueFromList(queueId) {
if (!queueId) return;
if (!confirm('确定要删除这个批量任务队列吗?此操作不可恢复。')) {
return;
}
try {
const response = await apiFetch(`/api/batch-tasks/${queueId}`, {
method: 'DELETE',
});
if (!response.ok) {
const result = await response.json().catch(() => ({}));
throw new Error(result.error || '删除批量任务队列失败');
}
// 如果当前正在查看这个队列的详情,关闭详情模态框
if (batchQueuesState.currentQueueId === queueId) {
closeBatchQueueDetailModal();
}
// 刷新队列列表
refreshBatchQueues();
} catch (error) {
console.error('删除批量任务队列失败:', error);
alert('删除批量任务队列失败: ' + error.message);
}
}
// 关闭批量任务队列详情模态框
function closeBatchQueueDetailModal() {
const modal = document.getElementById('batch-queue-detail-modal');
if (modal) {
modal.style.display = 'none';
}
batchQueuesState.currentQueueId = null;
stopBatchQueueRefresh();
}
// 开始批量队列刷新
function startBatchQueueRefresh(queueId) {
if (batchQueuesState.refreshInterval) {
clearInterval(batchQueuesState.refreshInterval);
}
batchQueuesState.refreshInterval = setInterval(() => {
if (batchQueuesState.currentQueueId === queueId) {
showBatchQueueDetail(queueId);
refreshBatchQueues();
} else {
stopBatchQueueRefresh();
}
}, 3000); // 每3秒刷新一次
}
// 停止批量队列刷新
function stopBatchQueueRefresh() {
if (batchQueuesState.refreshInterval) {
clearInterval(batchQueuesState.refreshInterval);
batchQueuesState.refreshInterval = null;
}
}
// 刷新批量任务队列列表
async function refreshBatchQueues() {
await loadBatchQueues(batchQueuesState.currentPage);
}
// 查看批量任务的对话
function viewBatchTaskConversation(conversationId) {
if (!conversationId) return;
// 关闭批量任务详情模态框
closeBatchQueueDetailModal();
// 直接使用URL hash跳转,让router处理页面切换和对话加载
// 这样更可靠,因为router会确保页面切换完成后再加载对话
window.location.hash = `chat?conversation=${conversationId}`;
}
// 编辑批量任务的状态
const editBatchTaskState = {
queueId: null,
taskId: null
};
// 从元素获取任务信息并打开编辑模态框
function editBatchTaskFromElement(button) {
const taskItem = button.closest('.batch-task-item');
if (!taskItem) {
console.error('无法找到任务项元素');
return;
}
const queueId = taskItem.getAttribute('data-queue-id');
const taskId = taskItem.getAttribute('data-task-id');
const taskMessage = taskItem.getAttribute('data-task-message');
if (!queueId || !taskId) {
console.error('任务信息不完整');
return;
}
// 解码HTML实体
const decodedMessage = taskMessage
.replace(/'/g, "'")
.replace(/"/g, '"')
.replace(/\\n/g, '\n');
editBatchTask(queueId, taskId, decodedMessage);
}
// 打开编辑批量任务模态框
function editBatchTask(queueId, taskId, currentMessage) {
editBatchTaskState.queueId = queueId;
editBatchTaskState.taskId = taskId;
const modal = document.getElementById('edit-batch-task-modal');
const messageInput = document.getElementById('edit-task-message');
if (!modal || !messageInput) {
console.error('编辑任务模态框元素不存在');
return;
}
messageInput.value = currentMessage;
modal.style.display = 'block';
// 聚焦到输入框
setTimeout(() => {
messageInput.focus();
messageInput.select();
}, 100);
// 添加ESC键监听
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
closeEditBatchTaskModal();
document.removeEventListener('keydown', handleKeyDown);
}
};
document.addEventListener('keydown', handleKeyDown);
// 添加Enter+Ctrl/Cmd保存功能
const handleKeyPress = (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
saveBatchTask();
document.removeEventListener('keydown', handleKeyPress);
}
};
messageInput.addEventListener('keydown', handleKeyPress);
}
// 关闭编辑批量任务模态框
function closeEditBatchTaskModal() {
const modal = document.getElementById('edit-batch-task-modal');
if (modal) {
modal.style.display = 'none';
}
editBatchTaskState.queueId = null;
editBatchTaskState.taskId = null;
}
// 保存批量任务
async function saveBatchTask() {
const queueId = editBatchTaskState.queueId;
const taskId = editBatchTaskState.taskId;
const messageInput = document.getElementById('edit-task-message');
if (!queueId || !taskId) {
alert('任务信息不完整');
return;
}
if (!messageInput) {
alert('无法获取任务消息输入框');
return;
}
const message = messageInput.value.trim();
if (!message) {
alert('任务消息不能为空');
return;
}
try {
const response = await apiFetch(`/api/batch-tasks/${queueId}/tasks/${taskId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: message }),
});
if (!response.ok) {
const result = await response.json().catch(() => ({}));
throw new Error(result.error || '更新任务失败');
}
// 关闭编辑模态框
closeEditBatchTaskModal();
// 刷新队列详情
if (batchQueuesState.currentQueueId === queueId) {
showBatchQueueDetail(queueId);
}
// 刷新队列列表
refreshBatchQueues();
} catch (error) {
console.error('保存任务失败:', error);
alert('保存任务失败: ' + error.message);
}
}
// 显示添加批量任务模态框
function showAddBatchTaskModal() {
const queueId = batchQueuesState.currentQueueId;
if (!queueId) {
alert('队列信息不存在');
return;
}
const modal = document.getElementById('add-batch-task-modal');
const messageInput = document.getElementById('add-task-message');
if (!modal || !messageInput) {
console.error('添加任务模态框元素不存在');
return;
}
messageInput.value = '';
modal.style.display = 'block';
// 聚焦到输入框
setTimeout(() => {
messageInput.focus();
}, 100);
// 添加ESC键监听
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
closeAddBatchTaskModal();
document.removeEventListener('keydown', handleKeyDown);
}
};
document.addEventListener('keydown', handleKeyDown);
// 添加Enter+Ctrl/Cmd保存功能
const handleKeyPress = (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
saveAddBatchTask();
messageInput.removeEventListener('keydown', handleKeyPress);
}
};
messageInput.addEventListener('keydown', handleKeyPress);
}
// 关闭添加批量任务模态框
function closeAddBatchTaskModal() {
const modal = document.getElementById('add-batch-task-modal');
const messageInput = document.getElementById('add-task-message');
if (modal) {
modal.style.display = 'none';
}
if (messageInput) {
messageInput.value = '';
}
}
// 保存添加的批量任务
async function saveAddBatchTask() {
const queueId = batchQueuesState.currentQueueId;
const messageInput = document.getElementById('add-task-message');
if (!queueId) {
alert('队列信息不存在');
return;
}
if (!messageInput) {
alert('无法获取任务消息输入框');
return;
}
const message = messageInput.value.trim();
if (!message) {
alert('任务消息不能为空');
return;
}
try {
const response = await apiFetch(`/api/batch-tasks/${queueId}/tasks`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: message }),
});
if (!response.ok) {
const result = await response.json().catch(() => ({}));
throw new Error(result.error || '添加任务失败');
}
// 关闭添加任务模态框
closeAddBatchTaskModal();
// 刷新队列详情
if (batchQueuesState.currentQueueId === queueId) {
showBatchQueueDetail(queueId);
}
// 刷新队列列表
refreshBatchQueues();
} catch (error) {
console.error('添加任务失败:', error);
alert('添加任务失败: ' + error.message);
}
}
// 从元素获取任务信息并删除任务
function deleteBatchTaskFromElement(button) {
const taskItem = button.closest('.batch-task-item');
if (!taskItem) {
console.error('无法找到任务项元素');
return;
}
const queueId = taskItem.getAttribute('data-queue-id');
const taskId = taskItem.getAttribute('data-task-id');
const taskMessage = taskItem.getAttribute('data-task-message');
if (!queueId || !taskId) {
console.error('任务信息不完整');
return;
}
// 解码HTML实体以显示消息
const decodedMessage = taskMessage
.replace(/'/g, "'")
.replace(/"/g, '"')
.replace(/\\n/g, '\n');
// 截断长消息用于确认对话框
const displayMessage = decodedMessage.length > 50
? decodedMessage.substring(0, 50) + '...'
: decodedMessage;
if (!confirm(`确定要删除这个任务吗?\n\n任务内容: ${displayMessage}\n\n此操作不可恢复。`)) {
return;
}
deleteBatchTask(queueId, taskId);
}
// 删除批量任务
async function deleteBatchTask(queueId, taskId) {
if (!queueId || !taskId) {
alert('任务信息不完整');
return;
}
try {
const response = await apiFetch(`/api/batch-tasks/${queueId}/tasks/${taskId}`, {
method: 'DELETE',
});
if (!response.ok) {
const result = await response.json().catch(() => ({}));
throw new Error(result.error || '删除任务失败');
}
// 刷新队列详情
if (batchQueuesState.currentQueueId === queueId) {
showBatchQueueDetail(queueId);
}
// 刷新队列列表
refreshBatchQueues();
} catch (error) {
console.error('删除任务失败:', error);
alert('删除任务失败: ' + error.message);
}
}
// 导出函数
window.showBatchImportModal = showBatchImportModal;
window.closeBatchImportModal = closeBatchImportModal;
window.createBatchQueue = createBatchQueue;
window.showBatchQueueDetail = showBatchQueueDetail;
window.startBatchQueue = startBatchQueue;
window.pauseBatchQueue = pauseBatchQueue;
window.deleteBatchQueue = deleteBatchQueue;
window.closeBatchQueueDetailModal = closeBatchQueueDetailModal;
window.refreshBatchQueues = refreshBatchQueues;
window.viewBatchTaskConversation = viewBatchTaskConversation;
window.editBatchTask = editBatchTask;
window.editBatchTaskFromElement = editBatchTaskFromElement;
window.closeEditBatchTaskModal = closeEditBatchTaskModal;
window.saveBatchTask = saveBatchTask;
window.filterBatchQueues = filterBatchQueues;
window.goBatchQueuesPage = goBatchQueuesPage;
window.changeBatchQueuesPageSize = changeBatchQueuesPageSize;
window.showAddBatchTaskModal = showAddBatchTaskModal;
window.closeAddBatchTaskModal = closeAddBatchTaskModal;
window.saveAddBatchTask = saveAddBatchTask;
window.deleteBatchTaskFromElement = deleteBatchTaskFromElement;
window.deleteBatchQueueFromList = deleteBatchQueueFromList;