前端
样式将使用 Tailwind 完成,但我不会介绍如何设置 Tailwind。您可以在此处阅读有关如何设置和使用 Tailwind 的信息。
创建 TimePicker 组件
由于我们的 API 接受startTime和endTime,所以让我们使用TimePicker来创建一个组件react-select。
使用react-select只是将其他功能添加到选择菜单中,例如搜索选项,但这对本文并不重要,可以跳过。
让我们分解一下TimePicker下面的 React 组件:
进口和组件申报。首先,我们导入必要的包并声明我们的TimePicker
组件。该TimePicker
组件接受 props id
、label
、value
、onChange
和maxDuration
:
import React, { useState, useEffect, useCallback } from 'react'; import Select from 'react-select'; const TimePicker = ({ id, label, value, onChange, maxDuration }) => {
解析value
prop。该value
prop 预计是一个时间字符串(格式HH:MM:SS
)。这里我们将时间分为小时、分钟和秒:
const [hours, minutes, seconds] = value.split(':').map((v) => parseInt(v, 10));
计算最大值。maxDuration
是根据音频持续时间可以选择的最大时间(以秒为单位)。它被转换为小时、分钟和秒:
const validMaxDuration = maxDuration === Infinity ? 0 : maxDuration const maxHours = Math.floor(validMaxDuration / 3600); const maxMinutes = Math.floor((validMaxDuration % 3600) / 60); const maxSeconds = Math.floor(validMaxDuration % 60);
时间选项选择。我们为可能的小时、分钟和秒选项创建数组,并创建状态挂钩来管理分钟和秒选项:
const hoursOptions = Array.from({ length: Math.max(0, maxHours) + 1 }, (_, i) => i); const minutesSecondsOptions = Array.from({ length: 60 }, (_, i) => i); const [minuteOptions, setMinuteOptions] = useState(minutesSecondsOptions); const [secondOptions, setSecondOptions] = useState(minutesSecondsOptions);
更新值函数。onChange
该函数通过调用作为 prop 传入的函数来更新当前值:
const updateValue = (newHours, newMinutes, newSeconds) => { onChange(`${String(newHours).padStart(2, '0')}:${String(newMinutes).padStart(2, '0')}:${String(newSeconds).padStart(2, '0')}`); };
更新分秒选项功能。此功能根据所选的小时和分钟更新分钟和秒选项:
const updateMinuteAndSecondOptions = useCallback((newHours, newMinutes) => { const minutesSecondsOptions = Array.from({ length: 60 }, (_, i) => i); let newMinuteOptions = minutesSecondsOptions; let newSecondOptions = minutesSecondsOptions; if (newHours === maxHours) { newMinuteOptions = Array.from({ length: Math.max(0, maxMinutes) + 1 }, (_, i) => i); if (newMinutes === maxMinutes) { newSecondOptions = Array.from({ length: Math.max(0, maxSeconds) + 1 }, (_, i) => i); } } setMinuteOptions(newMinuteOptions); setSecondOptions(newSecondOptions); }, [maxHours, maxMinutes, maxSeconds]);
效果挂钩。这会调用updateMinuteAndSecondOptions
何时hours
或minutes
更改:
useEffect(() => { updateMinuteAndSecondOptions(hours, minutes); }, [hours, minutes, updateMinuteAndSecondOptions]);
辅助功能。这两个辅助函数将时间整数转换为选择选项,反之亦然:
const toOption = (value) => ({ value: value, label: String(value).padStart(2, '0'), }); const fromOption = (option) => option.value;
渲染。该render
函数显示时间选择器,它由库管理的三个下拉菜单(小时、分钟、秒)组成react-select
。更改选择框中的值将调用updateValue
和updateMinuteAndSecondOptions
,这已在上面进行了解释。
您可以在GitHub上找到 TimePicker 组件的完整源代码。
主要成分
现在让我们通过替换 来构建主要的前端组件App.js
。
应用程序组件将实现具有以下功能的转录页面:
- 定义时间格式转换的辅助函数。
- 更新startTime并endTime基于TimePicker组件的选择。
- 定义一个getAudioDuration函数来检索音频文件的持续时间并更新audioDuration状态。
- 处理要转录的音频文件的文件上传。
- 定义一个transcribeAudio函数,通过向我们的 API 发出 HTTP POST 请求来发送音频文件。
- 渲染文件上传的 UI。
- TimePicker用于选择startTime和 的渲染组件endTime。
- 显示通知消息。
- 显示转录的文本。
让我们将该组件分解为几个较小的部分:
导入和辅助函数。导入必要的模块并定义时间转换的辅助函数:
import React, { useState, useCallback } from 'react'; import { useDropzone } from 'react-dropzone'; // for file upload import axios from 'axios'; // to make network request import TimePicker from './TimePicker'; // our custom TimePicker import { toast, ToastContainer } from 'react-toastify'; // for toast notification // Helper functions (timeToSeconds, secondsToTime, timeToMinutesAndSeconds)
组件声明和状态挂钩。声明TranscriptionPage
组件并初始化状态挂钩:
const TranscriptionPage = () => { const [uploading, setUploading] = useState(false); const [transcription, setTranscription] = useState(''); const [audioFile, setAudioFile] = useState(null); const [startTime, setStartTime] = useState('00:00:00'); const [endTime, setEndTime] = useState('00:10:00'); // 10 minutes default endtime const [audioDuration, setAudioDuration] = useState(null); // ...
事件处理程序。定义各种事件处理程序 - 用于处理开始时间更改、获取音频持续时间、处理文件删除和转录音频:
const handleStartTimeChange = (newStartTime) => { //... }; const getAudioDuration = (file) => { //... }; const onDrop = useCallback((acceptedFiles) => { //... }, []); const transcribeAudio = async () => { // we'll explain this in detail shortly //... };
使用 Dropzone 挂钩。使用库useDropzone
中的钩子react-dropzone
来处理文件丢失:
const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({ onDrop, accept: 'audio/*', });
渲染。最后,渲染组件。这包括用于文件上传的拖放区、TimePicker
用于设置开始和结束时间的组件、用于启动转录过程的按钮以及用于显示转录结果的显示。
该transcribeAudio
函数是一个异步函数,负责将音频文件发送到服务器进行转录。让我们来分解一下:
const transcribeAudio = async () => { setUploading(true); try { const formData = new FormData(); audioFile && formData.append('file', audioFile); formData.append('startTime', timeToMinutesAndSeconds(startTime)); formData.append('endTime', timeToMinutesAndSeconds(endTime)); const response = await axios.post(`http://localhost:3001/api/transcribe`, formData, { headers: { 'Content-Type': 'multipart/form-data' }, }); setTranscription(response.data.transcription); toast.success('Transcription successful.') } catch (error) { toast.error('An error occurred during transcription.'); } finally { setUploading(false); } };
下面是更详细的介绍:
- setUploading(true);。此行将uploading状态设置为true,我们用它来向用户指示转录过程已经开始。
- const formData = new FormData();。FormData是一个 Web API,用于将表单数据发送到服务器。它允许我们发送键值对,其中值可以是 Blob、文件或字符串。
- 如果 不为 null ( ),则audioFile会附加到对象。开始时间和结束时间也会附加到对象中,但首先会转换为格式。formData``audioFile && formData.append('file', audioFile);``formData``MM:SS
- 该axios.post方法用于将 发送formData到服务器端点 ( http://localhost:3001/api/transcribe)。更改http://localhost:3001为服务器地址。这是通过await关键字完成的,这意味着该函数将暂停并等待 Promise 被解析或被拒绝。
- 如果请求成功,响应对象将包含转录结果 ( response.data.transcription)。transcription然后使用该函数将其设置为状态setTranscription。然后会显示成功的 Toast 通知。
- 如果在此过程中发生错误,则会显示错误 Toast 通知。
- 在该finally块中,无论结果如何(成功或错误),uploading状态都会被设置回false以允许用户重试。
本质上,该transcribeAudio
函数负责协调整个转录过程,包括处理表单数据、发出服务器请求和处理服务器响应。
您可以在GitHub上找到 App 组件的完整源代码。
结论
我们已经到了最后,现在有了一个完整的 Web 应用程序,可以利用 Whisper 的强大功能将语音转录为文本。
我们绝对可以添加更多功能,但我会让您自己构建其余的功能。希望我们已经为您提供了一个良好的开端。
这是完整的源代码: