语音对话
This commit is contained in:
@@ -30,6 +30,8 @@ static const char *s_web_index =
|
||||
" button { border:0; border-radius:8px; padding:8px 12px; cursor:pointer; background:#0b66d0; color:#fff; }\n"
|
||||
" button.alt { background:#5f6f82; }\n"
|
||||
" button.warn { background:#cf2f2f; }\n"
|
||||
" button.talk { background:#198754; touch-action:none; user-select:none; }\n"
|
||||
" button.talk.active { background:#d68910; }\n"
|
||||
" pre { background:#0f1720; color:#d6e2f5; border-radius:8px; padding:10px; overflow:auto; min-height:120px; }\n"
|
||||
" .small { font-size: 12px; color:#6a778a; }\n"
|
||||
" label { font-size:13px; color:#44566c; display:block; margin: 10px 0 4px 0; }\n"
|
||||
@@ -102,6 +104,12 @@ static const char *s_web_index =
|
||||
" </div>\n"
|
||||
" <div class='small'>Submit prompt only: ESP32-S3 calls DashScope Z-Image, downloads PNG, rasterizes and prints.</div>\n"
|
||||
"\n"
|
||||
" <label>Voice Tap2Talk</label>\n"
|
||||
" <div class='row'>\n"
|
||||
" <button id='btnVoiceTalk' class='talk'>点击开始对话</button>\n"
|
||||
" </div>\n"
|
||||
" <div class='small'>点击一次开始语音输入,再点击一次结束(自动建立语音会话)。</div>\n"
|
||||
"\n"
|
||||
" <div class='small'>Tip: connect printer first, then submit print jobs.</div>\n"
|
||||
" <pre id='out'>Ready.</pre>\n"
|
||||
" </div>\n"
|
||||
@@ -121,7 +129,10 @@ static const char *s_web_index =
|
||||
" const imgScaleToWidthEl = document.getElementById('imgScaleToWidth');\n"
|
||||
" const imgInvertEl = document.getElementById('imgInvert');\n"
|
||||
" const imgPromptExtendEl = document.getElementById('imgPromptExtend');\n"
|
||||
" const btnVoiceTalkEl = document.getElementById('btnVoiceTalk');\n"
|
||||
" const thermalPromptSuffix = 'black and white line art, pure white background, high contrast, clean edges, minimal texture, no dark background, no heavy shadows, centered subject, thermal printer friendly';\n"
|
||||
" let voiceTalkHolding = false;\n"
|
||||
" let voiceTalkBusy = false;\n"
|
||||
"\n"
|
||||
" function log(obj) {\n"
|
||||
" if (typeof obj === 'string') {\n"
|
||||
@@ -147,6 +158,97 @@ static const char *s_web_index =
|
||||
" return n;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function sleep(ms) {\n"
|
||||
" return new Promise((resolve) => setTimeout(resolve, ms));\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function setVoiceTalkButtonState(active) {\n"
|
||||
" if (active) {\n"
|
||||
" btnVoiceTalkEl.classList.add('active');\n"
|
||||
" btnVoiceTalkEl.textContent = '点击结束对话';\n"
|
||||
" } else {\n"
|
||||
" btnVoiceTalkEl.classList.remove('active');\n"
|
||||
" btnVoiceTalkEl.textContent = '点击开始对话';\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function syncVoiceTalkUi(st) {\n"
|
||||
" const active = !!(st && st.voice_tap_active);\n"
|
||||
" voiceTalkHolding = active;\n"
|
||||
" setVoiceTalkButtonState(active);\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function voiceReady(st) {\n"
|
||||
" return !!(st && st.voice_session_active && st.voice_ws_connected && st.voice_started);\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" async function ensureVoiceSessionReady() {\n"
|
||||
" let st = await callApi('/v1/voice/status', 'GET');\n"
|
||||
" syncVoiceTalkUi(st);\n"
|
||||
" if (voiceReady(st)) {\n"
|
||||
" return st;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" await callApi('/v1/voice/session/start', 'POST', {});\n"
|
||||
" const deadline = Date.now() + 10000;\n"
|
||||
" while (Date.now() < deadline) {\n"
|
||||
" await sleep(250);\n"
|
||||
" st = await callApi('/v1/voice/status', 'GET');\n"
|
||||
" syncVoiceTalkUi(st);\n"
|
||||
" if (voiceReady(st)) {\n"
|
||||
" return st;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" throw new Error('voice session is not ready, please retry');\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" async function beginVoiceTalk() {\n"
|
||||
" if (voiceTalkBusy) {\n"
|
||||
" const st = await callApi('/v1/voice/status', 'GET');\n"
|
||||
" syncVoiceTalkUi(st);\n"
|
||||
" return st;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" voiceTalkBusy = true;\n"
|
||||
" try {\n"
|
||||
" let st = await ensureVoiceSessionReady();\n"
|
||||
" if (st && st.voice_tap_active) {\n"
|
||||
" syncVoiceTalkUi(st);\n"
|
||||
" return st;\n"
|
||||
" }\n"
|
||||
" await callApi('/v1/voice/tap/start', 'POST', {});\n"
|
||||
" st = await callApi('/v1/voice/status', 'GET');\n"
|
||||
" syncVoiceTalkUi(st);\n"
|
||||
" return st;\n"
|
||||
" } finally {\n"
|
||||
" voiceTalkBusy = false;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" async function endVoiceTalk() {\n"
|
||||
" if (voiceTalkBusy) {\n"
|
||||
" const st = await callApi('/v1/voice/status', 'GET');\n"
|
||||
" syncVoiceTalkUi(st);\n"
|
||||
" return st;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" voiceTalkBusy = true;\n"
|
||||
" try {\n"
|
||||
" let st = await callApi('/v1/voice/status', 'GET');\n"
|
||||
" syncVoiceTalkUi(st);\n"
|
||||
" if (!(st && st.voice_tap_active)) {\n"
|
||||
" return st;\n"
|
||||
" }\n"
|
||||
" await callApi('/v1/voice/tap/cancel', 'POST', {});\n"
|
||||
" st = await callApi('/v1/voice/status', 'GET');\n"
|
||||
" syncVoiceTalkUi(st);\n"
|
||||
" return st;\n"
|
||||
" } finally {\n"
|
||||
" voiceTalkBusy = false;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" function buildImagePromptRequest() {\n"
|
||||
" const prompt = imagePromptEl.value.trim();\n"
|
||||
" if (!prompt) {\n"
|
||||
@@ -222,6 +324,30 @@ static const char *s_web_index =
|
||||
" const req = buildImagePromptRequest();\n"
|
||||
" return callApi('/v1/print/image', 'POST', req);\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" btnVoiceTalkEl.addEventListener('click', () => run(async () => {\n"
|
||||
" const st = await callApi('/v1/voice/status', 'GET');\n"
|
||||
" syncVoiceTalkUi(st);\n"
|
||||
" if (st && st.voice_tap_active) {\n"
|
||||
" return endVoiceTalk();\n"
|
||||
" }\n"
|
||||
" return beginVoiceTalk();\n"
|
||||
" }));\n"
|
||||
" window.addEventListener('blur', () => run(async () => {\n"
|
||||
" const st = await callApi('/v1/voice/status', 'GET');\n"
|
||||
" syncVoiceTalkUi(st);\n"
|
||||
" if (st && st.voice_tap_active) {\n"
|
||||
" return endVoiceTalk();\n"
|
||||
" }\n"
|
||||
" return st;\n"
|
||||
" }));\n"
|
||||
" setVoiceTalkButtonState(false);\n"
|
||||
" (async () => {\n"
|
||||
" try {\n"
|
||||
" const st = await callApi('/v1/voice/status', 'GET');\n"
|
||||
" syncVoiceTalkUi(st);\n"
|
||||
" } catch (_) {}\n"
|
||||
" })();\n"
|
||||
" </script>\n"
|
||||
"</body>\n"
|
||||
"</html>\n";
|
||||
|
||||
Reference in New Issue
Block a user