添加了 TTS 相关服务并更新了设置

This commit is contained in:
1600822305 2025-04-12 11:57:00 +08:00
parent b2a0a029d2
commit 788fb1fc17
18 changed files with 866 additions and 139 deletions

View File

@ -3,6 +3,8 @@ productName: Cherry Studio
directories:
buildResources: build
files:
- out/**/*
- package.json
- '!{.vscode,.yarn,.github}'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'

View File

@ -76,6 +76,7 @@
"color": "^5.0.0",
"diff": "^7.0.0",
"docx": "^9.0.2",
"edge-tts-node": "^1.5.7",
"electron-log": "^5.1.5",
"electron-store": "^8.2.0",
"electron-updater": "^6.3.9",

View File

@ -25,6 +25,9 @@ export enum IpcChannel {
// MsTTS
MsTTS_GetVoices = 'mstts:get-voices',
MsTTS_Synthesize = 'mstts:synthesize',
MsTTS_SynthesizeStream = 'mstts:synthesize-stream',
MsTTS_StreamData = 'mstts:stream-data',
MsTTS_StreamEnd = 'mstts:stream-end',
// Open
Open_Path = 'open:path',

View File

@ -1,5 +1,5 @@
import { IpcChannel } from '@shared/IpcChannel'
import { ipcMain } from 'electron'
import { BrowserWindow, ipcMain } from 'electron'
import * as MsTTSService from './MsTTSService'
@ -14,4 +14,34 @@ export function registerMsTTSIpcHandlers(): void {
ipcMain.handle(IpcChannel.MsTTS_Synthesize, (_, text: string, voice: string, outputFormat: string) =>
MsTTSService.synthesize(text, voice, outputFormat)
)
// 流式合成语音
ipcMain.handle(IpcChannel.MsTTS_SynthesizeStream, async (event, requestId: string, text: string, voice: string, outputFormat: string) => {
const window = BrowserWindow.fromWebContents(event.sender)
if (!window) return
try {
await MsTTSService.synthesizeStream(
text,
voice,
outputFormat,
(chunk: Uint8Array) => {
// 发送音频数据块
if (!window.isDestroyed()) {
window.webContents.send(IpcChannel.MsTTS_StreamData, requestId, chunk)
}
},
() => {
// 发送流结束信号
if (!window.isDestroyed()) {
window.webContents.send(IpcChannel.MsTTS_StreamEnd, requestId)
}
}
)
return { success: true }
} catch (error) {
console.error('流式TTS合成失败:', error)
return { success: false, error: error instanceof Error ? error.message : String(error) }
}
})
}

View File

@ -3,7 +3,8 @@ import path from 'node:path'
import { app } from 'electron'
import log from 'electron-log'
import { EdgeTTS } from 'node-edge-tts' // listVoices is no longer needed here
import { EdgeTTS } from 'node-edge-tts' // 旧版TTS库
import { MsEdgeTTS, OUTPUT_FORMAT } from 'edge-tts-node' // 新版支持流式的TTS库
// --- START OF HARDCODED VOICE LIST ---
// WARNING: This list is static and may become outdated.
@ -437,6 +438,77 @@ class MsTTSService {
return MsTTSService.instance
}
/**
*
* @param text
* @param voice ShortName ( 'zh-CN-XiaoxiaoNeural')
* @param outputFormat ( 'audio-24khz-48kbitrate-mono-mp3')
* @param onData
* @param onEnd
*/
public async synthesizeStream(
text: string,
voice: string,
outputFormat: string,
onData: (chunk: Uint8Array) => void,
onEnd: () => void
): Promise<void> {
try {
// 记录详细的请求信息
log.info(`流式微软在线TTS合成语音: 文本="${text.substring(0, 30)}...", 语音=${voice}, 格式=${outputFormat}`)
// 验证输入参数
if (!text || text.trim() === '') {
throw new Error('要合成的文本不能为空')
}
if (!voice || voice.trim() === '') {
throw new Error('语音名称不能为空')
}
// 创建一个新的MsEdgeTTS实例
const tts = new MsEdgeTTS({
enableLogger: false // 禁用内部日志
})
// 设置元数据
let msOutputFormat: OUTPUT_FORMAT
if (outputFormat.includes('mp3')) {
msOutputFormat = OUTPUT_FORMAT.AUDIO_24KHZ_48KBITRATE_MONO_MP3
} else if (outputFormat.includes('webm')) {
msOutputFormat = OUTPUT_FORMAT.WEBM_24KHZ_16BIT_MONO_OPUS
} else {
msOutputFormat = OUTPUT_FORMAT.AUDIO_24KHZ_48KBITRATE_MONO_MP3
}
await tts.setMetadata(voice, msOutputFormat)
// 创建流
const audioStream = tts.toStream(text)
// 监听数据事件
audioStream.on('data', (data: Buffer) => {
onData(data)
})
// 监听结束事件
audioStream.on('end', () => {
log.info(`流式微软在线TTS合成成功`)
onEnd()
})
// 监听错误事件
audioStream.on('error', (error: Error) => {
log.error(`流式微软在线TTS语音合成失败:`, error)
throw error
})
} catch (error: any) {
// 记录详细的错误信息
log.error(`流式微软在线TTS语音合成失败 (语音=${voice}):`, error)
throw error
}
}
/**
* ()
* @returns
@ -556,6 +628,16 @@ export const synthesize = async (text: string, voice: string, outputFormat: stri
return await MsTTSService.getInstance().synthesize(text, voice, outputFormat)
}
export const synthesizeStream = async (
text: string,
voice: string,
outputFormat: string,
onData: (chunk: Uint8Array) => void,
onEnd: () => void
) => {
return await MsTTSService.getInstance().synthesizeStream(text, voice, outputFormat, onData, onEnd)
}
export const cleanupTtsTempFiles = async () => {
await MsTTSService.getInstance().cleanupTempDir()
}

View File

@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
interface Props {
onTranscribed: (text: string) => void
onTranscribed: (text: string, isFinal?: boolean) => void
disabled?: boolean
style?: React.CSSProperties
}
@ -33,7 +33,7 @@ const ASRButton: FC<Props> = ({ onTranscribed, disabled = false, style }) => {
try {
// 添加事件监听器监听服务器发送的stopped消息
const originalCallback = ASRService.resultCallback
const stopCallback = (text: string) => {
const stopCallback = (text: string, isFinal?: boolean) => {
// 如果是空字符串,只重置状态,不调用原始回调
if (text === '') {
setIsProcessing(false)

View File

@ -2,7 +2,7 @@ import { SoundOutlined } from '@ant-design/icons'
import TTSService from '@renderer/services/TTSService'
import { Message } from '@renderer/types'
import { Button, Tooltip } from 'antd'
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
interface TTSButtonProps {
@ -14,38 +14,45 @@ const TTSButton: React.FC<TTSButtonProps> = ({ message, className }) => {
const { t } = useTranslation()
const [isSpeaking, setIsSpeaking] = useState(false)
// 添加TTS状态变化事件监听器
useEffect(() => {
const handleTTSStateChange = (event: CustomEvent) => {
const { isPlaying } = event.detail
console.log('TTS按钮检测到TTS状态变化:', isPlaying)
setIsSpeaking(isPlaying)
}
// 添加事件监听器
window.addEventListener('tts-state-change', handleTTSStateChange as EventListener)
// 组件卸载时移除事件监听器
return () => {
window.removeEventListener('tts-state-change', handleTTSStateChange as EventListener)
}
}, [])
// 初始化时检查TTS状态
useEffect(() => {
// 检查当前是否正在播放
const isCurrentlyPlaying = TTSService.isCurrentlyPlaying()
if (isCurrentlyPlaying !== isSpeaking) {
setIsSpeaking(isCurrentlyPlaying)
}
}, [])
const handleTTS = useCallback(async () => {
if (isSpeaking) {
TTSService.stop()
setIsSpeaking(false)
return
return // 不需要手动设置状态,事件监听器会处理
}
setIsSpeaking(true)
try {
console.log('点击TTS按钮开始播放消息')
await TTSService.speakFromMessage(message)
// 监听播放结束
const checkPlayingStatus = () => {
if (!TTSService.isCurrentlyPlaying()) {
setIsSpeaking(false)
clearInterval(checkInterval)
}
}
const checkInterval = setInterval(checkPlayingStatus, 500)
// 安全机制,确保即使出错也会重置状态
setTimeout(() => {
if (isSpeaking) {
TTSService.stop()
setIsSpeaking(false)
clearInterval(checkInterval)
}
}, 30000) // 30秒后检查
// 不需要手动设置状态,事件监听器会处理
} catch (error) {
console.error('TTS error:', error)
// 出错时才需要手动重置状态
setIsSpeaking(false)
}
}, [isSpeaking, message])

View File

@ -77,6 +77,7 @@ let _files: FileType[] = []
const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) => {
const [text, setText] = useState(_text)
const [asrCurrentText, setAsrCurrentText] = useState('')
const [inputFocus, setInputFocus] = useState(false)
const { assistant, addTopic, model, setModel, updateAssistant } = useAssistant(_assistant.id)
const {
@ -787,18 +788,40 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
}
}
// 如果是语音通话消息,创建一个新的助手对象,并设置模型
// 如果是语音通话消息,创建一个新的助手对象,并设置模型和提示词
let assistantToUse = assistant
if ((data.isVoiceCall || data.useVoiceCallModel) && userMessage.model) {
if (data.isVoiceCall || data.useVoiceCallModel) {
// 创建一个新的助手对象,以避免修改原始助手
assistantToUse = { ...assistant }
// 设置助手的模型为语音通话专用模型
assistantToUse.model = userMessage.model
console.log(
'为语音通话消息创建了新的助手对象,并设置了模型:',
userMessage.model.name || userMessage.model.id
)
// 如果有语音通话专用模型,设置助手的模型
if (userMessage.model) {
assistantToUse.model = userMessage.model
console.log(
'为语音通话消息创建了新的助手对象,并设置了模型:',
userMessage.model.name || userMessage.model.id
)
}
// 添加语音通话专属提示词
const voiceCallPrompt = `当前是语音通话模式。请注意:
1.
2. 使Markdown等
3. 使
4. 使
5. 便
6. 使
7. 使
8. 使`
// 如果助手已经有提示词,则在其后添加语音通话专属提示词
if (assistantToUse.prompt) {
assistantToUse.prompt += '\n\n' + voiceCallPrompt
} else {
assistantToUse.prompt = voiceCallPrompt
}
console.log('为语音通话消息添加了专属提示词')
}
// 分发发送消息的action
@ -806,6 +829,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
// 清空输入框
setText('')
// 重置语音识别状态
setAsrCurrentText('')
console.log('已触发发送消息事件')
}, 300)
@ -1121,18 +1146,28 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
<ToolbarMenu>
<TranslateButton text={text} onTranslated={onTranslated} isLoading={isTranslating} />
<ASRButton
onTranscribed={(transcribedText) => {
onTranscribed={(transcribedText, isFinal) => {
// 如果是空字符串,不做任何处理
if (!transcribedText) return
// 将识别的文本添加到当前输入框
setText((prevText) => {
// 如果当前有文本,添加空格后再添加识别的文本
if (prevText.trim()) {
if (isFinal) {
// 最终结果,添加到输入框中
setText((prevText) => {
// 如果当前输入框为空,直接设置为识别的文本
if (!prevText.trim()) {
return transcribedText
}
// 否则,添加识别的文本到输入框中,用空格分隔
return prevText + ' ' + transcribedText
}
return transcribedText
})
})
// 清除当前识别的文本
setAsrCurrentText('')
} else {
// 中间结果,保存到状态变量中,但不更新输入框
setAsrCurrentText(transcribedText)
}
}}
/>
<VoiceCallButton disabled={loading} />

View File

@ -153,15 +153,20 @@ const MessageItem: FC<Props> = ({
// 自动播放TTS的逻辑
useEffect(() => {
// 如果是最后一条助手消息且消息状态为成功且不是正在生成中且TTS已启用且语音通话窗口已打开
// 如果是最后一条助手消息且消息状态为成功且不是正在生成中且TTS已启用
// 注意只有在语音通话窗口打开时才自动播放TTS
if (
isLastMessage &&
isAssistantMessage &&
message.status === 'success' &&
!generating &&
ttsEnabled &&
isVoiceCallActive
ttsEnabled
) {
// 如果语音通话窗口没有打开则不自动播放TTS
if (!isVoiceCallActive) {
console.log('不自动播放TTS因为语音通话窗口没有打开:', isVoiceCallActive)
return
}
// 检查是否需要跳过自动TTS
if (skipNextAutoTTS) {
console.log(
@ -205,16 +210,6 @@ const MessageItem: FC<Props> = ({
} else if (message.id === lastPlayedMessageId) {
console.log('不自动播放TTS因为该消息已经播放过:', message.id)
}
} else if (
isLastMessage &&
isAssistantMessage &&
message.status === 'success' &&
!generating &&
ttsEnabled &&
!isVoiceCallActive
) {
// 如果语音通话窗口没有打开则不自动播放TTS
console.log('不自动播放TTS因为语音通话窗口没有打开')
}
}, [
isLastMessage,

View File

@ -9,14 +9,25 @@ const TTSStopButton: React.FC = () => {
const { t } = useTranslation()
const [isVisible, setIsVisible] = useState(false)
// 检查是否正在播放TTS
// 添加TTS状态变化事件监听器
useEffect(() => {
const checkPlayingStatus = setInterval(() => {
const isPlaying = TTSService.isCurrentlyPlaying()
const handleTTSStateChange = (event: CustomEvent) => {
const { isPlaying } = event.detail
console.log('全局TTS停止按钮检测到TTS状态变化:', isPlaying)
setIsVisible(isPlaying)
}, 500)
}
return () => clearInterval(checkPlayingStatus)
// 添加事件监听器
window.addEventListener('tts-state-change', handleTTSStateChange as EventListener)
// 初始检查当前状态
const isCurrentlyPlaying = TTSService.isCurrentlyPlaying()
setIsVisible(isCurrentlyPlaying)
// 组件卸载时移除事件监听器
return () => {
window.removeEventListener('tts-state-change', handleTTSStateChange as EventListener)
}
}, [])
// 停止TTS播放
@ -26,17 +37,7 @@ const TTSStopButton: React.FC = () => {
// 强制停止所有TTS播放
TTSService.stop()
// 等待一下,确保播放已经完全停止
await new Promise((resolve) => setTimeout(resolve, 100))
// 再次检查并停止,确保强制停止
if (TTSService.isCurrentlyPlaying()) {
console.log('第一次停止未成功,再次尝试')
TTSService.stop()
}
// 立即隐藏按钮
setIsVisible(false)
// 不需要手动设置状态,事件监听器会处理
// 显示停止消息
window.message.success({ content: t('chat.tts.stopped', { defaultValue: '已停止语音播放' }), key: 'tts-stopped' })

View File

@ -163,15 +163,14 @@ class ASRService {
if (data.data.isFinal) {
console.log('[ASRService] 收到最终结果,调用回调函数,文本:', data.data.text)
// 保存当前回调函数并立即清除,防止重复处理
const tempCallback = this.resultCallback
this.resultCallback = null
// 不再清除回调函数,允许继续处理后续语音
// const tempCallback = this.resultCallback
// this.resultCallback = null
// 调用回调函数
tempCallback(data.data.text, true)
// 直接调用回调函数
this.resultCallback(data.data.text, true)
window.message.success({ content: i18n.t('settings.asr.success'), key: 'asr-processing' })
} else if (this.isRecording) {
// 只在录音中才处理中间结果
} else if (this.isRecording) { // 只在录音中才处理中间结果
// 非最终结果,也调用回调,但标记为非最终
console.log('[ASRService] 收到中间结果,调用回调函数,文本:', data.data.text)
this.resultCallback(data.data.text, false)
@ -237,6 +236,14 @@ class ASRService {
return
}
// 先设置回调函数,确保在任何情况下都能正确设置
if (onTranscribed && typeof onTranscribed === 'function') {
console.log('[ASRService] 设置结果回调函数')
this.resultCallback = onTranscribed
} else {
console.warn('[ASRService] 未提供有效的回调函数')
}
// 如果是使用本地服务器
if (asrServiceType === 'local') {
// 连接WebSocket服务器
@ -292,11 +299,6 @@ class ASRService {
}
}
// 保存回调函数(如果提供了)
if (onTranscribed && typeof onTranscribed === 'function') {
this.resultCallback = onTranscribed
}
// 发送开始命令
if (this.ws && this.wsConnected) {
this.ws.send(JSON.stringify({ type: 'start' }))
@ -376,11 +378,11 @@ class ASRService {
}, 100)
}
// 添加额外的安全措施,确保在停止后也清除回调
setTimeout(() => {
// 在停止后的一段时间内清除回调,防止后续结果被处理
this.resultCallback = null
}, 3000) // 3秒后清除回调
// 不再清除回调函数,允许连续说多句话
// setTimeout(() => {
// // 在停止后的一段时间内清除回调,防止后续结果被处理
// this.resultCallback = null
// }, 3000) // 3秒后清除回调
} else {
throw new Error('WebSocket连接未就绪')
}

View File

@ -257,8 +257,8 @@ class VoiceCallServiceClass {
} else {
// 如果是临时结果,更新当前的识别结果
this._currentTranscript = text
// 显示累积结果 + 当前临时结果
this.callbacks?.onTranscript(this._accumulatedTranscript + ' ' + text)
// 显示当前临时结果,不与累积结果拼接
this.callbacks?.onTranscript(text)
}
// 在录音过程中只更新transcript不触发handleUserSpeech
@ -595,12 +595,10 @@ class VoiceCallServiceClass {
8. 使`
// 创建系统指令消息
const systemMessage = getUserMessage({
assistant,
topic,
type: 'text',
const systemMessage = {
role: 'system',
content: voiceCallPrompt
})
}
// 修改用户消息的内容
userMessage.content = text
@ -637,14 +635,24 @@ class VoiceCallServiceClass {
if (!this.isMuted && this.isCallActive) {
// 手动设置语音状态
this.callbacks?.onSpeakingStateChange(true)
// 添加TTS状态变化事件监听器
const handleTTSStateChange = (event: CustomEvent) => {
const { isPlaying } = event.detail
console.log('语音通话中检测到TTS状态变化:', isPlaying)
this.callbacks?.onSpeakingStateChange(isPlaying)
}
// 添加事件监听器
window.addEventListener('tts-state-change', handleTTSStateChange as EventListener)
// 开始播放
this.ttsService.speak(fullResponse)
// 确保语音结束后状态正确
// 设置超时安全机制,确保事件监听器被移除
setTimeout(() => {
if (this.ttsService && !this.ttsService.isCurrentlyPlaying()) {
this.callbacks?.onSpeakingStateChange(false)
}
}, 1000) // 1秒后检查TTS状态
window.removeEventListener('tts-state-change', handleTTSStateChange as EventListener)
}, 30000) // 30秒后移除事件监听器
}
// 更新对话历史
@ -910,9 +918,7 @@ class VoiceCallServiceClass {
this.ttsService.stop()
console.log('强制停止TTS播放')
// 手动触发TTS状态变化事件确保 UI 状态更新
const event = new CustomEvent('tts-state-change', { detail: { isPlaying: false } })
window.dispatchEvent(event)
// 注意不需要手动触发事件因为在TTSService.stop()中已经触发了
}
setPaused(paused: boolean) {

View File

@ -0,0 +1,90 @@
/**
*
* TTS的音频数据
*/
export class AudioStreamProcessor {
private audioContext: AudioContext | null = null
private audioQueue: Uint8Array[] = []
private isProcessing: boolean = false
// 回调函数
public onAudioBuffer: ((buffer: AudioBuffer) => void) | null = null
/**
*
*/
public async initialize(): Promise<void> {
// 创建音频上下文
this.audioContext = new AudioContext()
this.audioQueue = []
this.isProcessing = false
}
/**
*
* @param chunk
*/
public async processAudioChunk(chunk: Uint8Array): Promise<void> {
if (!this.audioContext) {
throw new Error('AudioStreamProcessor not initialized')
}
// 将数据块添加到队列
this.audioQueue.push(chunk)
// 如果没有正在处理,开始处理
if (!this.isProcessing) {
this.processQueue()
}
}
/**
*
*/
private async processQueue(): Promise<void> {
if (!this.audioContext || this.audioQueue.length === 0) {
this.isProcessing = false
return
}
this.isProcessing = true
// 获取队列中的第一个数据块
const chunk = this.audioQueue.shift()!
try {
// 解码音频数据
// 将SharedArrayBuffer转换为ArrayBuffer
const arrayBuffer = chunk.buffer instanceof SharedArrayBuffer
? new Uint8Array(chunk.buffer).buffer
: chunk.buffer
const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer)
// 调用回调函数
if (this.onAudioBuffer) {
this.onAudioBuffer(audioBuffer)
}
} catch (error) {
console.error('解码音频数据失败:', error)
}
// 继续处理队列中的下一个数据块
this.processQueue()
}
/**
*
*/
public async finish(): Promise<void> {
// 等待队列处理完成
while (this.audioQueue.length > 0) {
await new Promise(resolve => setTimeout(resolve, 100))
}
// 关闭音频上下文
if (this.audioContext) {
await this.audioContext.close()
this.audioContext = null
}
}
}

