From ff7322e1432383806e70c2ab543edac8476a1091 Mon Sep 17 00:00:00 2001 From: Claude Dev Date: Fri, 13 Mar 2026 22:51:51 +0100 Subject: [PATCH] =?UTF-8?q?Fortschrittsbalken:=20stetiger=20Creep=20statt?= =?UTF-8?q?=20fixer=20Spr=C3=BCnge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Balken kriecht jetzt asymptotisch innerhalb jeder Phase (queued 2-10%, recherche 12-30%, analyse 33-60%, faktencheck 65-92%) statt bei Phasenwechsel hart zu springen. 3% des Restwegs pro 500ms-Tick sorgt für gleichmäßige Bewegung unabhängig von der Phasendauer. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/static/js/components.js | 56 ++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/src/static/js/components.js b/src/static/js/components.js index f6b2efe..f2d9263 100644 --- a/src/static/js/components.js +++ b/src/static/js/components.js @@ -228,6 +228,8 @@ const UI = { _progressStartTime: null, _progressTimer: null, + _progressCreepTimer: null, + _progressCurrentPercent: 0, /** * Fortschrittsanzeige einblenden und Status setzen. @@ -279,11 +281,25 @@ const UI = { else if (i + 1 === step.active) el.classList.add('active'); }); + // Phasen-Bereiche für stetigen Fortschritt + const ranges = { + queued: { from: 2, to: 10 }, + researching: { from: 12, to: 30 }, + deep_researching: { from: 12, to: 30 }, + analyzing: { from: 33, to: 60 }, + factchecking: { from: 65, to: 92 }, + cancelling: { from: 0, to: 5 }, + }; + + const range = ranges[status] || ranges.queued; const fill = document.getElementById('progress-fill'); - const percent = step.active === 0 ? 5 : Math.round((step.active / 3) * 100); if (fill) { - fill.style.width = percent + '%'; + // Nicht rückwärts gehen, mindestens auf Phasen-Start springen + this._progressCurrentPercent = Math.max(this._progressCurrentPercent, range.from); + fill.style.width = this._progressCurrentPercent + '%'; + this._startProgressCreep(range.to); } + const percent = Math.round(this._progressCurrentPercent); // ARIA-Werte auf der Progressbar aktualisieren bar.setAttribute('aria-valuenow', String(percent)); @@ -314,6 +330,33 @@ const UI = { }, 1000); }, + /** + * Creep-Animation starten: Balken kriecht asymptotisch zum Ziel. + */ + _startProgressCreep(target) { + this._stopProgressCreep(); + this._progressCreepTimer = setInterval(() => { + const remaining = target - this._progressCurrentPercent; + if (remaining <= 0.1) return; + // 3% des Restwegs pro Tick → asymptotisch, nie überschießend + this._progressCurrentPercent += remaining * 0.03; + const fill = document.getElementById('progress-fill'); + if (fill) fill.style.width = this._progressCurrentPercent + '%'; + const bar = document.getElementById('progress-bar'); + if (bar) bar.setAttribute('aria-valuenow', String(Math.round(this._progressCurrentPercent))); + }, 500); + }, + + /** + * Creep-Animation stoppen. + */ + _stopProgressCreep() { + if (this._progressCreepTimer) { + clearInterval(this._progressCreepTimer); + this._progressCreepTimer = null; + } + }, + /** * Abschluss-Animation: Grüner Balken mit Summary-Text. */ @@ -321,8 +364,10 @@ const UI = { const bar = document.getElementById('progress-bar'); if (!bar) return; - // Timer stoppen + // Timer und Creep stoppen this._stopProgressTimer(); + this._stopProgressCreep(); + this._progressCurrentPercent = 100; // Alle Steps auf done ['step-researching', 'step-analyzing', 'step-factchecking'].forEach(id => { @@ -369,8 +414,9 @@ const UI = { if (!bar) return; bar.style.display = 'block'; - // Timer stoppen + // Timer und Creep stoppen this._stopProgressTimer(); + this._stopProgressCreep(); // Error-Klasse bar.classList.remove('progress-bar--complete'); @@ -416,6 +462,8 @@ const UI = { bar.classList.remove('progress-bar--complete', 'progress-bar--error'); } this._stopProgressTimer(); + this._stopProgressCreep(); + this._progressCurrentPercent = 0; }, /**