Improve upload screen readability and help
Deploy contracts-flask / validate (push) Canceled after 0s
Deploy contracts-flask / validate (push) Canceled after 0s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+97
-45
@@ -7,31 +7,31 @@
|
||||
<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; --navy: #001c34; --topbar-muted: #a4acb3; --topbar-active: #8ec5ff; --topbar-done: #4ade80; }
|
||||
:root { --brand-primary: #2563eb; --brand-primary-dark: #1d4ed8; --brand-gray: #cbd5e1; --brand-grey-light: #f1f5f9; --background: #ffffff; --foreground: #0f172a; --muted: #475569; --destructive: #b91c1c; --green: #15803d; --navy: #082f49; --topbar-muted: #cbd5e1; --topbar-active: #bfdbfe; --topbar-done: #86efac; }
|
||||
* { 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; }
|
||||
body { font-family: system-ui, sans-serif; font-size: 16px; color: var(--foreground); background: #f8fafc; min-height: 100vh; }
|
||||
.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 { display: flex; align-items: center; gap: 8px; margin-left: auto; color: var(--topbar-muted); font-size: 12px; 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 { flex-shrink: 0; min-height: 40px; cursor: pointer; user-select: none; padding: 6px 14px; border-radius: 8px; border: 1px solid rgba(255,255,255,.65); color: #fff; background: transparent; font: 600 14px 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); }
|
||||
.content { max-width: 1160px; margin: 36px auto; padding: 0 24px; }
|
||||
.page-title { color: var(--navy); font-size: 32px; font-weight: 650; line-height: 1.2; }
|
||||
.page-sub { max-width: 850px; margin: 10px 0 26px; color: var(--muted); font-size: 16px; line-height: 1.55; }
|
||||
.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; }
|
||||
.btn { height: 36px; border-radius: 6px; gap: 6px; padding: 0 14px; font-size: 14px; font-family: inherit; cursor: pointer; display: inline-flex; align-items: center; border: 1px solid var(--brand-gray); background: var(--background); }
|
||||
.card-header { background: #e8f0f8; padding: 15px 18px; font-weight: 650; font-size: 18px; display: flex; align-items: center; gap: 10px; }
|
||||
.card-body { padding: 20px; }
|
||||
.btn { min-height: 42px; border-radius: 7px; gap: 8px; padding: 0 16px; font-size: 15px; font-family: inherit; cursor: pointer; display: inline-flex; align-items: center; border: 1px solid var(--brand-gray); background: var(--background); }
|
||||
.btn-primary { background: var(--brand-primary); border-color: var(--brand-primary); color: #fff; }
|
||||
.btn-primary:hover { background: var(--brand-primary-dark); }
|
||||
.btn:disabled { opacity: .5; cursor: not-allowed; }
|
||||
.table-wrap { border-radius: 6px; border: 1px solid var(--brand-gray); overflow-y: auto; max-height: 50vh; margin-top: 12px; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
th { background: var(--brand-grey-light); text-transform: uppercase; padding: 6px 10px; border-right: 1px solid var(--brand-gray); text-align: left; font-weight: 600; font-size: 11px; color: var(--muted); }
|
||||
td { padding: 6px 10px; border-right: 1px solid var(--brand-gray); border-bottom: 1px solid var(--brand-gray); }
|
||||
th { background: var(--brand-grey-light); text-transform: uppercase; padding: 9px 12px; border-right: 1px solid var(--brand-gray); text-align: left; font-weight: 650; font-size: 12px; color: #334155; }
|
||||
td { padding: 9px 12px; border-right: 1px solid var(--brand-gray); border-bottom: 1px solid var(--brand-gray); }
|
||||
tr:hover td { background: rgba(243,244,246,.5); }
|
||||
.name-cell { max-width: 340px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.num-cell { text-align: right; white-space: nowrap; }
|
||||
@@ -75,33 +75,59 @@
|
||||
.text-pane th { font-size: 10px; padding: 4px 6px; }
|
||||
.text-pane td { padding: 3px 6px; font-size: 11px; }
|
||||
.text-pane .pane-title { font-weight: 600; font-size: 12px; margin-bottom: 8px; color: var(--muted); text-transform: uppercase; }
|
||||
.upload-intro { margin-bottom: 18px; color: #334155; line-height: 1.55; }
|
||||
.upload-intro > p { margin-bottom: 12px; }
|
||||
.upload-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 14px 0; }
|
||||
.upload-fact { padding: 12px 14px; border: 1px solid #cbd5e1; border-radius: 8px; background: #f8fafc; }
|
||||
.upload-fact strong { display: block; margin-bottom: 3px; color: #0f172a; font-size: 14px; }
|
||||
.upload-fact span { display: block; color: #334155; font-size: 14px; line-height: 1.45; }
|
||||
.workflow-note { padding: 11px 14px; border-left: 4px solid var(--brand-primary); border-radius: 4px; background: #eff6ff; color: #1e3a8a; }
|
||||
.help-modal { max-width: 860px; }
|
||||
.help-body { padding: 24px 28px; font-size: 16px; line-height: 1.7; color: #1e293b; }
|
||||
.help-body h3 { margin: 24px 0 8px; color: #0f3554; font-size: 19px; line-height: 1.35; }
|
||||
.help-body h3:first-child { margin-top: 0; }
|
||||
.help-body p { margin: 0 0 10px; }
|
||||
.help-body ul, .help-body ol { padding-left: 24px; margin: 8px 0 16px; }
|
||||
.help-body li { margin: 5px 0; padding-left: 3px; }
|
||||
.help-body code { padding: 1px 5px; border-radius: 4px; background: #eef2f7; color: #172554; font-size: .92em; }
|
||||
.help-body pre { font-size: 14px !important; line-height: 1.55; color: #172554; }
|
||||
.help-callout { margin: 12px 0 16px; padding: 12px 15px; border-left: 4px solid #2563eb; border-radius: 4px; background: #eff6ff; }
|
||||
.help-callout.warning { border-color: #b45309; background: #fffbeb; }
|
||||
#filePicker .file-picker { color: var(--foreground); font-family: system-ui, sans-serif; }
|
||||
#filePicker .fp-toolbar { margin: 0 0 8px; }
|
||||
#filePicker .fp-toolbar button { min-height: 36px; border: 1px solid var(--brand-gray); border-radius: 6px; padding: 0 14px; color: var(--foreground); background: var(--background); font: inherit; }
|
||||
#filePicker .fp-toolbar { margin: 0 0 10px; }
|
||||
#filePicker .fp-toolbar button { min-height: 44px; border: 1px solid var(--brand-gray); border-radius: 7px; padding: 0 16px; color: var(--foreground); background: var(--background); font: 15px system-ui, sans-serif; }
|
||||
#filePicker .fp-toolbar button:first-of-type { color: #fff; background: var(--brand-primary); border-color: var(--brand-primary); }
|
||||
#filePicker .fp-toolbar button.secondary { color: var(--foreground); background: var(--background); }
|
||||
#filePicker .fp-toolbar button.quiet { color: var(--muted); }
|
||||
#filePicker .fp-table-wrap { max-height: 50vh; overflow: auto; border: 1px solid var(--brand-gray); border-radius: 6px; }
|
||||
#filePicker .fp-table { font: 13px system-ui, sans-serif; }
|
||||
#filePicker .fp-table th, #filePicker .fp-table td { padding: 6px 10px; border-color: var(--brand-gray); }
|
||||
#filePicker .fp-table th { color: var(--muted); letter-spacing: 0; }
|
||||
#filePicker .fp-table { font: 15px/1.45 system-ui, sans-serif; }
|
||||
#filePicker .fp-table th, #filePicker .fp-table td { padding: 10px 12px; border-color: var(--brand-gray); }
|
||||
#filePicker .fp-table th { color: #334155; letter-spacing: 0; }
|
||||
#filePicker .tree-name { min-width: 0; padding-top: 0; padding-bottom: 0; color: var(--foreground); font-family: inherit; }
|
||||
#filePicker .tree-file .tree-name { overflow: hidden; text-overflow: ellipsis; }
|
||||
#filePicker .fp-actions { white-space: nowrap; }
|
||||
#filePicker .fp-action { margin-right: 4px; padding: 3px 6px; border: 0; border-radius: 4px; color: var(--brand-primary); background: transparent; cursor: pointer; font: 11px system-ui, sans-serif; }
|
||||
#filePicker .fp-action { margin-right: 4px; padding: 5px 7px; border: 0; border-radius: 4px; color: #1d4ed8; background: transparent; cursor: pointer; font: 13px system-ui, sans-serif; }
|
||||
#filePicker .fp-status-uploading { color: var(--brand-primary); }
|
||||
#filePicker .fp-status-parsing { color: var(--brand-primary); }
|
||||
#filePicker .fp-status-parsed { color: var(--green); }
|
||||
#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; }
|
||||
#filePicker .fp-status, #filePicker .fp-count { color: #334155; font: 14px system-ui, sans-serif; }
|
||||
#filePicker .fp-count { margin-top: 6px; }
|
||||
#uploadAdvanced { margin-top: 16px !important; font-size: 14px !important; }
|
||||
#uploadAdvanced > summary { color: #334155 !important; font-size: 14px; font-weight: 600; }
|
||||
@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; }
|
||||
.page-title { font-size: 27px; }
|
||||
.card-body { padding: 16px; }
|
||||
.upload-facts { grid-template-columns: 1fr; }
|
||||
.help-body { padding: 18px; font-size: 15px; }
|
||||
.help-body h3 { font-size: 18px; }
|
||||
.topbar .help { padding: 6px 10px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -116,7 +142,7 @@
|
||||
<span id="stepGroups">○ Группировка</span><span>→</span>
|
||||
<span id="stepCompare">○ Сравнение</span>
|
||||
</div>
|
||||
<button class="help" type="button" onclick="openAbout()" aria-controls="aboutModalOverlay">HELP</button>
|
||||
<button class="help" type="button" onclick="openAbout()" aria-controls="aboutModalOverlay">Справка</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
@@ -128,15 +154,21 @@
|
||||
Загрузка договоров/приложений/спецификаций
|
||||
</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 (архивы раскрываются рекурсивно). Лимиты: 50 МБ на файл, 500 МБ суммарно. При выборе папки можно фильтровать состав через корневой <code>obscure.lst</code>.</div>
|
||||
<div class="upload-intro">
|
||||
<p><strong>Добавьте договоры, дополнительные соглашения и спецификации.</strong> Можно выбрать отдельные файлы или целую папку. ZIP-архивы раскрываются автоматически, включая вложенные архивы.</p>
|
||||
<div class="upload-facts">
|
||||
<div class="upload-fact"><strong>Поддерживаемые форматы</strong><span>PDF, DOC, DOCX и ZIP с документами</span></div>
|
||||
<div class="upload-fact"><strong>Ограничения</strong><span>До 50 МБ на файл и до 500 МБ на весь набор</span></div>
|
||||
</div>
|
||||
<p class="workflow-note"><strong>Порядок выбирать не нужно.</strong> После загрузки система определит типы документов, предложит группы и расположит документы по датам. Перед сравнением проверьте предложенные группы.</p>
|
||||
</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);">
|
||||
<i data-lucide="scale" style="width:16px;height:16px;"></i> Общее сравнение
|
||||
</button>
|
||||
|
||||
<details style="margin-top:8px;font-size:12px;">
|
||||
<details id="uploadAdvanced" style="margin-top:8px;font-size:12px;">
|
||||
<summary style="cursor:pointer;color:var(--muted);">⚙ Промпты LLM</summary>
|
||||
<div style="display:flex;gap:8px;margin-top:6px;">
|
||||
<button class="btn" id="promptBtnExtract" style="font-size:11px;height:28px;flex:1;">📄 Извлечение</button>
|
||||
@@ -224,41 +256,61 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="aboutModalOverlay" onclick="closeAbout(event)">
|
||||
<div class="modal" style="max-width:720px;" onclick="event.stopPropagation()">
|
||||
<div class="modal help-modal" 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()" aria-label="Закрыть справку" style="margin-left:auto;font-size:18px;">×</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>Загрузите договоры, дополнительные соглашения и спецификации. Система разберёт документы, определит их тип и сгруппирует связанные документы для сравнения.</p>
|
||||
<div class="modal-body help-body" style="max-height:75vh;" id="aboutBody">
|
||||
<h3>Что делает Контрактор</h3>
|
||||
<p>Контрактор извлекает содержимое договоров и приложений, определяет тип и реквизиты документов, объединяет связанные документы в группы и показывает, как менялась спецификация по мере появления новых документов.</p>
|
||||
<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>
|
||||
<h3>1. Добавьте документы</h3>
|
||||
<ul>
|
||||
<li>Нажмите <strong>«Выбрать файлы»</strong>, чтобы добавить отдельные документы, или <strong>«Выбрать папку»</strong>, чтобы загрузить набор вместе с подпапками.</li>
|
||||
<li>Поддерживаются PDF, DOC, DOCX и ZIP. Архивы раскрываются рекурсивно; неподдерживаемые файлы в них не добавляются.</li>
|
||||
<li>Ограничения: не более 50 МБ на один файл и 500 МБ на весь набор.</li>
|
||||
<li>В таблице видны путь, размер и состояние каждого файла. Если файл обработан, кнопки <strong>«Детали»</strong> и <strong>«Текст»</strong> позволяют проверить результат разбора.</li>
|
||||
</ul>
|
||||
<div class="help-callout warning"><strong>Важно для сканов.</strong> Если PDF состоит из изображений страниц и не содержит текстового слоя, извлечённый текст может быть пустым или неполным. Проверьте его кнопкой «Текст» до сравнения.</div>
|
||||
|
||||
<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>
|
||||
<h3>2. При необходимости отфильтруйте папку</h3>
|
||||
<p>Чтобы загрузить из выбранной папки только часть файлов, создайте в её корне текстовый файл <code>obscure.lst</code>. Если такого файла нет, выбираются все поддерживаемые документы. Сам файл фильтра в список документов не попадает.</p>
|
||||
<ul>
|
||||
<li>Указывайте по одной маске на строку. Пустые строки и строки, начинающиеся с <code>#</code>, игнорируются.</li>
|
||||
<li><code>*</code> заменяет часть имени внутри папки, <code>?</code> — один символ, <code>**</code> позволяет учитывать подпапки.</li>
|
||||
<li>Начните маску с <code>!</code>, чтобы исключить совпавшие файлы. Исключения применяются после включающих правил.</li>
|
||||
<li>Пути указываются относительно выбранной папки; вложенные файлы <code>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 в корне и подпапках
|
||||
<pre style="background:var(--brand-grey-light);border:1px solid var(--brand-gray);border-radius:8px;padding:12px 14px;overflow-x:auto;margin:10px 0 16px;"># Включить PDF из папки и подпапок
|
||||
**/*.pdf
|
||||
|
||||
# Исключить черновики
|
||||
!**/*draft*.*</pre>
|
||||
|
||||
<h3>После загрузки</h3>
|
||||
<p><b>1. Классификация</b> — нажмите «Классифицировать»: система определит тип документа и связанные реквизиты.</p>
|
||||
<p><b>2. Группы</b> — проверьте автоматически сформированные группы документов.</p>
|
||||
<p><b>3. Сравнение</b> — нажмите «Сравнить» у нужной группы или запустите «Общее сравнение».</p>
|
||||
<h3>3. Классифицируйте и проверьте группы</h3>
|
||||
<p>Нажмите <strong>«Классифицировать»</strong>. Система попытается определить тип документа, номер, дату, контрагента и связь с договором. Затем документы будут предложены в группах и упорядочены по датам.</p>
|
||||
<p>Перед сравнением проверьте, что в каждой группе собраны документы одного договора и распознанные реквизиты выглядят верно. Документы, которые не удалось уверенно связать, отображаются отдельно.</p>
|
||||
|
||||
<h3>4. Запустите сравнение</h3>
|
||||
<p>Нажмите <strong>«Сравнить эту группу»</strong>, чтобы обработать одну группу, или <strong>«Общее сравнение»</strong>, чтобы запустить сравнение для доступных групп.</p>
|
||||
<p>Система последовательно сопоставляет спецификацию с документами группы. В результате могут появиться операции:</p>
|
||||
<ul>
|
||||
<li><strong>ADD</strong> — добавлена новая услуга или позиция.</li>
|
||||
<li><strong>UPDATE</strong> — изменились распознанные данные позиции, например цена или количество.</li>
|
||||
<li><strong>DELETE</strong> — позиция исключена.</li>
|
||||
<li><strong>UNRESOLVED</strong> — изменение не удалось однозначно сопоставить; его нужно проверить вручную.</li>
|
||||
</ul>
|
||||
<div class="help-callout"><strong>Проверяйте результат по документу.</strong> Сравнение строится автоматически, а не заменяет юридическую или финансовую проверку исходных условий.</div>
|
||||
|
||||
<h3>Если что-то пошло не так</h3>
|
||||
<ul>
|
||||
<li>При ошибке загрузки проверьте формат и ограничения размера файла; строка файла покажет статус и доступные детали.</li>
|
||||
<li>Если документ не распознан или попал не в ту группу, сначала откройте его «Текст» и проверьте, что содержимое извлеклось полностью.</li>
|
||||
<li>Если сравнение пометило изменение как нерешённое, сверьте позицию и её реквизиты в исходных документах.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user