|
|
@@ -0,0 +1,150 @@
|
|
|
+import { useEffect, useState, useRef, useMemo } from "react"
|
|
|
+import { Typography } from "antd"
|
|
|
+import styles from "../index.module.scss"
|
|
|
+
|
|
|
+const { Text } = Typography
|
|
|
+
|
|
|
+interface ITranslationItem {
|
|
|
+ lang: string
|
|
|
+ text: string
|
|
|
+}
|
|
|
+
|
|
|
+interface ICaptionData {
|
|
|
+ userName: string
|
|
|
+ content: string
|
|
|
+ translations?: ITranslationItem[]
|
|
|
+ timestamp: number
|
|
|
+}
|
|
|
+
|
|
|
+interface ICaptionDisplayProps {
|
|
|
+ visible?: boolean
|
|
|
+ captionLanguages?: string[]
|
|
|
+ sttData?: any
|
|
|
+}
|
|
|
+
|
|
|
+const CaptionDisplay = (props: ICaptionDisplayProps) => {
|
|
|
+ const { visible = true, captionLanguages = ["live"], sttData = {} } = props
|
|
|
+ const [captionList, setCaptionList] = useState<ICaptionData[]>([])
|
|
|
+ const captionRef = useRef<HTMLDivElement>(null)
|
|
|
+
|
|
|
+ // 监听STT数据变化,更新字幕列表
|
|
|
+ useEffect(() => {
|
|
|
+ if (sttData.transcribe1 || sttData.transcribe2) {
|
|
|
+ const newCaption: ICaptionData = {
|
|
|
+ userName: sttData.userName || "用户",
|
|
|
+ content: sttData.transcribe1 || sttData.transcribe2 || "",
|
|
|
+ translations: [],
|
|
|
+ timestamp: Date.now(),
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理翻译文本
|
|
|
+ if (sttData.translate1List?.length) {
|
|
|
+ sttData.translate1List.forEach((text: string, index: number) => {
|
|
|
+ if (text) {
|
|
|
+ newCaption.translations?.push({
|
|
|
+ lang: `translate${index + 1}`,
|
|
|
+ text,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sttData.translate2List?.length) {
|
|
|
+ sttData.translate2List.forEach((text: string, index: number) => {
|
|
|
+ if (text) {
|
|
|
+ newCaption.translations?.push({
|
|
|
+ lang: `translate${index + 2}`,
|
|
|
+ text,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ setCaptionList((prev) => {
|
|
|
+ // 只保留最新的5条字幕
|
|
|
+ const newList = [...prev, newCaption]
|
|
|
+ return newList.slice(-5)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }, [sttData])
|
|
|
+
|
|
|
+ // 根据选择的语言过滤字幕内容
|
|
|
+ const filteredCaptionList = useMemo(() => {
|
|
|
+ return captionList.map((caption) => {
|
|
|
+ const filteredCaption: ICaptionData = {
|
|
|
+ ...caption,
|
|
|
+ translations: caption.translations?.filter((translation) =>
|
|
|
+ captionLanguages.includes(translation.lang),
|
|
|
+ ),
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果不显示原文,清空内容
|
|
|
+ if (!captionLanguages.includes("live")) {
|
|
|
+ filteredCaption.content = ""
|
|
|
+ }
|
|
|
+
|
|
|
+ return filteredCaption
|
|
|
+ })
|
|
|
+ }, [captionList, captionLanguages])
|
|
|
+
|
|
|
+ // 自动滚动到最新字幕
|
|
|
+ useEffect(() => {
|
|
|
+ if (captionRef.current) {
|
|
|
+ captionRef.current.scrollTop = captionRef.current.scrollHeight
|
|
|
+ }
|
|
|
+ }, [captionList])
|
|
|
+
|
|
|
+ if (!visible) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className={styles.captionDisplay}>
|
|
|
+ <div className={styles.captionHeader}>
|
|
|
+ <Text strong>实时转录结果</Text>
|
|
|
+ <Text type="secondary">
|
|
|
+ {captionList.length > 0 ? `最新 ${captionList.length} 条` : "等待转录结果..."}
|
|
|
+ </Text>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className={styles.captionContainer} ref={captionRef}>
|
|
|
+ {filteredCaptionList.length === 0 ? (
|
|
|
+ <div className={styles.emptyCaption}>
|
|
|
+ <Text type="secondary">暂无转录内容,请开始语音转录...</Text>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ filteredCaptionList.map((item, index) => (
|
|
|
+ <div key={index} className={styles.captionItem}>
|
|
|
+ <div className={styles.captionMeta}>
|
|
|
+ <Text className={styles.userName}>{item.userName}:</Text>
|
|
|
+ <Text type="secondary" className={styles.timestamp}>
|
|
|
+ {new Date(item.timestamp).toLocaleTimeString()}
|
|
|
+ </Text>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {item.content && (
|
|
|
+ <div className={styles.captionContent}>
|
|
|
+ <Text>{item.content}</Text>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {item.translations && item.translations.length > 0 && (
|
|
|
+ <div className={styles.translations}>
|
|
|
+ {item.translations.map((translation, transIndex) => (
|
|
|
+ <div key={transIndex} className={styles.translationItem}>
|
|
|
+ <Text type="secondary">
|
|
|
+ [{translation.lang}]: {translation.text}
|
|
|
+ </Text>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ ))
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export default CaptionDisplay
|