Tailwind CSS 实战:基于 Kooboo 构建 AI 对话框页面(五):语音合成输出与交互增强

article/2025/8/3 20:03:43
Tailwind CSS 实战,基于Kooboo构建AI对话框页面(一)
Tailwind CSS 实战,基于Kooboo构建AI对话框页面(二):实现交互功能
Tailwind CSS 实战,基于 Kooboo 构建 AI 对话框页面(三):实现暗黑模式主题切换
Tailwind CSS 实战,基于 Kooboo 构建 AI 对话框页面(四):语音识别输入功能

继前几篇实现语音识别功能后,本文将聚焦于语音合成输出的落地,结合 Web Speech Synthesis API 与 Tailwind CSS,构建「语音输入→AI 处理→语音输出」的完整交互闭环,并通过 UI 优化增强操作反馈。

一、关于语音合成

语音合成(Text-to-Speech, TTS)就是把文字变成声音。在 AI 对话框里加上这个功能,能让 AI “开口说话”,比如用户打字提问后,AI 不仅显示文字回复,还能读出答案。这对习惯听语音的用户或双手忙碌的场景非常友好,让交互更自然。

核心技术:Web Speech Synthesis API

这是浏览器自带的 API,不需要额外安装库,直接用 JavaScript 就能调用。它支持多语言(如中文、英文),还能调整语速、音调等参数,非常适合前端开发。

二、一步一步实现语音合成功能

1. 准备工作:初始化语音合成对象

首先,在 JavaScript 里创建一个语音合成实例,并定义一些变量来跟踪当前播放状态:

const synth = window.speechSynthesis; // 语音合成对象  
let currentUtterance = null; // 当前正在播放的语音实例  
let lastPlayedText = ''; // 最后播放的文本,用于全局播放控制  

2. 播放语音的核心函数:speak ()

这个函数负责把文本转为语音并播放,还会更新按钮和状态指示器的样式。

function speak(text, button = null) {  // 先停止当前正在播放的语音  if (currentUtterance) {  synth.cancel();  }  // 创建语音实例  const utterance = new SpeechSynthesisUtterance(text);  // 设置语音参数(从全局设置中获取)  utterance.rate = speechSettings.rate; // 语速(0.5倍最慢,2倍最快)  utterance.pitch = speechSettings.pitch; // 音调(0最低,2最高)  utterance.volume = speechSettings.volume; // 音量(0-1)  utterance.lang = speechSettings.lang; // 语言,如'zh-CN'(中文)或'en-US'(英文)  // 播放时更新UI:按钮变绿,显示加载动画  if (button) {  button.classList.add('active'); // 添加激活态样式(绿色背景)  button.innerHTML = '<i class="fa fa-pause"></i>'; // 切换为暂停图标  const indicator = button.parentElement.querySelector('.voice-indicator');  indicator?.classList.add('active'); // 显示动态脉冲效果  }  // 绑定播放结束事件:播放完后重置按钮状态  utterance.onend = () => {  if (button) {  button.classList.remove('active');  button.innerHTML = '<i class="fa fa-play"></i>';  indicator?.classList.remove('active');  }  };  // 开始播放  synth.speak(utterance);  currentUtterance = utterance; // 记录当前播放的实例  lastPlayedText = text; // 记录最后播放的文本  
}  

3. 添加播放按钮到 AI 消息气泡

在 AI 回复的 HTML 里,每个消息气泡右侧加一个播放按钮。点击按钮时,调用上面的speak函数:

<div class="max-w-[70%] relative">  <div class="bg-[var(--chat-bubble-ai)] p-4 rounded-lg">  <p>您好!这是AI的语音回复。</p>  </div>  <!-- 播放按钮:点击时调用speak函数,传入文本和当前按钮 -->  <button  class="play-button absolute right-4 top-4"  onclick="speak('您好!这是AI的语音回复。', this)"  >  <i class="fa fa-play text-gray-500"></i>  </button>  <!-- 语音状态指示器:播放时显示绿色脉冲 -->  <div class="voice-indicator absolute right-12 top-5"></div>  
</div>  

4. 全局播放按钮:一键控制所有语音

在页面右下角加一个固定按钮,方便用户暂停 / 继续所有语音,或重播最后一条:

<button id="globalPlayButton" class="fixed bottom-4 right-4 w-12 h-12 rounded-full bg-blue-500 text-white">  <i class="fa fa-play"></i>  
</button>  

点击事件逻辑:

