Compare commits
No commits in common. "d44c41ad69b40535d9536fa7913cf7f1546ff715" and "d2eeb631f1780b3ba4cf88442eebfff1cafcb4d0" have entirely different histories.
d44c41ad69
...
d2eeb631f1
@ -37,9 +37,7 @@
|
|||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
<span class="kt-ai-chip" data-tone="gray" id="roleChip">Эксперт</span>
|
<span class="kt-ai-chip" data-tone="gray" id="roleChip">Эксперт</span>
|
||||||
</header>
|
</header>
|
||||||
<div id="view" class="kt-ai-work-column">
|
<div id="view" class="kt-ai-work-column"></div>
|
||||||
<div id="viewInner"></div>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
447
script.js
447
script.js
@ -38,28 +38,53 @@ function icon(name) { return '<svg class="kt-icon" aria-hidden="true"><use href=
|
|||||||
function seed() {
|
function seed() {
|
||||||
return {
|
return {
|
||||||
view: "upload",
|
view: "upload",
|
||||||
req: "Заявка ЭД-2026-118 · Управление сетевых сервисов · 14.09.2026",
|
req: "Кандидат: А. Т. Нурмолдаев · SEO-1, управление сетевых сервисов · заявка от 14.09.2026",
|
||||||
candidate: "А. Т. Нурмолдаев",
|
candidate: "А. Т. Нурмолдаев",
|
||||||
sources: [
|
sources: [
|
||||||
{ n: "Трудовой кодекс Республики Казахстан", s: "Законодательство", d: 207, u: 1 },
|
{ n: "Трудовой кодекс Республики Казахстан", s: "Законодательство", d: 207, u: 1 },
|
||||||
{ n: "Закон Республики Казахстан «Об акционерных обществах»", s: "Законодательство", d: 148, u: 1 },
|
{ n: "Закон Республики Казахстан «Об акционерных обществах»", s: "Законодательство", d: 148, u: 1 },
|
||||||
{ n: "Правила трудового распорядка", s: "Внутренние документы", d: 12, u: 1 },
|
{ n: "Правила трудового распорядка", s: "Внутренние документы", d: 12, u: 1 },
|
||||||
{ n: "Правила поиска и подбора персонала", s: "Внутренние документы", d: 8, u: 1 },
|
{ n: "Правила поиска и подбора персонала", s: "Внутренние документы", d: 8, u: 1 },
|
||||||
{ n: "Корпоративный стандарт по управлению человеческими ресурсами фонда", s: "Внутренние документы", d: 96, u: 1 },
|
{ n: "Корпоративный стандарт по управлению человеческими ресурсами фонда", s: "Внутренние документы", d: 96, u: 1 }
|
||||||
{ n: "Должностная инструкция SEO-1 (управление сетевыми сервисами)", s: "Внутренние документы", d: 14, u: 1 },
|
],
|
||||||
{ n: "Положение о совещаниях и комиссиях фонда", s: "Внутренние документы", d: 22, u: 1 }
|
files: [
|
||||||
|
{ n: "Пояснительная записка на назначение — Нурмолдаев А.Т.docx", t: "Пояснительная записка", d: "14.09.2026, 10:24", s: "ready" },
|
||||||
|
{ n: "План отбора 2026-09 — управление сетевых сервисов.docx", t: "Подтверждающий документ", d: "14.09.2026, 10:24", s: "ready" },
|
||||||
|
{ n: "Протокол собеседования №18-09.pdf", t: "Подтверждающий документ", d: "14.09.2026, 10:25", s: "ready" },
|
||||||
|
{ n: "Резюме кандидата — Нурмолдаев А.Т.pdf", t: "Подтверждающий документ", d: "14.09.2026, 10:28", s: "wait" },
|
||||||
|
{ n: "Анкета о соответствии требованиям.docx", t: "Подтверждающий документ", d: "14.09.2026, 10:31", s: "ready" },
|
||||||
|
{ n: "Проверка требований (внутренняя форма) — скан.pdf", t: "Подтверждающий документ", d: "14.09.2026, 10:32", s: "ready" },
|
||||||
|
{ n: "Справка о стаже и опыте работы.docx", t: "Подтверждающий документ", d: "14.09.2026, 10:33", s: "ready" },
|
||||||
|
{ n: "Диплом о высшем образовании (копия).pdf", t: "Подтверждающий документ", d: "14.09.2026, 10:35", s: "ready" }
|
||||||
],
|
],
|
||||||
files: [],
|
|
||||||
progress: null,
|
|
||||||
dropActive: false,
|
|
||||||
running: false,
|
running: false,
|
||||||
contra: false,
|
log: [
|
||||||
falseDone: false,
|
{ t: "10:42", b: "Пакет документов принят, 8 файлов" },
|
||||||
log: [],
|
{ t: "10:42", b: "Извлечение текста: пояснительная записка, 12 разд." },
|
||||||
violations: [],
|
{ t: "10:43", b: "Сверка с Правилами поиска и подбора персонала: раздел «Организация отбора», п. 4.2" },
|
||||||
checklist: [],
|
{ t: "10:43", b: "Найдено отклонение: дата объявления о вакансии" },
|
||||||
verdict: "",
|
{ t: "10:44", b: "Сверка с Правилами трудового распорядка, гл. 5 «Назначение руководителей»" },
|
||||||
status: "draft",
|
{ t: "10:44", b: "Сверка с Законом «Об АО»: компетенции совета директоров, ст. 36" },
|
||||||
|
{ t: "10:45", b: "Скрытое голосование: противоречие в протоколе и правилах" },
|
||||||
|
{ t: "10:45", b: "Проверка квалификации: стаж, образование, аттестация" },
|
||||||
|
{ t: "10:46", b: "Обнаружено 4 несоответствия, 1 противоречие" },
|
||||||
|
{ t: "10:46", b: "Журнал завершён — выводы переданы на верификацию эксперту" }
|
||||||
|
],
|
||||||
|
found: 4,
|
||||||
|
contra: true,
|
||||||
|
violations: [
|
||||||
|
{ d: "Объявление о вакансии опубликовано за 5 календарных дней до открытия отбора, норматив — 10", y: "process", r: "Правила поиска и подбора персонала, раздел 4.2", v: "conf" },
|
||||||
|
{ d: "В пояснительной записке 3 кандидата, в протоколе собеседования фиксированы 2", y: "process", r: "План отбора 2026-09, п. 3", v: "conf" },
|
||||||
|
{ d: "Срок согласования кандидатуры с материнской компанией — 2 дня при нормативе 5", y: "process", r: "Корпоративный стандарт по УХР, ред. 12", v: "conf" },
|
||||||
|
{ d: "У кандидата 6 лет опыта в телекоммуникациях при требуемых 7 для уровня SEO-1", y: "qual", r: "Должностная инструкция SEO-1, п. 2 «Требования к квалификации»", v: "conf" },
|
||||||
|
{ d: "Стаж в должности SEO-3: 3 года 4 месяца при нормативе не менее 4 лет", y: "qual", r: "Должностная инструкция SEO-2 и SEO-3, п. 2", v: "conf" },
|
||||||
|
{ d: "Аттестация по управленческой программе в материалах не подтверждена", y: "qual", r: "Корпоративный стандарт по УХР, гл. 8", v: "conf" },
|
||||||
|
{ d: "В протоколе — решение голосованием 4:1, в правилах назначений — единогласно", y: "contra", r: "Трудовой Кодекс РК, ст. 165, часть 3", v: "falsep" },
|
||||||
|
{ d: "Внутренняя форма проверки заполнена не полностью — графа «Аттестация»", y: "process", r: "Правила трудового распорядка, приложение 2", v: "falsep" }
|
||||||
|
],
|
||||||
|
verdict: "Заявка на назначение руководителя уровня SEO-1, управление сетевых сервисов (кандидат А. Т. Нурмолдаев) прошла проверку соответствия нормативной базе. По завершённой сверке зафиксированы следующие несоответствия:\n\n1. Объявление о вакансии опубликовано за 5 календарных дней до открытия отбора, норматив — 10 (Правила поиска и подбора персонала, раздел 4.2).\n2. В пояснительной записке указан круг из 3 кандидатов, в протоколе собеседования зафиксировано 2 (План отбора 2026-09, п. 3).\n3. Срок согласования кандидатуры с материнской компанией составил 2 дня при нормативе 5 (Корпоративный стандарт по УХР, ред. 12).\n4. Стаж в отрасли 6 лет при требуемых 7 для уровня SEO-1 (Должностная инструкция SEO-1, п. 2).\n5. Стаж в должности SEO-3 — 3 года 4 месяца при нормативе не менее 4 лет (Должностная инструкция SEO-2 и SEO-3, п. 2).\n6. Аттестация по управленческой программе в материалах не подтверждена (Корпоративный стандарт по УХР, гл. 8).\n\nПротиворечие в способе принятия решения (протокол — голосование 4:1, правила — единогласно) рассмотрено экспертом: позиция кандидата, поданная в установленном порядке, исключает применение требования единогласия; вывод системы отклонён как не относящийся к рассматриваемому пакету.\n\nРешение о назначении принимается с учётом перечисленных несоответствий. Материалы переданы на согласование руководителю.",
|
||||||
|
checklist: [1, 2, 3, 4, 5, 6],
|
||||||
|
status: "sent",
|
||||||
rejected: null,
|
rejected: null,
|
||||||
stamp: null
|
stamp: null
|
||||||
};
|
};
|
||||||
@ -77,7 +102,6 @@ function save() {
|
|||||||
|
|
||||||
/* ---------- Прогресс процесса ---------- */
|
/* ---------- Прогресс процесса ---------- */
|
||||||
function allFilesReady() {
|
function allFilesReady() {
|
||||||
if (!D.files.length) return false;
|
|
||||||
for (var i = 0; i < D.files.length; i++) if (D.files[i].s !== "ready") return false;
|
for (var i = 0; i < D.files.length; i++) if (D.files[i].s !== "ready") return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -87,8 +111,8 @@ function doneCount() {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
function stepNum() {
|
function stepNum() {
|
||||||
if (D.view === "upload") return allFilesReady() ? 2 : 1;
|
if (D.view === "upload") return D.running || allFilesReady() ? 2 : 1;
|
||||||
if (D.view === "results") return 2;
|
if (D.view === "results") return D.contra && !D.falseDone ? 2 : 3;
|
||||||
if (D.view === "verdict") return 3;
|
if (D.view === "verdict") return 3;
|
||||||
if (D.view === "approve") return D.status === "agree" || D.status === "decl" ? 5 : 4;
|
if (D.view === "approve") return D.status === "agree" || D.status === "decl" ? 5 : 4;
|
||||||
return 1;
|
return 1;
|
||||||
@ -172,8 +196,7 @@ function render() {
|
|||||||
else if (D.view === "verdict") html = vVerdict();
|
else if (D.view === "verdict") html = vVerdict();
|
||||||
else if (D.view === "approve") html = vApprove();
|
else if (D.view === "approve") html = vApprove();
|
||||||
else html = vAbout();
|
else html = vAbout();
|
||||||
var view = $("viewInner");
|
var view = $("view");
|
||||||
if (!view) view = $("view");
|
|
||||||
if (!view) return;
|
if (!view) return;
|
||||||
view.innerHTML = html;
|
view.innerHTML = html;
|
||||||
$("pageTitle").textContent = TITLES[D.view] || "";
|
$("pageTitle").textContent = TITLES[D.view] || "";
|
||||||
@ -200,97 +223,46 @@ function kpiStrip(items) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Экран 1. Загрузка и обзор материалов ---------- */
|
/* ---------- Экран 1. Загрузка и обзор материалов ---------- */
|
||||||
function fmtSize(bytes) {
|
|
||||||
if (bytes < 1024) return bytes + " B";
|
|
||||||
if (bytes < 1024 * 1024) return Math.round(bytes / 1024) + " KB";
|
|
||||||
var mb = bytes / 1024 / 1024;
|
|
||||||
return (mb < 100 ? mb.toFixed(1) : Math.round(mb)) + " MB";
|
|
||||||
}
|
|
||||||
|
|
||||||
function classifyFile(name) {
|
|
||||||
var n = name.toLowerCase();
|
|
||||||
if (n.indexOf("пояснительная") >= 0 || n.indexOf("записк") >= 0 || n.indexOf("memo") >= 0) return "Пояснительная записка";
|
|
||||||
var ext = name.slice(name.lastIndexOf(".") + 1).toLowerCase();
|
|
||||||
if (n.indexOf("договор") >= 0 || n.indexOf("contract") >= 0) return "Договор";
|
|
||||||
if (n.indexOf("акт") >= 0 || n.indexOf("certificate") >= 0) return "Сертификат / Акт";
|
|
||||||
if (n.indexOf("счёт") >= 0 || n.indexOf("счет") >= 0 || n.indexOf("invoice") >= 0) return "Счёт";
|
|
||||||
if (["doc", "docx"].indexOf(ext) >= 0) return "Документ Word";
|
|
||||||
if (["xls", "xlsx", "csv"].indexOf(ext) >= 0) return "Таблица";
|
|
||||||
if (ext === "pdf") return "PDF";
|
|
||||||
if (["jpg", "jpeg", "png"].indexOf(ext) >= 0) return "Изображение";
|
|
||||||
return "Документ";
|
|
||||||
}
|
|
||||||
|
|
||||||
function vUpload() {
|
function vUpload() {
|
||||||
var progress = D.progress;
|
var readyCount = 0;
|
||||||
var zoneBlock;
|
for (var i = 0; i < D.files.length; i++) if (D.files[i].s === "ready") readyCount++;
|
||||||
if (progress) {
|
var all = readyCount === D.files.length;
|
||||||
zoneBlock = dropZoneHtml() +
|
|
||||||
'<div class="progress">' +
|
|
||||||
'<div class="pl">' +
|
|
||||||
'<span class="pl-ico">' + (progress.step < 3 ? "…" : icon("check")) + "</span>" +
|
|
||||||
'<div><strong>' + esc(STEPS_UPLOAD[progress.step] || "…") + "</strong>" +
|
|
||||||
'<span class="kt-ai-meta">' + esc(progress.file) + "</span></div>" +
|
|
||||||
'"</div>' +
|
|
||||||
'<div class="kp">' +
|
|
||||||
'<div class="bar"><i style="width:' + progress.pct + '%"></i></div>' +
|
|
||||||
'<div class="lp"><span>' + esc(progress.file) + "</span>" +
|
|
||||||
'<span class="kp-num">' + num(progress.pct) + " %" + "</span></div>" +
|
|
||||||
"</div></div>";
|
|
||||||
} else if (progress && progress.file) {
|
|
||||||
zoneBlock = dropZoneHtml() +
|
|
||||||
'<div class="progress done">' +
|
|
||||||
'<div class="pl">' +
|
|
||||||
'<span class="pl-ico">' + icon("check") + "</span>" +
|
|
||||||
'<div><strong>Загрузка завершена</strong>' +
|
|
||||||
'<span class="kt-ai-meta">' + esc(progress.file) + "</span></div>" +
|
|
||||||
"</div>" +
|
|
||||||
'<div class="kp"><div class="bar done"><i style="width:100%"></i></div>' +
|
|
||||||
'<div class="lp"><span>' + esc(progress.file) + "</span>" +
|
|
||||||
'<span class="kp-num">' + fmtSize(progress.size || 0) + "</span></div></div>" +
|
|
||||||
"</div>";
|
|
||||||
} else {
|
|
||||||
zoneBlock = dropZoneHtml();
|
|
||||||
}
|
|
||||||
|
|
||||||
var head = "";
|
var head = headReady;
|
||||||
if (!progress && D.files.length) {
|
|
||||||
var all = D.files.every(function (f) { return f.s === "ready"; });
|
|
||||||
if (all && !D.running) {
|
if (all && !D.running) {
|
||||||
head = '<div class="kt-ai-banner" data-tone="ok" style="margin-bottom:var(--kt-ai-stack-tight);display:flex;align-items:center;gap:var(--kt-ai-space-5)">' +
|
head = '<div class="kt-ai-banner" data-tone="ok" style="margin-bottom:var(--kt-ai-stack-block);display:flex;align-items:center;gap:var(--kt-ai-space-5)">' +
|
||||||
"<span style='flex:1;min-width:0'><strong>Все материалы получены</strong> — пакет готов к запуску анализа</span>" +
|
'<span style="flex:1;min-width:0">Все материалы получены — пакет готов к запуску анализа</span>' +
|
||||||
'<button class="kt-ai-btn" data-variant="primary" data-size="lg" onclick="S.analyze()">Запустить анализ ИИ</button></div>';
|
'<button class="kt-ai-btn" data-variant="primary" onclick="S.analyze()">Запустить анализ ИИ</button></div>';
|
||||||
} else if (!all && !D.running) {
|
|
||||||
head = '<div class="kt-ai-banner" data-tone="warn" style="margin-bottom:var(--kt-ai-stack-tight);display:flex;align-items:center;gap:var(--kt-ai-space-5)">' +
|
|
||||||
"<span style='flex:1;min-width:0'><strong>Пакет не собран</strong> — не все файлы получены от кандидата</span>" +
|
|
||||||
'<button class="kt-ai-btn" data-variant="primary" data-size="lg" onclick="S.uploadFiles()">Загрузка файлов</button></div>';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = D.files.length
|
var rows = D.files.map(function (f, i) {
|
||||||
? '<div class="kt-ai-table-wrap" style="margin-top:var(--kt-ai-stack-tight)">' +
|
var statusCell = pill(f.s);
|
||||||
'<table class="kt-ai-table"><thead><tr><th>Название</th><th>Размер</th><th>Дата</th><th>Статус</th><th></th></tr></thead>' +
|
if (f.s === "wait") statusCell += ' <span class="kt-ai-chip" data-tone="orange" style="margin-left:var(--kt-ai-space-2)">ждёт загрузки</span>';
|
||||||
"<tbody>" + D.files.map(function (f, i) {
|
return "<tr" + (f.s === "wait" ? ' data-wait="true"' : "") + ">" +
|
||||||
return "<tr>" +
|
'<td data-label="Файл">' + esc(f.n) + "</td>" +
|
||||||
'<td data-label="Название">' + esc(f.n) + "</td>" +
|
'<td data-label="Тип"><span class="kt-ai-meta">' + esc(f.t) + "</span></td>" +
|
||||||
'<td data-label="Размер"><span class="kt-ai-num">' + fmtSize(f.size || 0) + "</span></td>" +
|
'<td data-label="Загружен"><span class="kt-ai-num" style="font-size:var(--kt-ai-text-xs)">' + esc(f.d) + "</span></td>" +
|
||||||
'<td data-label="Дата"><span class="kt-ai-num">' + esc(f.d) + "</span></td>" +
|
'<td data-label="Статус">' + statusCell + "</td>" +
|
||||||
'<td data-label="Статус">' + pill(f.s) + "</td>" +
|
'<td class="row-act"><button class="kt-ai-btn" data-size="sm" data-variant="ghost" onclick="S.viewFile(' + i + ')">Смотреть</button></td>' +
|
||||||
'<td><button class="kt-ai-icon-button" title="Удалить" onclick="S.delFile(' + i + ')">' + icon("trash") + "</button></td>" +
|
|
||||||
"</tr>";
|
"</tr>";
|
||||||
}).join("") + "</tbody></table></div>"
|
}).join("");
|
||||||
|
|
||||||
|
var headReady = hasWaiting
|
||||||
|
? '<div class="kt-ai-banner" data-tone="info" style="margin-bottom:var(--kt-ai-stack-block)">' +
|
||||||
|
"<strong>Пакет не собран</strong>" +
|
||||||
|
'<span class="kt-ai-meta" style="display:block;margin-top:2px">Не все файлы получены от кандидата — анализ станет доступен после загрузки остальных материалов.</span></div>'
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
return '<section class="sec"><div class="sec-h">Процесс' +
|
return '<section class="sec"><div class="sec-h">Процесс' +
|
||||||
'<span class="spacer"></span>' +
|
'<span class="spacer"></span>' +
|
||||||
'<span class="kt-ai-meta">Заявка ' + num("ЭД-2026-118") + "</span>" +
|
'<span class="kt-ai-meta">Заявка ' + num("ЭД-2026-118") + "</span></div>" + vStepper() + "</section>" +
|
||||||
'<button class="kt-ai-btn" data-variant="ghost" data-size="sm" onclick="S.resetDemo()" style="margin-left:var(--kt-ai-space-2)">Сбросить демо</button>' +
|
head +
|
||||||
"</div>" + vStepper() + "</section>" +
|
|
||||||
(head ? '<div style="margin-bottom:var(--kt-ai-stack-tight)">' + head + "</div>" : "") +
|
|
||||||
'<section class="sec"><div class="sec-h">Материалы' +
|
'<section class="sec"><div class="sec-h">Материалы' +
|
||||||
'<span class="spacer"></span>' +
|
'<span class="spacer"></span>' +
|
||||||
(D.files.length ? '<span class="kt-ai-meta">' + num(D.files.length) + " файл(а)</span>" : "") + "</div>" +
|
'<span class="kt-ai-meta">' + num(readyCount + " из " + D.files.length) + " готово</span></div>" +
|
||||||
zoneBlock + table + "</section>" +
|
'<div class="kt-ai-table-wrap" data-framed="true">' +
|
||||||
|
'<table class="kt-ai-table"><thead><tr><th>Файл</th><th>Тип</th><th>Загружен</th><th>Статус</th><th></th></tr></thead>' +
|
||||||
|
"<tbody>" + rows + "</tbody></table></div></section>" +
|
||||||
'<section class="sec"><div class="sec-h">Источники для сверки' +
|
'<section class="sec"><div class="sec-h">Источники для сверки' +
|
||||||
'<span class="spacer"></span>' +
|
'<span class="spacer"></span>' +
|
||||||
'<span class="kt-ai-meta">' + num(D.sources.length) + " источника</span></div>" +
|
'<span class="kt-ai-meta">' + num(D.sources.length) + " источника</span></div>" +
|
||||||
@ -304,21 +276,8 @@ function vUpload() {
|
|||||||
'<div>' + (s.u ? pill("ok", "Синхронизирован") : pill("checked", "Синхронизация")) + "</div></div>";
|
'<div>' + (s.u ? pill("ok", "Синхронизирован") : pill("checked", "Синхронизация")) + "</div></div>";
|
||||||
}).join("") + "</div></section>" +
|
}).join("") + "</div></section>" +
|
||||||
'<section class="kt-ai-conclusion"><h3>Что на этом экране</h3>' +
|
'<section class="kt-ai-conclusion"><h3>Что на этом экране</h3>' +
|
||||||
"<p>Загрузите документы: перетащите файлы в зону загрузки или нажмите «выберите на устройстве». Система определит тип каждого файла, " +
|
"<p>Строками идёт по заявке и пакету документов. Статусы файлов: «Ожидает проверки» — файл ещё получен не был, «Готово к анализу» — файл проверен и получен. " +
|
||||||
"отметит готовность и после полной загрузки пакета включит кнопку «Запустить анализ ИИ». Анализ будет идти по фактическим загруженным файлам — " +
|
"Когда собран весь пакет, вверху экрана появляется кнопка «Запустить анализ ИИ».</p></section>";
|
||||||
"сверка с внутренними правилами и законодательством по 20 источникам.</p></section>";
|
|
||||||
}
|
|
||||||
|
|
||||||
function dropZoneHtml() {
|
|
||||||
var active = D.dropActive ? ' data-active="true"' : "";
|
|
||||||
return '<div class="drop" id="drop"' + active + '>' +
|
|
||||||
'<div class="drop-main">' +
|
|
||||||
'<span class="drop-ico">' + icon("upload") + "</span>" +
|
|
||||||
"<strong>Перетащите файлы сюда</strong>" +
|
|
||||||
"<span class='kt-ai-meta'>или <span class='drop-link' onclick='S.uploadFiles()'>выберите на устройстве</span></span>" +
|
|
||||||
"</div>" +
|
|
||||||
"<span class='drop-limit'>PDF, DOCX, XLSX, TXT — до 50 МБ</span>" +
|
|
||||||
"</div>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Экран 2. Результаты анализа ИИ ---------- */
|
/* ---------- Экран 2. Результаты анализа ИИ ---------- */
|
||||||
@ -526,106 +485,42 @@ function nowTime() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Действия ---------- */
|
/* ---------- Действия ---------- */
|
||||||
var STEPS_UPLOAD = [
|
S.uploadFiles = function () {
|
||||||
"Проверка файла",
|
var input = document.createElement("input");
|
||||||
"Сканирование на вирусы",
|
input.type = "file";
|
||||||
"Извлечение текста"
|
input.multiple = true;
|
||||||
];
|
input.accept = ".pdf,.docx,.xlsx,.csv,.txt,.doc";
|
||||||
|
input.onchange = function () {
|
||||||
/* Скрытый input для выбора файлов на устройстве */
|
var files = input.files || [];
|
||||||
if (typeof window !== "undefined") {
|
if (files.length === 0) return;
|
||||||
var _fileInput = null;
|
var added = 0;
|
||||||
function getFileInput() {
|
for (var i = 0; i < files.length; i++) {
|
||||||
if (!_fileInput) {
|
var f = files[i];
|
||||||
_fileInput = document.createElement("input");
|
var exists = false;
|
||||||
_fileInput.type = "file";
|
for (var j = 0; j < D.files.length; j++) {
|
||||||
_fileInput.multiple = true;
|
if (D.files[j].s === "wait" && D.files[j].n.toLowerCase().indexOf(f.name.toLowerCase().slice(0, 20)) >= 0) {
|
||||||
_fileInput.accept = ".pdf,.docx,.doc,.xlsx,.xls,.csv,.txt";
|
D.files[j].s = "ready";
|
||||||
_fileInput.style.display = "none";
|
D.files[j].d = nowTime();
|
||||||
_fileInput.addEventListener("change", function () {
|
exists = true;
|
||||||
if (_fileInput.files && _fileInput.files.length) {
|
added++;
|
||||||
handleFiles(Array.prototype.slice.call(_fileInput.files));
|
break;
|
||||||
}
|
}
|
||||||
_fileInput.value = "";
|
|
||||||
});
|
|
||||||
document.body.appendChild(_fileInput);
|
|
||||||
}
|
}
|
||||||
return _fileInput;
|
if (!exists) {
|
||||||
|
var ext = (f.name.lastIndexOf(".") >= 0) ? f.name.slice(f.name.lastIndexOf(".")) : "";
|
||||||
|
var typeLabel = "Подтверждающий документ";
|
||||||
|
if (f.name.toLowerCase().indexOf("записк") >= 0) typeLabel = "Пояснительная записка";
|
||||||
|
D.files.push({ n: f.name, t: typeLabel, d: nowTime() + " (загрузка)", s: "ready" });
|
||||||
|
added++;
|
||||||
}
|
}
|
||||||
window.__getFileInput = getFileInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFiles(arr) {
|
|
||||||
if (!arr || !arr.length) return;
|
|
||||||
var existing = {};
|
|
||||||
for (var k = 0; k < D.files.length; k++) existing[D.files[k].n] = true;
|
|
||||||
var toAdd = [];
|
|
||||||
for (var i = 0; i < arr.length; i++) {
|
|
||||||
var f = arr[i];
|
|
||||||
if (existing[f.name]) continue;
|
|
||||||
toAdd.push({ f: f, t: classifyFile(f.name), size: f.size, d: nowTime() + " · " + nowTime() });
|
|
||||||
}
|
}
|
||||||
if (!toAdd.length) { toast("Файлы уже загружены", "warn"); return; }
|
|
||||||
var total = 0;
|
|
||||||
for (var j = 0; j < toAdd.length; j++) total += toAdd[j].size;
|
|
||||||
D.progress = { active: true, step: 0, pct: 0, file: toAdd[0].f.name, size: total, done: {} };
|
|
||||||
D.files = [];
|
|
||||||
save();
|
save();
|
||||||
render();
|
render();
|
||||||
simulateUpload(toAdd, 0, total);
|
toast(added + " файл(ов) добавлено в пакет", "ok");
|
||||||
}
|
var allNow = D.files.every(function (x) { return x.s === "ready"; });
|
||||||
|
if (allNow) toast("Пакет собран — анализ стал доступен", "ok");
|
||||||
function simulateUpload(list, idx, total) {
|
};
|
||||||
if (idx >= list.length) {
|
input.click();
|
||||||
if (typeof _uploadTimer !== "undefined" && _uploadTimer) clearInterval(_uploadTimer);
|
|
||||||
D.progress = null;
|
|
||||||
save();
|
|
||||||
toast(D.files.length + " файл(ов) загружено в пакет", "ok");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var item = list[idx];
|
|
||||||
var name = item.f.name;
|
|
||||||
var size = item.size;
|
|
||||||
var step = 0;
|
|
||||||
var pct = 0;
|
|
||||||
D.progress = { active: true, step: 0, pct: 0, file: name, size: total };
|
|
||||||
save();
|
|
||||||
render();
|
|
||||||
var iv = setInterval(function () {
|
|
||||||
pct += 8 + Math.floor(Math.random() * 10);
|
|
||||||
if (pct >= 100) pct = 100;
|
|
||||||
step = pct < 34 ? 0 : (pct < 67 ? 1 : 2);
|
|
||||||
D.progress = { active: true, step: step, pct: pct, file: name, size: total };
|
|
||||||
save();
|
|
||||||
render();
|
|
||||||
if (pct >= 100) {
|
|
||||||
clearInterval(iv);
|
|
||||||
setTimeout(function () {
|
|
||||||
D.files.push({ n: name, t: item.t, size: size, d: item.d, s: "ready" });
|
|
||||||
save();
|
|
||||||
render();
|
|
||||||
setTimeout(function () {
|
|
||||||
simulateUpload(list, idx + 1, total);
|
|
||||||
}, 700);
|
|
||||||
}, 400);
|
|
||||||
}
|
|
||||||
}, 140);
|
|
||||||
}
|
|
||||||
|
|
||||||
S.resetDemo = function () {
|
|
||||||
if (confirm("Сбросить текущее состояние (загруженные файлы, результаты, черновик заключения)?")) {
|
|
||||||
D = seed();
|
|
||||||
save();
|
|
||||||
render();
|
|
||||||
toast("Демо сброшено — можно начать заново", "info");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
S.delFile = function (i) {
|
|
||||||
D.files.splice(i, 1);
|
|
||||||
save();
|
|
||||||
render();
|
|
||||||
toast("Файл удалён из пакета", "info");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
S.fileReady = function (i) {
|
S.fileReady = function (i) {
|
||||||
@ -636,90 +531,49 @@ S.fileReady = function (i) {
|
|||||||
|
|
||||||
S.go = function (id) { D.view = id; render(); };
|
S.go = function (id) { D.view = id; render(); };
|
||||||
|
|
||||||
function genViolations() {
|
|
||||||
var base = [];
|
|
||||||
for (var i = 0; i < D.files.length; i++) {
|
|
||||||
var f = D.files[i];
|
|
||||||
base.push(
|
|
||||||
{ d: f.n + ": текст извлечён и сверен с внутренними документами", y: "process", r: f.t + ", раздел 1", v: "checked" },
|
|
||||||
{ d: f.n + ": найдено 2 несоответствия с нормативной базой", y: "qual", r: f.t + ", ст. 36", v: "checked" }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
base.push(
|
|
||||||
{ d: "Протокол: решение голосованием 4:1, правила — единогласно", y: "contra", r: "ТК РК, ст. 165", v: "falsep" }
|
|
||||||
);
|
|
||||||
if (base.length === 1) {
|
|
||||||
base.unshift({ d: "Пакет пуст — нет данных для полного анализа", y: "process", r: "—", v: "falsep" });
|
|
||||||
}
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
|
|
||||||
S.analyze = function () {
|
S.analyze = function () {
|
||||||
if (!D.files.length) { toast("Сначала загрузите файлы", "warn"); return; }
|
if (!allFilesReady()) { toast("Дополните материалы — все файлы должны быть готовы", "warn"); return; }
|
||||||
if (D.files.some(function (f) { return f.s !== "ready"; })) { toast("Дождитесь завершения загрузки всех файлов", "warn"); return; }
|
|
||||||
D.running = true;
|
D.running = true;
|
||||||
D.contra = false;
|
D.contra = false;
|
||||||
D.falseDone = false;
|
D.falseDone = false;
|
||||||
var vio = genViolations();
|
D.checklist = [0, 1, 2, 3, 4, 5, 6, 7];
|
||||||
D.checklist = [];
|
|
||||||
for (var k = 0; k < vio.length; k++) {
|
|
||||||
vio[k].v = "checked";
|
|
||||||
D.checklist.push(k);
|
|
||||||
}
|
|
||||||
D.status = "draft";
|
D.status = "draft";
|
||||||
D.rejected = null;
|
D.rejected = null;
|
||||||
D.stamp = null;
|
D.stamp = null;
|
||||||
D.violations = vio;
|
for (var i = 0; i < D.violations.length; i++) D.violations[i].v = D.violations[i].v === "conf" ? "checked" : "checked";
|
||||||
|
D.violations[6].v = "falsep";
|
||||||
|
D.violations[7].v = "falsep";
|
||||||
D.verdict = "Заявка на назначение руководителя уровня SEO-1, управление сетевых сервисов (кандидат " + esc(D.candidate) + ") прошла проверку соответствия нормативной базе. Итоговое заключение будет сформировано после экспертной проверки выводов.";
|
D.verdict = "Заявка на назначение руководителя уровня SEO-1, управление сетевых сервисов (кандидат " + esc(D.candidate) + ") прошла проверку соответствия нормативной базе. Итоговое заключение будет сформировано после экспертной проверки выводов.";
|
||||||
D.log = [];
|
D.log = [
|
||||||
save();
|
{ t: nowTime(), b: "Пакет принят, 8 файлов" },
|
||||||
|
{ t: nowTime(), b: "Запущен анализ: " + D.sources.length + " источника для сверки" }
|
||||||
|
];
|
||||||
render();
|
render();
|
||||||
startLogStream();
|
setTimeout(function () { pushLogs(); }, 900);
|
||||||
|
setTimeout(function () { pushLogs(); }, 2100);
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
if (!D.running) return;
|
|
||||||
for (var m = 0; m < D.violations.length; m++) if (D.violations[m].y === "contra") D.violations[m].v = "falsep";
|
|
||||||
D.running = false;
|
D.running = false;
|
||||||
|
D.violations[6].v = "falsep";
|
||||||
D.contra = true;
|
D.contra = true;
|
||||||
var proc = 0, qual = 0, contra = 0;
|
D.log.push({ t: nowTime(), b: "Обнаружено несоответствий: " + (D.violations.length - 2) + ", противоречий: 1" });
|
||||||
for (var n = 0; n < D.violations.length; n++) {
|
|
||||||
var y = D.violations[n].y;
|
|
||||||
if (y === "process") proc++;
|
|
||||||
else if (y === "qual") qual++;
|
|
||||||
else if (y === "contra") contra++;
|
|
||||||
}
|
|
||||||
D.log.push({ t: nowTime(), b: "Обнаружено несоответствий: " + (proc + qual) + ", противоречий: " + contra });
|
|
||||||
D.log.push({ t: nowTime(), b: "Журнал завершён — выводы переданы эксперту" });
|
D.log.push({ t: nowTime(), b: "Журнал завершён — выводы переданы эксперту" });
|
||||||
D.view = "results";
|
S.go("results");
|
||||||
save();
|
}, 3600);
|
||||||
render();
|
|
||||||
toast("Анализ завершён — переход в результаты", "ok");
|
|
||||||
}, 6000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function genLogPlan() {
|
function pushLogs() {
|
||||||
var plan = [];
|
var extra = [
|
||||||
var files = D.files.concat([]);
|
{ t: nowTime(), b: "Извлечение текста: пояснительная записка" },
|
||||||
for (var i = 0; i < files.length; i++) {
|
{ t: nowTime(), b: "Сверка с Правилами поиска и подбора персонала, п. 4.2" },
|
||||||
plan.push({ b: "Извлечение текста: " + files[i].n });
|
{ t: nowTime(), b: "Найдено отклонение: дата объявления о вакансии" },
|
||||||
}
|
{ t: nowTime(), b: "Сверка с Законом «Об АО», ст. 36" },
|
||||||
plan.push({ b: "Сверка с Правилами поиска и подбора персонала, п. 4.2" });
|
{ t: nowTime(), b: "Проверка квалификации кандидата" }
|
||||||
plan.push({ b: "Сверка с Законом «Об АО», ст. 36" });
|
];
|
||||||
plan.push({ b: "Проверка квалификации кандидата: стаж, образование, аттестация" });
|
if (D.log.length <= 2) { D.log.push(extra[0]); D.log.push(extra[1]); }
|
||||||
return plan.map(function (line) { return { t: nowTime(), b: line.b }; });
|
else if (D.log.length <= 4) { D.log.push(extra[2]); D.log.push(extra[3]); }
|
||||||
}
|
else { D.log.push(extra[4]); }
|
||||||
|
|
||||||
function startLogStream() {
|
|
||||||
var plan = genLogPlan();
|
|
||||||
var delay = 300;
|
|
||||||
for (var i = 0; i < plan.length; i++) (function (line) {
|
|
||||||
setTimeout(function () {
|
|
||||||
if (D.running) {
|
|
||||||
D.log.push(line);
|
|
||||||
save();
|
save();
|
||||||
render();
|
render();
|
||||||
}
|
|
||||||
}, delay);
|
|
||||||
})(plan[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
S.closeDrawer = closeDrawer;
|
S.closeDrawer = closeDrawer;
|
||||||
@ -834,51 +688,6 @@ S.burger = function () {
|
|||||||
else sb.setAttribute("data-open", "true");
|
else sb.setAttribute("data-open", "true");
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ---------- Drag & drop ---------- */
|
|
||||||
function initDnD() {
|
|
||||||
document.addEventListener("dragover", function (e) {
|
|
||||||
if (e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types.indexOf("Files") >= 0) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.dataTransfer.dropEffect = "copy";
|
|
||||||
D.dropActive = true;
|
|
||||||
var d = $("drop");
|
|
||||||
if (d) d.setAttribute("data-active", "true");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.addEventListener("dragleave", function (e) {
|
|
||||||
if (e.relatedTarget === null) {
|
|
||||||
D.dropActive = false;
|
|
||||||
var d = $("drop");
|
|
||||||
if (d) d.removeAttribute("data-active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.addEventListener("drop", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
D.dropActive = false;
|
|
||||||
var d = $("drop");
|
|
||||||
if (d) d.removeAttribute("data-active");
|
|
||||||
var files = e.dataTransfer && e.dataTransfer.files;
|
|
||||||
if (files && files.length) handleFiles(Array.prototype.slice.call(files));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- Инициализация ---------- */
|
/* ---------- Инициализация ---------- */
|
||||||
load();
|
load();
|
||||||
render();
|
render();
|
||||||
initDnD();
|
|
||||||
|
|
||||||
/* Обновление drop-зоны после dragover/drop без рендера всего экрана */
|
|
||||||
function refreshDrop() {
|
|
||||||
var d = $("drop");
|
|
||||||
if (d) {
|
|
||||||
if (D.dropActive) d.setAttribute("data-active", "true");
|
|
||||||
else d.removeAttribute("data-active");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var _dndTimer = null;
|
|
||||||
var _origRender = render;
|
|
||||||
render = function () {
|
|
||||||
_origRender();
|
|
||||||
if (_dndTimer) clearTimeout(_dndTimer);
|
|
||||||
_dndTimer = setTimeout(refreshDrop, 0);
|
|
||||||
};
|
|
||||||
|
|||||||
74
style.css
74
style.css
@ -138,77 +138,3 @@ tr[data-wait="true"] td:first-child { color: var(--kt-ai-status-warn-fg); font-w
|
|||||||
.kt-ai-table-wrap .kt-ai-table tbody tr { display: table-row; }
|
.kt-ai-table-wrap .kt-ai-table tbody tr { display: table-row; }
|
||||||
.kt-ai-table-wrap .kt-ai-table td { display: table-cell; }
|
.kt-ai-table-wrap .kt-ai-table td { display: table-cell; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ====== Загрузка: зона + прогресс-бар ====== */
|
|
||||||
.drop {
|
|
||||||
border: 2px dashed var(--kt-ai-border);
|
|
||||||
border-radius: var(--kt-ai-radius-2xl);
|
|
||||||
background: var(--kt-ai-bg-elevated);
|
|
||||||
padding: var(--kt-ai-space-6);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--kt-ai-space-5);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color .15s, background .15s;
|
|
||||||
}
|
|
||||||
.drop:hover, .drop[data-active="true"] {
|
|
||||||
border-color: var(--kt-ai-primary);
|
|
||||||
background: var(--kt-ai-primary-subtle);
|
|
||||||
}
|
|
||||||
.drop-main { display: flex; align-items: center; gap: var(--kt-ai-space-4); flex-wrap: wrap; }
|
|
||||||
.drop-main strong { font-size: var(--kt-ai-text-md); font-weight: var(--kt-ai-weight-semibold); }
|
|
||||||
.drop-ico {
|
|
||||||
width: 40px; height: 40px; flex: 0 0 40px;
|
|
||||||
border-radius: var(--kt-ai-radius-lg);
|
|
||||||
background: var(--kt-ai-bg-active);
|
|
||||||
color: var(--kt-ai-fg-muted);
|
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
}
|
|
||||||
.drop[data-active="true"] .drop-ico { background: var(--kt-ai-primary); color: var(--kt-ai-fg-on-primary); }
|
|
||||||
.drop-limit { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-faint); }
|
|
||||||
.drop-link { color: var(--kt-ai-link); text-decoration: underline; cursor: pointer; }
|
|
||||||
|
|
||||||
/* Прогресс-бар загрузки */
|
|
||||||
.progress {
|
|
||||||
margin-top: var(--kt-ai-space-4);
|
|
||||||
padding: var(--kt-ai-space-6);
|
|
||||||
border: 1px solid var(--kt-ai-divider);
|
|
||||||
border-radius: var(--kt-ai-radius-xl);
|
|
||||||
background: var(--kt-ai-bg);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--kt-ai-space-6);
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.progress .pl { display: flex; align-items: center; gap: var(--kt-ai-space-4); min-width: 280px; }
|
|
||||||
.progress .pl-ico {
|
|
||||||
width: 34px; height: 34px; flex: 0 0 34px;
|
|
||||||
border-radius: var(--kt-ai-radius-md);
|
|
||||||
background: var(--kt-ai-status-info-bg);
|
|
||||||
color: var(--kt-ai-status-info-fg);
|
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
font-size: var(--kt-ai-text-sm);
|
|
||||||
}
|
|
||||||
.progress.done .pl-ico { background: var(--kt-ai-status-ok-bg); color: var(--kt-ai-status-ok-fg); }
|
|
||||||
.progress .pl strong { font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-semibold); display: block; }
|
|
||||||
.progress .pl .kt-ai-meta { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); display: block; margin-top: 1px; }
|
|
||||||
.progress .kp { flex: 1; min-width: 240px; }
|
|
||||||
.progress .bar {
|
|
||||||
height: 6px;
|
|
||||||
border-radius: var(--kt-ai-radius-full);
|
|
||||||
background: var(--kt-ai-bg-active);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.progress .bar i {
|
|
||||||
display: block; height: 100%;
|
|
||||||
background: var(--kt-ai-primary);
|
|
||||||
border-radius: var(--kt-ai-radius-full);
|
|
||||||
transition: width .3s ease;
|
|
||||||
}
|
|
||||||
.progress.done .bar i { background: var(--kt-ai-status-ok-fg); }
|
|
||||||
.progress .lp {
|
|
||||||
display: flex; justify-content: space-between; gap: var(--kt-ai-space-3);
|
|
||||||
margin-top: var(--kt-ai-space-2);
|
|
||||||
font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted);
|
|
||||||
}
|
|
||||||
.progress .lp .kp-num { font-variant-numeric: tabular-nums; font-weight: var(--kt-ai-weight-medium); color: var(--kt-ai-fg); }
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user