diff --git a/projects/translate-h5/src/api/getDialog.ts b/projects/translate-h5/src/api/getDialog.ts new file mode 100644 index 0000000..4312f32 --- /dev/null +++ b/projects/translate-h5/src/api/getDialog.ts @@ -0,0 +1,39 @@ +import useAxios from "axios-hooks"; +import { Result } from "@/types/http"; + +export interface MockResult { + id: number; +} + +export interface MockPage { + id: number; +} + +/** + * fetch the data + * 详细使用可以查看 useAxios 的文档 + */ +export const useGetDialog = () => { + const url = `/app-api/ai/dialog/getDialog`; + + const [{ data, loading, error }, execute] = useAxios>( + { + url, + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }, + { manual: true } // 手动触发 + ); + + const getDialog = () => { + return execute({ + headers: { + "Content-Type": "application/json", + }, + }); + }; + + return { data, loading, error, getDialog }; +}; diff --git a/projects/translate-h5/src/api/translate.ts b/projects/translate-h5/src/api/translate.ts index ded13d2..4c8e61c 100644 --- a/projects/translate-h5/src/api/translate.ts +++ b/projects/translate-h5/src/api/translate.ts @@ -14,7 +14,7 @@ export interface MockPage { * 详细使用可以查看 useAxios 的文档 */ export const useUploadAudio = () => { - const url = `/app-api/ai/sample/translate`; + const url = `/app-api/ai/dialog/translate`; const [{ data, loading, error }, execute] = useAxios>( { diff --git a/projects/translate-h5/src/assets/translate/demo.WAV b/projects/translate-h5/src/assets/translate/demo.WAV new file mode 100644 index 0000000..9f4928e Binary files /dev/null and b/projects/translate-h5/src/assets/translate/demo.WAV differ diff --git a/projects/translate-h5/src/http/axios-instance.ts b/projects/translate-h5/src/http/axios-instance.ts index b28dd2e..5751940 100644 --- a/projects/translate-h5/src/http/axios-instance.ts +++ b/projects/translate-h5/src/http/axios-instance.ts @@ -47,5 +47,5 @@ class AxiosInstance { } } -const baseURL = import.meta.env.VITE_BASE_URL || "http://192.168.1.231:48080"; +const baseURL = import.meta.env.VITE_BASE_URL || "https://petshy.tashowz.com"; export const axiosInstance = new AxiosInstance(baseURL).getInstance(); diff --git a/projects/translate-h5/src/types/http.d.ts b/projects/translate-h5/src/types/http.d.ts index 019239c..045e8aa 100644 --- a/projects/translate-h5/src/types/http.d.ts +++ b/projects/translate-h5/src/types/http.d.ts @@ -1,14 +1,14 @@ interface Page { - total: number; - size: number; - current: number; - pages: number; - records: T[]; + total: number; + size: number; + current: number; + pages: number; + records: T[]; } export interface Result { - success: boolean; - code: number; - message: string; - data: T; -} \ No newline at end of file + success: boolean; + code: number; + message: string; + data: T; +} diff --git a/projects/translate-h5/src/view/home/translate/component/message/index.tsx b/projects/translate-h5/src/view/home/translate/component/message/index.tsx index faedb4f..0c04af3 100644 --- a/projects/translate-h5/src/view/home/translate/component/message/index.tsx +++ b/projects/translate-h5/src/view/home/translate/component/message/index.tsx @@ -6,10 +6,12 @@ import catSvg from "@/assets/translate/cat.svg"; import pigSvg from "@/assets/translate/pig.svg"; import { Message } from "../../../types"; import "./index.less"; +import { Refresh } from "@icon-park/react"; interface DefinedProps { data: Message[]; isRecording: boolean; + onRefresh: (formData: FormData, messageId: number) => void; } function Index(props: DefinedProps) { @@ -26,16 +28,16 @@ function Index(props: DefinedProps) { const onVoiceChange = () => { setIsPlating(!isPlaying); }; - const playAudio = (messageId: number, audioUrl: string) => { + const playAudio = (id: number, audioUrl: string) => { if (isRecording) { Toast.show("录音中,无法播放音频"); return; } - if (currentPlayingId === messageId) { - if (audioRefs.current[messageId]) { - audioRefs.current[messageId].pause(); - audioRefs.current[messageId].currentTime = 0; + if (currentPlayingId === id && audioRefs.current[id]) { + if (audioRefs.current[id]) { + audioRefs.current[id].pause(); + audioRefs.current[id].currentTime = 0; } setCurrentPlayingId(undefined); setIsPlating(false); @@ -43,11 +45,11 @@ function Index(props: DefinedProps) { } stopAllAudio(); - if (!audioRefs.current[messageId]) { - audioRefs.current[messageId] = new Audio(audioUrl); + if (!audioRefs.current[id]) { + audioRefs.current[id] = new Audio(audioUrl); } - const audio = audioRefs.current[messageId]; + const audio = audioRefs.current[id]; audio.currentTime = 0; audio.onended = () => { @@ -55,8 +57,24 @@ function Index(props: DefinedProps) { setIsPlating(false); }; - audio.onerror = (error) => { - console.error("音频播放错误:", error); + audio.onerror = () => { + const error = audio.error; + switch (error?.code) { + case MediaError.MEDIA_ERR_ABORTED: + console.warn("音频播放被用户中断"); + break; + case MediaError.MEDIA_ERR_NETWORK: + console.warn("网络错误,无法加载音频"); + break; + case MediaError.MEDIA_ERR_DECODE: + console.warn("解码失败,可能是格式不支持"); + break; + case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED: + console.warn("浏览器不支持该音频格式"); + break; + default: + console.warn("未知错误"); + }; Toast.show("音频播放失败"); setIsPlating(false); }; @@ -64,7 +82,7 @@ function Index(props: DefinedProps) { audio .play() .then(() => { - setCurrentPlayingId(messageId); + setCurrentPlayingId(id); setIsPlating(true); }) .catch((error) => { @@ -87,7 +105,7 @@ function Index(props: DefinedProps) { } }); }; - const renderAvatar = (type?: "pig" | "cat" | "dog") => { + const renderAvatar = (type?: "pig" | "cat" | "dog" | "") => { if (type === "pig") { ; } @@ -97,31 +115,43 @@ function Index(props: DefinedProps) { return ; }; + const refreshMessage = async (messageId: number, e: React.MouseEvent) => { + e.stopPropagation(); + const formData = new FormData(); + formData.append("msgId", messageId.toString()); + props.onRefresh(formData, messageId); + }; + return (
{data.map((item, index) => ( -
playAudio(item.id, item.audioUrl)}> - {renderAvatar(item.type)} +
playAudio(item.id, item.contentText)}> + {renderAvatar(item.petType)}
- {item.name} + {item.petName} - {item.timestamp} + {item.createTime}
-
{item.duration}''
+
{item.contentDuration}''
{item.isTranslating ? (
翻译中...
+ ) : item.transStatus === 1 ? ( +
{item.transResult ?? "暂无翻译结果"}
) : ( -
{item.translatedText}
+
+ 翻译失败,请重试 + refreshMessage(item.id, e)} size="12" fill="#333" /> +
)}
@@ -141,6 +171,8 @@ function Index(props: DefinedProps) {
+ +
); } diff --git a/projects/translate-h5/src/view/home/translate/component/voice/index.less b/projects/translate-h5/src/view/home/translate/component/voice/index.less index 2cfcb68..36222d3 100644 --- a/projects/translate-h5/src/view/home/translate/component/voice/index.less +++ b/projects/translate-h5/src/view/home/translate/component/voice/index.less @@ -1,11 +1,19 @@ .voice-record { - position: relative; + position: fixed; + bottom: 0; + width: 100%; + background: #fff; display: flex; align-items: center; justify-content: center; flex-direction: column; padding: 12px 0px; box-shadow: 1px 2px 4px 3px #eee; + // 不被挤压 + flex-shrink: 0; + min-height: 100px; /* 添加 min-height 防止被压缩 */ + height: 100px; /* 保持原始高度 */ + flex-basis: 100px; /* 明确指定基础大小,防止 flex 缩放影响 */ .adm-progress-circle-info { height: 32px; } diff --git a/projects/translate-h5/src/view/home/translate/component/voice/index.tsx b/projects/translate-h5/src/view/home/translate/component/voice/index.tsx index 78a835a..0d876c9 100644 --- a/projects/translate-h5/src/view/home/translate/component/voice/index.tsx +++ b/projects/translate-h5/src/view/home/translate/component/voice/index.tsx @@ -5,16 +5,16 @@ import microphoneSvg from "@/assets/translate/microphone.svg"; import microphoneDisabledSvg from "@/assets/translate/microphoneDisabledSvg.svg"; import { createStartRecordSound, createSendSound } from "@/utils/voice"; import "./index.less"; -import { useUploadAudio } from "@/api/translate"; import VConsole from "vconsole"; + interface DefinedProps { - onRecordingComplete: (url: string, finalDuration: number) => void; + onRecordingComplete: (url: string, finalDuration: number, formData: FormData) => void; isRecording: boolean; onSetIsRecording: (flag: boolean) => void; + dialogId: number; } function Index(props: DefinedProps) { - const { isRecording } = props; - const { loading: _uploadLoading, error: _uploadError, uploadAudio } = useUploadAudio(); + const { isRecording, dialogId } = props; const [hasPermission, setHasPermission] = useState(false); //是否有权限 const [isPermissioning, setIsPermissioning] = useState(true); //获取权限中 const [recordingDuration, setRecordingDuration] = useState(0); //录音时长进度 @@ -67,17 +67,6 @@ function Index(props: DefinedProps) { console.error("音效初始化失败:", error); } }; - const handleUploadAudio = async (formData: FormData) => { - // 打印FormData内容 - - console.log(formData); - try { - const response = await uploadAudio(formData); - console.log("上传成功:", response.data); - } catch (error) { - console.error("上传失败:", error); - } - }; const renderBtn = useCallback(() => { if (!hasPermission) { //没有权限 @@ -239,10 +228,24 @@ function Index(props: DefinedProps) { return; } - const formData = new FormData(); - formData.append("file", blob); + const formData: FormData = new FormData(); + formData.append( + "file", + blob, + new Date().getTime() + "." + blob.type.split("/").pop()?.split(";")[0] + ); - await handleUploadAudio(formData); + // 把demoWAV替换成blob + + // const response = await fetch(demoWAV); + // const arrayBuffer = await response.arrayBuffer(); + // const demoWAVBlob = new Blob([arrayBuffer], { type: "audio/wav" }); + + // formData.append("file", demoWAVBlob); + formData.append("dialogId", `${dialogId}`); + + // await handleUploadAudio(formData); + // const audioUrl = URL.createObjectURL(demoWAVBlob); const audioUrl = URL.createObjectURL(blob); const audio = new Audio(); audio.src = audioUrl; @@ -255,7 +258,9 @@ function Index(props: DefinedProps) { } // alert(audio.duration); playSound(sendSoundRef); - props.onRecordingComplete?.(audioUrl, Math.floor(audio.duration)); + const contentDuration = Math.floor(audio.duration); + formData.append("contentDuration", `${contentDuration}`); + props.onRecordingComplete?.(audioUrl, contentDuration, formData); }); }, [isCancelledRef, isRecording, sendSoundRef] diff --git a/projects/translate-h5/src/view/home/translate/index.tsx b/projects/translate-h5/src/view/home/translate/index.tsx index 4d32edb..987b5c1 100644 --- a/projects/translate-h5/src/view/home/translate/index.tsx +++ b/projects/translate-h5/src/view/home/translate/index.tsx @@ -4,8 +4,9 @@ import MessageCom from "./component/message"; import VoiceRecord from "./component/voice"; import { XPopup, FloatingMenu, type FloatMenuItemConfig } from "@workspace/shared"; import type { Message } from "../types"; +import { useGetDialog } from "@/api/getDialog"; +import { useUploadAudio } from "@/api/translate"; -import { mockTranslateAudio } from "@/utils/voice"; import dogSvg from "@/assets/translate/dog.svg"; import catSvg from "@/assets/translate/cat.svg"; import pigSvg from "@/assets/translate/pig.svg"; @@ -14,6 +15,7 @@ import SearchCom from "./component/search"; interface DefinedProps { searchVisible: boolean; } + const menuItems: FloatMenuItemConfig[] = [ { icon: , type: "dog" }, { icon: , type: "cat" }, @@ -25,30 +27,56 @@ const menuItems: FloatMenuItemConfig[] = [ ]; function Index(props: DefinedProps) { const { searchVisible } = props; + const { loading: _uploadLoading, error: _uploadError, getDialog } = useGetDialog(); + const { loading: _audioLoading, error: _audioError, uploadAudio } = useUploadAudio(); const [messages, setMessages] = useState([]); const [isRecording, setIsRecording] = useState(false); //是否录音中 const [currentLanguage, setCurrentLanguage] = useState(); const [visible, setVisible] = useState(false); + const [dialogId, setDialogId] = useState(0); + useEffect(() => { setCurrentLanguage(menuItems[0]); + + fetchInitialMessages(); }, []); + + // 添加初始化数据的逻辑 + const fetchInitialMessages = async () => { + try { + // 这里替换为实际的API调用 + // const response = await fetch('/api/messages'); + const response = await getDialog(); + // console.log(response); + + const initialMessages: Message[] = response.data?.data?.messages || []; + + setDialogId(response.data?.data?.dialogId); + setMessages(initialMessages); + } catch (error) { + console.error("获取初始化数据失败:", error); + Toast.show("获取消息失败"); + // 失败时设置为空数组 + setMessages([]); + } + }; + //完成录音 const onRecordingComplete = useCallback( - (audioUrl: string, actualDuration: number) => { + (audioUrl: string, actualDuration: number, formData: FormData) => { + console.log(audioUrl, "audioUrl") const newMessage: Message = { id: Date.now(), - type: "dog", - audioUrl, - name: "生无可恋喵", - duration: actualDuration, - timestamp: Date.now(), + contentText: audioUrl, + petName: "匹配档案中。。。", + contentDuration: actualDuration, isTranslating: true, }; setMessages((prev) => [...prev, newMessage]); setTimeout(() => { - onTranslateAudio(newMessage.id); + onTranslateAudio(formData, newMessage.id); }, 1000); Toast.show("语音已发送"); @@ -58,22 +86,45 @@ function Index(props: DefinedProps) { //翻译 const onTranslateAudio = useCallback( - async (messageId: number) => { + async (formData: FormData, id: number) => { try { - const translatedText = await mockTranslateAudio(); + const response = await uploadAudio(formData); + const translatedData = response.data; + console.log(translatedData, "translatedText"); - setMessages((prev) => - prev.map((msg) => - msg.id === messageId ? { ...msg, translatedText, isTranslating: false } : msg - ) - ); + if (translatedData.data.transStatus) { + setMessages((prev) => + prev.map((msg) => + msg.id === id + ? { + ...msg, + ...translatedData.data, + isTranslating: false, + } + : msg + ) + ); + } else { + setMessages((prev) => + prev.map((msg) => + msg.id === id + ? { + ...msg, + id: translatedData.data.id, + transStatus: translatedData.data.transStatus, + isTranslating: false, + } + : msg + ) + ); + } } catch (error) { console.error("翻译失败:", error); Toast.show("翻译失败,请重试"); setMessages((prev) => prev.map((msg) => - msg.id === messageId + msg.id === id ? { ...msg, isTranslating: false, @@ -87,6 +138,20 @@ function Index(props: DefinedProps) { [messages] ); + const refreshMessages = (formData: FormData, messageId: number) => { + setMessages((prev) => + prev.map((msg) => + msg.id === messageId + ? { + ...msg, + isTranslating: true, + } + : msg + ) + ); + onTranslateAudio(formData, messageId); + }; + const onSetIsRecording = (flag: boolean) => { setIsRecording(flag); }; @@ -107,8 +172,13 @@ function Index(props: DefinedProps) { )} - +