View File

@ -5,6 +5,9 @@ import { TTSServiceInterface } from './TTSServiceInterface'
// 全局变量来跟踪当前正在播放的语音
let currentUtterance: SpeechSynthesisUtterance | null = null
// 全局变量来跟踪是否正在播放
export let isEdgeTTSPlaying = false
/**
* Edge TTS服务实现类
*/
@ -50,10 +53,12 @@ export class EdgeTTSService implements TTSServiceInterface {
if (currentUtterance) {
currentUtterance = null
}
isEdgeTTSPlaying = false
// 创建语音合成器实例
const utterance = new SpeechSynthesisUtterance(text)
currentUtterance = utterance
isEdgeTTSPlaying = true
// 获取可用的语音合成声音
const voices = window.speechSynthesis.getVoices()
@ -93,6 +98,7 @@ export class EdgeTTSService implements TTSServiceInterface {
utterance.onend = () => {
console.log('语音合成已结束')
currentUtterance = null
isEdgeTTSPlaying = false
// 分发一个自定义事件,通知语音合成已结束
// 这样TTSService可以监听这个事件并重置播放状态
@ -100,9 +106,14 @@ export class EdgeTTSService implements TTSServiceInterface {
document.dispatchEvent(event)
}
utterance.onerror = (event) => {
console.error('语音合成错误:', event)
utterance.onerror = (errorEvent) => {
console.error('语音合成错误:', errorEvent)
currentUtterance = null
isEdgeTTSPlaying = false
// 在错误时也触发结束事件,确保状态更新
const completeEvent = new CustomEvent('edgeTTSComplete', { detail: { text, error: true } })
document.dispatchEvent(completeEvent)
}
// 开始语音合成
@ -147,6 +158,7 @@ export class EdgeTTSService implements TTSServiceInterface {
// 停止当前正在播放的语音
window.speechSynthesis.cancel()
isEdgeTTSPlaying = false
// 创建语音合成器实例
const utterance = new SpeechSynthesisUtterance(text)

View File

@ -14,6 +14,7 @@ export class TTSService {
private static instance: TTSService
private audioElement: HTMLAudioElement | null = null
private isPlaying = false
private playingServiceType: string | null = null
// 错误消息节流控制
private lastErrorTime = 0
@ -42,15 +43,34 @@ export class TTSService {
// 监听音频播放结束事件
this.audioElement.addEventListener('ended', () => {
this.isPlaying = false
console.log('TTS播放结束')
// 只有在非EdgeTTS服务时才直接更新状态
if (this.playingServiceType !== 'edge') {
this.updatePlayingState(false)
console.log('TTS播放结束 (音频元素事件)')
}
})
// 监听浏览器TTS直接播放结束的自定义事件
document.addEventListener('edgeTTSComplete', () => {
console.log('收到浏览器TTS直接播放结束事件')
this.isPlaying = false
this.updatePlayingState(false)
})
// 监听全局的speechSynthesis状态
if ('speechSynthesis' in window) {
// 创建一个定时器定期检查speechSynthesis的状态
setInterval(() => {
// 只有在使用EdgeTTS且标记为正在播放时才检查
if (this.isPlaying && this.playingServiceType === 'edge') {
// 检查是否还在播放
const isSpeaking = window.speechSynthesis.speaking;
if (!isSpeaking) {
console.log('检测到speechSynthesis不再播放更新状态')
this.updatePlayingState(false);
}
}
}, 500); // 每500毫秒检查一次
}
}
/**
@ -82,6 +102,32 @@ export class TTSService {
return this.speak(filteredText)
}
/**
*
* @param isPlaying
*/
private updatePlayingState(isPlaying: boolean): void {
// 只有状态变化时才更新和触发事件
if (this.isPlaying !== isPlaying) {
this.isPlaying = isPlaying;
console.log(`TTS播放状态更新: ${isPlaying ? '开始播放' : '停止播放'}`)
// 触发自定义事件通知其他组件TTS状态变化
const event = new CustomEvent('tts-state-change', { detail: { isPlaying } })
window.dispatchEvent(event)
// 如果停止播放,清除服务类型
if (!isPlaying) {
this.playingServiceType = null;
// 确保Web Speech API也停止
if ('speechSynthesis' in window) {
window.speechSynthesis.cancel();
}
}
}
}
/**
*
* @param text
@ -101,6 +147,8 @@ export class TTSService {
// 如果正在播放,先停止
if (this.isPlaying) {
this.stop()
// 添加短暂延迟,确保上一个播放完全停止
await new Promise(resolve => setTimeout(resolve, 100))
}
// 确保文本不为空
@ -114,6 +162,8 @@ export class TTSService {
const latestSettings = store.getState().settings
const serviceType = latestSettings.ttsServiceType || 'openai'
console.log('使用的TTS服务类型:', serviceType)
// 记录当前使用的服务类型
this.playingServiceType = serviceType
console.log('当前TTS设置详情:', {
ttsServiceType: serviceType,
ttsEdgeVoice: latestSettings.ttsEdgeVoice,
@ -161,7 +211,8 @@ export class TTSService {
}
})
this.isPlaying = true
// 更新播放状态
this.updatePlayingState(true)
console.log('开始播放TTS音频')
// 释放URL对象
@ -172,22 +223,10 @@ export class TTSService {
const isEdgeTTS = serviceType === 'edge'
const isSmallBlob = audioBlob.size < 100
// 如果是浏览器TTS直接播放则等待当前语音合成结束
if (isEdgeTTS && isSmallBlob) {
// 检查全局变量中的当前语音合成状态
// 如果还在播放,则不重置播放状态
// 注意:这里我们无法直接访问 EdgeTTSService 中的 currentUtterance
// 所以我们使用定时器来检查语音合成是否完成
console.log('浏览器TTS直接播放中等待语音合成结束')
// 保持播放状态,直到语音合成结束
// 使用定时器来检查语音合成是否完成
// 大多数语音合成应该在几秒内完成
setTimeout(() => {
this.isPlaying = false
console.log('浏览器TTS直接播放完成')
}, 10000) // 10秒后自动重置状态
} else {
this.isPlaying = false
// 对于非EdgeTTS服务直接更新状态
// EdgeTTS服务的状态更新由定时器和edgeTTSComplete事件处理
if (!(isEdgeTTS && isSmallBlob)) {
this.updatePlayingState(false)
}
}
@ -216,13 +255,17 @@ export class TTSService {
if (this.audioElement) {
this.audioElement.pause()
this.audioElement.currentTime = 0
this.isPlaying = false
console.log('强制停止TTS播放')
// 触发自定义事件通知其他组件TTS已停止
const event = new CustomEvent('tts-state-change', { detail: { isPlaying: false } })
window.dispatchEvent(event)
}
// 如果是EdgeTTS确保Web Speech API也停止
if ('speechSynthesis' in window) {
window.speechSynthesis.cancel()
console.log('停止Web Speech API播放')
}
// 更新状态并触发事件
this.updatePlayingState(false)
}
/**

View File

@ -9,4 +9,15 @@ export interface TTSServiceInterface {
* @returns Blob对象的Promise
*/
synthesize(text: string): Promise<Blob>
/**
* ()
* @param text
* @param onStart
* @param onData
* @param onEnd
* @param onError
* @returns ID
*/
synthesizeStream?(text: string, onStart: () => void, onData: (audioChunk: AudioBuffer) => void, onEnd: () => void, onError: (error: Error) => void): Promise<string>
}

8
temp.txt Normal file
View File

@ -0,0 +1,8 @@
// 不再自动清除回调函数,允许持续接收语音识别结果
// setTimeout(() => {
// // 发送重置命令,确保浏览器不会继续发送结果
// ASRService.cancelRecording()
//
// // 清除ASRService中的回调函数防止后续结果被处理
// ASRService.resultCallback = null
// }, 2000) // 2秒后强制取消作为安全措施

415
yarn.lock
View File

@ -3952,6 +3952,7 @@ __metadata:
diff: "npm:^7.0.0"
docx: "npm:^9.0.2"
dotenv-cli: "npm:^7.4.2"
edge-tts-node: "npm:^1.5.7"
electron: "npm:31.7.6"
electron-builder: "npm:^24.13.3"
electron-devtools-installer: "npm:^3.2.0"
@ -4439,6 +4440,17 @@ __metadata:
languageName: node
linkType: hard
"asn1.js@npm:^4.10.1":
version: 4.10.1
resolution: "asn1.js@npm:4.10.1"
dependencies:
bn.js: "npm:^4.0.0"
inherits: "npm:^2.0.1"
minimalistic-assert: "npm:^1.0.0"
checksum: 10c0/afa7f3ab9e31566c80175a75b182e5dba50589dcc738aa485be42bdd787e2a07246a4b034d481861123cbe646a7656f318f4f1cad2e9e5e808a210d5d6feaa88
languageName: node
linkType: hard
"asn1@npm:~0.2.3":
version: 0.2.6
resolution: "asn1@npm:0.2.6"
@ -4520,7 +4532,7 @@ __metadata:
languageName: node
linkType: hard
"axios@npm:^1.7.3, axios@npm:^1.7.7":
"axios@npm:^1.5.0, axios@npm:^1.7.3, axios@npm:^1.7.7":
version: 1.8.4
resolution: "axios@npm:1.8.4"
dependencies:
@ -4678,6 +4690,20 @@ __metadata:
languageName: node
linkType: hard
"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.9":
version: 4.12.1
resolution: "bn.js@npm:4.12.1"
checksum: 10c0/b7f37a0cd5e4b79142b6f4292d518b416be34ae55d6dd6b0f66f96550c8083a50ffbbf8bda8d0ab471158cb81aa74ea4ee58fe33c7802e4a30b13810e98df116
languageName: node
linkType: hard
"bn.js@npm:^5.2.1":
version: 5.2.1
resolution: "bn.js@npm:5.2.1"
checksum: 10c0/bed3d8bd34ec89dbcf9f20f88bd7d4a49c160fda3b561c7bb227501f974d3e435a48fb9b61bc3de304acab9215a3bda0803f7017ffb4d0016a0c3a740a283caa
languageName: node
linkType: hard
"body-parser@npm:^2.0.1":
version: 2.1.0
resolution: "body-parser@npm:2.1.0"
@ -4730,6 +4756,13 @@ __metadata:
languageName: node
linkType: hard
"brorand@npm:^1.0.1, brorand@npm:^1.1.0":
version: 1.1.0
resolution: "brorand@npm:1.1.0"
checksum: 10c0/6f366d7c4990f82c366e3878492ba9a372a73163c09871e80d82fb4ae0d23f9f8924cb8a662330308206e6b3b76ba1d528b4601c9ef73c2166b440b2ea3b7571
languageName: node
linkType: hard
"browser-image-compression@npm:^2.0.2":
version: 2.0.2
resolution: "browser-image-compression@npm:2.0.2"
@ -4739,6 +4772,72 @@ __metadata:
languageName: node
linkType: hard
"browserify-aes@npm:^1.0.4, browserify-aes@npm:^1.2.0":
version: 1.2.0
resolution: "browserify-aes@npm:1.2.0"
dependencies:
buffer-xor: "npm:^1.0.3"
cipher-base: "npm:^1.0.0"
create-hash: "npm:^1.1.0"
evp_bytestokey: "npm:^1.0.3"
inherits: "npm:^2.0.1"
safe-buffer: "npm:^5.0.1"
checksum: 10c0/967f2ae60d610b7b252a4cbb55a7a3331c78293c94b4dd9c264d384ca93354c089b3af9c0dd023534efdc74ffbc82510f7ad4399cf82bc37bc07052eea485f18
languageName: node
linkType: hard
"browserify-cipher@npm:^1.0.1":
version: 1.0.1
resolution: "browserify-cipher@npm:1.0.1"
dependencies:
browserify-aes: "npm:^1.0.4"
browserify-des: "npm:^1.0.0"
evp_bytestokey: "npm:^1.0.0"
checksum: 10c0/aa256dcb42bc53a67168bbc94ab85d243b0a3b56109dee3b51230b7d010d9b78985ffc1fb36e145c6e4db151f888076c1cfc207baf1525d3e375cbe8187fe27d
languageName: node
linkType: hard
"browserify-des@npm:^1.0.0":
version: 1.0.2
resolution: "browserify-des@npm:1.0.2"
dependencies:
cipher-base: "npm:^1.0.1"
des.js: "npm:^1.0.0"
inherits: "npm:^2.0.1"
safe-buffer: "npm:^5.1.2"
checksum: 10c0/943eb5d4045eff80a6cde5be4e5fbb1f2d5002126b5a4789c3c1aae3cdddb1eb92b00fb92277f512288e5c6af330730b1dbabcf7ce0923e749e151fcee5a074d
languageName: node
linkType: hard
"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.1.0":
version: 4.1.1
resolution: "browserify-rsa@npm:4.1.1"
dependencies:
bn.js: "npm:^5.2.1"
randombytes: "npm:^2.1.0"
safe-buffer: "npm:^5.2.1"
checksum: 10c0/b650ee1192e3d7f3d779edc06dd96ed8720362e72ac310c367b9d7fe35f7e8dbb983c1829142b2b3215458be8bf17c38adc7224920843024ed8cf39e19c513c0
languageName: node
linkType: hard
"browserify-sign@npm:^4.2.3":
version: 4.2.3
resolution: "browserify-sign@npm:4.2.3"
dependencies:
bn.js: "npm:^5.2.1"
browserify-rsa: "npm:^4.1.0"
create-hash: "npm:^1.2.0"
create-hmac: "npm:^1.1.7"
elliptic: "npm:^6.5.5"
hash-base: "npm:~3.0"
inherits: "npm:^2.0.4"
parse-asn1: "npm:^5.1.7"
readable-stream: "npm:^2.3.8"
safe-buffer: "npm:^5.2.1"
checksum: 10c0/30c0eba3f5970a20866a4d3fbba2c5bd1928cd24f47faf995f913f1499214c6f3be14bb4d6ec1ab5c6cafb1eca9cb76ba1c2e1c04ed018370634d4e659c77216
languageName: node
linkType: hard
"browserslist@npm:^4.21.1, browserslist@npm:^4.24.0":
version: 4.24.4
resolution: "browserslist@npm:4.24.4"
@ -4812,6 +4911,13 @@ __metadata:
languageName: node
linkType: hard
"buffer-xor@npm:^1.0.3":
version: 1.0.3
resolution: "buffer-xor@npm:1.0.3"
checksum: 10c0/fd269d0e0bf71ecac3146187cfc79edc9dbb054e2ee69b4d97dfb857c6d997c33de391696d04bdd669272751fa48e7872a22f3a6c7b07d6c0bc31dbe02a4075c
languageName: node
linkType: hard
"buffer@npm:^5.1.0, buffer@npm:^5.2.0, buffer@npm:^5.2.1, buffer@npm:^5.5.0":
version: 5.7.1
resolution: "buffer@npm:5.7.1"
@ -5209,6 +5315,16 @@ __metadata:
languageName: node
linkType: hard
"cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3":
version: 1.0.6
resolution: "cipher-base@npm:1.0.6"
dependencies:
inherits: "npm:^2.0.4"
safe-buffer: "npm:^5.2.1"
checksum: 10c0/f73268e0ee6585800875d9748f2a2377ae7c2c3375cba346f75598ac6f6bc3a25dec56e984a168ced1a862529ffffe615363f750c40349039d96bd30fba0fca8
languageName: node
linkType: hard
"classcat@npm:^5.0.3":
version: 5.0.5
resolution: "classcat@npm:5.0.5"
@ -5601,6 +5717,43 @@ __metadata:
languageName: node
linkType: hard
"create-ecdh@npm:^4.0.4":
version: 4.0.4
resolution: "create-ecdh@npm:4.0.4"
dependencies:
bn.js: "npm:^4.1.0"
elliptic: "npm:^6.5.3"
checksum: 10c0/77b11a51360fec9c3bce7a76288fc0deba4b9c838d5fb354b3e40c59194d23d66efe6355fd4b81df7580da0661e1334a235a2a5c040b7569ba97db428d466e7f
languageName: node
linkType: hard
"create-hash@npm:^1.1.0, create-hash@npm:^1.1.2, create-hash@npm:^1.2.0":
version: 1.2.0
resolution: "create-hash@npm:1.2.0"
dependencies:
cipher-base: "npm:^1.0.1"
inherits: "npm:^2.0.1"
md5.js: "npm:^1.3.4"
ripemd160: "npm:^2.0.1"
sha.js: "npm:^2.4.0"
checksum: 10c0/d402e60e65e70e5083cb57af96d89567954d0669e90550d7cec58b56d49c4b193d35c43cec8338bc72358198b8cbf2f0cac14775b651e99238e1cf411490f915
languageName: node
linkType: hard
"create-hmac@npm:^1.1.4, create-hmac@npm:^1.1.7":
version: 1.1.7
resolution: "create-hmac@npm:1.1.7"
dependencies:
cipher-base: "npm:^1.0.3"
create-hash: "npm:^1.1.0"
inherits: "npm:^2.0.1"
ripemd160: "npm:^2.0.0"
safe-buffer: "npm:^5.0.1"
sha.js: "npm:^2.4.8"
checksum: 10c0/24332bab51011652a9a0a6d160eed1e8caa091b802335324ae056b0dcb5acbc9fcf173cf10d128eba8548c3ce98dfa4eadaa01bd02f44a34414baee26b651835
languageName: node
linkType: hard
"cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6":
version: 7.0.6
resolution: "cross-spawn@npm:7.0.6"
@ -5619,6 +5772,26 @@ __metadata:
languageName: node
linkType: hard
"crypto-browserify@npm:^3.12.0":
version: 3.12.1
resolution: "crypto-browserify@npm:3.12.1"
dependencies:
browserify-cipher: "npm:^1.0.1"
browserify-sign: "npm:^4.2.3"
create-ecdh: "npm:^4.0.4"
create-hash: "npm:^1.2.0"
create-hmac: "npm:^1.1.7"
diffie-hellman: "npm:^5.0.3"
hash-base: "npm:~3.0.4"
inherits: "npm:^2.0.4"
pbkdf2: "npm:^3.1.2"
public-encrypt: "npm:^4.0.3"
randombytes: "npm:^2.1.0"
randomfill: "npm:^1.0.4"
checksum: 10c0/184a2def7b16628e79841243232ab5497f18d8e158ac21b7ce90ab172427d0a892a561280adc08f9d4d517bce8db2a5b335dc21abb970f787f8e874bd7b9db7d
languageName: node
linkType: hard
"css-box-model@npm:^1.2.1":
version: 1.2.1
resolution: "css-box-model@npm:1.2.1"
@ -6078,6 +6251,16 @@ __metadata:
languageName: node
linkType: hard
"des.js@npm:^1.0.0":
version: 1.1.0
resolution: "des.js@npm:1.1.0"
dependencies:
inherits: "npm:^2.0.1"
minimalistic-assert: "npm:^1.0.0"
checksum: 10c0/671354943ad67493e49eb4c555480ab153edd7cee3a51c658082fcde539d2690ed2a4a0b5d1f401f9cde822edf3939a6afb2585f32c091f2d3a1b1665cd45236
languageName: node
linkType: hard
"destroy@npm:^1.2.0":
version: 1.2.0
resolution: "destroy@npm:1.2.0"
@ -6149,6 +6332,17 @@ __metadata:
languageName: node
linkType: hard
"diffie-hellman@npm:^5.0.3":
version: 5.0.3
resolution: "diffie-hellman@npm:5.0.3"
dependencies:
bn.js: "npm:^4.1.0"
miller-rabin: "npm:^4.0.0"
randombytes: "npm:^2.0.0"
checksum: 10c0/ce53ccafa9ca544b7fc29b08a626e23a9b6562efc2a98559a0c97b4718937cebaa9b5d7d0a05032cc9c1435e9b3c1532b9e9bf2e0ede868525922807ad6e1ecf
languageName: node
linkType: hard
"dingbat-to-unicode@npm:^1.0.1":
version: 1.0.1
resolution: "dingbat-to-unicode@npm:1.0.1"
@ -6383,6 +6577,22 @@ __metadata:
languageName: node
linkType: hard
"edge-tts-node@npm:^1.5.7":
version: 1.5.7
resolution: "edge-tts-node@npm:1.5.7"
dependencies:
axios: "npm:^1.5.0"
buffer: "npm:^6.0.3"
crypto-browserify: "npm:^3.12.0"
isomorphic-ws: "npm:^5.0.0"
process: "npm:^0.11.10"
randombytes: "npm:^2.1.0"
stream-browserify: "npm:^3.0.0"
ws: "npm:^8.14.1"
checksum: 10c0/83b5df1d5312163006643fb6e6a9ca37ca6bc4c871b66f0c800f3e0bb1b2473fa0d67c125b256e2d93b493b8fbf59aaab06f60d4fa15fe3b7e2d3fb796016b1f
languageName: node
linkType: hard
"ee-first@npm:1.1.1":
version: 1.1.1
resolution: "ee-first@npm:1.1.1"
@ -6548,6 +6758,21 @@ __metadata:
languageName: node
linkType: hard
"elliptic@npm:^6.5.3, elliptic@npm:^6.5.5":
version: 6.6.1
resolution: "elliptic@npm:6.6.1"
dependencies:
bn.js: "npm:^4.11.9"
brorand: "npm:^1.1.0"
hash.js: "npm:^1.0.0"
hmac-drbg: "npm:^1.0.1"
inherits: "npm:^2.0.4"
minimalistic-assert: "npm:^1.0.1"
minimalistic-crypto-utils: "npm:^1.0.1"
checksum: 10c0/8b24ef782eec8b472053793ea1e91ae6bee41afffdfcb78a81c0a53b191e715cbe1292aa07165958a9bbe675bd0955142560b1a007ffce7d6c765bcaf951a867
languageName: node
linkType: hard
"emittery@npm:^1.0.3":
version: 1.1.0
resolution: "emittery@npm:1.1.0"
@ -7304,6 +7529,17 @@ __metadata:
languageName: node
linkType: hard
"evp_bytestokey@npm:^1.0.0, evp_bytestokey@npm:^1.0.3":
version: 1.0.3
resolution: "evp_bytestokey@npm:1.0.3"
dependencies:
md5.js: "npm:^1.3.4"
node-gyp: "npm:latest"
safe-buffer: "npm:^5.1.1"
checksum: 10c0/77fbe2d94a902a80e9b8f5a73dcd695d9c14899c5e82967a61b1fc6cbbb28c46552d9b127cff47c45fcf684748bdbcfa0a50410349109de87ceb4b199ef6ee99
languageName: node
linkType: hard
"execa@npm:^8.0.1":
version: 8.0.1
resolution: "execa@npm:8.0.1"
@ -8480,7 +8716,28 @@ __metadata:
languageName: node
linkType: hard
"hash.js@npm:^1.1.7":
"hash-base@npm:^3.0.0":
version: 3.1.0
resolution: "hash-base@npm:3.1.0"
dependencies:
inherits: "npm:^2.0.4"
readable-stream: "npm:^3.6.0"
safe-buffer: "npm:^5.2.0"
checksum: 10c0/663eabcf4173326fbb65a1918a509045590a26cc7e0964b754eef248d281305c6ec9f6b31cb508d02ffca383ab50028180ce5aefe013e942b44a903ac8dc80d0
languageName: node
linkType: hard
"hash-base@npm:~3.0, hash-base@npm:~3.0.4":
version: 3.0.5
resolution: "hash-base@npm:3.0.5"
dependencies:
inherits: "npm:^2.0.4"
safe-buffer: "npm:^5.2.1"
checksum: 10c0/6dc185b79bad9b6d525cd132a588e4215380fdc36fec6f7a8a58c5db8e3b642557d02ad9c367f5e476c7c3ad3ccffa3607f308b124e1ed80e3b80a1b254db61e
languageName: node
linkType: hard
"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3, hash.js@npm:^1.1.7":
version: 1.1.7
resolution: "hash.js@npm:1.1.7"
dependencies:
@ -8704,6 +8961,17 @@ __metadata:
languageName: node
linkType: hard
"hmac-drbg@npm:^1.0.1":
version: 1.0.1
resolution: "hmac-drbg@npm:1.0.1"
dependencies:
hash.js: "npm:^1.0.3"
minimalistic-assert: "npm:^1.0.0"
minimalistic-crypto-utils: "npm:^1.0.1"
checksum: 10c0/f3d9ba31b40257a573f162176ac5930109816036c59a09f901eb2ffd7e5e705c6832bedfff507957125f2086a0ab8f853c0df225642a88bf1fcaea945f20600d
languageName: node
linkType: hard
"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2":
version: 3.3.2
resolution: "hoist-non-react-statics@npm:3.3.2"
@ -9073,7 +9341,7 @@ __metadata:
languageName: node
linkType: hard
"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3":
"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4":
version: 2.0.4
resolution: "inherits@npm:2.0.4"
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
@ -9441,6 +9709,15 @@ __metadata:
languageName: node
linkType: hard
"isomorphic-ws@npm:^5.0.0":
version: 5.0.0
resolution: "isomorphic-ws@npm:5.0.0"
peerDependencies:
ws: "*"
checksum: 10c0/a058ac8b5e6efe9e46252cb0bc67fd325005d7216451d1a51238bc62d7da8486f828ef017df54ddf742e0fffcbe4b1bcc2a66cc115b027ed0180334cd18df252
languageName: node
linkType: hard
"isstream@npm:~0.1.2":
version: 0.1.2
resolution: "isstream@npm:0.1.2"
@ -10372,6 +10649,17 @@ __metadata:
languageName: node
linkType: hard
"md5.js@npm:^1.3.4":
version: 1.3.5
resolution: "md5.js@npm:1.3.5"
dependencies:
hash-base: "npm:^3.0.0"
inherits: "npm:^2.0.1"
safe-buffer: "npm:^5.1.2"
checksum: 10c0/b7bd75077f419c8e013fc4d4dada48be71882e37d69a44af65a2f2804b91e253441eb43a0614423a1c91bb830b8140b0dc906bc797245e2e275759584f4efcc5
languageName: node
linkType: hard
"md5@npm:^2.3.0":
version: 2.3.0
resolution: "md5@npm:2.3.0"
@ -11288,6 +11576,18 @@ __metadata:
languageName: node
linkType: hard
"miller-rabin@npm:^4.0.0":
version: 4.0.1
resolution: "miller-rabin@npm:4.0.1"
dependencies:
bn.js: "npm:^4.0.0"
brorand: "npm:^1.0.1"
bin:
miller-rabin: bin/miller-rabin
checksum: 10c0/26b2b96f6e49dbcff7faebb78708ed2f5f9ae27ac8cbbf1d7c08f83cf39bed3d418c0c11034dce997da70d135cc0ff6f3a4c15dc452f8e114c11986388a64346
languageName: node
linkType: hard
"mime-db@npm:1.52.0":
version: 1.52.0
resolution: "mime-db@npm:1.52.0"
@ -11405,13 +11705,20 @@ __metadata:
languageName: node
linkType: hard
"minimalistic-assert@npm:^1.0.1":
"minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1":
version: 1.0.1
resolution: "minimalistic-assert@npm:1.0.1"
checksum: 10c0/96730e5601cd31457f81a296f521eb56036e6f69133c0b18c13fe941109d53ad23a4204d946a0d638d7f3099482a0cec8c9bb6d642604612ce43ee536be3dddd
languageName: node
linkType: hard
"minimalistic-crypto-utils@npm:^1.0.1":
version: 1.0.1
resolution: "minimalistic-crypto-utils@npm:1.0.1"
checksum: 10c0/790ecec8c5c73973a4fbf2c663d911033e8494d5fb0960a4500634766ab05d6107d20af896ca2132e7031741f19888154d44b2408ada0852446705441383e9f8
languageName: node
linkType: hard
"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
version: 3.1.2
resolution: "minimatch@npm:3.1.2"
@ -12473,6 +12780,20 @@ __metadata:
languageName: node
linkType: hard
"parse-asn1@npm:^5.0.0, parse-asn1@npm:^5.1.7":
version: 5.1.7
resolution: "parse-asn1@npm:5.1.7"
dependencies:
asn1.js: "npm:^4.10.1"
browserify-aes: "npm:^1.2.0"
evp_bytestokey: "npm:^1.0.3"
hash-base: "npm:~3.0"
pbkdf2: "npm:^3.1.2"
safe-buffer: "npm:^5.2.1"
checksum: 10c0/05eb5937405c904eb5a7f3633bab1acc11f4ae3478a07ef5c6d81ce88c3c0e505ff51f9c7b935ebc1265c868343793698fc91025755a895d0276f620f95e8a82
languageName: node
linkType: hard
"parse-bmfont-ascii@npm:^1.0.3":
version: 1.0.6
resolution: "parse-bmfont-ascii@npm:1.0.6"
@ -12661,6 +12982,19 @@ __metadata:
languageName: node
linkType: hard
"pbkdf2@npm:^3.1.2":
version: 3.1.2
resolution: "pbkdf2@npm:3.1.2"
dependencies:
create-hash: "npm:^1.1.2"
create-hmac: "npm:^1.1.4"
ripemd160: "npm:^2.0.1"
safe-buffer: "npm:^5.0.1"
sha.js: "npm:^2.4.8"
checksum: 10c0/5a30374e87d33fa080a92734d778cf172542cc7e41b96198c4c88763997b62d7850de3fbda5c3111ddf79805ee7c1da7046881c90ac4920b5e324204518b05fd
languageName: node
linkType: hard
"pdf-parse@npm:1.1.1":
version: 1.1.1
resolution: "pdf-parse@npm:1.1.1"
@ -13054,6 +13388,20 @@ __metadata:
languageName: node
linkType: hard
"public-encrypt@npm:^4.0.3":
version: 4.0.3
resolution: "public-encrypt@npm:4.0.3"
dependencies:
bn.js: "npm:^4.1.0"
browserify-rsa: "npm:^4.0.0"
create-hash: "npm:^1.1.0"
parse-asn1: "npm:^5.0.0"
randombytes: "npm:^2.0.1"
safe-buffer: "npm:^5.1.2"
checksum: 10c0/6c2cc19fbb554449e47f2175065d6b32f828f9b3badbee4c76585ac28ae8641aafb9bb107afc430c33c5edd6b05dbe318df4f7d6d7712b1093407b11c4280700
languageName: node
linkType: hard
"pump@npm:^3.0.0":
version: 3.0.2
resolution: "pump@npm:3.0.2"
@ -13138,6 +13486,25 @@ __metadata:
languageName: node
linkType: hard
"randombytes@npm:^2.0.0, randombytes@npm:^2.0.1, randombytes@npm:^2.0.5, randombytes@npm:^2.1.0":
version: 2.1.0
resolution: "randombytes@npm:2.1.0"
dependencies:
safe-buffer: "npm:^5.1.0"
checksum: 10c0/50395efda7a8c94f5dffab564f9ff89736064d32addf0cc7e8bf5e4166f09f8ded7a0849ca6c2d2a59478f7d90f78f20d8048bca3cdf8be09d8e8a10790388f3
languageName: node
linkType: hard
"randomfill@npm:^1.0.4":
version: 1.0.4
resolution: "randomfill@npm:1.0.4"
dependencies:
randombytes: "npm:^2.0.5"
safe-buffer: "npm:^5.1.0"
checksum: 10c0/11aeed35515872e8f8a2edec306734e6b74c39c46653607f03c68385ab8030e2adcc4215f76b5e4598e028c4750d820afd5c65202527d831d2a5f207fe2bc87c
languageName: node
linkType: hard
"range-parser@npm:^1.2.1, range-parser@npm:~1.2.1":
version: 1.2.1
resolution: "range-parser@npm:1.2.1"
@ -13925,7 +14292,7 @@ __metadata:
languageName: node
linkType: hard
"readable-stream@npm:3, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0":
"readable-stream@npm:3, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0":
version: 3.6.2
resolution: "readable-stream@npm:3.6.2"
dependencies:
@ -13936,7 +14303,7 @@ __metadata:
languageName: node
linkType: hard
"readable-stream@npm:^2.0.6, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.0, readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6":
"readable-stream@npm:^2.0.6, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.0, readable-stream@npm:^2.3.5, readable-stream@npm:^2.3.8, readable-stream@npm:~2.3.6":
version: 2.3.8
resolution: "readable-stream@npm:2.3.8"
dependencies:
@ -14416,6 +14783,16 @@ __metadata:
languageName: node
linkType: hard
"ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1":
version: 2.0.2
resolution: "ripemd160@npm:2.0.2"
dependencies:
hash-base: "npm:^3.0.0"
inherits: "npm:^2.0.1"
checksum: 10c0/f6f0df78817e78287c766687aed4d5accbebc308a8e7e673fb085b9977473c1f139f0c5335d353f172a915bb288098430755d2ad3c4f30612f4dd0c901cd2c3a
languageName: node
linkType: hard
"roarr@npm:^2.15.3":
version: 2.15.4
resolution: "roarr@npm:2.15.4"
@ -14551,7 +14928,7 @@ __metadata:
languageName: node
linkType: hard
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0":
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0":
version: 5.2.1
resolution: "safe-buffer@npm:5.2.1"
checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3
@ -14747,6 +15124,18 @@ __metadata:
languageName: node
linkType: hard
"sha.js@npm:^2.4.0, sha.js@npm:^2.4.8":
version: 2.4.11
resolution: "sha.js@npm:2.4.11"
dependencies:
inherits: "npm:^2.0.1"
safe-buffer: "npm:^5.0.1"
bin:
sha.js: ./bin.js
checksum: 10c0/b7a371bca8821c9cc98a0aeff67444a03d48d745cb103f17228b96793f455f0eb0a691941b89ea1e60f6359207e36081d9be193252b0f128e0daf9cfea2815a5
languageName: node
linkType: hard
"shallowequal@npm:1.1.0":
version: 1.1.0
resolution: "shallowequal@npm:1.1.0"
@ -15135,6 +15524,16 @@ __metadata:
languageName: node
linkType: hard
"stream-browserify@npm:^3.0.0":
version: 3.0.0
resolution: "stream-browserify@npm:3.0.0"
dependencies:
inherits: "npm:~2.0.4"
readable-stream: "npm:^3.5.0"
checksum: 10c0/ec3b975a4e0aa4b3dc5e70ffae3fc8fd29ac725353a14e72f213dff477b00330140ad014b163a8cbb9922dfe90803f81a5ea2b269e1bbfd8bd71511b88f889ad
languageName: node
linkType: hard
"stream-head@npm:^3.0.0":
version: 3.0.0
resolution: "stream-head@npm:3.0.0"
@ -16670,7 +17069,7 @@ __metadata:
languageName: node
linkType: hard
"ws@npm:^8.13.0, ws@npm:^8.18.0":
"ws@npm:^8.13.0, ws@npm:^8.14.1, ws@npm:^8.18.0":
version: 8.18.1
resolution: "ws@npm:8.18.1"
peerDependencies: