mirror of
https://github.com/wasrusgen/zashita-brandbook.git
synced 2026-06-03 15:04:49 +00:00
feat: Elena intake v2 - intent chips + voice input
- New greeting: lists all services, open-ended question - 4 intent chips: Проверить / Составить / Оспорить / Доверенность - Voice input via Web Speech API (ru-RU), mic pulse animation - Keyword routing for free text (check vs create vs power) - Creation flow: type selector -> Договор/Доверенность/Претензия/Другое - Hidden mic if browser unsupported, permission error handling
This commit is contained in:
parent
4647b5f7d5
commit
5a3bc12945
175
mockup.html
175
mockup.html
@ -391,6 +391,35 @@ body{font-family:var(--font-ui);background:var(--surf);color:var(--ink);line-hei
|
||||
.msp-date-hdr{height:28px;position:relative;border-bottom:1px solid #c8c8d8;background:#e8e8f0}
|
||||
.msp-date-tick{position:absolute;top:0;bottom:0;display:flex;align-items:center;font-size:10px;font-weight:700;color:#555;padding-left:4px;border-left:1px solid #c8c8d8;white-space:nowrap}
|
||||
|
||||
/* ── ELENA INTAKE v2 ── */
|
||||
.intake-v2{margin:0 0 80px}
|
||||
.intent-chips{display:flex;gap:10px;margin:4px 0 4px 51px;flex-wrap:wrap}
|
||||
.intent-chip{background:var(--card);border:1.5px solid var(--line);border-radius:13px;padding:12px 16px;cursor:pointer;font-size:13px;font-weight:700;flex:1;min-width:160px;transition:border-color .15s,transform .1s}
|
||||
.intent-chip:hover{border-color:var(--bg);transform:translateY(-1px)}
|
||||
.intent-chip:active{transform:translateY(0)}
|
||||
.intent-chip .ic-ico{font-size:20px;margin-bottom:4px}
|
||||
.intent-chip .ic-lbl{font-weight:700;color:var(--ink)}
|
||||
.intent-chip .ic-sub{font-size:11px;color:var(--mut);font-weight:400;margin-top:2px}
|
||||
.voice-input-row{display:flex;gap:8px;margin:12px 0 12px 51px;align-items:center}
|
||||
.voice-input-row input{flex:1;border:1.5px solid var(--line);border-radius:11px;padding:11px 14px;font-size:14px;font-family:inherit;outline:none;background:#fff;color:var(--ink)}
|
||||
.voice-input-row input:focus{border-color:var(--bg)}
|
||||
.voice-btn{width:44px;height:44px;border-radius:50%;border:1.5px solid var(--line);background:#fff;cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;transition:all .15s;flex-shrink:0}
|
||||
.voice-btn:hover{border-color:var(--bg);background:var(--tint)}
|
||||
.voice-btn.recording{background:#fee2e2;border-color:#ef4444;animation:voicePulse .8s ease infinite}
|
||||
@keyframes voicePulse{0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,.4)}50%{box-shadow:0 0 0 8px rgba(239,68,68,0)}}
|
||||
.voice-btn.no-support{display:none}
|
||||
.send-btn{width:44px;height:44px;border-radius:11px;border:none;background:var(--bg);color:#fff;cursor:pointer;font-size:16px;font-weight:700;flex-shrink:0}
|
||||
.send-btn:hover{background:var(--bghv)}
|
||||
.voice-hint{font-size:11px;color:var(--mut);margin:0 0 8px 51px}
|
||||
/* Шаг составления */
|
||||
.create-step{margin:8px 0 80px 0}
|
||||
.create-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:4px 0 16px 51px}
|
||||
.create-type-card{background:var(--card);border:1.5px solid var(--line);border-radius:13px;padding:14px;cursor:pointer;transition:border-color .15s}
|
||||
.create-type-card:hover{border-color:var(--bg)}
|
||||
.create-type-card .ctc-ico{font-size:24px;margin-bottom:6px}
|
||||
.create-type-card .ctc-name{font-weight:700;font-size:13px}
|
||||
.create-type-card .ctc-sub{font-size:11px;color:var(--mut);margin-top:2px}
|
||||
|
||||
/* ── RETURNING CLIENT ── */
|
||||
.ret-greet{font-size:30px;font-weight:800;line-height:1.2;margin-bottom:24px;letter-spacing:-.5px}
|
||||
.ret-sub{font-size:16px;color:rgba(255,255,255,.7);margin-bottom:22px}
|
||||
@ -1961,6 +1990,152 @@ window.addEventListener('DOMContentLoaded', checkReturning);
|
||||
});
|
||||
})();
|
||||
|
||||
/* ── ELENA INTAKE v2 ── */
|
||||
function elenaIntent(intent) {
|
||||
var custom = document.getElementById('intake-custom').value.trim();
|
||||
|
||||
// Маршрутизация по intent
|
||||
if (intent === 'check' || intent === 'dispute') {
|
||||
// → существующий флоу анализа
|
||||
var ackMap = {
|
||||
'check': 'Хорошо — загрузите договор или вставьте текст, разберём вместе.',
|
||||
'dispute': 'Понял — нужен протокол разногласий. Сначала загрузите договор, я выделю спорные пункты.',
|
||||
};
|
||||
setMode('mid');
|
||||
document.getElementById('el-ack').innerHTML = ackMap[intent] || '';
|
||||
document.getElementById('el-step1').style.display = 'none';
|
||||
document.getElementById('el-step-create') && (document.getElementById('el-step-create').style.display = 'none');
|
||||
document.getElementById('el-step-upload').style.display = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (intent === 'create') {
|
||||
document.getElementById('el-step1').style.display = 'none';
|
||||
document.getElementById('el-step-create').style.display = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (intent === 'power') {
|
||||
document.getElementById('el-step1').style.display = 'none';
|
||||
document.getElementById('el-step-create').style.display = '';
|
||||
// Прокрутим к карточке доверенности
|
||||
setTimeout(function(){ var el = document.querySelector('.create-type-card'); if(el) el.scrollIntoView({behavior:'smooth'}); }, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
if (intent === 'custom') {
|
||||
if (!custom) { document.getElementById('intake-custom').focus(); return; }
|
||||
// Простая маршрутизация по ключевым словам
|
||||
var low = custom.toLowerCase();
|
||||
var isCheck = /провер|анализ|риск|посмотр/.test(low);
|
||||
var isCreate = /состав|написа|подготов|создай|сделай/.test(low);
|
||||
var isPower = /доверенност/.test(low);
|
||||
if (isPower || isCreate) {
|
||||
statTrack('custom', custom);
|
||||
document.getElementById('el-step1').style.display = 'none';
|
||||
document.getElementById('el-step-create').style.display = '';
|
||||
} else {
|
||||
// По умолчанию — анализ
|
||||
statTrack('custom', custom);
|
||||
setMode('mid');
|
||||
document.getElementById('el-ack').innerHTML = 'Понял: «' + custom + '». Разберёмся — загрузите договор.';
|
||||
document.getElementById('el-step1').style.display = 'none';
|
||||
document.getElementById('el-step-create') && (document.getElementById('el-step-create').style.display = 'none');
|
||||
document.getElementById('el-step-upload').style.display = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function elenaCreate(type) {
|
||||
var msgs = {
|
||||
contract: 'Договор — отличный выбор. Уточните: какой тип и между кем? Например: «договор оказания услуг между ИП и физлицом». Чем точнее — тем лучше результат.',
|
||||
power: 'Доверенность готовлю быстро. Укажите: кто доверяет, кому, и что именно разрешает делать (продать авто, представлять в суде, получить документы)?',
|
||||
claim: 'Претензию составлю под вашу ситуацию. Опишите: к кому претензия, что нарушили и какой результат хотите (возврат денег, замена товара, неустойка)?',
|
||||
other: 'Расскажите что нужно составить — постараюсь помочь или подберу ближайший шаблон.',
|
||||
};
|
||||
var msg = msgs[type] || msgs.other;
|
||||
document.getElementById('el-step-create').style.display = 'none';
|
||||
// Показываем ответ Елены и поле ввода
|
||||
var wrap = document.querySelector('.chatwrap');
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = '<div class="msg"><div class="av"><img src="logos/elena-photo.jpg"></div><div class="bubble"><div class="nm">Елена</div>' + msg + '</div></div>' +
|
||||
'<div class="voice-input-row" style="margin-left:0">' +
|
||||
'<input id="create-input" placeholder="Опишите детали…" style="flex:1;border:1.5px solid var(--line);border-radius:11px;padding:11px 14px;font-size:14px;font-family:inherit;outline:none">' +
|
||||
'<button class="voice-btn" onclick="toggleVoice(\'create-input\')" title="Голосовой ввод">🎤</button>' +
|
||||
'<button class="send-btn" onclick="toast(\'✍️ Принято — начинаю составлять документ по вашему запросу\')">→</button>' +
|
||||
'</div>';
|
||||
wrap.appendChild(div);
|
||||
div.scrollIntoView({behavior:'smooth'});
|
||||
}
|
||||
|
||||
/* ── ГОЛОСОВОЙ ВВОД ── */
|
||||
var _voiceActive = false;
|
||||
var _voiceRecog = null;
|
||||
|
||||
function toggleVoice(targetId) {
|
||||
var inputId = targetId || 'intake-custom';
|
||||
var btn = document.getElementById('voice-btn');
|
||||
|
||||
var SR = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
if (!SR) {
|
||||
var hint = document.getElementById('voice-hint');
|
||||
if (hint) hint.textContent = '⚠ Голосовой ввод не поддерживается в этом браузере';
|
||||
return;
|
||||
}
|
||||
|
||||
if (_voiceActive) {
|
||||
if (_voiceRecog) _voiceRecog.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
_voiceRecog = new SR();
|
||||
_voiceRecog.lang = 'ru-RU';
|
||||
_voiceRecog.interimResults = true;
|
||||
_voiceRecog.continuous = false;
|
||||
|
||||
_voiceRecog.onstart = function() {
|
||||
_voiceActive = true;
|
||||
if (btn) { btn.classList.add('recording'); btn.textContent = '🔴'; }
|
||||
var hint = document.getElementById('voice-hint');
|
||||
if (hint) hint.textContent = '🎙 Говорите…';
|
||||
};
|
||||
|
||||
_voiceRecog.onresult = function(e) {
|
||||
var transcript = '';
|
||||
for (var i = e.resultIndex; i < e.results.length; i++) {
|
||||
transcript += e.results[i][0].transcript;
|
||||
}
|
||||
var inp = document.getElementById(inputId);
|
||||
if (inp) inp.value = transcript;
|
||||
};
|
||||
|
||||
_voiceRecog.onend = function() {
|
||||
_voiceActive = false;
|
||||
if (btn) { btn.classList.remove('recording'); btn.textContent = '🎤'; }
|
||||
var hint = document.getElementById('voice-hint');
|
||||
if (hint) hint.textContent = '';
|
||||
};
|
||||
|
||||
_voiceRecog.onerror = function(e) {
|
||||
_voiceActive = false;
|
||||
if (btn) { btn.classList.remove('recording'); btn.textContent = '🎤'; }
|
||||
var hint = document.getElementById('voice-hint');
|
||||
if (hint) hint.textContent = e.error === 'not-allowed' ? '⚠ Разрешите доступ к микрофону' : '⚠ Ошибка записи: ' + e.error;
|
||||
};
|
||||
|
||||
_voiceRecog.start();
|
||||
}
|
||||
|
||||
// Скрыть mic если нет поддержки
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
var SR = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
if (!SR) {
|
||||
var btn = document.getElementById('voice-btn');
|
||||
if (btn) btn.classList.add('no-support');
|
||||
}
|
||||
});
|
||||
|
||||
/* ── СТАТУС ЗАКАЗА ── */
|
||||
const OS_DEADLINES = {
|
||||
protocol: { 1:'до 12 часов', 2:'до 24 часов', 3:'до 48 часов', sub:'после получения файла договора' },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user