Files
contracts-flask/site/templates/index.html
T
RepinoidandCopilot 5966c76a0d
Deploy contracts-flask / validate (push) Canceled after 0s
Improve upload screen readability and help
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-30 12:53:07 +03:00

327 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Сверка договоров — LLM</title>
<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: #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: 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: 12px; white-space: nowrap; }
.pipeline-stepper > span:nth-child(even) { color: rgba(255,255,255,.4); }
.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: 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: #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: 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; }
.status-ok { color: var(--green); }
.status-err { color: var(--destructive); }
.summary-row td { background: rgba(34,197,94,.05); font-weight: 600; }
.empty-row td { color: var(--muted); text-align: center; padding: 24px; }
.remove-btn { cursor: pointer; color: #f87171; background: none; border: none; padding: 2px 4px; font-size: 16px; line-height: 1; }
.remove-btn:hover { color: #ef4444; }
.info-btn { cursor: pointer; color: var(--muted); background: none; border: none; padding: 2px 4px; font-size: 14px; display: none; }
.info-btn:hover { color: var(--brand-primary); }
.info-btn.visible { display: inline; }
.first-row td { background: rgba(37,99,235,.06); font-weight: 600; }
.arrow-btn { cursor: pointer; color: var(--muted); background: none; border: none; padding: 2px; font-size: 12px; line-height: 1; }
.arrow-btn:hover { color: var(--brand-primary); }
.arrow-btn:disabled { opacity: .3; cursor: default; color: var(--muted); }
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,.4); z-index: 100; justify-content: center; align-items: center; }
.modal-overlay.open { display: flex; }
.modal { background: var(--background); border-radius: 12px; border: 1px solid var(--brand-gray); box-shadow: 0 4px 24px rgba(0,0,0,.15); max-width: 520px; width: 90%; max-height: 80vh; display: flex; flex-direction: column; }
.modal.wide { max-width: 900px; }
.modal-header { padding: 14px 16px; border-bottom: 1px solid var(--brand-gray); display: flex; align-items: center; gap: 8px; font-weight: 600; flex-shrink: 0; }
.modal-body { padding: 16px; overflow-y: auto; }
.modal-body .kv { display: flex; margin-bottom: 6px; font-size: 13px; }
.modal-body .kv .k { color: var(--muted); width: 110px; flex-shrink: 0; }
.modal-body .kv .v { word-break: break-all; }
.diff-added { background: rgba(34,197,94,.1); }
.diff-deleted { background: rgba(239,68,68,.1); }
.diff-changed { background: rgba(234,179,8,.1); }
.diff-added td { color: var(--green); }
.diff-deleted td { color: var(--destructive); text-decoration: line-through; }
.diff-field-old { color: var(--destructive); text-decoration: line-through; font-size: 11px; }
.diff-field-new { color: var(--green); font-weight: 600; }
@keyframes spin { to { transform: rotate(360deg); } }
.spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid rgba(255,255,255,.3); border-top-color: #fff; border-radius: 50%; animation: spin .6s linear infinite; }
.text-panes { display: flex; gap: 0; }
.text-pane { flex: 1; min-width: 0; overflow-y: auto; max-height: 65vh; }
.text-pane:first-child { border-right: 1px solid var(--brand-gray); padding-right: 12px; }
.text-pane:last-child { padding-left: 12px; }
.text-pane pre { background: var(--brand-grey-light); padding: 10px; border-radius: 6px; font-size: 11px; white-space: pre-wrap; word-break: break-word; overflow-x: auto; margin: 0; max-height: 60vh; overflow-y: auto; }
.text-pane table { font-size: 11px; width: 100%; }
.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 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: 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: 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: #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: 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>
<body>
<div class="topbar">
<img src="/static/logo.svg" alt="Nubes">
<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>
<button class="help" type="button" onclick="openAbout()" aria-controls="aboutModalOverlay">Справка</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>
Загрузка договоров/приложений/спецификаций
</div>
<div class="card-body">
<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 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>
<button class="btn" id="promptBtnDiff" style="font-size:11px;height:28px;flex:1;">🔄 Сравнение</button>
<button class="btn" id="promptBtnClassify" style="font-size:11px;height:28px;flex:1;">🏷️ Классификация</button>
</div>
<div style="display:flex;gap:8px;margin-top:3px;font-size:10px;color:var(--muted);">
<span style="flex:1;">для базового договора</span>
<span style="flex:1;">для допсоглашений</span>
<span style="flex:1;">автоопределение</span>
</div>
</details>
</div>
</div>
<div class="card" id="diffCard" style="display:none;">
<div class="card-header">
<i data-lucide="file-diff" style="width:18px;height:18px;"></i>
Классификация и группы <span id="diffStatus" style="font-weight:400;font-size:12px;margin-left:8px;"></span>
</div>
<div class="card-body" id="diffBody"></div>
</div>
<div class="card" id="compareCard" style="display:none;margin-top:16px;">
<div class="card-header">
<i data-lucide="scale" style="width:18px;height:18px;"></i>
Результаты сравнения <span id="compareStatus" style="font-weight:400;font-size:12px;margin-left:8px;"></span>
</div>
<div class="card-body" id="compareBody"></div>
</div>
<div class="card" id="chatCard" style="display:none;margin-top:16px;">
<div class="card-header">
<i data-lucide="message-circle" style="width:18px;height:18px;"></i>
Задать вопрос по договору <span style="font-weight:400;font-size:11px;color:var(--muted);">— используются все загруженные документы</span>
</div>
<div class="card-body">
<div id="chatMessages" style="margin-bottom:8px;font-size:13px;"></div>
<div style="display:flex;gap:8px;">
<input type="text" id="chatInput" placeholder="Какая общая сумма? Что изменилось? ..." style="flex:1;height:32px;border:1px solid var(--brand-gray);border-radius:6px;padding:0 8px;font-size:13px;">
<button class="btn btn-primary" id="chatSend" style="height:32px;font-size:13px;">→</button>
</div>
</div>
</div>
</div>
<div class="modal-overlay" id="modalOverlay" onclick="closeModal(event)">
<div class="modal" onclick="event.stopPropagation()">
<div class="modal-header">
<i data-lucide="file-text" style="width:18px;height:18px;"></i>
<span id="modalTitle">Информация о файле</span>
<button class="remove-btn" onclick="closeModal()" style="margin-left:auto;font-size:18px;">&#xd7;</button>
</div>
<div class="modal-body" id="modalBody"></div>
</div>
</div>
<div class="modal-overlay" id="promptModalOverlay" onclick="closePromptModal(event)">
<div class="modal" style="max-width:680px;" onclick="event.stopPropagation()">
<div class="modal-header">
<i data-lucide="settings" style="width:18px;height:18px;"></i>
<span id="promptModalTitle">Промпт: Сравнение</span>
<button class="remove-btn" onclick="closePromptModal()" style="margin-left:auto;font-size:18px;">&#xd7;</button>
</div>
<div class="modal-body" style="padding:16px;">
<div style="display:flex;gap:6px;margin-bottom:4px;">
<button class="btn" id="promptTabExtract" style="font-size:11px;height:26px;">📄 Извлечение</button>
<button class="btn" id="promptTabDiff" style="font-size:11px;height:26px;background:var(--brand-primary);color:#fff;">🔄 Сравнение</button>
<button class="btn" id="promptTabClassify" style="font-size:11px;height:26px;">🏷️ Классификация</button>
</div>
<div style="font-size:10px;color:var(--muted);margin-bottom:10px;">
Извлечение — для базового договора &nbsp;|&nbsp; Сравнение — для допсоглашений &nbsp;|&nbsp; Классификация — автоопределение типа/даты/контрагента
</div>
<textarea id="promptEditor" style="width:100%;height:220px;font-family:monospace;font-size:11px;border:1px solid var(--brand-gray);border-radius:6px;padding:8px;box-sizing:border-box;"></textarea>
<div style="display:flex;gap:8px;margin-top:8px;align-items:center;">
<input id="promptName" placeholder="Имя версии" style="width:140px;font-size:11px;border:1px solid var(--brand-gray);border-radius:4px;padding:4px 6px;">
<input id="promptNotes" placeholder="Что изменили" style="flex:1;font-size:11px;border:1px solid var(--brand-gray);border-radius:4px;padding:4px 6px;">
<button class="btn" id="promptSave" style="font-size:11px;height:28px;background:var(--green);color:#fff;border-color:var(--green);">Сохранить</button>
</div>
<span id="promptStatus" style="font-size:11px;color:var(--muted);display:block;margin-top:4px;"></span>
<div style="margin-top:12px;font-size:11px;color:var(--muted);font-weight:600;">История версий</div>
<div id="promptHistory" style="max-height:180px;overflow-y:auto;margin-top:4px;"></div>
</div>
</div>
</div>
<div class="modal-overlay" id="aboutModalOverlay" onclick="closeAbout(event)">
<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;">&#xd7;</button>
</div>
<div class="modal-body help-body" style="max-height:75vh;" id="aboutBody">
<h3>Что делает Контрактор</h3>
<p>Контрактор извлекает содержимое договоров и приложений, определяет тип и реквизиты документов, объединяет связанные документы в группы и показывает, как менялась спецификация по мере появления новых документов.</p>
<p>Система помогает найти изменения, но результат нужно проверить по исходным документам: неоднозначные места могут быть отмечены как нерешённые.</p>
<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>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="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>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>
<script src="/file-picker/file-picker.iife.js?v={{ config['VERSION'] }}"></script>
<script src="/static/state.js?v={{ config['VERSION'] }}"></script>
<script src="/static/app_utils.js?v={{ config['VERSION'] }}"></script>
<script src="/static/files.js?v={{ config['VERSION'] }}"></script>
<script src="/static/groups.js?v={{ config['VERSION'] }}"></script>
<script src="/static/compare.js?v={{ config['VERSION'] }}"></script>
<script src="/static/app.js?v={{ config['VERSION'] }}"></script>
</body>
</html>