diff --git a/mockup.html b/mockup.html
index 30b061c..148d2af 100644
--- a/mockup.html
+++ b/mockup.html
@@ -2746,6 +2746,8 @@ function startScan() {
});
// Сохраняем в localStorage — переживёт перезагрузку страницы
try { localStorage.setItem('zashita_deadlines', JSON.stringify(_DEADLINES)); } catch(e){}
+ // Сохраняем договор в хранилище (persistent memory)
+ _saveContract(data.meta, data.risks, data.deadlines, text);
}
})
.catch(function(e){ console.warn('API /deadlines:', e); });
@@ -4527,37 +4529,134 @@ var _apiAvailable = null; // null=не проверяли, true/false
// Текущий контекст договора (deadlines из последнего анализа)
var _contractDeadlines = null;
-var _chatHistory = []; // история чата для /api/elena
-// Собирает контекст клиента для передачи Елене
+// ── PERSISTENT MEMORY ──────────────────────────────────────────────────────────
+var _MEM_HISTORY = 'zashita_chat_v1'; // история сообщений
+var _MEM_CONTRACTS = 'zashita_contracts_v1'; // хранилище договоров
+var _MEM_DOSSIER = 'zashita_dossier_v1'; // сжатое досье компании
+
+// История чата — загружается из localStorage, переживает перезагрузку
+var _chatHistory = (function() {
+ try { var h = localStorage.getItem(_MEM_HISTORY); return h ? JSON.parse(h) : []; }
+ catch(e) { return []; }
+})();
+
+function _saveHistory() {
+ try { localStorage.setItem(_MEM_HISTORY, JSON.stringify(_chatHistory.slice(-60))); }
+ catch(e) {}
+}
+
+// Хранилище договоров
+function _saveContract(meta, risks, deadlines, textPreview) {
+ try {
+ var contracts = JSON.parse(localStorage.getItem(_MEM_CONTRACTS) || '[]');
+ var c = {
+ id: Date.now(),
+ ts: new Date().toISOString(),
+ type: (meta && meta.type) || 'Договор',
+ counterparty: (meta && (meta.counterparty || meta.party_b || meta.contractor)) || '',
+ start: (meta && meta.start) || '',
+ end: (meta && meta.end) || '',
+ deadlines_count: (deadlines || []).length,
+ risks_critical: (risks || []).filter(function(r){ return r.level === 'critical'; }).length,
+ top_risks: (risks || []).slice(0,3).map(function(r){ return r.title; }),
+ preview: (textPreview || '').slice(0, 150)
+ };
+ // Дедупликация по типу+контрагенту
+ contracts = contracts.filter(function(x){
+ return !(x.type === c.type && x.counterparty === c.counterparty);
+ });
+ contracts.unshift(c);
+ localStorage.setItem(_MEM_CONTRACTS, JSON.stringify(contracts.slice(0, 15)));
+ } catch(e) {}
+}
+
+function _getContracts() {
+ try { return JSON.parse(localStorage.getItem(_MEM_CONTRACTS) || '[]'); }
+ catch(e) { return []; }
+}
+
+// Досье — структурированные факты о клиенте
+function _getDossier() {
+ try { return JSON.parse(localStorage.getItem(_MEM_DOSSIER) || 'null'); }
+ catch(e) { return null; }
+}
+
+function _updateDossier(patch) {
+ // patch: {facts:[], decisions:[], open:[]}
+ try {
+ var d = _getDossier() || { facts:[], decisions:[], open:[], updated:'' };
+ if (patch.facts) d.facts = (d.facts.concat(patch.facts)).slice(-20);
+ if (patch.decisions) d.decisions = (d.decisions.concat(patch.decisions)).slice(-10);
+ if (patch.open) d.open = patch.open; // заменяем открытые вопросы
+ d.updated = new Date().toISOString();
+ localStorage.setItem(_MEM_DOSSIER, JSON.stringify(d));
+ } catch(e) {}
+}
+
+// Извлекаем факты из ответа Елены (без LLM — простые эвристики)
+function _extractFacts(userMsg, elenaReply) {
+ var facts = [], decisions = [], open = [];
+ var u = (userMsg||'').toLowerCase(), e = (elenaReply||'').toLowerCase();
+ if (/уже отправил|отправили|подписал|оплатил|сделал/.test(u))
+ decisions.push(userMsg.slice(0,80));
+ if (/не успею|не знаю|непонятно|что делать/.test(u))
+ open.push(userMsg.slice(0,80));
+ return { facts: facts, decisions: decisions, open: open };
+}
+// ───────────────────────────────────────────────────────────────────────────────
+
+// Собирает контекст клиента для передачи Елене (включая persistent memory)
function _buildElenaContext() {
var clientName = '';
- var caseContext = '';
try {
- // Имя из localStorage (B2B или физлицо)
var b2b = JSON.parse(localStorage.getItem('zashita_b2b') || 'null');
if (b2b && b2b.name) clientName = b2b.name;
- // Имя из stats (если физлицо)
if (!clientName) {
var stats = JSON.parse(localStorage.getItem('zashita_intake_stats') || '[]');
if (stats.length && stats[0].name) clientName = stats[0].name;
}
} catch(e) {}
- // Контекст дела — ТОЛЬКО если пользователь реально загрузил договор в этой сессии
- // НЕ берём из demo-дедлайнов/_rcLastContext чтобы не путать Елену чужим контекстом
- var contractText = (document.getElementById('el-paste') || {}).value || '';
- if (contractText && contractText.length > 50) {
- // Есть реальный текст договора — используем первые 300 символов как контекст
- caseContext = contractText.slice(0, 300);
- } else if (_rcLastContext && _rcLastContext.caseName && _rcLastContext._userLoaded) {
- // _userLoaded = флаг что пользователь сам загрузил, не demo
- caseContext = _rcLastContext.caseName;
+ var parts = [];
+
+ // 1. Хранилище договоров — Елена знает все договоры клиента
+ var contracts = _getContracts();
+ if (contracts.length) {
+ var cList = contracts.map(function(c) {
+ var s = c.type;
+ if (c.counterparty) s += ' с ' + c.counterparty;
+ if (c.risks_critical) s += ' (' + c.risks_critical + ' крит. риска)';
+ return s;
+ }).join('; ');
+ parts.push('Договоры клиента: ' + cList);
}
+ // 2. Активные сроки
+ var activeDL = (_DEADLINES || []).filter(function(d){ return !d.done; });
+ if (activeDL.length) {
+ parts.push('Активные сроки: ' + activeDL.slice(0,4).map(function(d){
+ return '«' + d.title + '» ' + d.date;
+ }).join(', '));
+ }
+
+ // 3. Досье — факты и решения из прошлых сессий
+ var dossier = _getDossier();
+ if (dossier) {
+ if (dossier.decisions && dossier.decisions.length)
+ parts.push('Принятые решения: ' + dossier.decisions.slice(-3).join('; '));
+ if (dossier.open && dossier.open.length)
+ parts.push('Открытые вопросы: ' + dossier.open.slice(0,2).join('; '));
+ }
+
+ // 4. Текст загруженного договора (текущая сессия)
+ var contractText = (document.getElementById('el-paste') || {}).value || '';
+ if (contractText && contractText.length > 50)
+ parts.push('Текущий договор (загружен): ' + contractText.slice(0, 300));
+
return {
client_name: clientName,
- case_context: caseContext,
+ case_context: parts.join('\n'),
parties: _postalData ? {
counterparty: _postalData.counterparty,
counterEmail: _postalData.counterEmail
@@ -4638,8 +4737,12 @@ function _elenaApi(txt, intent, callback) {
var parsed = _parseElenaActions(d.reply);
_chatHistory.push({role:'user', content: txt});
_chatHistory.push({role:'assistant', content: parsed.text});
- // Передаём в callback объект {text, actions} — совместимо со старым кодом
- // (старый код ожидает строку — если передать строку, ничего не сломается)
+ // Сохраняем историю в localStorage после каждого обмена
+ _saveHistory();
+ // Обновляем досье фактами из этого обмена
+ var facts = _extractFacts(txt, parsed.text);
+ if (facts.decisions.length || facts.open.length)
+ _updateDossier(facts);
callback(parsed.text, parsed.actions);
} else {
callback(null, []);
@@ -6431,4 +6534,36 @@ function tab(name){
+