feat: 更新音频处理逻辑,添加最后识别文本功能并优化相关提示信息

This commit is contained in:
2025-12-23 23:53:42 +08:00
parent 4407c6157f
commit b6157deec9
5 changed files with 59 additions and 24 deletions

View File

@@ -7,7 +7,7 @@ import tailwindcss from '@tailwindcss/vite';
const isDev = process.env.NODE_ENV === 'development'; const isDev = process.env.NODE_ENV === 'development';
let target = process.env.VITE_API_URL || 'http://localhost:51015'; let target = process.env.VITE_API_URL || 'http://localhost:51515';
const apiProxy = { target: target, changeOrigin: true, ws: true, rewriteWsOrigin: true, secure: false, cookieDomainRewrite: 'localhost' }; const apiProxy = { target: target, changeOrigin: true, ws: true, rewriteWsOrigin: true, secure: false, cookieDomainRewrite: 'localhost' };
let proxy = { let proxy = {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@kevisual/light-code-center", "name": "@kevisual/light-code-center",
"version": "0.0.1", "version": "0.0.2",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"basename": "/root/light-code-center", "basename": "/root/light-code-center",
@@ -8,7 +8,7 @@
"dev": "astro dev", "dev": "astro dev",
"build": "astro build", "build": "astro build",
"preview": "astro preview", "preview": "astro preview",
"pub": "envision deploy ./dist -k light-code-center -v 0.0.1 -u", "pub": "envision deploy ./dist -k light-code-center -v 0.0.2 -u -y y",
"ui": "pnpm dlx shadcn@latest add ", "ui": "pnpm dlx shadcn@latest add ",
"sn": "pnpm dlx shadcn@latest add " "sn": "pnpm dlx shadcn@latest add "
}, },

View File

