// frontend/js/components/customSelect.js var CustomSelect = class _CustomSelect { constructor(container) { this.container = container; this.trigger = this.container.querySelector(".custom-select-trigger"); this.panel = this.container.querySelector(".custom-select-panel"); if (!this.trigger || !this.panel) { console.warn("CustomSelect cannot initialize: missing .custom-select-trigger or .custom-select-panel.", this.container); return; } this.nativeSelect = this.container.querySelector("select"); this.triggerText = this.trigger.querySelector("span"); this.template = this.panel.querySelector(".custom-select-option-template"); if (typeof _CustomSelect.openInstance === "undefined") { _CustomSelect.openInstance = null; _CustomSelect.initGlobalListener(); } if (this.nativeSelect) { this.generateOptions(); this.updateTriggerText(); } this.bindEvents(); } static initGlobalListener() { document.addEventListener("click", (event) => { if (_CustomSelect.openInstance && !_CustomSelect.openInstance.container.contains(event.target)) { _CustomSelect.openInstance.close(); } }); } generateOptions() { this.panel.querySelectorAll(":scope > *:not(.custom-select-option-template)").forEach((child) => child.remove()); Array.from(this.nativeSelect.options).forEach((option) => { let item; if (this.template) { item = this.template.cloneNode(true); item.classList.remove("custom-select-option-template"); item.removeAttribute("hidden"); } else { item = document.createElement("a"); item.href = "#"; item.className = "block px-4 py-2 text-sm text-zinc-700 hover:bg-zinc-100 dark:text-zinc-200 dark:hover:bg-zinc-600"; } item.classList.add("custom-select-option"); item.textContent = option.textContent; item.dataset.value = option.value; if (option.selected) { item.classList.add("is-selected"); } this.panel.appendChild(item); }); } bindEvents() { this.trigger.addEventListener("click", (event) => { if (this.trigger.classList.contains("is-disabled")) { return; } event.stopPropagation(); if (_CustomSelect.openInstance && _CustomSelect.openInstance !== this) { _CustomSelect.openInstance.close(); } this.toggle(); }); if (this.nativeSelect) { this.panel.addEventListener("click", (event) => { event.preventDefault(); const option = event.target.closest(".custom-select-option"); if (option) { this.selectOption(option); } }); } } selectOption(optionEl) { const selectedValue = optionEl.dataset.value; if (this.nativeSelect.value !== selectedValue) { this.nativeSelect.value = selectedValue; this.nativeSelect.dispatchEvent(new Event("change", { bubbles: true })); } this.updateTriggerText(); this.panel.querySelectorAll(".custom-select-option").forEach((el) => el.classList.remove("is-selected")); optionEl.classList.add("is-selected"); this.close(); } updateTriggerText() { if (!this.nativeSelect || !this.triggerText) return; const selectedOption = this.nativeSelect.options[this.nativeSelect.selectedIndex]; if (selectedOption) { this.triggerText.textContent = selectedOption.textContent; } } toggle() { this.panel.classList.toggle("hidden"); if (this.panel.classList.contains("hidden")) { if (_CustomSelect.openInstance === this) { _CustomSelect.openInstance = null; } } else { _CustomSelect.openInstance = this; } } open() { this.panel.classList.remove("hidden"); _CustomSelect.openInstance = this; } close() { this.panel.classList.add("hidden"); if (_CustomSelect.openInstance === this) { _CustomSelect.openInstance = null; } } }; // frontend/js/components/taskCenter.js var TaskCenterManager = class { constructor() { this.tasks = []; this.activePolls = /* @__PURE__ */ new Map(); this.heartbeatInterval = null; this.MINIMUM_TASK_DISPLAY_TIME_MS = 800; this.hasUnreadCompletedTasks = false; this.isAnimating = false; this.countdownTimer = null; this.trigger = document.getElementById("task-hub-trigger"); this.panel = document.getElementById("task-hub-panel"); this.countdownBar = document.getElementById("task-hub-countdown-bar"); this.countdownRing = document.getElementById("task-hub-countdown-ring"); this.indicator = document.getElementById("task-hub-indicator"); this.clearBtn = document.getElementById("task-hub-clear-btn"); this.taskListContainer = document.getElementById("task-list-container"); this.emptyState = document.getElementById("task-list-empty"); } // [THE FINAL, DEFINITIVE VERSION] init() { if (!this.trigger || !this.panel) { console.warn("Task Center UI core elements not found. Initialization skipped."); return; } this.trigger.addEventListener("click", (event) => { event.stopPropagation(); if (this.isAnimating) return; if (this.panel.classList.contains("hidden")) { this._handleUserInteraction(); this.openPanel(); } else { this.closePanel(); } }); document.addEventListener("click", (event) => { if (!this.panel.classList.contains("hidden") && !this.isAnimating && !this.panel.contains(event.target) && !this.trigger.contains(event.target)) { this.closePanel(); } }); this.trigger.addEventListener("mouseenter", this._stopCountdown.bind(this)); this.panel.addEventListener("mouseenter", this._handleUserInteraction.bind(this)); const handleMouseLeave = () => { if (!this.panel.classList.contains("hidden")) { this._startCountdown(); } }; this.panel.addEventListener("mouseleave", handleMouseLeave); this.trigger.addEventListener("mouseleave", handleMouseLeave); this.clearBtn?.addEventListener("click", this.clearCompletedTasks.bind(this)); this.taskListContainer.addEventListener("click", (event) => { const toggleHeader = event.target.closest("[data-task-toggle]"); if (!toggleHeader) return; this._handleUserInteraction(); const taskItem = toggleHeader.closest(".task-list-item"); const content = taskItem.querySelector("[data-task-content]"); if (!content) return; const isCollapsed = content.classList.contains("collapsed"); toggleHeader.classList.toggle("expanded", isCollapsed); if (isCollapsed) { content.classList.remove("collapsed"); content.style.maxHeight = `${content.scrollHeight}px`; content.style.opacity = "1"; content.addEventListener("transitionend", () => { if (!content.classList.contains("collapsed")) content.style.maxHeight = "none"; }, { once: true }); } else { content.style.maxHeight = `${content.scrollHeight}px`; requestAnimationFrame(() => { content.style.maxHeight = "0px"; content.style.opacity = "0"; content.classList.add("collapsed"); }); } }); this._render(); this._startHeartbeat(); console.log("Task Center UI Initialized [Multi-Task Heartbeat Polling Architecture - IGNITED]."); } async startTask(taskDefinition) { try { const initialTaskData = await taskDefinition.start(); if (!initialTaskData || !initialTaskData.id) throw new Error("Task definition did not return a valid initial task object."); const newTask = { id: initialTaskData.id, definition: taskDefinition, data: initialTaskData, timestamp: /* @__PURE__ */ new Date(), startTime: Date.now() }; if (!initialTaskData.is_running) { console.log(`[TaskCenter] Task ${newTask.id} completed synchronously. Skipping poll.`); taskDefinition.renderToastNarrative(newTask.data, {}, toastManager); this.tasks.unshift(newTask); this._render(); this._handleTaskCompletion(newTask); return; } this.tasks.unshift(newTask); this.activePolls.set(newTask.id, newTask); this._render(); this.openPanel(); taskDefinition.renderToastNarrative(newTask.data, {}, toastManager); this._updateIndicatorState(); } catch (error) { console.error("Failed to start task:", error); toastManager.show(`\u4EFB\u52A1\u542F\u52A8\u5931\u8D25: ${error.message}`, "error"); } } _startHeartbeat() { if (this.heartbeatInterval) return; this.heartbeatInterval = setInterval(this._tick.bind(this), 1500); } _stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } } async _tick() { if (this.activePolls.size === 0) { return; } for (const taskId of [...this.activePolls.keys()]) { const task = this.activePolls.get(taskId); if (!task) continue; try { const response = await task.definition.poll(taskId); if (!response.success || !response.data) throw new Error(response.message || "Polling failed"); const oldData = { ...task.data }; task.data = response.data; this._updateTaskItemInHistory(task.id, task.data); task.definition.renderToastNarrative(task.data, oldData, toastManager); if (!task.data.is_running) { this._handleTaskCompletion(task); } } catch (error) { console.error(`Polling for task ${taskId} failed:`, error); task.data.error = error.message; this._updateTaskItemInHistory(task.id, task.data); this._handleTaskCompletion(task); } } } _handleTaskCompletion(task) { this.activePolls.delete(task.id); this._updateIndicatorState(); const toastId = `task-${task.id}`; const finalize = async () => { await toastManager.dismiss(toastId, !task.data.error); this._updateTaskItemInDom(task); this.hasUnreadCompletedTasks = true; this._updateIndicatorState(); if (task.data.error) { if (task.definition.onError) task.definition.onError(task.data); } else { if (task.definition.onSuccess) task.definition.onSuccess(task.data); } }; const elapsedTime = Date.now() - task.startTime; const remainingTime = this.MINIMUM_TASK_DISPLAY_TIME_MS - elapsedTime; if (remainingTime > 0) { setTimeout(finalize, remainingTime); } else { finalize(); } } // [REFACTORED for robustness] _updateIndicatorState() { const hasRunningTasks = this.activePolls.size > 0; const shouldBeVisible = hasRunningTasks || this.hasUnreadCompletedTasks; this.indicator.classList.toggle("hidden", !shouldBeVisible); } // [REFACTORED for robustness] clearCompletedTasks() { this.tasks = this.tasks.filter((task) => this.activePolls.has(task.id)); this.hasUnreadCompletedTasks = false; this._render(); } // [NEW SAFETY METHOD] _updateTaskItemInHistory(taskId, newData) { const taskInHistory = this.tasks.find((t) => t.id === taskId); if (taskInHistory) { taskInHistory.data = newData; } } // --- 渲染与DOM操作 --- _render() { this.taskListContainer.innerHTML = this.tasks.map((task) => this._createTaskItemHtml(task)).join(""); const hasTasks = this.tasks.length > 0; this.taskListContainer.classList.toggle("hidden", !hasTasks); this.emptyState.classList.toggle("hidden", hasTasks); this._updateIndicatorState(); } _createTaskItemHtml(task) { const innerHtml = task.definition.renderTaskCenterItem(task.data, task.timestamp, this._formatTimeAgo); return `
${this._capitalizeFirstLetter(type)}
${title}