"use client"; import { useCallback, useRef } from "react"; import { UploadOutlined } from "@ant-design/icons"; import { Node, NodeProps } from "@xyflow/react"; import { Spin } from "antd"; import { useI18n } from "@/i18n"; import { useWorkflowStore } from "@/store/workflowStore"; import { SmartAudioNodeData } from "@/types"; import { uploadFileToGatewayMedia } from "@/utils/gatewayMediaUpload"; import { deriveSmartAudioMode } from "@/utils/smartAudioMode"; import { AudioInputNodeView } from "./AudioInputNode"; import { GenerateAudioNodeView } from "./GenerateAudioNode"; type SmartAudioNodeType = Node; const MAX_FILE_SIZE = 50 * 1024 * 1024; function SmartAudioUploadToolbar({ id, selected, isUploading, }: { id: string; selected?: boolean; isUploading: boolean; }) { const { t } = useI18n(); const updateNodeData = useWorkflowStore((state) => state.updateNodeData); const selectSingleNode = useWorkflowStore((state) => state.selectSingleNode); const fileInputRef = useRef(null); const applyAudio = useCallback( (audioFile: string, filename: string, format: string, duration: number | null) => { updateNodeData(id, { audioFile, audioFileRef: undefined, filename, format, duration, }); }, [id, updateNodeData] ); const handleFile = useCallback( (file: File) => { if (!file.type.match(/^audio\//)) { alert(t("upload.audioUnsupported")); return; } if (file.size > MAX_FILE_SIZE) { alert(t("upload.audioTooLarge")); return; } updateNodeData(id, { uploadStatus: "loading", uploadError: null }); const metadataUrl = URL.createObjectURL(file); const applyUpload = (duration: number | null) => { URL.revokeObjectURL(metadataUrl); uploadFileToGatewayMedia(file, "audio") .then((uploaded) => { updateNodeData(id, { uploadStatus: "idle", uploadError: null }); applyAudio(uploaded.url, uploaded.filename, uploaded.contentType, duration); }) .catch((error) => { const message = error instanceof Error ? error.message : "Media upload failed"; updateNodeData(id, { uploadStatus: "error", uploadError: message }); alert(message); }); }; const audio = new Audio(metadataUrl); audio.onloadedmetadata = () => applyUpload(Number.isFinite(audio.duration) ? audio.duration : null); audio.onerror = () => applyUpload(null); }, [applyAudio, id, t, updateNodeData] ); const handleFileChange = useCallback( (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) handleFile(file); }, [handleFile] ); const handleOpenFileDialog = useCallback(() => { if (isUploading) return; selectSingleNode(id); fileInputRef.current?.click(); }, [id, isUploading, selectSingleNode]); return ( <> {selected && (
)} ); } function SmartAudioEmptyView({ id, data, selected, }: { id: string; data: SmartAudioNodeData; selected?: boolean; }) { const isUploading = data.uploadStatus === "loading"; return (
{isUploading && (
)}
); } export function SmartAudioNode({ id, data, selected }: NodeProps) { const edges = useWorkflowStore((state) => state.edges); const mode = deriveSmartAudioMode(id, data, edges); if (mode === "generate") { return ; } if (mode === "audio") { return ; } return ; }