11import { memo } from "react" ;
2- import { type NodeProps , useReactFlow } from "@xyflow/react" ;
3- import { Music } from "lucide-react" ;
2+ import {
3+ type NodeProps ,
4+ useReactFlow ,
5+ NodeToolbar ,
6+ Position ,
7+ } from "@xyflow/react" ;
8+ import { Music , Download } from "lucide-react" ;
49import type { AudioNodeData } from "./types" ;
510import { GenerateAudio } from "../../../wailsjs/go/ai/Service" ;
11+ import { DownloadAssetFile } from "../../../wailsjs/go/database/Service" ;
612import { Skeleton } from "@/components/ui/skeleton" ;
713import { BaseNode } from "./base-node" ;
814import { useNodeRun } from "../../hooks/use-node-run" ;
915import { Trans } from "@lingui/react/macro" ;
1016import { useLingui } from "@lingui/react" ;
1117import { msg } from "@lingui/core/macro" ;
18+ import { ButtonGroup } from "../ui/button-group" ;
19+ import { Button } from "@/components/ui/button" ;
20+ import { toast } from "sonner" ;
1221
1322export const AudioNode = memo ( ( props : NodeProps ) => {
1423 const { id, data } = props ;
24+ const isSelected = props . selected ;
1525 const nodeData = data as unknown as AudioNodeData ;
1626 const { updateNodeData } = useReactFlow ( ) ;
1727 const { _ } = useLingui ( ) ;
@@ -28,6 +38,21 @@ export const AudioNode = memo((props: NodeProps) => {
2838 } ,
2939 } ) ;
3040
41+ const handleSave = async ( ) => {
42+ if ( ! nodeData . audioUrl ) {
43+ toast . error ( _ ( msg `No audio to save` ) ) ;
44+ return ;
45+ }
46+ try {
47+ const filename = nodeData . audioUrl . split ( "/" ) . pop ( ) || "" ;
48+ await DownloadAssetFile ( filename ) ;
49+ toast . success ( _ ( msg `Asset saved` ) ) ;
50+ } catch ( err ) {
51+ console . error ( "Failed to save asset:" , err ) ;
52+ toast . error ( _ ( msg `Failed to save` ) ) ;
53+ }
54+ } ;
55+
3156 return (
3257 < BaseNode
3358 { ...props }
@@ -38,6 +63,24 @@ export const AudioNode = memo((props: NodeProps) => {
3863 minWidth = { 200 }
3964 minHeight = { 75 }
4065 >
66+ < NodeToolbar
67+ isVisible = { isSelected }
68+ position = { Position . Top }
69+ align = "center"
70+ offset = { 30 }
71+ >
72+ < ButtonGroup >
73+ < Button
74+ onClick = { handleSave }
75+ title = "Download"
76+ size = { "icon" }
77+ variant = { "outline" }
78+ >
79+ < Download className = "h-4 w-4" />
80+ </ Button >
81+ </ ButtonGroup >
82+ </ NodeToolbar >
83+
4184 < div className = "p-4 flex items-center justify-center bg-muted/20 w-full flex-1" >
4285 { nodeData . processing ? (
4386 < div className = "size-full space-y-2" >
@@ -46,7 +89,9 @@ export const AudioNode = memo((props: NodeProps) => {
4689 ) : nodeData . audioUrl ? (
4790 < audio src = { nodeData . audioUrl } controls className = "size-full" />
4891 ) : (
49- < div className = "text-xs text-muted-foreground italic" > < Trans > No audio yet</ Trans > </ div >
92+ < div className = "text-xs text-muted-foreground italic" >
93+ < Trans > No audio yet</ Trans >
94+ </ div >
5095 ) }
5196 </ div >
5297 </ BaseNode >
0 commit comments