Show AI structure processing progress
This commit is contained in:
@@ -17,6 +17,7 @@ def _page(title: str, body: str) -> str:
|
||||
<link rel="stylesheet" href="{_asset_url("html5.css")}" />
|
||||
<script defer src="{_asset_url("htmx.min.js")}"></script>
|
||||
<script defer src="{_asset_url("htmx-ext-sse.js")}"></script>
|
||||
<script defer src="{_asset_url("html5-ai-structure.js")}"></script>
|
||||
</head>
|
||||
<body>{body}</body>
|
||||
</html>"""
|
||||
|
||||
@@ -51,6 +51,7 @@ def render_html5_ai_structure_form(project_id: str, *, saved_credentials: dict[s
|
||||
hx-post="/html5/projects/{quote(project_id)}/ai-structure/run"
|
||||
hx-target="[data-html5-ai-structure-result]"
|
||||
hx-swap="innerHTML"
|
||||
hx-indicator="[data-ai-structure-progress]"
|
||||
>
|
||||
<label>
|
||||
<span>Папка с cf/cfe или выгрузкой</span>
|
||||
@@ -82,6 +83,20 @@ def render_html5_ai_structure_form(project_id: str, *, saved_credentials: dict[s
|
||||
</label>
|
||||
<button class="primary" type="submit">Подготовить для ИИ</button>
|
||||
</form>
|
||||
<section class="ai-structure-progress" data-ai-structure-progress hidden aria-live="polite">
|
||||
<div class="ai-progress-head">
|
||||
<span class="ai-progress-spinner" aria-hidden="true"></span>
|
||||
<strong>Подготовка выполняется</strong>
|
||||
<small data-ai-structure-elapsed>00:00</small>
|
||||
</div>
|
||||
<div class="ai-progress-bar"><span data-ai-structure-bar></span></div>
|
||||
<dl class="ai-progress-metrics">
|
||||
<div><dt>Прошло</dt><dd data-ai-structure-elapsed-label>0 сек</dd></div>
|
||||
<div><dt>Осталось примерно</dt><dd data-ai-structure-eta>считаем</dd></div>
|
||||
<div><dt>Стадия</dt><dd data-ai-structure-stage>Запуск запроса</dd></div>
|
||||
</dl>
|
||||
<p class="muted padded">Окно не зависло: сервер копирует сетевые файлы, строит normalized/SIR модель и пишет Codex-пакет. Большие cf/cfe или SMB-папки могут выполняться несколько минут.</p>
|
||||
</section>
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
(function () {
|
||||
const jobs = new WeakMap();
|
||||
|
||||
function formatClock(seconds) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const rest = seconds % 60;
|
||||
return String(minutes).padStart(2, "0") + ":" + String(rest).padStart(2, "0");
|
||||
}
|
||||
|
||||
function formatHuman(seconds) {
|
||||
if (seconds < 60) {
|
||||
return seconds + " сек";
|
||||
}
|
||||
return Math.floor(seconds / 60) + " мин " + String(seconds % 60).padStart(2, "0");
|
||||
}
|
||||
|
||||
function stageFor(seconds) {
|
||||
if (seconds < 10) {
|
||||
return "Проверка путей и учетных данных";
|
||||
}
|
||||
if (seconds < 45) {
|
||||
return "Копирование входных файлов";
|
||||
}
|
||||
if (seconds < 120) {
|
||||
return "Разбор структуры 1С";
|
||||
}
|
||||
if (seconds < 240) {
|
||||
return "Построение индексов для ИИ";
|
||||
}
|
||||
return "Запись Codex-пакета";
|
||||
}
|
||||
|
||||
function etaFor(seconds) {
|
||||
if (seconds < 10) {
|
||||
return "1-5 мин";
|
||||
}
|
||||
if (seconds < 45) {
|
||||
return "до 5 мин";
|
||||
}
|
||||
if (seconds < 120) {
|
||||
return "2-6 мин";
|
||||
}
|
||||
if (seconds < 300) {
|
||||
return "еще несколько минут";
|
||||
}
|
||||
return "зависит от размера SMB-папки";
|
||||
}
|
||||
|
||||
function update(progress, startedAt) {
|
||||
const seconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1000));
|
||||
const elapsed = progress.querySelector("[data-ai-structure-elapsed]");
|
||||
const elapsedLabel = progress.querySelector("[data-ai-structure-elapsed-label]");
|
||||
const eta = progress.querySelector("[data-ai-structure-eta]");
|
||||
const stage = progress.querySelector("[data-ai-structure-stage]");
|
||||
const bar = progress.querySelector("[data-ai-structure-bar]");
|
||||
if (elapsed) {
|
||||
elapsed.textContent = formatClock(seconds);
|
||||
}
|
||||
if (elapsedLabel) {
|
||||
elapsedLabel.textContent = formatHuman(seconds);
|
||||
}
|
||||
if (eta) {
|
||||
eta.textContent = etaFor(seconds);
|
||||
}
|
||||
if (stage) {
|
||||
stage.textContent = stageFor(seconds);
|
||||
}
|
||||
if (bar) {
|
||||
bar.style.width = Math.min(92, 8 + seconds * 0.35) + "%";
|
||||
}
|
||||
}
|
||||
|
||||
function start(form) {
|
||||
const progress = document.querySelector("[data-ai-structure-progress]");
|
||||
if (!progress) {
|
||||
return;
|
||||
}
|
||||
const previous = jobs.get(form);
|
||||
if (previous) {
|
||||
window.clearInterval(previous.timer);
|
||||
}
|
||||
const startedAt = Date.now();
|
||||
progress.hidden = false;
|
||||
progress.setAttribute("data-ai-structure-progress-state", "running");
|
||||
update(progress, startedAt);
|
||||
const timer = window.setInterval(function () {
|
||||
update(progress, startedAt);
|
||||
}, 1000);
|
||||
jobs.set(form, { progress: progress, timer: timer });
|
||||
}
|
||||
|
||||
function stop(form) {
|
||||
const job = jobs.get(form);
|
||||
if (!job) {
|
||||
return;
|
||||
}
|
||||
window.clearInterval(job.timer);
|
||||
job.progress.setAttribute("data-ai-structure-progress-state", "done");
|
||||
window.setTimeout(function () {
|
||||
job.progress.hidden = true;
|
||||
}, 500);
|
||||
jobs.delete(form);
|
||||
}
|
||||
|
||||
document.addEventListener("htmx:beforeRequest", function (event) {
|
||||
const form = event.target && event.target.closest ? event.target.closest(".ai-structure-form") : null;
|
||||
if (form) {
|
||||
start(form);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("htmx:afterRequest", function (event) {
|
||||
const form = event.target && event.target.closest ? event.target.closest(".ai-structure-form") : null;
|
||||
if (form) {
|
||||
stop(form);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("htmx:sendError", function (event) {
|
||||
const form = event.target && event.target.closest ? event.target.closest(".ai-structure-form") : null;
|
||||
if (form) {
|
||||
stop(form);
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -14,9 +14,11 @@
|
||||
.access-builder{border-bottom:1px solid var(--line);background:#fff}.access-builder-form{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:12px;border-bottom:1px solid var(--line);background:#fbfcfe}.access-builder-form label{display:grid;gap:5px;font-size:11px;font-weight:900;color:var(--muted);text-transform:uppercase}.access-builder-form input,.access-builder-form textarea{min-width:0;border:1px solid var(--line);background:#fff;padding:0 8px;color:var(--text);font:13px/1.3 system-ui,-apple-system,Segoe UI,sans-serif;text-transform:none}.access-builder-form input{height:32px}.access-builder-form textarea{min-height:68px;padding:8px;resize:vertical}.access-builder-actions{grid-column:1/-1;display:flex;gap:8px;justify-content:flex-end}.access-builder-result{border-top:1px solid var(--line);background:#fff}
|
||||
.access-card[hx-get]{cursor:pointer}.access-card[hx-get]:hover{background:#f8fbff}.access-user-detail{border-bottom:1px solid var(--line);background:#fff}
|
||||
.ai-structure-form{display:grid;grid-template-columns:1.4fr 1.4fr 180px 120px 180px 180px 120px auto;gap:10px;align-items:end;padding:12px 16px;border-bottom:1px solid var(--line);background:#fbfcfe}.ai-structure-form label{display:grid;gap:5px;font-size:11px;font-weight:900;color:var(--muted);text-transform:uppercase}.ai-structure-form input{height:32px;min-width:0;border:1px solid var(--line);background:#fff;padding:0 8px;color:var(--text);font:13px/1.3 system-ui,-apple-system,Segoe UI,sans-serif;text-transform:none}.ai-structure-form .checkbox-row{display:flex;align-items:center;gap:7px;height:32px}.ai-structure-form .checkbox-row input{width:16px;height:16px}.ai-structure-result{background:#fff}
|
||||
.ai-structure-progress{border-bottom:1px solid var(--line);background:#fff}.ai-progress-head{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid var(--line);background:#fbfcfe}.ai-progress-head strong{flex:1}.ai-progress-head small{font-weight:900;color:var(--muted);font-variant-numeric:tabular-nums}.ai-progress-spinner{width:18px;height:18px;border:3px solid #c9d6ec;border-top-color:var(--brand);border-radius:50%;animation:aiProgressSpin 900ms linear infinite}.ai-progress-bar{height:6px;background:#eef2f7;overflow:hidden}.ai-progress-bar span{display:block;width:8%;height:100%;background:var(--brand);transition:width 700ms ease}.ai-progress-metrics{display:grid;grid-template-columns:repeat(3,1fr);margin:0}.ai-progress-metrics div{padding:10px 12px;border-right:1px solid var(--line);border-bottom:1px solid var(--line)}.ai-progress-metrics div:last-child{border-right:0}.ai-progress-metrics dt{font-size:11px;font-weight:900;text-transform:uppercase;color:var(--muted)}.ai-progress-metrics dd{margin:3px 0 0;font-weight:900}.ai-structure-progress[hidden]{display:none}.ai-structure-progress.htmx-request,.ai-structure-progress[data-ai-structure-progress-state="running"]{display:block}@keyframes aiProgressSpin{to{transform:rotate(360deg)}}
|
||||
@media(max-width:980px){.layout{grid-template-columns:1fr;height:auto}.tree,.inspector{max-height:320px}.editor{min-height:520px}.hero{display:block}.shell{padding:16px}}
|
||||
@media(max-width:980px){.access-layout{grid-template-columns:1fr;height:auto}.access-nav,.access-side{max-height:360px}.access-role-grid{grid-template-columns:1fr}.access-role-grid .access-card:nth-child(odd){border-right:0}}
|
||||
@media(max-width:980px){.access-builder-form{grid-template-columns:1fr}.access-builder-actions{justify-content:stretch}.access-builder-actions button{flex:1}}
|
||||
@media(max-width:980px){.ai-structure-form{grid-template-columns:1fr}}
|
||||
@media(max-width:980px){.ai-progress-metrics{grid-template-columns:1fr}}
|
||||
@media(max-width:980px){.form-designer-body{grid-template-columns:1fr}.form-property-panel{border-left:0;border-top:1px solid var(--line)}.form-field[data-html5-form-width="half"],.form-field[data-html5-form-width="third"]{grid-column:1/-1}.form-editor-row[data-html5-form-element-editor]{grid-template-columns:1fr}.property-row{grid-template-columns:1fr}}
|
||||
@media(max-width:980px){.setup-layout{grid-template-columns:1fr}.setup-metrics{grid-template-columns:1fr 1fr}.settings-form{grid-template-columns:1fr}}
|
||||
|
||||
@@ -1735,6 +1735,9 @@ def test_ai_structure_prepare_writes_ai_ready_package(tmp_path: Path):
|
||||
"Пути должны быть доступны серверу",
|
||||
"smb_username",
|
||||
"smb_password",
|
||||
"data-ai-structure-progress",
|
||||
"Осталось примерно",
|
||||
"html5-ai-structure.js",
|
||||
full_page=True,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user