Spaces:
Sleeping
Sleeping
| /* ===== Task Timer ===== */ | |
| window.NAITimer = { | |
| startTime: null, | |
| timerInterval: null, | |
| history: [], // recent task durations for ETA | |
| MAX_HISTORY: 10, | |
| start() { | |
| this.startTime = Date.now(); | |
| this._updateDisplay(); | |
| this.timerInterval = setInterval(() => this._updateDisplay(), 100); | |
| this._showTimer(true); | |
| }, | |
| stop(completed = true) { | |
| if (this.startTime && completed) { | |
| const duration = Date.now() - this.startTime; | |
| this.history.push(duration); | |
| if (this.history.length > this.MAX_HISTORY) this.history.shift(); | |
| } | |
| clearInterval(this.timerInterval); | |
| this.timerInterval = null; | |
| this.startTime = null; | |
| // Show final time briefly, then hide | |
| setTimeout(() => this._showTimer(false), 3000); | |
| }, | |
| _getAvgDuration() { | |
| if (this.history.length === 0) return 0; | |
| return this.history.reduce((a, b) => a + b, 0) / this.history.length; | |
| }, | |
| _formatTime(ms) { | |
| const s = Math.floor(ms / 1000); | |
| const m = Math.floor(s / 60); | |
| const sec = s % 60; | |
| const tenths = Math.floor((ms % 1000) / 100); | |
| if (m > 0) return `${m}:${String(sec).padStart(2, '0')}.${tenths}`; | |
| return `${sec}.${tenths}s`; | |
| }, | |
| _updateDisplay() { | |
| const el = document.getElementById('task-timer'); | |
| if (!el || !this.startTime) return; | |
| const elapsed = Date.now() - this.startTime; | |
| const avg = this._getAvgDuration(); | |
| let text = this._formatTime(elapsed); | |
| if (avg > 0) { | |
| const remaining = Math.max(0, avg - elapsed); | |
| if (remaining > 0) { | |
| text += ` / 预计 ${this._formatTime(avg)}`; | |
| } else { | |
| text += ` / 预计 ${this._formatTime(avg)}`; | |
| } | |
| } | |
| el.textContent = text; | |
| // Progress bar | |
| const bar = document.getElementById('task-timer-bar'); | |
| if (bar && avg > 0) { | |
| const pct = Math.min(100, (elapsed / avg) * 100); | |
| bar.style.width = pct + '%'; | |
| bar.className = 'task-timer-bar-fill' + (pct >= 100 ? ' overtime' : ''); | |
| } | |
| }, | |
| _showTimer(show) { | |
| const wrap = document.getElementById('task-timer-wrap'); | |
| if (wrap) wrap.style.display = show ? 'flex' : 'none'; | |
| }, | |
| init() { | |
| // Create timer UI in header | |
| const headerRight = document.querySelector('.header-right'); | |
| if (!headerRight) return; | |
| const wrap = document.createElement('div'); | |
| wrap.id = 'task-timer-wrap'; | |
| wrap.className = 'task-timer-wrap'; | |
| wrap.style.display = 'none'; | |
| wrap.innerHTML = ` | |
| <div class="task-timer-bar-bg"> | |
| <div id="task-timer-bar" class="task-timer-bar-fill" style="width:0%"></div> | |
| </div> | |
| <span id="task-timer" class="task-timer-text"></span> | |
| `; | |
| headerRight.insertBefore(wrap, headerRight.firstChild); | |
| }, | |
| }; |