Match Contracts UI to Obfuscator and add Help
Deploy contracts-flask / validate (push) Canceled after 0s

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Repinoid
2026-10-30 10:40:44 +03:00
co-authored by Copilot
parent c79006737f
commit 961d60a79e
5 changed files with 64 additions and 25 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
"""Конфигурация приложения — все настройки в одном месте."""
import os
VERSION = "2.0.22"
VERSION = "2.0.23"
LLM_URL = os.getenv("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions")
LLM_KEY = os.getenv("LLM_API_KEY", "")
+1 -1
View File
@@ -58,7 +58,7 @@ filePicker = window.FilePicker.initFilePicker({
function renderStepper(state) {
var steps = state.ui.steps;
var map = { upload: 'stepUpload', classify: 'stepClassify', groups: 'stepGroups', compare: 'stepCompare' };
var colors = { '○': 'var(--muted)', '⏳': 'var(--brand-primary)', '✓': 'var(--green)' };
var colors = { '○': 'var(--topbar-muted)', '⏳': 'var(--topbar-active)', '✓': 'var(--topbar-done)' };
Object.keys(map).forEach(function(key) {
var el = document.getElementById(map[key]);
if (el) {
+2 -2
View File
@@ -65,14 +65,14 @@ window.moveUp = moveUp;
window.moveDown = moveDown;
/**
* Открыть модальное окно "О сервисе".
* Открыть модальное окно справки.
*/
function openAbout() {
document.getElementById('aboutModalOverlay').classList.add('open');
}
/**
* Закрыть модальное окно "О сервисе" (по клику на оверлей или кнопку).
* Закрыть модальное окно справки по клику на оверлей или кнопку.
*/
function closeAbout(e) {
if (e && e.target !== document.getElementById('aboutModalOverlay')) return;
+57 -21
View File
@@ -7,13 +7,20 @@
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<script src="https://unpkg.com/lucide@latest"></script>
<style>
:root { --brand-primary: #2563eb; --brand-primary-dark: #1d4ed8; --brand-gray: #d1d5db; --brand-grey-light: #f3f4f6; --background: #ffffff; --foreground: #1a1a1a; --muted: #6b7280; --destructive: #ef4444; --green: #22c55e; }
:root { --brand-primary: #2563eb; --brand-primary-dark: #1d4ed8; --brand-gray: #d1d5db; --brand-grey-light: #f3f4f6; --background: #ffffff; --foreground: #1a1a1a; --muted: #6b7280; --destructive: #ef4444; --green: #22c55e; --navy: #001c34; --topbar-muted: #a4acb3; --topbar-active: #8ec5ff; --topbar-done: #4ade80; }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; font-size: 14px; color: var(--foreground); background: var(--brand-grey-light); min-height: 100vh; }
.topbar { background: var(--background); border-bottom: 1px solid var(--brand-gray); padding: 0 24px; height: 56px; display: flex; align-items: center; gap: 16px; position: sticky; top: 0; z-index: 50; }
.topbar img { height: 28px; }
.topbar .title { font-weight: 600; font-size: 16px; }
.content { max-width: 900px; margin: 32px auto; padding: 0 16px; }
.topbar { background: var(--navy); color: #fff; padding: 0 24px; height: 56px; display: flex; align-items: center; gap: 12px; position: sticky; top: 0; z-index: 50; }
.topbar img { height: 20px; filter: brightness(0) invert(1); }
.topbar .title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; font-size: 15px; }
.topbar .ver { flex-shrink: 0; color: var(--topbar-muted); font-size: 12px; }
.pipeline-stepper { display: flex; align-items: center; gap: 8px; margin-left: auto; color: var(--topbar-muted); font-size: 11px; white-space: nowrap; }
.pipeline-stepper > span:nth-child(even) { color: rgba(255,255,255,.4); }
.topbar .help { flex-shrink: 0; cursor: pointer; user-select: none; padding: 6px 16px; border-radius: 8px; border: 1px solid rgba(255,255,255,.4); color: #fff; background: transparent; font: 500 13px system-ui, sans-serif; }
.topbar .help:hover { background: rgba(255,255,255,.1); }
.content { max-width: 960px; margin: 40px auto; padding: 0 24px; }
.page-title { color: var(--navy); font-size: 28px; font-weight: 500; line-height: 1.2; }
.page-sub { margin: 8px 0 24px; color: var(--muted); }
.card { background: var(--background); border-radius: 12px; border: 1px solid var(--brand-gray); box-shadow: 0 1px 2px rgba(0,0,0,.05); overflow: hidden; margin-bottom: 16px; }
.card-header { background: var(--brand-grey-light); padding: 12px 16px; font-weight: 600; font-size: 16px; display: flex; align-items: center; gap: 8px; }
.card-body { padding: 16px; }
@@ -88,22 +95,33 @@
#filePicker .fp-status-duplicate { color: var(--muted); }
#filePicker .fp-status-error { color: var(--destructive); }
#filePicker .fp-status, #filePicker .fp-count { color: var(--muted); font: 12px system-ui, sans-serif; }
@media (max-width: 900px) { .pipeline-stepper { display: none; } }
@media (max-width: 600px) {
.topbar { padding: 0 12px; gap: 8px; }
.topbar .title { font-size: 13px; }
.topbar .help { padding: 6px 10px; }
.content { margin: 24px auto; padding: 0 12px; }
.page-title { font-size: 24px; }
}
</style>
</head>
<body>
<div class="topbar">
<img src="/static/logo.svg" alt="Nubes">
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v{{ config["VERSION"] }}</span></span>
<div id="pipelineStepper" style="display:flex;gap:8px;font-size:11px;align-items:center;color:var(--muted);">
<span class="title">Контрактор</span>
<span class="ver">v{{ config["VERSION"] }}</span>
<div id="pipelineStepper" class="pipeline-stepper">
<span id="stepUpload">○ Загрузка</span><span>→</span>
<span id="stepClassify">○ Классификация</span><span>→</span>
<span id="stepGroups">○ Группировка</span><span>→</span>
<span id="stepCompare">○ Сравнение</span>
</div>
<span style="margin-left:auto;font-size:12px;color:var(--brand-primary);cursor:pointer;white-space:nowrap;" onclick="openAbout()">ℹ️ О сервисе</span>
<button class="help" type="button" onclick="openAbout()" aria-controls="aboutModalOverlay">HELP</button>
</div>
<div class="content">
<h1 class="page-title">Сверка договоров</h1>
<p class="page-sub">Загрузите договоры, дополнительные соглашения и спецификации — система классифицирует их, сгруппирует и покажет изменения.</p>
<div class="card">
<div class="card-header">
<i data-lucide="folder-open" style="width:18px;height:18px;"></i>
@@ -111,7 +129,7 @@
</div>
<div class="card-body">
<div style="text-align:right;font-size:11px;color:var(--muted);margin-bottom:6px;">Порядок определяется автоматически при классификации</div>
<div style="font-size:11px;color:var(--muted);margin-bottom:6px;">Выберите PDF, DOC или DOCX. ZIP раскрываются рекурсивно; правила из корневого <code>obscure.lst</code> применяются при выборе папки.</div>
<div style="font-size:11px;color:var(--muted);margin-bottom:6px;">PDF, DOC, DOCX и ZIP (архивы раскрываются рекурсивно). Лимиты: 50 МБ на файл, 500 МБ суммарно. При выборе папки можно фильтровать состав через корневой <code>obscure.lst</code>.</div>
<div id="filePicker"></div>
<button class="btn btn-primary" id="llmBtn" style="width:100%;justify-content:center;margin-top:8px;display:none;background:var(--green);border-color:var(--green);">
@@ -209,20 +227,38 @@
<div class="modal" style="max-width:720px;" onclick="event.stopPropagation()">
<div class="modal-header">
<i data-lucide="info" style="width:18px;height:18px;"></i>
<span>О сервисе — архитектура и принцип работы</span>
<button class="remove-btn" onclick="closeAbout()" style="margin-left:auto;font-size:18px;">&#xd7;</button>
<span>Контрактор — справка</span>
<button class="remove-btn" onclick="closeAbout()" aria-label="Закрыть справку" style="margin-left:auto;font-size:18px;">&#xd7;</button>
</div>
<div class="modal-body" style="padding:16px;font-size:13px;line-height:1.6;max-height:65vh;overflow-y:auto;" id="aboutBody">
<h3 style="margin-top:0;">Что это</h3>
<p>Автоматический разбор и сравнение договоров с помощью ИИ (LLM). Загружаете договоры, допсоглашения и спецификации вперемешку — система сама разбирает что есть что, группирует по контрактам и находит все изменения между версиями.</p>
<h3>Как пользоваться</h3>
<p><b>1. Загрузка</b> — выберите файлы (.docx, .doc, .pdf, .zip). Можно загружать всё вперемешку. Порядок не важен — классификация разберётся.</p>
<p><b>2. Классификация</b> — нажмите «Классифицировать». Система через LLM определит тип, номер, дату, контрагента.</p>
<p><b>3. Группы</b> — документы автоматически группируются по договорам.</p>
<p><b>4. Сравнение</b> — для каждой группы нажмите «▶ Сравнить». Результат: ADD/UPDATE/DELETE.</p>
<p><b>5. Промпты</b> — три вида инструкций для ИИ с версионированием.</p>
<h3>На выходе</h3>
<p>Структурированная спецификация + полная история изменений с привязкой к документам-источникам.</p>
<h3 style="margin-top:0;">Обработка договоров</h3>
<p>Загрузите договоры, дополнительные соглашения и спецификации. Система разберёт документы, определит их тип и сгруппирует связанные документы для сравнения.</p>
<h3>Выбор файлов</h3>
<ul style="padding-left:20px;margin:6px 0 12px;">
<li>Поддерживаются документы PDF, DOC и DOCX; ZIP-архивы раскрываются рекурсивно.</li>
<li>Можно выбрать отдельные файлы или папку. Порядок выбора не влияет на классификацию.</li>
<li>Лимит — 50 МБ на файл и 500 МБ суммарно.</li>
</ul>
<h3>Фильтр папки через obscure.lst</h3>
<p>Чтобы выбрать из папки не все документы, положите файл <code>obscure.lst</code> в её корень. Файл фильтра не добавляется в список документов; если его нет, выбираются все поддерживаемые документы.</p>
<ul style="padding-left:20px;margin:6px 0 12px;">
<li>Одна маска на строку; пустые строки игнорируются, строки с <code>#</code> — комментарии.</li>
<li><code>!</code> в начале маски задаёт исключение; исключения применяются после включающих правил.</li>
<li><code>*</code> соответствует символам внутри одного сегмента пути, <code>?</code> — одному символу, <code>**</code> — подпапкам.</li>
<li>Пути задаются относительно выбранной папки; регистр не учитывается. Вложенный <code>subdir/obscure.lst</code> не используется.</li>
</ul>
<pre style="font-size:12px;background:var(--brand-grey-light);border:1px solid var(--brand-gray);border-radius:8px;padding:10px 12px;overflow-x:auto;margin:8px 0;"># PDF в корне и подпапках
**/*.pdf
# Исключить черновики
!**/*draft*.*</pre>
<h3>После загрузки</h3>
<p><b>1. Классификация</b> — нажмите «Классифицировать»: система определит тип документа и связанные реквизиты.</p>
<p><b>2. Группы</b> — проверьте автоматически сформированные группы документов.</p>
<p><b>3. Сравнение</b> — нажмите «Сравнить» у нужной группы или запустите «Общее сравнение».</p>
</div>
</div>
</div>