globalPlayButton.addEventListener('click', () => {  if (synth.speaking) {  // 如果正在播放,切换暂停/继续  synth.paused ? synth.resume() : synth.pause();  } else if (lastPlayedText) {  // 如果没有播放,重播最后一条消息  speak(lastPlayedText);  }  
});  

三、设计语音设置面板:让用户自定义声音

为什么需要设置面板?

不同用户对语音的偏好不同,比如有人喜欢快语速,有人喜欢低沉的声音。设置面板允许用户调整参数,并保存设置到本地,下次打开页面时自动应用。

面板里有什么?

  1. 语速滑动条:控制语音速度(0.5x 到 2x)。
  2. 音调滑动条:调整声音高低(0 到 2)。
  3. 音量滑动条:设置音量大小(0% 到 100%)。
  4. 自动播放开关:AI 回复生成后是否自动播放语音。
  5. 测试按钮:用当前设置播放测试语音。

HTML 结构:

<div id="voiceSettings" class="voice-settings">  <h3>语音设置</h3>  <label>语速</label>  <input type="range" id="rateSlider" min="0.5" max="2" step="0.1" value="1">  <span id="rateValue">1.0x</span>  <label>音调</label>  <input type="range" id="pitchSlider" min="0.5" max="2" step="0.1" value="1">  <span id="pitchValue">1.0x</span>  <label>音量</label>  <input type="range" id="volumeSlider" min="0" max="1" step="0.1" value="1">  <span id="volumeValue">100%</span>  <label>  <input type="checkbox" id="autoPlayToggle" checked>  自动播放AI回复  </label>  <button id="testVoiceButton">测试语音</button>  
</div>  

保存设置到本地:

localStorage存储用户设置,页面刷新后不会丢失:

// 监听滑动条变化,更新设置并保存  
rateSlider.addEventListener('input', (e) => {  speechSettings.rate = parseFloat(e.target.value);  rateValue.textContent = `${speechSettings.rate.toFixed(1)}x`;  localStorage.setItem('speechSettings', JSON.stringify(speechSettings));  
});  // 页面加载时读取本地设置  
const savedSettings = localStorage.getItem('speechSettings');  
if (savedSettings) {  speechSettings = JSON.parse(savedSettings);  rateSlider.value = speechSettings.rate;  // 同步更新其他滑动条和开关  
}  

四、完整代码:从 HTML 到 JavaScript

HTML 部分:

包含暗黑模式切换、语音设置按钮、消息容器、输入框和全局播放按钮。注意 AI 消息里的播放按钮和状态指示器:

<body>  <!-- 暗黑模式切换按钮 -->  <button id="darkModeToggle">...</button>  <!-- 语音设置按钮 -->  <button id="voiceSettingsToggle"><i class="fa fa-sliders"></i></button>  <!-- 语音设置面板 -->  <div id="voiceSettings" class="voice-settings">  <!-- 滑动条和开关 -->  </div>  <!-- 聊天窗口 -->  <div id="messageContainer">  <!-- AI初始消息,包含播放按钮和状态指示器 -->   </div>  <!-- 输入框和发送按钮 -->  <div class="input-wrapper">  <button id="voiceButton"><i class="fa fa-microphone"></i></button>  <input id="messageInput" placeholder="输入消息...">  </div>  <!-- 全局播放按钮 -->  <button id="globalPlayButton"><i class="fa fa-play"></i></button>  
</body>  

JavaScript 部分:

核心逻辑包括语音合成、语音识别、设置管理和 UI 交互。这里重点看speak函数和设置面板的逻辑:

// 语音设置参数  
let speechSettings = {  rate: 1,  pitch: 1,  volume: 1,  autoPlay: true,  lang: 'zh-CN'  
};  // 播放语音函数  
function speak(text, button) {  // 停止当前播放  synth.cancel();  // 创建语音实例并设置参数  const utterance = new SpeechSynthesisUtterance(text);  Object.assign(utterance, speechSettings);  // 更新按钮和指示器状态  if (button) {  button.classList.add('active');  button.querySelector('.fa').classList.remove('fa-play').add('fa-pause');  button.nextElementSibling.classList.add('active');  }  // 播放结束后重置状态  utterance.onend = () => {  if (button) {  button.classList.remove('active');  button.querySelector('.fa').classList.remove('fa-pause').add('fa-play');  button.nextElementSibling.classList.remove('active');  }  };  synth.speak(utterance);  
}  // 自动播放AI回复  
function addAIResponse(response) {  // 添加消息到页面  messageContainer.innerHTML += `...`;  // 如果开启自动播放,调用speak函数  if (speechSettings.autoPlay) {  speak(response, messageContainer.lastElementChild.querySelector('.play-button'));  }  
}  

五、常见问题(初学者必看)

1. 为什么语音不播放?

  • 检查浏览器支持:Web Speech Synthesis API 在 Chrome、Edge 等现代浏览器中支持较好,IE 不支持
  • 权限问题:浏览器可能需要用户交互(如点击按钮)才能播放声音,直接在页面加载时播放可能被阻止。
  • 网络问题:部分浏览器需要联网才能使用语音合成。

2. 如何添加更多语言?

修改speechSettings.lang的值即可,比如:

speechSettings.lang = 'en-US'; // 美式英语  
speechSettings.lang = 'ja-JP'; // 日语  

注意:浏览器需要支持该语言的语音引擎,可能需要安装语音包。

3. 按钮样式不生效怎么办?

  • 确保 Tailwind CSS 的类名正确,比如rounded-fullbg-green-500是否存在。
  • 检查 CSS 变量是否定义,如--voice-playing是否在:root.dark中声明。

4. 如何调试?

  • 使用浏览器开发者工具(F12),在控制台查看错误信息。
  • speak函数中添加console.log('播放语音:', text),确认函数是否被正确调用。

最终效果:

  1. 用户输入文字或语音,AI 回复文本并自动播放语音(若开启自动播放)。
  2. 点击消息气泡旁的播放按钮,可手动播放 / 暂停。
  3. 拖动设置面板的滑动条,实时调整语速、音调、音量。
  4. 点击全局播放按钮,快速控制所有语音。

结语

通过这篇教程,你学会了如何用 Web Speech Synthesis API 实现语音合成,结合 Tailwind CSS 设计交互界面,并通过设置面板提升用户体验。现在,你的 AI 对话框已经具备 “听” 和 “说” 的能力,形成完整的语音交互闭环。

 


http://www.hkcw.cn/article/oNJucxQfPJ.shtml

相关文章

【MySQL】MVCC与Read View

目录 一、数据库并发的三种场景 二、读写场景的MVCC &#xff08;一&#xff09;表中的三个隐藏字段 &#xff08;二&#xff09;undo 日志 &#xff08;三&#xff09;模拟MVCC &#xff08;四&#xff09;Read View &#xff08;五&#xff09;当前读和快照读 三、RC和…

代码随想录打卡|Day53 图论(Floyd 算法精讲 、A * 算法精讲 (A star算法)、最短路算法总结篇、图论总结 )

图论part11 Floyd 算法精讲 代码随想录链接 题目链接 代码 三维DP数组 import java.util.Scanner;public class Main {// 定义最大距离值&#xff0c;避免使用Integer.MAX_VALUE防止加法溢出public static final int INF 100000000; // 10^8足够大且不会溢出public static…

CSS Day07

1.搭建项目目录 2.网页头部SEO三大标签 3.Favicon图标与版心 &#xff08;1&#xff09;Favicon图标 &#xff08;2&#xff09;版心 4.快捷导航 5.头部-布局 6.头部-logo 7.头部-导航 8.头部-搜索 9头部-购物车 10.底部-布局 11.底部-服务区域 12.底部-帮助中心 13.底部-版权…

leetcode hot100刷题日记——29.合并两个有序链表

解答&#xff1a; 方法一&#xff1a;递归 递归的边界条件是啥呢&#xff1f; 递归别想那么多具体步骤&#xff0c;考虑大步骤&#xff0c;小的递归自己会去做的 class Solution { public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {//递归比较大小//先考虑…

Spring Boot 整合 Spring Security

DAY30.1 Java核心基础 Spring Boot 整合安全框架 Spring Security 、Shiro Spring Security Spring Security 的核心功能包括认证、授权、攻击防护&#xff0c;通过大量的过滤器和拦截器进行请求的拦截和验证&#xff0c;实现安全校验的功能。 Spring Security 将校验逻辑…

深度剖析Node.js的原理及事件方式

早些年就接触过Node.js&#xff0c;当时对于这个连接前后端框架就感到很特别。尤其是以独特的异步阻塞特性&#xff0c;重塑了了服务器端编程的范式。后来陆陆续续做了不少项目&#xff0c;通过实践对它或多或少增强了不少理解。今天&#xff0c;我试着将从将从原理层剖析其运行…

智慧景区一体化建设方案

随着2023年文旅部《关于推动智慧旅游发展的指导意见》出台&#xff0c;全国景区掀起数字化转型浪潮。如何在激烈竞争中脱颖而出&#xff1f;智慧景区一体化建设方案&#xff0c;正以“一机游遍景区、一屏掌控全局”的革新模式&#xff0c;重新定义旅游体验与管理效率。本文深度…

使用 SymPy 操作三维向量的反对称矩阵

在三维空间中&#xff0c;一个 3 1 3 \times 1 31 向量可以转换为一个 3 3 3 \times 3 33 的反对称矩阵。这种转换在物理学、机器人学和计算机视觉等领域非常有用。本文将详细介绍如何在 Python 的 SymPy 库中定义和使用这种反对称矩阵。 数学背景 对于一个三维向量 v …

LangChain表达式(LCEL)实操案例1

案例1&#xff1a;写一篇短文&#xff0c;然后对这篇短文进行打分 from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.runnables import RunnableWithMessageHist…

CppCon 2014 学习:HOW UBISOFT MONTREAL DEVELOPS GAMES FOR MULTICORE

多核处理器&#xff08;Multicore Processor&#xff09; 的基本特性&#xff0c;下面是对每点的简要说明&#xff1a; &#x1f539; Multicore&#xff08;多核&#xff09; 指一个物理处理器上集成了 多个 CPU 核心&#xff0c;每个核心可以独立执行指令。 &#x1f538;…

STL解析——String类详解(使用篇)

目录 sring接口解析 1.string简介 2.默认成员函数 2.1构造函数 2.2析构函数 2.3赋值重载 3.迭代器 3.1初识迭代器 3.2迭代器的使用 3.3特殊迭代器 3.4范围for 4.大小接口 4.1字符长度相关接口 4.2空间大小相关接口 5.其他常用接口 5.1operator[ ] 5.2增 5.3查 5…

Android 代码阅读环境搭建:VSCODE + SSH + CLANGD(详细版)

在阅读Android源码&#xff08;AOSP超过1亿行代码&#xff09;时&#xff0c;开发者常面临索引失败、跳转卡顿等问题。本教程将手把手教你搭建基于VSCode SSH Clangd的终极阅读环境&#xff0c;实现秒级符号跳转、精准代码提示和高效远程开发。 一、环境架构解析 1.1 方案组…

JAVA 集合的进阶 泛型的继承和通配符

1 泛型通配符 可以对传递的类型进行限定 1.1 格式 ? 表示不确定的类型 &#xff1f;extends E&#xff1a; 表示可以传递 E 或者 E 所有的子类类型 &#xff1f;super E&#xff1a; 表示可以传递 E 或者 E 所有的父类类…

改写自己的浏览器插件工具 myChromeTools

1. 起因&#xff0c; 目的: 前面我写过&#xff0c; 自己的一个浏览器插件小工具 最近又增加一个小功能&#xff0c;可以自动滚动页面&#xff0c;尤其是对于那些瀑布流加载的网页。最新的代码都在这里 2. 先看效果 3. 过程: 代码 1, 模拟鼠标自然滚动 // 处理滚动控制逻辑…

由sigmod权重曲线存在锯齿的探索

深度学习的知识点&#xff0c;一般按照执行流程&#xff0c;有 网络层类型&#xff0c;归一化&#xff0c;激活函数&#xff0c;学习率&#xff0c;损失函数&#xff0c;优化器。如果是研究生上课学的应该系统一点&#xff0c;自学的话知识点一开始有点乱。 一、激活函数Sigmod…

仿腾讯会议——优化:多条tcp连接

1、添加用户信息结构 2、添加注册视频音频结构体 3、 完成函数注册视频音频

File—IO流

因为变量&#xff0c;数组&#xff0c;对象&#xff0c;集合这些数据容器都在内存中&#xff0c;一旦程序结束&#xff0c;或者断电&#xff0c;数据就丢失了。想要长久保存&#xff0c;就要存在文件中&#xff08;File&#xff09; 文件可以长久保存数据。 文件在电脑磁盘中…

【Zephyr 系列 2】用 Zephyr 玩转 Arduino UNO / MEGA,实现串口通信与 CLI 命令交互

🎯 本篇目标 在 Ubuntu 下将 Zephyr 运行在 Arduino UNO / MEGA 上 打通串口通信,实现通过串口发送命令与反馈 使用 Zephyr Shell 模块,实现 CLI 命令处理 🪧 为什么 Arduino + Zephyr? 虽然 Arduino 开发板通常用于简单的 C/C++ 开发,但 Zephyr 的支持范围远超 STM32…

最悉心的指导教程——阿里云创建ECS实例教程+Vue+Django前后端的服务器部署(通过宝塔面板)

各位看官老爷们&#xff0c;点击关注不迷路哟。你的点赞、收藏&#xff0c;一键三连&#xff0c;是我持续更新的动力哟&#xff01;&#xff01;&#xff01; 阿里云创建ECS实例教程 注意&#xff1a; 阿里云有300元额度的免费适用期哟 白嫖~~~~ 注册了阿里云账户后&#x…

【Android】如何抓取 Android 设备的 UDP/TCP 数据包?

目录 前言理解抓包tcpdump 实时抓包Wireshark 解包抓包后的一些思考 前言 在真正接触 UDP/TCP 抓包之前&#xff0c;我一直以为这是一项高深莫测的技术。可当我们真正了解之后才发现&#xff0c;其实并没有那么复杂——不过如此。 所谓的大佬&#xff0c;往往只是掌握了你尚未…