From 6e80948c0251ba0c1ded4cab59df04992e05022d Mon Sep 17 00:00:00 2001 From: WASRUSGEN Date: Thu, 28 May 2026 18:22:55 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20Elena=20answers=20'=D0=A7=D1=82=D0=BE=20?= =?UTF-8?q?=D0=BD=D1=83=D0=B6=D0=BD=D0=BE=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=3F'=20with=20concrete=20steps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _rcLastContext: stores active deadline when Elena mentions it - _DL_ACTIONS: step-by-step actions per deadline type (payment / notification / renewal / termination / penalty / other) - _buildDlAnswer(): renders numbered steps + legal warning + CTA buttons - retChatSend(): detects action questions, answers in-chat (no redirect) - API path: real Elena response with deadline context injected - Fallback: template redirect only when no context + no API - initReturnChat(): updated urgentDL message prompts follow-up question - _rcAddBubble: supports raw HTML (isRawHtml param) - _rcEscape: safe text rendering for plain messages Co-Authored-By: Claude Sonnet 4.6 --- mockup.html | 213 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 200 insertions(+), 13 deletions(-) diff --git a/mockup.html b/mockup.html index 704fcc4..bd396c3 100644 --- a/mockup.html +++ b/mockup.html @@ -4649,18 +4649,25 @@ function _rcAddTyping() { function _rcRemoveTyping() { var el = document.getElementById('rc-typing'); if (el) el.remove(); } -function _rcAddBubble(html, isUser) { +function _rcAddBubble(content, isUser, isRawHtml) { var msgs = document.getElementById('rchat-msgs'); if (!msgs) return; var row = document.createElement('div'); row.className = 'hc-msg' + (isUser ? ' hc-user-msg' : ''); + var inner = isRawHtml ? content : _rcEscape(content); if (!isUser) { - row.innerHTML = '
' + html + '
'; + row.innerHTML = '
' + inner + '
'; } else { - row.innerHTML = '
' + html + '
'; + row.innerHTML = '
' + inner + '
'; } msgs.appendChild(row); msgs.scrollTop = msgs.scrollHeight; } +function _rcEscape(s) { + return String(s) + .replace(/&/g,'&').replace(//g,'>') + .replace(/\n/g,'
'); +} + function _rcShowControls() { var r = document.getElementById('rchat-replies'); var i = document.getElementById('rchat-input-row'); @@ -4732,11 +4739,24 @@ function initReturnChat() { context += (context ? ' ' : '') + 'Подписка ' + (sNames[subPlan]||'') + ' активна.'; } + // Сохраняем контекст — чтобы ответить на "Что нужно сделать?" + if (urgentDL) { + _rcLastContext = { + type: 'deadline', + dl: urgentDL.dl, + diff: urgentDL.diff, + caseName: urgentDL.dl.caseName || '' + }; + } + var callToAction = ''; if (urgentDL) { - callToAction = '⚠️ Срочно: ' + urgentDL.dl.title + ' — через ' + urgentDL.diff - + (urgentDL.diff === 1 ? ' день' : urgentDL.diff < 5 ? ' дня' : ' дней') - + '. Нужно действовать.'; + var diffLabel = urgentDL.diff < 0 + ? 'просрочен на ' + Math.abs(urgentDL.diff) + ' дн.' + : urgentDL.diff === 0 ? 'сегодня' + : 'через ' + urgentDL.diff + (urgentDL.diff === 1 ? ' день' : urgentDL.diff < 5 ? ' дня' : ' дней'); + callToAction = '⚠️ Срочно: ' + urgentDL.dl.title + ' — ' + diffLabel + + '. Напишите «что нужно сделать» — объясню конкретные шаги.'; } else if (pendingRefund) { callToAction = 'Ваша заявка на возврат в обработке — ответим в течение 10 рабочих дней.'; } else if (!credits && !subPlan) { @@ -4783,6 +4803,115 @@ function initReturnChat() { } } +// Контекст последнего сообщения Елены в returning chat +// { type: 'deadline', dl: {title, type, date, quote, diff}, caseName } +var _rcLastContext = null; + +// Конкретные шаги по типу срока +var _DL_ACTIONS = { + payment: { + steps: [ + 'Проверьте реквизиты для оплаты в тексте договора', + 'Проведите платёж — сохраните квитанцию или выписку', + 'Направьте подтверждение оплаты контрагенту (скан/скриншот)', + 'Если уже просрочено — платите немедленно, день просрочки = начисление пени' + ], + warn: 'Просрочка оплаты — основание для начисления пени и потенциального расторжения договора.' + }, + notification: { + steps: [ + 'Составьте письменное уведомление (могу помочь — нажмите «Составить»)', + 'Отправьте заказным письмом с уведомлением о вручении — это фиксирует дату', + 'Продублируйте на email контрагента с запросом подтверждения получения', + 'Сохраните чек об отправке и трек-номер' + ], + warn: 'Устное уведомление не имеет юридической силы — только письменное с подтверждением доставки.' + }, + renewal: { + steps: [ + 'Решите: хотите продлить договор или нет?', + 'Если НЕ продлеваете — направьте уведомление об отказе от продления до истечения срока', + 'Если продлеваете — никаких действий не нужно, договор продлится автоматически', + 'Проверьте условия пролонгации: на тех же условиях или изменятся?' + ], + warn: 'Пропустить срок уведомления = автоматическое продление на тех же условиях ещё на весь период.' + }, + termination: { + steps: [ + 'Проверьте основание расторжения — оно должно быть в договоре или ГК РФ (ст. 450)', + 'Составьте уведомление о расторжении с указанием основания и даты (могу помочь)', + 'Направьте заказным письмом с уведомлением о вручении', + 'Зафиксируйте передачу имущества / возврат предоплаты актом' + ], + warn: 'Расторжение без соблюдения порядка = риск иска о возмещении убытков.' + }, + penalty: { + steps: [ + 'Зафиксируйте факт нарушения — скриншоты, переписка, акты', + 'Рассчитайте размер пени по формуле из договора', + 'Направьте претензию с требованием оплатить пени и основной долг', + 'Установите срок ответа — 10–30 дней, затем иск' + ], + warn: 'Без письменной претензии суд может снизить неустойку по ст. 333 ГК РФ.' + }, + other: { + steps: [ + 'Изучите конкретный пункт договора, к которому относится срок', + 'Определите что именно нужно сделать: подписать, оплатить, уведомить или передать', + 'Зафиксируйте исполнение письменно — акт, квитанция, письмо', + 'Сохраните все подтверждения' + ], + warn: '' + } +}; + +function _getDlActionType(dl) { + if (!dl) return 'other'; + var t = (dl.type || '').toLowerCase(); + var title = (dl.title || '').toLowerCase(); + if (/оплат|платёж|payment/.test(t + title)) return 'payment'; + if (/уведомл|notification/.test(t + title)) return 'notification'; + if (/пролонг|продлен|renewal/.test(t + title)) return 'renewal'; + if (/расторж|termination/.test(t + title)) return 'termination'; + if (/пен|штраф|penalty/.test(t + title)) return 'penalty'; + return 'other'; +} + +function _buildDlAnswer(ctx) { + var dl = ctx.dl; + var actionType = _getDlActionType(dl); + var actions = _DL_ACTIONS[actionType] || _DL_ACTIONS.other; + + var diffText = ''; + if (dl.diff < 0) diffText = '⚠️ Просрочен на ' + Math.abs(dl.diff) + ' дн.'; + else if (dl.diff === 0) diffText = '🔴 Срок истекает сегодня'; + else diffText = '🕐 Осталось ' + dl.diff + ' дн.'; + + var stepsHtml = actions.steps.map(function(s, i){ + return '
' + + '' + (i+1) + '' + + '' + s + '
'; + }).join(''); + + var warnHtml = actions.warn + ? '
' + actions.warn + '
' + : ''; + + var ctaHtml = + '
' + + (actionType === 'notification' || actionType === 'termination' + ? '' + : '') + + '' + + '
'; + + return '
Елена
' + + '' + dl.title + ' — ' + diffText + + (dl.quote ? '
' + dl.quote + '
' : '

') + + 'Что нужно сделать:
' + stepsHtml + '
' + + warnHtml + ctaHtml; +} + function retChatSend() { var inp = document.getElementById('rchat-inp'); if (!inp) return; @@ -4792,18 +4921,75 @@ function retChatSend() { var i = document.getElementById('rchat-input-row'); if (r) r.style.display = 'none'; if (i) i.style.display = 'none'; _rcAddBubble(txt, true); + var t = txt.toLowerCase(); + + // ── Вопрос о действии по активному контексту срока ── + var isActionQ = /что (нужно|делать|сделать)|как (быть|поступить|действовать)|помоги|что значит|объясни|расскажи подробн|дальше|следующий шаг/.test(t); + if (isActionQ && _rcLastContext && _rcLastContext.type === 'deadline') { + setTimeout(function(){ + _rcAddTyping(); + setTimeout(function(){ + _rcRemoveTyping(); + var html = _buildDlAnswer(_rcLastContext); + _rcAddBubble(html, false, true); // true = raw html + _rcShowControls(); + }, 800); + }, 300); + return; + } + + // ── Если API доступен — спрашиваем Елену реально ── + var isLegalQ = !/баланс|кредит|оплат|мои дела|кабинет/.test(t); + if (isLegalQ && _apiAvailable) { + setTimeout(function(){ + _rcAddTyping(); + var ctx = _rcLastContext && _rcLastContext.type === 'deadline' + ? '\n[Контекст: клиент спрашивает о сроке «' + _rcLastContext.dl.title + '» по договору ' + (_rcLastContext.caseName || '') + ']' + : ''; + fetch(API_BASE + '/api/elena', { + method: 'POST', + headers: {'Content-Type':'application/json'}, + body: JSON.stringify({ + text: txt + ctx, + intent: 'question', + history: _chatHistory.slice(-6), + deadlines: _contractDeadlines + }) + }) + .then(function(res){ return res.json(); }) + .then(function(data){ + _rcRemoveTyping(); + var reply = data.reply || '...'; + _chatHistory.push({role:'user', content: txt}); + _chatHistory.push({role:'assistant', content: reply}); + _rcAddBubble(reply, false); + _rcShowControls(); + }) + .catch(function(){ + _rcRemoveTyping(); + _rcFallbackReply(t); + }); + }, 300); + return; + } + + // ── Fallback — шаблонные редиректы ── + _rcFallbackReply(t); +} + +function _rcFallbackReply(t) { var intent = 'question'; - if (/срок|уведомл|дедлайн/.test(t)) intent = 'deadlines'; - else if (/баланс|кредит|оплат/.test(t)) intent = 'pay'; + if (/срок|уведомл|дедлайн/.test(t)) intent = 'deadlines'; + else if (/баланс|кредит|оплат/.test(t)) intent = 'pay'; else if (/новый|загруз|проверит|договор/.test(t)) intent = 'new'; - else if (/дела|кабинет/.test(t)) intent = 'cabinet'; + else if (/дела|кабинет/.test(t)) intent = 'cabinet'; var replies = { deadlines: { elena: 'Открываю ваши сроки — всё самое срочное там 📋', action: function(){ go('cabinet'); tab('sroki'); } }, - pay: { elena: 'Показываю баланс и варианты пополнения 💳', action: function(){ go('pay'); } }, - new: { elena: 'Отличное решение! Загружайте новый документ — разберём 🔍', action: function(){ go('elena'); } }, + pay: { elena: 'Показываю варианты пополнения 💳', action: function(){ go('pay'); } }, + new: { elena: 'Загружайте новый документ — разберём 🔍', action: function(){ go('elena'); } }, cabinet: { elena: 'Открываю ваши дела 📂', action: function(){ go('cabinet'); } }, - question: { elena: 'Хорошо! Перехожу к Елене — она ответит на любой юридический вопрос 💬', action: function(){ go('elena'); } } + question: { elena: 'Перехожу к полному чату — там отвечу подробнее 💬', action: function(){ go('elena'); } } }; var resp = replies[intent] || replies.question; setTimeout(function(){ @@ -4811,7 +4997,8 @@ function retChatSend() { setTimeout(function(){ _rcRemoveTyping(); _rcAddBubble(resp.elena, false); - setTimeout(resp.action, 900); + if (resp.action) setTimeout(resp.action, 900); + _rcShowControls(); }, 700); }, 300); }