mirror of
https://github.com/wasrusgen/zashita-brandbook.git
synced 2026-06-03 18:24:48 +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-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}
|
.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 ── */
|
/* ── RETURNING CLIENT ── */
|
||||||
.ret-greet{font-size:30px;font-weight:800;line-height:1.2;margin-bottom:24px;letter-spacing:-.5px}
|
.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}
|
.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 = {
|
const OS_DEADLINES = {
|
||||||
protocol: { 1:'до 12 часов', 2:'до 24 часов', 3:'до 48 часов', sub:'после получения файла договора' },
|
protocol: { 1:'до 12 часов', 2:'до 24 часов', 3:'до 48 часов', sub:'после получения файла договора' },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user