v1.4 — день/ночь в Управление, кнопка подтверждения, комбо текст+микрофон
This commit is contained in:
+44
-21
@@ -26,18 +26,20 @@
|
||||
<div class="page">
|
||||
<div class="main">
|
||||
<div class="card">
|
||||
<div class="card-header">Управление</div>
|
||||
<div class="card-header">
|
||||
Управление
|
||||
<span style="float:right;cursor:pointer;" id="btnTheme">🌙</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div style="display:flex;gap:8px;margin-bottom:8px;">
|
||||
<span class="tag">🤖 GPT-OSS-120B</span>
|
||||
<span class="tag">🎤 Whisper-v3-Turbo</span>
|
||||
</div>
|
||||
<canvas id="meter" width="400" height="60" style="width:100%;border-radius:6px;background:var(--grey-light);margin-bottom:8px;"></canvas>
|
||||
<div style="display:flex;gap:.5rem;align-items:center;">
|
||||
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;">
|
||||
<button id="btnRecord" class="btn btn-primary">🎙 Записать</button>
|
||||
<button id="btnPlay" class="btn" disabled>▶ Прослушать</button>
|
||||
<button id="btnReset" class="btn">🗑 Сброс</button>
|
||||
<button id="btnTheme" class="btn">🌙</button>
|
||||
</div>
|
||||
<div id="status" class="status"></div>
|
||||
</div>
|
||||
@@ -47,7 +49,7 @@
|
||||
<div class="card-header">Диалог</div>
|
||||
<div class="card-body">
|
||||
<div style="display:flex;gap:.5rem;margin-bottom:10px;">
|
||||
<input id="txtInput" type="text" placeholder="Введите текст или используйте микрофон..." style="flex:1;padding:.5rem .75rem;border:1px solid var(--border);border-radius:6px;font-size:.9rem;outline:none;background:var(--card);color:var(--text);">
|
||||
<input id="txtInput" type="text" placeholder="Текст + микрофон — что набрали и что сказали — всё в одном запросе к LLM" style="flex:1;padding:.5rem .75rem;border:1px solid var(--border);border-radius:6px;font-size:.9rem;outline:none;background:var(--card);color:var(--text);">
|
||||
<button id="btnSend" class="btn" style="font-size:13px;">➤</button>
|
||||
</div>
|
||||
<div id="chat" style="min-height:200px;max-height:340px;overflow-y:auto;"></div>
|
||||
@@ -64,7 +66,7 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const VERSION = 'v1.3';
|
||||
const VERSION = 'v1.4';
|
||||
document.getElementById('ver').textContent = VERSION;
|
||||
|
||||
const btnRecord = document.getElementById('btnRecord');
|
||||
@@ -76,13 +78,15 @@ const meter = document.getElementById('meter');
|
||||
const meterCtx = meter.getContext('2d');
|
||||
const txtInput = document.getElementById('txtInput');
|
||||
const btnSend = document.getElementById('btnSend');
|
||||
const btnTheme = document.getElementById('btnTheme');
|
||||
|
||||
let mediaRecorder, chunks = [], userBlob = null;
|
||||
let isRecording = false, micAnalyser = null, micAnimId = null;
|
||||
let audioContext = null, chatHistory = [];
|
||||
let pendingText = null, pendingSendBtn = null;
|
||||
|
||||
// Светлая тема — по умолчанию
|
||||
if (localStorage.getItem('say_dark') === '1') document.body.classList.add('dark');
|
||||
// Светлая тема по умолчанию
|
||||
if (localStorage.getItem('say_dark') === '1') { document.body.classList.add('dark'); btnTheme.textContent = '☀️'; }
|
||||
|
||||
let history = JSON.parse(localStorage.getItem('say_history') || '[]');
|
||||
|
||||
@@ -123,17 +127,21 @@ function fmt(s) {
|
||||
|
||||
renderHistory();
|
||||
|
||||
// ===== Отправка текста =====
|
||||
async function sendText(text) {
|
||||
// ===== Отправка =====
|
||||
async function doSend() {
|
||||
let text = '';
|
||||
if (txtInput.value.trim()) text += txtInput.value.trim() + ' ';
|
||||
if (pendingText) text += pendingText;
|
||||
if (!text.trim()) return;
|
||||
txtInput.value = '';
|
||||
chat.innerHTML += '<div class="msg user">⌨ ' + fmt(text) + '</div>';
|
||||
if (pendingSendBtn) { pendingSendBtn.remove(); pendingSendBtn = null; pendingText = null; }
|
||||
chat.innerHTML += '<div class="msg user">🗣 ' + fmt(text.trim()) + '</div>';
|
||||
chat.scrollTop = chat.scrollHeight;
|
||||
await askLLM(text);
|
||||
await askLLM(text.trim());
|
||||
}
|
||||
|
||||
btnSend.onclick = () => sendText(txtInput.value);
|
||||
txtInput.onkeydown = e => { if (e.key === 'Enter') sendText(txtInput.value); };
|
||||
btnSend.onclick = doSend;
|
||||
txtInput.onkeydown = e => { if (e.key === 'Enter') doSend(); };
|
||||
|
||||
// ===== Запись =====
|
||||
btnRecord.onclick = async () => {
|
||||
@@ -167,7 +175,7 @@ async function startRecording() {
|
||||
if (audioContext) audioContext.close();
|
||||
userBlob = new Blob(chunks, { type: 'audio/webm' });
|
||||
btnPlay.disabled = false;
|
||||
await transcribeAndChat();
|
||||
await transcribe();
|
||||
};
|
||||
mediaRecorder.start();
|
||||
}
|
||||
@@ -200,8 +208,8 @@ btnPlay.onclick = () => {
|
||||
a.play(); setStatus('🔊 Ваша запись...');
|
||||
};
|
||||
|
||||
// ===== Транскрибация → LLM =====
|
||||
async function transcribeAndChat() {
|
||||
// ===== Транскрибация → подтверждение → LLM =====
|
||||
async function transcribe() {
|
||||
let tStart = Date.now();
|
||||
setStatus('🎤 Транскрибация... 0с');
|
||||
const tTimer = setInterval(() => setStatus('🎤 Транскрибация... ' + ((Date.now() - tStart) / 1000).toFixed(0) + 'с'), 200);
|
||||
@@ -214,9 +222,25 @@ async function transcribeAndChat() {
|
||||
const text = tData.text || '(не распознано)';
|
||||
const tElapsed = ((Date.now() - tStart) / 1000).toFixed(1);
|
||||
setStatus('✅ Транскрибация: ' + tElapsed + 'с');
|
||||
chat.innerHTML += '<div class="msg user">🗣 ' + fmt(text) + '</div>';
|
||||
|
||||
// Показываем распознанный текст + кнопку подтверждения
|
||||
const msgDiv = document.createElement('div');
|
||||
msgDiv.className = 'msg user';
|
||||
msgDiv.innerHTML = '🗣 ' + fmt(text);
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn btn-primary';
|
||||
btn.textContent = '✓ Отправить';
|
||||
btn.style.marginTop = '8px'; btn.style.fontSize = '.8rem';
|
||||
btn.onclick = () => {
|
||||
pendingText = text;
|
||||
pendingSendBtn = btn;
|
||||
btn.textContent = '✓ Отмечено';
|
||||
btn.style.background = '#16a34a'; btn.style.borderColor = '#16a34a';
|
||||
doSend();
|
||||
};
|
||||
msgDiv.appendChild(btn);
|
||||
chat.appendChild(msgDiv);
|
||||
chat.scrollTop = chat.scrollHeight;
|
||||
await askLLM(text);
|
||||
} catch (err) {
|
||||
clearInterval(tTimer);
|
||||
setStatus('⛔ Ошибка: ' + err.message);
|
||||
@@ -264,12 +288,11 @@ async function askLLM(text) {
|
||||
}
|
||||
|
||||
// Тема
|
||||
document.getElementById('btnTheme').onclick = () => {
|
||||
btnTheme.onclick = () => {
|
||||
const dark = !document.body.classList.toggle('dark');
|
||||
document.getElementById('btnTheme').textContent = dark ? '☀️' : '🌙';
|
||||
btnTheme.textContent = dark ? '☀️' : '🌙';
|
||||
localStorage.setItem('say_dark', dark ? '1' : '0');
|
||||
};
|
||||
document.getElementById('btnTheme').textContent = document.body.classList.contains('dark') ? '☀️' : '🌙';
|
||||
|
||||
document.getElementById('btnReset').onclick = () => {
|
||||
chatHistory = []; chat.innerHTML = ''; setStatus('');
|
||||
|
||||
Reference in New Issue
Block a user