v3.3.1: редактор промпта LLM — сохранить/сбросить/по умолчанию

This commit is contained in:
2026-06-18 09:32:58 +04:00
parent 958865c397
commit ce4871227c
4 changed files with 142 additions and 8 deletions
+79
View File
@@ -94,6 +94,16 @@
<button class="btn btn-primary" id="llmBtn" style="width:100%;justify-content:center;margin-top:8px;display:none;">
<i data-lucide="scale" style="width:16px;height:16px;"></i> Сравнить (LLM)
</button>
<details style="margin-top:8px;font-size:12px;">
<summary style="cursor:pointer;color:var(--muted);">⚙ Промпт LLM</summary>
<textarea id="promptEditor" style="width:100%;height:200px;font-family:monospace;font-size:11px;border:1px solid var(--brand-gray);border-radius:6px;padding:8px;margin-top:4px;" placeholder="Промпт для LLM..."></textarea>
<div style="display:flex;gap:8px;margin-top:4px;">
<button class="btn" id="promptSave" style="font-size:11px;height:28px;">Сохранить</button>
<button class="btn" id="promptReset" style="font-size:11px;height:28px;">По умолчанию</button>
<span id="promptStatus" style="font-size:11px;color:var(--muted);line-height:28px;"></span>
</div>
</details>
</div>
</div>
@@ -135,6 +145,32 @@
<script>
lucide.createIcons();
const DEFAULT_PROMPT = `Ты — анализатор договоров. Ниже текст спецификации услуг из договора облачного провайдера.
Найди ВСЕ таблицы со списком услуг. Извлеки каждую строку в JSON-массив.
Для каждой строки верни:
- row_num: номер по порядку (целое число)
- name: полное наименование услуги (весь текст из ячейки)
- price: цена за единицу в рублях (число, из колонки "Цена")
- qty: количество/объём (число, из колонки "Объем")
- sum: итоговая сумма (число, из колонки "Сумма")
- date_start: дата начала оказания (строка YYYY-MM-DD)
ПРАВИЛА:
1. Пропускай итоговые строки ("Итого...") и строки с подписями.
2. Если ячейка пустая — ставь null.
3. Если не можешь определить значение — ставь null и добавь в unresolved.
4. Верни ТОЛЬКО JSON, без пояснений.
Пример ответа:
{"rows":[{"row_num":1,"name":"Аренда стойко-места","price":213905.44,"qty":3,"sum":641716.32,"date_start":"2026-04-01"}],"unresolved":[]}
Текст документа:
---
{parsed_text}
---`;
const fileInput = document.getElementById('fileInput');
const parseBtn = document.getElementById('parseBtn');
const fileTable = document.getElementById('fileTable');
@@ -452,6 +488,14 @@
document.getElementById('llmBtn').addEventListener('click', function() {
if (!contractId) return;
// Сохранить промпт на сервер перед стартом
var p = promptEditor.value.trim() || DEFAULT_PROMPT;
fetch('/llm/prompt/' + contractId, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({prompt: p})
});
var llmBtn = document.getElementById('llmBtn');
llmBtn.disabled = true;
llmBtn.innerHTML = '<span style="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;"></span> LLM-анализ...';
@@ -586,6 +630,41 @@
if (e.key === 'Enter') document.getElementById('chatSend').click();
});
// ── Редактор промпта ──────────────────────────────────────────
var promptEditor = document.getElementById('promptEditor');
// Загрузить из localStorage
var saved = localStorage.getItem('llm_prompt');
promptEditor.value = saved || DEFAULT_PROMPT;
document.getElementById('promptSave').addEventListener('click', function() {
var p = promptEditor.value.trim();
if (!p) return;
localStorage.setItem('llm_prompt', p);
document.getElementById('promptStatus').textContent = '✓ сохранено';
setTimeout(function() { document.getElementById('promptStatus').textContent = ''; }, 1500);
// Отправить на сервер
if (contractId) {
fetch('/llm/prompt/' + contractId, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({prompt: p})
});
}
});
document.getElementById('promptReset').addEventListener('click', function() {
promptEditor.value = DEFAULT_PROMPT;
localStorage.removeItem('llm_prompt');
document.getElementById('promptStatus').textContent = '✓ сброшен';
setTimeout(function() { document.getElementById('promptStatus').textContent = ''; }, 1500);
});
// При старте LLM — сохранить промпт для этого contractId
var origLlmClick = document.getElementById('llmBtn').onclick;
// Переопределим ниже...
var style = document.createElement('style');
style.textContent = '@keyframes spin { to { transform: rotate(360deg); } }';
document.head.appendChild(style);