@@ -363,7 +363,8 @@ export const VadVoice = () => {
addVoice, addVoice,
setError: setStoreError, setError: setStoreError,
relatimeParialText, relatimeParialText,
relatimeFinalText relatimeFinalText,
lastRecognizedText
} = useVoiceStore(); } = useVoiceStore();
const showText = relatimeFinalText || relatimeParialText; const showText = relatimeFinalText || relatimeParialText;
// 使用设置 store // 使用设置 store
@@ -399,10 +400,11 @@ export const VadVoice = () => {
onSpeechEnd: async (audio) => { onSpeechEnd: async (audio) => {
try { try {
const wavBuffer = utils.encodeWAV(audio) const wavBuffer = utils.encodeWAV(audio)
const audioBlob = new Blob([wavBuffer], { type: 'audio/wav' })
const tempUrl = URL.createObjectURL(audioBlob)
const relatime = useVoiceStore.getState().relatime; const relatime = useVoiceStore.getState().relatime;
relatime?.sendBase64?.(utils.arrayBufferToBase64(wavBuffer)); relatime?.sendBase64?.(utils.arrayBufferToBase64(wavBuffer));
const audioBlob = new Blob([wavBuffer], { type: 'audio/wav' })
const tempUrl = URL.createObjectURL(audioBlob)
// 从实际音频文件获取准确时长 // 从实际音频文件获取准确时长
const getDuration = (): Promise<number> => { const getDuration = (): Promise<number> => {
return new Promise((resolve) => { return new Promise((resolve) => {
@@ -413,8 +415,9 @@ export const VadVoice = () => {
}); });
}); });
}; };
relatime?.showCostTime?.();
const duration = await getDuration(); const duration = await getDuration();
relatime?.showCostTime?.();
console.log(`Detected speech end. Duration: ${duration.toFixed(2)}s`); console.log(`Detected speech end. Duration: ${duration.toFixed(2)}s`);
// 使用 store 添加语音记录 // 使用 store 添加语音记录
@@ -431,7 +434,9 @@ export const VadVoice = () => {
onSpeechRealStart: () => { onSpeechRealStart: () => {
console.log('VAD real start'); console.log('VAD real start');
setRealListen(true); setRealListen(true);
} const relatime = useVoiceStore.getState().relatime;
relatime?.setStartTime?.(Date.now());
},
}); });
ref.current = myvad; ref.current = myvad;
@@ -524,8 +529,8 @@ export const VadVoice = () => {
voiceList.length === 0 ? ( voiceList.length === 0 ? (
<div className="text-center text-gray-400 text-sm py-8"> <div className="text-center text-gray-400 text-sm py-8">
<div className="mb-2">🎤</div> <div className="mb-2">🎤</div>
<div>No recordings yet</div> <div></div>
<div className="text-xs mt-1">Start talking to record</div> <div className="text-xs mt-1"></div>
</div> </div>
) : ( ) : (
<ShowVoicePlayer data={voiceList} /> <ShowVoicePlayer data={voiceList} />
@@ -537,8 +542,8 @@ export const VadVoice = () => {
initializeVAD(listen) initializeVAD(listen)
}}> }}>
<div className="mb-2">🎤</div> <div className="mb-2">🎤</div>
<div>Click anywhere to initialize microphone</div> <div></div>
<div className="text-xs mt-1">Browser requires user interaction for microphone access</div> <div className="text-xs mt-1">访</div>
</div> </div>
)} )}
</div> </div>
@@ -576,6 +581,27 @@ export const VadVoice = () => {
)} )}
</div> </div>
<div className=" "> <div className=" ">
{lastRecognizedText && (
<div className="flex">
<div className="text-xs text-gray-400 mt-1 truncate">
🕘 : {lastRecognizedText}
</div>
<div className="cursor-pointer " onClick={() => {
// copy
navigator.clipboard.writeText(lastRecognizedText).then(() => {
toast.success('已复制', {
autoClose: 500,
position: 'top-center'
});
}).catch((error) => {
console.error('复制失败:', error);
toast.error('复制失败,请手动选择文字复制');
});
}}>
<Copy className='text-gray-400' />
</div>
</div>
)}
{showText && ( {showText && (
<div className="flex"> <div className="flex">
<div className="text-xs text-gray-600 mt-1 truncate"> <div className="text-xs text-gray-600 mt-1 truncate">

View File

@@ -5,6 +5,7 @@ export class Relatime {
asr: WSServer asr: WSServer
ready = false ready = false
timeoutHandle: NodeJS.Timeout | null = null timeoutHandle: NodeJS.Timeout | null = null
startTime: number = 0
constructor() { constructor() {
// const url = new URL('/ws/asr', "http://localhost:51015") // const url = new URL('/ws/asr', "http://localhost:51015")
const url = new URL('/ws/asr', window.location.origin) const url = new URL('/ws/asr', window.location.origin)
@@ -51,15 +52,20 @@ export class Relatime {
} }
sendBase64(data: string) { sendBase64(data: string) {
if (!this.ready) return; if (!this.ready) return;
this.asr.ws.send(JSON.stringify({ voice: data, format: 'float32' })); console.log('send 花费时间:', Date.now() - this.startTime);
if (this.timeoutHandle) { this.asr.ws.send(JSON.stringify({ voice: data, format: 'float32', time: Date.now() }));
clearTimeout(this.timeoutHandle); // if (this.timeoutHandle) {
} // clearTimeout(this.timeoutHandle);
this.timeoutHandle = setTimeout(() => { // }
this.asr.sendBlankJson() // this.timeoutHandle = setTimeout(() => {
this.timeoutHandle = null; // this.asr.sendBlankJson()
}, 10000); // 5秒钟没有数据则发送空JSON保持连接 // this.timeoutHandle = null;
// }, 20000); // 20秒钟没有数据则发送空JSON保持连接
}
setStartTime(time: number) {
this.startTime = time;
}
showCostTime() {
console.log('当前花费时间:', Date.now() - this.startTime);
} }
} }

View File

@@ -26,6 +26,7 @@ interface VoiceState {
setLoading: (loading: boolean) => void; setLoading: (loading: boolean) => void;
relatime: Relatime; relatime: Relatime;
relatimeParialText: string; relatimeParialText: string;
lastRecognizedText: string;
relatimeFinalText: string; relatimeFinalText: string;
setRelatimeParialText: (text: string) => void; setRelatimeParialText: (text: string) => void;
setRelatimeFinalText: (text: string) => void; setRelatimeFinalText: (text: string) => void;
@@ -338,12 +339,14 @@ export const useVoiceStore = create<VoiceState>()(
relatimeFinalText: '', relatimeFinalText: '',
setRelatimeFinalText: (text: string) => { setRelatimeFinalText: (text: string) => {
set({ relatimeFinalText: text, relatimeParialText: '' }); const { relatimeFinalText } = get();
set(() => ({ relatimeFinalText: text, relatimeParialText: '', lastRecognizedText: relatimeFinalText }));
}, },
setRelatimeParialText: (text: string) => { setRelatimeParialText: (text: string) => {
set({ relatimeFinalText: '', relatimeParialText: text }); set({ relatimeParialText: text });
}, },
relatimeParialText: '', relatimeParialText: '',
lastRecognizedText: '',
}), }),
{ {
name: 'voice-store', // persist key name: 'voice-store', // persist key