mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 16:04:46 +00:00
feat: voice input button — Web Speech API (ru-RU), pulse animation, live transcription
This commit is contained in:
parent
9a6623fbc0
commit
24c6757d6f
@ -38,6 +38,14 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
.inp:focus{border-color:var(--mid);box-shadow:0 0 0 3px rgba(16,185,129,.1)}
|
||||
.send{width:44px;height:44px;border-radius:11px;background:var(--primary);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
||||
.send:hover{background:var(--dark)}.send:disabled{opacity:.4;cursor:default}
|
||||
/* Microphone */
|
||||
.mic{width:44px;height:44px;border-radius:11px;background:var(--light);border:1.5px solid rgba(4,120,87,.2);cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--primary);transition:all .15s;position:relative}
|
||||
.mic:hover{background:#D1FAE5;border-color:var(--primary)}
|
||||
.mic.rec{background:#FEF2F2;border-color:#EF4444;color:#EF4444}
|
||||
.mic.rec::after{content:'';position:absolute;inset:-4px;border-radius:14px;border:2px solid #EF4444;animation:micPulse 1.3s ease-out infinite;pointer-events:none}
|
||||
@keyframes micPulse{0%{transform:scale(1);opacity:.7}100%{transform:scale(1.25);opacity:0}}
|
||||
.mic-hint{position:absolute;bottom:64px;left:50%;transform:translateX(-50%);background:var(--ink);color:#fff;font-size:12px;padding:6px 12px;border-radius:8px;white-space:nowrap;display:none;z-index:20}
|
||||
.mic-hint.show{display:block}
|
||||
.build-btn{margin:0 20px 14px;padding:13px;border-radius:12px;background:linear-gradient(135deg,var(--dark),var(--primary));color:#fff;border:none;cursor:pointer;font-family:'Inter';font-weight:700;font-size:14px;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:8px}
|
||||
.build-btn:hover{opacity:.92}.build-btn:disabled{opacity:.5;cursor:default}
|
||||
.model-col{width:42%;max-width:560px;overflow-y:auto;padding:24px;background:#fafbfc;display:none}
|
||||
@ -92,7 +100,11 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
|
||||
Построить мою бизнес-модель →
|
||||
</button>
|
||||
<div class="inbar">
|
||||
<textarea class="inp" id="inp" rows="1" placeholder="Напишите Елене..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMsg()}"></textarea>
|
||||
<button class="mic" id="micBtn" onclick="toggleMic()" title="Голосовой ввод">
|
||||
<div class="mic-hint" id="micHint">Слушаю... говорите</div>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>
|
||||
</button>
|
||||
<textarea class="inp" id="inp" rows="1" placeholder="Напишите или скажите Елене..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMsg()}"></textarea>
|
||||
<button class="send" id="sendBtn" onclick="sendMsg()">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
|
||||
</button>
|
||||
@ -112,6 +124,47 @@ const inp = document.getElementById("inp");
|
||||
function esc(s){return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}
|
||||
function fmt(s){return esc(s).replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>")}
|
||||
|
||||
// ── Голосовой ввод (Web Speech API, русский) ──────────
|
||||
let recog=null, recording=false;
|
||||
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
if(SR){
|
||||
recog = new SR();
|
||||
recog.lang = "ru-RU";
|
||||
recog.continuous = true;
|
||||
recog.interimResults = true;
|
||||
let baseText = "";
|
||||
recog.onresult = e=>{
|
||||
let fin="", interim="";
|
||||
for(let i=e.resultIndex;i<e.results.length;i++){
|
||||
const t=e.results[i][0].transcript;
|
||||
if(e.results[i].isFinal) fin+=t; else interim+=t;
|
||||
}
|
||||
if(fin) baseText += fin;
|
||||
inp.value = (baseText + interim).trim();
|
||||
inp.style.height="auto"; inp.style.height=Math.min(inp.scrollHeight,120)+"px";
|
||||
};
|
||||
recog.onstart = ()=>{ baseText = inp.value ? inp.value+" " : ""; };
|
||||
recog.onend = ()=>{ if(recording){ try{recog.start()}catch(e){} } };
|
||||
recog.onerror = e=>{ if(e.error==="not-allowed"){ alert("Разрешите доступ к микрофону в браузере"); stopMic(); } };
|
||||
}
|
||||
function toggleMic(){
|
||||
if(!recog){ alert("Голосовой ввод не поддерживается этим браузером. Используйте Chrome."); return; }
|
||||
recording ? stopMic() : startMic();
|
||||
}
|
||||
function startMic(){
|
||||
recording=true;
|
||||
document.getElementById("micBtn").classList.add("rec");
|
||||
document.getElementById("micHint").classList.add("show");
|
||||
try{ recog.start() }catch(e){}
|
||||
}
|
||||
function stopMic(){
|
||||
recording=false;
|
||||
document.getElementById("micBtn").classList.remove("rec");
|
||||
document.getElementById("micHint").classList.remove("show");
|
||||
try{ recog.stop() }catch(e){}
|
||||
inp.focus();
|
||||
}
|
||||
|
||||
function addMsg(role, text){
|
||||
const m = document.createElement("div");
|
||||
m.className = "msg " + (role==="user"?"user":"elena");
|
||||
@ -144,6 +197,7 @@ async function init(){
|
||||
}
|
||||
|
||||
async function sendMsg(){
|
||||
if(recording) stopMic();
|
||||
const text = inp.value.trim();
|
||||
if(!text) return;
|
||||
inp.value=""; inp.style.height="auto";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user