v0.0.38: живой таймер обработки + индикация ИИ (live-блок, heartbeat llm в SSE)
Deploy drhider / validate (push) Canceled after 0s
Deploy drhider / validate (push) Canceled after 0s
This commit is contained in:
+1
-1
@@ -20,7 +20,7 @@ if _sys_path_root not in sys.path:
|
||||
sys.path.insert(0, _sys_path_root)
|
||||
|
||||
# Версия приложения (меняется при изменениях)
|
||||
VERSION = "0.0.37"
|
||||
VERSION = "0.0.38"
|
||||
|
||||
|
||||
def create_app():
|
||||
|
||||
@@ -103,6 +103,15 @@ def process_stream(sid):
|
||||
except queue.Empty:
|
||||
if cancel.is_set():
|
||||
return
|
||||
# Heartbeat: живая статистика LLM (для таймера в UI)
|
||||
try:
|
||||
yield (
|
||||
f"event: llm\n"
|
||||
f"data: {json.dumps({'active': llm.llm_active, 'elapsed': round(llm.llm_elapsed_now(), 1), 'tokens': llm.tokens_total})}\n\n"
|
||||
)
|
||||
except _disconnect_exceptions():
|
||||
cancel.set()
|
||||
return
|
||||
continue
|
||||
|
||||
kind = evt[0]
|
||||
|
||||
@@ -97,6 +97,21 @@
|
||||
}
|
||||
.stat-label { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-bottom: 4px; }
|
||||
.stat-value { font-size: 24px; font-weight: 800; color: #166534; }
|
||||
.live-block {
|
||||
display: none; margin-top: 12px; padding: 16px;
|
||||
background: linear-gradient(135deg, #eff6ff, #dbeafe);
|
||||
border: 2px solid var(--brand-primary); border-radius: 12px;
|
||||
}
|
||||
.live-block.show { display: block; }
|
||||
.live-title { font-size: 18px; font-weight: 800; color: #1e40af; margin-bottom: 6px; }
|
||||
.live-file { font-size: 15px; color: #1e3a8a; margin-bottom: 10px; word-break: break-word; }
|
||||
.live-timer { font-size: 40px; font-weight: 800; color: #1d4ed8; line-height: 1.1; }
|
||||
.live-llm {
|
||||
display: none; margin-top: 8px; padding: 8px 12px; border-radius: 8px;
|
||||
background: #fef3c7; border: 1px solid #f59e0b; color: #92400e;
|
||||
font-size: 18px; font-weight: 700; width: fit-content;
|
||||
}
|
||||
.live-llm.show { display: inline-block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -141,6 +156,12 @@
|
||||
<button class="btn btn-primary" id="dlZip" onclick="downloadZip()">📦 Скачать ZIP</button>
|
||||
<button class="btn" id="dlCsv" onclick="downloadCsv()">📋 Скачать CSV</button>
|
||||
</div>
|
||||
<div class="live-block" id="liveBlock">
|
||||
<div class="live-title">⏱ Обработка…</div>
|
||||
<div class="live-file" id="liveFile">—</div>
|
||||
<div class="live-timer" id="liveTimer">0.0 с</div>
|
||||
<div class="live-llm" id="liveLlm">🤖 ИИ обрабатывает… <span id="liveLlmTime">0.0 с</span></div>
|
||||
</div>
|
||||
<div class="stats-block" id="statsBlock">
|
||||
<div class="stats-title">📊 Итоги обработки</div>
|
||||
<div class="stats-row">
|
||||
@@ -149,7 +170,7 @@
|
||||
<div class="stat-value" id="stTotalTime">—</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">Время ИИ</div>
|
||||
<div class="stat-label">Из них ИИ</div>
|
||||
<div class="stat-value" id="stLlmTime">—</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
@@ -186,6 +207,7 @@ function resetAll() {
|
||||
st.textContent = '';
|
||||
db.classList.remove('show');
|
||||
document.getElementById('statsBlock').classList.remove('show');
|
||||
document.getElementById('liveBlock').classList.remove('show');
|
||||
ub.disabled = false;
|
||||
}
|
||||
|
||||
@@ -418,6 +440,21 @@ async function uploadFiles() {
|
||||
st.textContent = 'Обработка... ' + sec + 'с';
|
||||
}, 1000);
|
||||
|
||||
// Крупный live-блок «Идёт обработка» с тикающим таймером
|
||||
const liveBlock = document.getElementById('liveBlock');
|
||||
const liveTimerEl = document.getElementById('liveTimer');
|
||||
const liveFileEl = document.getElementById('liveFile');
|
||||
const liveLlm = document.getElementById('liveLlm');
|
||||
const liveLlmTime = document.getElementById('liveLlmTime');
|
||||
liveLlm.classList.remove('show');
|
||||
liveTimerEl.textContent = '0.0 с';
|
||||
liveFileEl.textContent = 'Подготовка…';
|
||||
liveBlock.classList.add('show');
|
||||
const liveRefresh = setInterval(() => {
|
||||
const sec = ((performance.now() - t0) / 1000).toFixed(1);
|
||||
liveTimerEl.textContent = sec + ' с';
|
||||
}, 200);
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
activeES = new EventSource('/api/process_stream/' + currentSid);
|
||||
@@ -428,6 +465,19 @@ async function uploadFiles() {
|
||||
const elapsed = ((performance.now() - fileTimers[d.idx]) / 1000).toFixed(1);
|
||||
ss(d.idx, '<span style="color:#2563eb">⏳ ' + elapsed + 'с</span>');
|
||||
}, 200);
|
||||
// Показываем текущий файл и его размер в live-блоке
|
||||
const f = sf[d.idx];
|
||||
const sz = f ? fs(f.size) : '';
|
||||
liveFileEl.textContent = 'Файл ' + (d.idx + 1) + '/' + total + ': ' + d.name + (sz ? ' (' + sz + ')' : '');
|
||||
});
|
||||
activeES.addEventListener('llm', function(e) {
|
||||
const d = JSON.parse(e.data);
|
||||
if (d.active) {
|
||||
liveLlmTime.textContent = d.elapsed + ' с';
|
||||
liveLlm.classList.add('show');
|
||||
} else {
|
||||
liveLlm.classList.remove('show');
|
||||
}
|
||||
});
|
||||
activeES.addEventListener('done', function(e) {
|
||||
const d = JSON.parse(e.data);
|
||||
@@ -439,10 +489,12 @@ async function uploadFiles() {
|
||||
activeES.close();
|
||||
activeES = null;
|
||||
clearInterval(ptimer);
|
||||
clearInterval(liveRefresh);
|
||||
liveBlock.classList.remove('show');
|
||||
const d = JSON.parse(e.data);
|
||||
const totalSec = ((performance.now() - t0) / 1000).toFixed(1);
|
||||
st.className = 'status done';
|
||||
st.textContent = '✅ Обработано ' + d.total + ' файлов за ' + totalSec + 'с';
|
||||
st.textContent = '✅ Обработано ' + d.total + ' файлов: общее ' + totalSec + 'с, из них ИИ ' + (d.llm_sec > 0 ? d.llm_sec + 'с' : '0с');
|
||||
// Крупный блок статистики
|
||||
const sb = document.getElementById('statsBlock');
|
||||
document.getElementById('stTotalTime').textContent = totalSec + ' с';
|
||||
@@ -461,11 +513,15 @@ async function uploadFiles() {
|
||||
activeES.close();
|
||||
activeES = null;
|
||||
clearInterval(ptimer);
|
||||
clearInterval(liveRefresh);
|
||||
liveBlock.classList.remove('show');
|
||||
reject(new Error('SSE connection failed'));
|
||||
};
|
||||
});
|
||||
} catch (err) {
|
||||
clearInterval(ptimer);
|
||||
clearInterval(liveRefresh);
|
||||
liveBlock.classList.remove('show');
|
||||
st.className = 'status error';
|
||||
st.textContent = 'Ошибка: ' + err.message;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user