Spaces:
Running
Running
File size: 5,826 Bytes
0573fbf 219a8a4 0573fbf 219a8a4 0573fbf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | import React, { useState, useRef, useCallback } from 'react';
import { Paper, Box, Typography, Button, List, ListItem, IconButton } from '@mui/material';
import { Square as StopIcon, Play as PlayIcon, Download as DownloadIcon } from 'lucide-react';
import api from '../api';
import { generatedFragmentsWindowStyles } from '../theme';
export default function GeneratedFragmentsWindow({ fragments, onDownload }) {
const [playingFragment, setPlayingFragment] = useState(null);
const audioRefs = useRef({});
const handlePlayPause = (fragment) => {
const audio = audioRefs.current[fragment.id];
if (!audio) return;
if (playingFragment === fragment.id) {
audio.pause();
setPlayingFragment(null);
} else {
if (playingFragment && audioRefs.current[playingFragment]) {
audioRefs.current[playingFragment].pause();
}
audio.play();
setPlayingFragment(fragment.id);
}
};
const setAudioRef = useCallback((fragmentId, audioElement) => {
if (audioElement) {
audioRefs.current[fragmentId] = audioElement;
}
}, []);
return (
<Paper
variant="outlined"
sx={generatedFragmentsWindowStyles.rootPaper}
>
<Box sx={generatedFragmentsWindowStyles.headerRow}>
<Box sx={generatedFragmentsWindowStyles.titleRow}>
<Box component="span" sx={generatedFragmentsWindowStyles.titleIcon}>
<DownloadIcon size={20} />
</Box>
<Typography variant="h6" sx={generatedFragmentsWindowStyles.titleText}>
Generated Fragments
</Typography>
</Box>
<Typography variant="caption" color="textSecondary" sx={generatedFragmentsWindowStyles.countText}>
{fragments.length}
</Typography>
</Box>
{fragments.length === 0 ? (
<Box
sx={generatedFragmentsWindowStyles.emptyState}
>
<Typography variant="body2">
No fragments generated yet
</Typography>
</Box>
) : (
<List
sx={generatedFragmentsWindowStyles.listRoot}
>
{fragments.slice().reverse().map((fragment) => (
<ListItem
key={fragment.id}
sx={generatedFragmentsWindowStyles.listItem}
>
<Box sx={generatedFragmentsWindowStyles.fragmentRow}>
<Box sx={generatedFragmentsWindowStyles.fragmentMeta}>
<Typography
variant="subtitle2"
sx={generatedFragmentsWindowStyles.fragmentPrompt}
>
{fragment.batchTotal > 1 && (
<Box component="span" sx={{ fontWeight: 700, mr: 0.75 }}>
[{fragment.batchIndex}/{fragment.batchTotal}]
</Box>
)}
{fragment.prompt}
</Typography>
<Typography variant="caption" color="textSecondary">
{fragment.duration}s
{fragment.cfgScale !== undefined && ` • CFG ${fragment.cfgScale}`}
{fragment.seed !== undefined && ` • Seed ${fragment.seed}`}
{' • '}{fragment.timestamp}
</Typography>
</Box>
<Box sx={generatedFragmentsWindowStyles.fragmentActions}>
<IconButton
size="small"
onClick={() => handlePlayPause(fragment)}
color={playingFragment === fragment.id ? "primary" : "default"}
sx={generatedFragmentsWindowStyles.playPauseButton(playingFragment === fragment.id)}
>
{playingFragment === fragment.id ? <StopIcon /> : <PlayIcon />}
</IconButton>
<Button
size="small"
variant="outlined"
startIcon={<DownloadIcon />}
onClick={() => onDownload(fragment)}
>
Download
</Button>
</Box>
</Box>
<audio
ref={el => setAudioRef(fragment.id, el)}
src={fragment.audioUrl}
onEnded={() => setPlayingFragment(null)}
onPause={() => setPlayingFragment(null)}
style={generatedFragmentsWindowStyles.hiddenAudio}
/>
</ListItem>
))}
</List>
)}
</Paper>
);
}
|