SHiNE-server/shine-UI/js/components/speech-input-modal.js

101 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
createMicrophoneRecorder,
isSpeechToTextConfigured,
transcribeAudioBySettings,
} from '../services/speech-tools-service.js';
import { state } from '../state.js';
function formatDuration(ms) {
const totalSec = Math.max(0, Math.floor(Number(ms || 0) / 1000));
const mm = String(Math.floor(totalSec / 60)).padStart(2, '0');
const ss = String(totalSec % 60).padStart(2, '0');
return `${mm}:${ss}`;
}
function showSttMissingConfigDialog(navigate) {
const goSettings = window.confirm(
'Распознавание речи не настроено. Перейти в настройки инструментов?'
);
if (goSettings) navigate('tools-settings-view');
}
export async function openSpeechInputModal({ navigate, onTextReady }) {
if (!isSpeechToTextConfigured(state.entrySettings)) {
showSttMissingConfigDialog(navigate);
return;
}
const root = document.getElementById('modal-root');
root.innerHTML = `
<div class="modal" id="speech-input-modal">
<div class="modal-card stack">
<h3 class="modal-title">Голосовой ввод</h3>
<p class="meta-muted" id="speech-input-status">Идёт запись...</p>
<div class="voice-level-wrap"><div class="voice-level-fill" id="speech-level-fill"></div></div>
<p class="meta-muted" id="speech-input-time">00:00</p>
<p class="inline-error" id="speech-input-error"></p>
<div class="form-actions-grid">
<button class="secondary-btn" type="button" id="speech-cancel">Отмена</button>
<button class="primary-btn" type="button" id="speech-ok">OK</button>
</div>
</div>
</div>
`;
const statusEl = root.querySelector('#speech-input-status');
const timeEl = root.querySelector('#speech-input-time');
const levelEl = root.querySelector('#speech-level-fill');
const errorEl = root.querySelector('#speech-input-error');
const cancelBtn = root.querySelector('#speech-cancel');
const okBtn = root.querySelector('#speech-ok');
const recorder = createMicrophoneRecorder();
let closed = false;
let busy = false;
const close = () => {
if (closed) return;
closed = true;
root.innerHTML = '';
};
const setBusy = (flag) => {
busy = !!flag;
cancelBtn.disabled = busy;
okBtn.disabled = busy;
okBtn.textContent = busy ? 'Распознаю...' : 'OK';
};
try {
await recorder.start(({ elapsedMs, level }) => {
if (timeEl) timeEl.textContent = formatDuration(elapsedMs);
if (levelEl) levelEl.style.width = `${Math.max(2, Math.round((Number(level) || 0) * 100))}%`;
});
} catch (error) {
close();
window.alert(`Не удалось получить доступ к микрофону: ${error?.message || 'unknown'}`);
return;
}
cancelBtn.addEventListener('click', () => {
recorder.cancel();
close();
});
okBtn.addEventListener('click', async () => {
if (busy) return;
setBusy(true);
errorEl.textContent = '';
statusEl.textContent = 'Распознаю речь...';
try {
const audioBlob = await recorder.stop();
const text = await transcribeAudioBySettings(audioBlob, state.entrySettings);
if (typeof onTextReady === 'function') onTextReady(text);
close();
} catch (error) {
setBusy(false);
statusEl.textContent = 'Идёт запись...';
errorEl.textContent = `Ошибка распознавания: ${error?.message || 'unknown'}`;
}
});
}