Chainlit集成GLM-4-9B-Chat-1M进阶添加语音输入输出功能想象一下你正在厨房忙碌双手沾满面粉突然想查一个菜谱。或者你正在开车想快速了解最新的新闻摘要。又或者你只是想闭上眼睛让AI助手给你讲个睡前故事。在这些场景下打字显然不是最方便的选择。这就是语音交互的魅力所在。今天我们就来为已经部署好的GLM-4-9B-Chat-1M大模型和Chainlit前端加上语音输入和输出的能力。这不仅仅是“锦上添花”而是让一个强大的文本模型真正具备了“听”和“说”的能力解锁了无数新的应用场景。1. 项目目标与准备工作1.1 我们要做什么简单来说我们要把一个纯文本的对话机器人升级成一个能听会说的智能助手。具体实现两个核心功能语音输入你可以直接对着麦克风说话系统会自动将你的语音转换成文字然后发送给GLM-4-9B模型处理。语音输出模型生成的文字回复可以自动转换成语音播放出来让你用耳朵“听”回复。1.2 你需要准备什么在开始之前请确保你已经按照之前的教程成功部署了基于vLLM的GLM-4-9B-Chat-1M模型并且能够通过Chainlit前端正常进行文本对话。此外我们还需要几个新的“帮手”一个支持录音的浏览器Chrome、Edge等现代浏览器都可以。这是语音输入的基础。Python环境你的Chainlit应用运行的环境。几个新的Python库我们将通过安装几个库来实现语音功能。2. 核心技术与库的选择为Chainlit添加语音功能我们主要会用到两个方向的库2.1 语音转文字STT - Speech-to-Text我们需要一个工具把用户说的话变成文字。这里有几个选择浏览器原生APIWeb Speech API这是最简单、最直接的方法。现代浏览器都内置了语音识别功能我们只需要在Chainlit的前端JavaScript中调用这个API即可。优点是完全免费、无需额外服务、延迟低。缺点是识别准确率可能因浏览器和语言环境而异且需要用户授权麦克风权限。本教程将主要采用这种方法。第三方云API比如百度、阿里云、腾讯云的语音识别服务。这些服务通常识别准确率更高支持更多方言和复杂场景但需要申请API Key并且可能产生费用。2.2 文字转语音TTS - Text-to-Speech同样我们需要把模型返回的文字变成声音。选择也有不少本地TTS库如pyttsx3, gTTS在服务器端生成语音文件然后发送给前端播放。pyttsx3调用系统自带的语音引擎gTTS调用Google的在线服务需网络。这种方法需要后端处理可能会增加服务器负载。前端TTSWeb Speech API Synthesis和语音识别一样浏览器也内置了语音合成功能。我们可以在前端直接用JavaScript把文字读出来。优点同样是免费、简单、延迟低。缺点是音色选择较少合成效果可能比较“机械”。本教程将采用这种方法以实现最轻量的集成。高质量TTS服务像微软Azure、Google Cloud的TTS服务能提供非常自然、接近真人的声音但同样涉及费用和API调用。为了保持教程的简洁和可复现性无需注册外部服务我们将组合使用浏览器的Web Speech API来实现双向的语音功能。这意味着所有语音处理都在你的本地浏览器中完成对服务器零负担。3. 动手改造为Chainlit注入语音能力现在让我们开始实际的代码工作。核心思路是修改Chainlit的页面添加两个按钮语音输入、语音输出和相应的JavaScript逻辑。3.1 创建自定义Chainlit前端页面Chainlit允许我们自定义Web界面。我们需要创建一个HTML文件来覆盖默认的聊天界面。在你的Chainlit项目根目录下创建一个名为chainlit.md的文件。这个文件是Chainlit的配置文件也可以用来注入自定义HTML/JS/CSS。编辑chainlit.md文件在文件末尾添加以下内容来引入我们自定义的脚本和样式# 自定义前端以添加语音功能 我们将在默认UI上添加语音控制按钮。 ## 自定义CSS style /* 语音按钮的样式 */ #voice-controls { position: fixed; bottom: 100px; /* 调整这个值让按钮出现在输入框上方合适的位置 */ right: 30px; display: flex; gap: 10px; z-index: 1000; } .voice-btn { width: 50px; height: 50px; border-radius: 50%; border: none; background-color: #4f46e5; /* 主色调 */ color: white; font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); transition: all 0.2s ease; } .voice-btn:hover { background-color: #4338ca; transform: scale(1.05); } .voice-btn.listening { background-color: #dc2626; /* 录音时变为红色 */ animation: pulse 1.5s infinite; } keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(220, 38, 38, 0); } 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0); } } .voice-btn:disabled { background-color: #9ca3af; cursor: not-allowed; } /* 语音状态提示 */ #voice-status { position: fixed; bottom: 160px; right: 30px; background: rgba(0, 0, 0, 0.8); color: white; padding: 8px 12px; border-radius: 6px; font-size: 14px; display: none; z-index: 1000; } /style ## 自定义JavaScript script // 等待页面完全加载 window.addEventListener(load, function() { // 给一点延时确保Chainlit的DOM元素已经渲染完毕 setTimeout(initVoiceControls, 1000); }); function initVoiceControls() { // 1. 创建语音控制按钮容器 const voiceControls document.createElement(div); voiceControls.id voice-controls; // 2. 创建语音输入按钮 const micBtn document.createElement(button); micBtn.className voice-btn; micBtn.innerHTML ; // 麦克风图标 micBtn.title 点击开始语音输入长按说话; // 3. 创建语音输出按钮 const speakerBtn document.createElement(button); speakerBtn.className voice-btn; speakerBtn.innerHTML ; // 喇叭图标 speakerBtn.title 点击朗读最新的AI回复; // 4. 创建状态提示框 const statusDiv document.createElement(div); statusDiv.id voice-status; // 将元素添加到页面 voiceControls.appendChild(micBtn); voiceControls.appendChild(speakerBtn); document.body.appendChild(voiceControls); document.body.appendChild(statusDiv); // 检查浏览器是否支持必要的API if (!(webkitSpeechRecognition in window) !(SpeechRecognition in window)) { micBtn.disabled true; micBtn.title 您的浏览器不支持语音识别API建议使用Chrome/Edge; showStatus(浏览器不支持语音识别, error); } if (!(speechSynthesis in window)) { speakerBtn.disabled true; speakerBtn.title 您的浏览器不支持语音合成API; } // 语音识别逻辑 const SpeechRecognition window.SpeechRecognition || window.webkitSpeechRecognition; let recognition null; let isListening false; let finalTranscript ; if (SpeechRecognition) { recognition new SpeechRecognition(); recognition.continuous false; // 说完一段就结束 recognition.interimResults true; // 实时返回中间结果 recognition.lang zh-CN; // 设置识别语言为中文 recognition.onstart function() { isListening true; micBtn.classList.add(listening); showStatus(正在聆听..., listening); }; recognition.onresult function(event) { let interimTranscript ; for (let i event.resultIndex; i event.results.length; i) { if (event.results[i].isFinal) { finalTranscript event.results[i][0].transcript; } else { interimTranscript event.results[i][0].transcript; } } // 实时显示识别到的文字可以优化为显示在输入框内 showStatus(识别中: ${interimTranscript}, interim); }; recognition.onend function() { isListening false; micBtn.classList.remove(listening); if (finalTranscript) { // 找到Chainlit的输入框并填入识别结果 const inputEl document.querySelector(textarea[class*MessageInput]) || document.querySelector(input[typetext]); if (inputEl) { inputEl.value finalTranscript; inputEl.focus(); // 可选自动触发输入事件让Chainlit的UI更新 inputEl.dispatchEvent(new Event(input, { bubbles: true })); showStatus(识别完成: ${finalTranscript}, success); } finalTranscript ; // 重置 } else { showStatus(请说话..., info); } }; recognition.onerror function(event) { console.error(语音识别错误:, event.error); isListening false; micBtn.classList.remove(listening); showStatus(识别错误: ${event.error}, error); }; } // 麦克风按钮点击事件 micBtn.addEventListener(click, function() { if (!recognition) return; if (isListening) { recognition.stop(); } else { finalTranscript ; recognition.start(); } }); // 语音合成逻辑 speakerBtn.addEventListener(click, function() { // 找到最新的AI回复消息 const aiMessages document.querySelectorAll([class*Message] [class*content]); let latestAIText ; // 简单遍历寻找最后一条非用户发送的消息内容 // 注意这里的选择器需要根据Chainlit实际生成的DOM结构进行调整 for (let i aiMessages.length - 1; i 0; i--) { const msgContainer aiMessages[i].closest([class*Message]); // 更稳健的方式是检查消息元素是否包含表示AI发送的类或属性 // 这里假设非用户发送的消息容器没有特定的用户标识类 if (msgContainer !msgContainer.querySelector([class*avatar])) { // 这是一个粗略的假设 latestAIText aiMessages[i].innerText || aiMessages[i].textContent; break; } } // 或者直接取最后一条消息如果UI结构稳定 // latestAIText aiMessages[aiMessages.length - 1]?.innerText || ; if (latestAIText.trim()) { speakText(latestAIText); showStatus(正在朗读..., speaking); } else { showStatus(未找到可朗读的AI回复, warning); } }); function speakText(text) { if (!window.speechSynthesis) return; // 停止当前可能正在进行的朗读 window.speechSynthesis.cancel(); const utterance new SpeechSynthesisUtterance(text); utterance.lang zh-CN; // 设置语言 utterance.rate 1.0; // 语速 utterance.pitch 1.0; // 音调 utterance.volume 1.0; // 音量 // 尝试选择一个中文语音如果可用 const voices speechSynthesis.getVoices(); const chineseVoice voices.find(v v.lang.startsWith(zh)); if (chineseVoice) utterance.voice chineseVoice; utterance.onend function() { showStatus(朗读完毕, success); }; utterance.onerror function(event) { console.error(语音合成错误:, event); showStatus(朗读出错, error); }; window.speechSynthesis.speak(utterance); } // 语音列表加载某些浏览器需要 if (speechSynthesis.onvoiceschanged ! undefined) { speechSynthesis.onvoiceschanged function() { // voices are loaded }; } function showStatus(message, type info) { statusDiv.textContent message; statusDiv.style.display block; statusDiv.style.backgroundColor type error ? #ef4444 : type success ? #10b981 : type warning ? #f59e0b : type listening ? #dc2626 : type speaking ? #8b5cf6 : rgba(0,0,0,0.8); // 3秒后自动隐藏 clearTimeout(window.statusTimer); window.statusTimer setTimeout(() { statusDiv.style.display none; }, 3000); } } /script关键点解释CSS部分定义了按钮的样式、位置和动画效果比如录音时的呼吸灯效果。JavaScript部分initVoiceControls函数在页面加载后运行创建按钮并注入逻辑。SpeechRecognitionAPI 用于处理语音输入。我们设置了中文识别(lang: zh-CN)并处理了开始、识别中、结束和错误事件。识别到的文字会被自动填入Chainlit的输入框。注意这里通过类名选择输入框 (textarea[class*MessageInput])如果Chainlit版本更新导致类名变化可能需要调整。speechSynthesisAPI 用于处理语音输出。点击喇叭按钮会查找页面上最新的AI回复文本并进行朗读。showStatus函数用于在页面上显示临时状态提示。3.2 调整Chainlit应用配置确保你的chainlit应用能够正确加载这个自定义配置。通常只要chainlit.md文件位于应用根目录Chainlit会自动读取它。现在重启你的Chainlit应用chainlit run app.py访问你的Chainlit应用你应该能在聊天界面的右下角看到新添加的和按钮。4. 功能测试与使用技巧4.1 测试语音输入点击按钮浏览器会请求麦克风权限请点击“允许”。按钮会变成红色并带有呼吸动画状态提示显示“正在聆听...”。对着麦克风清晰地说一段话比如“今天北京的天气怎么样”你说完后识别会自动停止。识别出的文字会出现在下方的输入框中。按回车键或点击发送即可像往常一样将问题发送给GLM-4-9B模型。小技巧在安静的环境下使用识别准确率更高。说话时语速适中吐字清晰。如果识别有误可以直接在输入框里修改文字再发送。4.2 测试语音输出先通过文本或语音输入向模型发送一个问题并等待AI回复。当AI的回复显示在聊天区域后点击按钮。系统会立即开始朗读最新的那条AI回复。状态提示会显示“正在朗读...”。朗读过程中可以再次点击按钮停止。小技巧浏览器的语音合成效果可能比较机械。如果你追求更自然的声音可以考虑将后端TTS方案如pyttsx3集成进来让服务器生成音频文件后推送给前端播放。4.3 可能遇到的问题与排查按钮不出现检查浏览器控制台F12是否有JavaScript错误。可能是选择器找不到Chainlit的DOM元素需要根据实际页面结构调整querySelector的选择器。麦克风无法使用检查浏览器是否已授权当前网站使用麦克风。检查系统麦克风设置是否正常。识别语言不对代码中默认设置为中文(zh-CN)。如果你需要其他语言可以修改recognition.lang的值例如en-US。语音合成没声音检查系统音量。检查浏览器控制台是否有错误。某些浏览器在页面非激活状态比如切到其他标签页时会暂停语音合成。找不到AI回复文本speakerBtn的点击事件处理函数中寻找AI回复消息的逻辑可能因Chainlit版本不同而失效。你需要打开浏览器的“开发者工具”在“元素”面板中仔细查看AI消息的HTML结构然后调整querySelector的选择器。5. 总结与进阶思路通过以上步骤我们成功地为基于Chainlit和GLM-4-9B的对话应用添加了基础的语音交互功能。整个过程没有修改任何后端Python代码完全通过前端增强实现体现了Chainlit良好的可扩展性。回顾一下我们实现的核心利用浏览器原生能力使用Web Speech API以零成本、低延迟的方式实现了语音的输入和输出。定制Chainlit前端通过chainlit.md配置文件注入自定义的HTML、CSS和JavaScript无缝集成新功能。提升用户体验增加了视觉反馈按钮状态、提示信息和简单的错误处理。如果你想更进一步提升语音识别准确率集成专业的语音识别云服务API如Azure Speech to Text。这需要在后端添加一个API调用端点前端将录音数据发送到后端处理。提升语音合成质量集成高质量的TTS服务如Azure Text to Speech。后端调用TTS API生成MP3文件返回给前端播放。这能获得媲美真人的声音。实现流式语音交互结合模型的流式输出实现“边生成边朗读”体验更流畅。添加唤醒词实现类似“小爱同学”的唤醒功能让应用在后台持续监听特定词语。支持多模态输入结合GLM-4-9B的视觉能力未来甚至可以尝试“语音描述图片”等复杂交互。语音交互是人机交互的自然演进方向。为你的AI应用加上“耳朵”和“嘴巴”它就能从冰冷的文字工具变成一个更加生动、便捷、无处不在的智能伙伴。现在就试试对你改造后的助手说句话吧获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
chainlit集成glm-4-9b-chat-1m进阶:添加语音输入输出功能
Chainlit集成GLM-4-9B-Chat-1M进阶添加语音输入输出功能想象一下你正在厨房忙碌双手沾满面粉突然想查一个菜谱。或者你正在开车想快速了解最新的新闻摘要。又或者你只是想闭上眼睛让AI助手给你讲个睡前故事。在这些场景下打字显然不是最方便的选择。这就是语音交互的魅力所在。今天我们就来为已经部署好的GLM-4-9B-Chat-1M大模型和Chainlit前端加上语音输入和输出的能力。这不仅仅是“锦上添花”而是让一个强大的文本模型真正具备了“听”和“说”的能力解锁了无数新的应用场景。1. 项目目标与准备工作1.1 我们要做什么简单来说我们要把一个纯文本的对话机器人升级成一个能听会说的智能助手。具体实现两个核心功能语音输入你可以直接对着麦克风说话系统会自动将你的语音转换成文字然后发送给GLM-4-9B模型处理。语音输出模型生成的文字回复可以自动转换成语音播放出来让你用耳朵“听”回复。1.2 你需要准备什么在开始之前请确保你已经按照之前的教程成功部署了基于vLLM的GLM-4-9B-Chat-1M模型并且能够通过Chainlit前端正常进行文本对话。此外我们还需要几个新的“帮手”一个支持录音的浏览器Chrome、Edge等现代浏览器都可以。这是语音输入的基础。Python环境你的Chainlit应用运行的环境。几个新的Python库我们将通过安装几个库来实现语音功能。2. 核心技术与库的选择为Chainlit添加语音功能我们主要会用到两个方向的库2.1 语音转文字STT - Speech-to-Text我们需要一个工具把用户说的话变成文字。这里有几个选择浏览器原生APIWeb Speech API这是最简单、最直接的方法。现代浏览器都内置了语音识别功能我们只需要在Chainlit的前端JavaScript中调用这个API即可。优点是完全免费、无需额外服务、延迟低。缺点是识别准确率可能因浏览器和语言环境而异且需要用户授权麦克风权限。本教程将主要采用这种方法。第三方云API比如百度、阿里云、腾讯云的语音识别服务。这些服务通常识别准确率更高支持更多方言和复杂场景但需要申请API Key并且可能产生费用。2.2 文字转语音TTS - Text-to-Speech同样我们需要把模型返回的文字变成声音。选择也有不少本地TTS库如pyttsx3, gTTS在服务器端生成语音文件然后发送给前端播放。pyttsx3调用系统自带的语音引擎gTTS调用Google的在线服务需网络。这种方法需要后端处理可能会增加服务器负载。前端TTSWeb Speech API Synthesis和语音识别一样浏览器也内置了语音合成功能。我们可以在前端直接用JavaScript把文字读出来。优点同样是免费、简单、延迟低。缺点是音色选择较少合成效果可能比较“机械”。本教程将采用这种方法以实现最轻量的集成。高质量TTS服务像微软Azure、Google Cloud的TTS服务能提供非常自然、接近真人的声音但同样涉及费用和API调用。为了保持教程的简洁和可复现性无需注册外部服务我们将组合使用浏览器的Web Speech API来实现双向的语音功能。这意味着所有语音处理都在你的本地浏览器中完成对服务器零负担。3. 动手改造为Chainlit注入语音能力现在让我们开始实际的代码工作。核心思路是修改Chainlit的页面添加两个按钮语音输入、语音输出和相应的JavaScript逻辑。3.1 创建自定义Chainlit前端页面Chainlit允许我们自定义Web界面。我们需要创建一个HTML文件来覆盖默认的聊天界面。在你的Chainlit项目根目录下创建一个名为chainlit.md的文件。这个文件是Chainlit的配置文件也可以用来注入自定义HTML/JS/CSS。编辑chainlit.md文件在文件末尾添加以下内容来引入我们自定义的脚本和样式# 自定义前端以添加语音功能 我们将在默认UI上添加语音控制按钮。 ## 自定义CSS style /* 语音按钮的样式 */ #voice-controls { position: fixed; bottom: 100px; /* 调整这个值让按钮出现在输入框上方合适的位置 */ right: 30px; display: flex; gap: 10px; z-index: 1000; } .voice-btn { width: 50px; height: 50px; border-radius: 50%; border: none; background-color: #4f46e5; /* 主色调 */ color: white; font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); transition: all 0.2s ease; } .voice-btn:hover { background-color: #4338ca; transform: scale(1.05); } .voice-btn.listening { background-color: #dc2626; /* 录音时变为红色 */ animation: pulse 1.5s infinite; } keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(220, 38, 38, 0); } 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0); } } .voice-btn:disabled { background-color: #9ca3af; cursor: not-allowed; } /* 语音状态提示 */ #voice-status { position: fixed; bottom: 160px; right: 30px; background: rgba(0, 0, 0, 0.8); color: white; padding: 8px 12px; border-radius: 6px; font-size: 14px; display: none; z-index: 1000; } /style ## 自定义JavaScript script // 等待页面完全加载 window.addEventListener(load, function() { // 给一点延时确保Chainlit的DOM元素已经渲染完毕 setTimeout(initVoiceControls, 1000); }); function initVoiceControls() { // 1. 创建语音控制按钮容器 const voiceControls document.createElement(div); voiceControls.id voice-controls; // 2. 创建语音输入按钮 const micBtn document.createElement(button); micBtn.className voice-btn; micBtn.innerHTML ; // 麦克风图标 micBtn.title 点击开始语音输入长按说话; // 3. 创建语音输出按钮 const speakerBtn document.createElement(button); speakerBtn.className voice-btn; speakerBtn.innerHTML ; // 喇叭图标 speakerBtn.title 点击朗读最新的AI回复; // 4. 创建状态提示框 const statusDiv document.createElement(div); statusDiv.id voice-status; // 将元素添加到页面 voiceControls.appendChild(micBtn); voiceControls.appendChild(speakerBtn); document.body.appendChild(voiceControls); document.body.appendChild(statusDiv); // 检查浏览器是否支持必要的API if (!(webkitSpeechRecognition in window) !(SpeechRecognition in window)) { micBtn.disabled true; micBtn.title 您的浏览器不支持语音识别API建议使用Chrome/Edge; showStatus(浏览器不支持语音识别, error); } if (!(speechSynthesis in window)) { speakerBtn.disabled true; speakerBtn.title 您的浏览器不支持语音合成API; } // 语音识别逻辑 const SpeechRecognition window.SpeechRecognition || window.webkitSpeechRecognition; let recognition null; let isListening false; let finalTranscript ; if (SpeechRecognition) { recognition new SpeechRecognition(); recognition.continuous false; // 说完一段就结束 recognition.interimResults true; // 实时返回中间结果 recognition.lang zh-CN; // 设置识别语言为中文 recognition.onstart function() { isListening true; micBtn.classList.add(listening); showStatus(正在聆听..., listening); }; recognition.onresult function(event) { let interimTranscript ; for (let i event.resultIndex; i event.results.length; i) { if (event.results[i].isFinal) { finalTranscript event.results[i][0].transcript; } else { interimTranscript event.results[i][0].transcript; } } // 实时显示识别到的文字可以优化为显示在输入框内 showStatus(识别中: ${interimTranscript}, interim); }; recognition.onend function() { isListening false; micBtn.classList.remove(listening); if (finalTranscript) { // 找到Chainlit的输入框并填入识别结果 const inputEl document.querySelector(textarea[class*MessageInput]) || document.querySelector(input[typetext]); if (inputEl) { inputEl.value finalTranscript; inputEl.focus(); // 可选自动触发输入事件让Chainlit的UI更新 inputEl.dispatchEvent(new Event(input, { bubbles: true })); showStatus(识别完成: ${finalTranscript}, success); } finalTranscript ; // 重置 } else { showStatus(请说话..., info); } }; recognition.onerror function(event) { console.error(语音识别错误:, event.error); isListening false; micBtn.classList.remove(listening); showStatus(识别错误: ${event.error}, error); }; } // 麦克风按钮点击事件 micBtn.addEventListener(click, function() { if (!recognition) return; if (isListening) { recognition.stop(); } else { finalTranscript ; recognition.start(); } }); // 语音合成逻辑 speakerBtn.addEventListener(click, function() { // 找到最新的AI回复消息 const aiMessages document.querySelectorAll([class*Message] [class*content]); let latestAIText ; // 简单遍历寻找最后一条非用户发送的消息内容 // 注意这里的选择器需要根据Chainlit实际生成的DOM结构进行调整 for (let i aiMessages.length - 1; i 0; i--) { const msgContainer aiMessages[i].closest([class*Message]); // 更稳健的方式是检查消息元素是否包含表示AI发送的类或属性 // 这里假设非用户发送的消息容器没有特定的用户标识类 if (msgContainer !msgContainer.querySelector([class*avatar])) { // 这是一个粗略的假设 latestAIText aiMessages[i].innerText || aiMessages[i].textContent; break; } } // 或者直接取最后一条消息如果UI结构稳定 // latestAIText aiMessages[aiMessages.length - 1]?.innerText || ; if (latestAIText.trim()) { speakText(latestAIText); showStatus(正在朗读..., speaking); } else { showStatus(未找到可朗读的AI回复, warning); } }); function speakText(text) { if (!window.speechSynthesis) return; // 停止当前可能正在进行的朗读 window.speechSynthesis.cancel(); const utterance new SpeechSynthesisUtterance(text); utterance.lang zh-CN; // 设置语言 utterance.rate 1.0; // 语速 utterance.pitch 1.0; // 音调 utterance.volume 1.0; // 音量 // 尝试选择一个中文语音如果可用 const voices speechSynthesis.getVoices(); const chineseVoice voices.find(v v.lang.startsWith(zh)); if (chineseVoice) utterance.voice chineseVoice; utterance.onend function() { showStatus(朗读完毕, success); }; utterance.onerror function(event) { console.error(语音合成错误:, event); showStatus(朗读出错, error); }; window.speechSynthesis.speak(utterance); } // 语音列表加载某些浏览器需要 if (speechSynthesis.onvoiceschanged ! undefined) { speechSynthesis.onvoiceschanged function() { // voices are loaded }; } function showStatus(message, type info) { statusDiv.textContent message; statusDiv.style.display block; statusDiv.style.backgroundColor type error ? #ef4444 : type success ? #10b981 : type warning ? #f59e0b : type listening ? #dc2626 : type speaking ? #8b5cf6 : rgba(0,0,0,0.8); // 3秒后自动隐藏 clearTimeout(window.statusTimer); window.statusTimer setTimeout(() { statusDiv.style.display none; }, 3000); } } /script关键点解释CSS部分定义了按钮的样式、位置和动画效果比如录音时的呼吸灯效果。JavaScript部分initVoiceControls函数在页面加载后运行创建按钮并注入逻辑。SpeechRecognitionAPI 用于处理语音输入。我们设置了中文识别(lang: zh-CN)并处理了开始、识别中、结束和错误事件。识别到的文字会被自动填入Chainlit的输入框。注意这里通过类名选择输入框 (textarea[class*MessageInput])如果Chainlit版本更新导致类名变化可能需要调整。speechSynthesisAPI 用于处理语音输出。点击喇叭按钮会查找页面上最新的AI回复文本并进行朗读。showStatus函数用于在页面上显示临时状态提示。3.2 调整Chainlit应用配置确保你的chainlit应用能够正确加载这个自定义配置。通常只要chainlit.md文件位于应用根目录Chainlit会自动读取它。现在重启你的Chainlit应用chainlit run app.py访问你的Chainlit应用你应该能在聊天界面的右下角看到新添加的和按钮。4. 功能测试与使用技巧4.1 测试语音输入点击按钮浏览器会请求麦克风权限请点击“允许”。按钮会变成红色并带有呼吸动画状态提示显示“正在聆听...”。对着麦克风清晰地说一段话比如“今天北京的天气怎么样”你说完后识别会自动停止。识别出的文字会出现在下方的输入框中。按回车键或点击发送即可像往常一样将问题发送给GLM-4-9B模型。小技巧在安静的环境下使用识别准确率更高。说话时语速适中吐字清晰。如果识别有误可以直接在输入框里修改文字再发送。4.2 测试语音输出先通过文本或语音输入向模型发送一个问题并等待AI回复。当AI的回复显示在聊天区域后点击按钮。系统会立即开始朗读最新的那条AI回复。状态提示会显示“正在朗读...”。朗读过程中可以再次点击按钮停止。小技巧浏览器的语音合成效果可能比较机械。如果你追求更自然的声音可以考虑将后端TTS方案如pyttsx3集成进来让服务器生成音频文件后推送给前端播放。4.3 可能遇到的问题与排查按钮不出现检查浏览器控制台F12是否有JavaScript错误。可能是选择器找不到Chainlit的DOM元素需要根据实际页面结构调整querySelector的选择器。麦克风无法使用检查浏览器是否已授权当前网站使用麦克风。检查系统麦克风设置是否正常。识别语言不对代码中默认设置为中文(zh-CN)。如果你需要其他语言可以修改recognition.lang的值例如en-US。语音合成没声音检查系统音量。检查浏览器控制台是否有错误。某些浏览器在页面非激活状态比如切到其他标签页时会暂停语音合成。找不到AI回复文本speakerBtn的点击事件处理函数中寻找AI回复消息的逻辑可能因Chainlit版本不同而失效。你需要打开浏览器的“开发者工具”在“元素”面板中仔细查看AI消息的HTML结构然后调整querySelector的选择器。5. 总结与进阶思路通过以上步骤我们成功地为基于Chainlit和GLM-4-9B的对话应用添加了基础的语音交互功能。整个过程没有修改任何后端Python代码完全通过前端增强实现体现了Chainlit良好的可扩展性。回顾一下我们实现的核心利用浏览器原生能力使用Web Speech API以零成本、低延迟的方式实现了语音的输入和输出。定制Chainlit前端通过chainlit.md配置文件注入自定义的HTML、CSS和JavaScript无缝集成新功能。提升用户体验增加了视觉反馈按钮状态、提示信息和简单的错误处理。如果你想更进一步提升语音识别准确率集成专业的语音识别云服务API如Azure Speech to Text。这需要在后端添加一个API调用端点前端将录音数据发送到后端处理。提升语音合成质量集成高质量的TTS服务如Azure Text to Speech。后端调用TTS API生成MP3文件返回给前端播放。这能获得媲美真人的声音。实现流式语音交互结合模型的流式输出实现“边生成边朗读”体验更流畅。添加唤醒词实现类似“小爱同学”的唤醒功能让应用在后台持续监听特定词语。支持多模态输入结合GLM-4-9B的视觉能力未来甚至可以尝试“语音描述图片”等复杂交互。语音交互是人机交互的自然演进方向。为你的AI应用加上“耳朵”和“嘴巴”它就能从冰冷的文字工具变成一个更加生动、便捷、无处不在的智能伙伴。现在就试试对你改造后的助手说句话吧获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。