Omarrran commited on
Commit
b3e360f
·
1 Parent(s): 2f53b74

Fix UI: input text visibility in dark mode, proper styling

Browse files
src/app/api/export-dataset/route.ts CHANGED
@@ -2,28 +2,23 @@ import { NextResponse } from 'next/server';
2
  import { promises as fs } from 'fs';
3
  import path from 'path';
4
  import archiver from 'archiver';
5
- import { getDataPath, sanitizePath } from '@/lib/dataPath';
6
 
7
  export async function GET(request: Request) {
8
  const { searchParams } = new URL(request.url);
9
- const datasetName = searchParams.get('dataset_name');
10
 
11
- if (!datasetName) {
12
- return NextResponse.json({ error: 'Dataset name required' }, { status: 400 });
13
- }
14
-
15
- // Sanitize dataset name to prevent path traversal
16
- const safeDatasetName = sanitizePath(datasetName);
17
- const datasetDir = getDataPath(safeDatasetName);
18
 
19
  try {
20
- await fs.access(datasetDir);
21
  } catch {
22
- return NextResponse.json({ error: 'Dataset not found' }, { status: 404 });
23
  }
24
 
25
  const archive = archiver('zip', {
26
- zlib: { level: 9 } // Sets the compression level.
27
  });
28
 
29
  const stream = new ReadableStream({
@@ -41,36 +36,75 @@ export async function GET(request: Request) {
41
  controller.error(err);
42
  });
43
 
44
- // Generate metadata.csv for LJSpeech compatibility
45
  try {
46
- const metadataPath = path.join(datasetDir, 'dataset_info.json');
47
- const metadataContent = await fs.readFile(metadataPath, 'utf-8');
48
- const metadata = JSON.parse(metadataContent);
49
-
50
- if (metadata.recordings && Array.isArray(metadata.recordings)) {
51
- const csvContent = metadata.recordings.map((r: { filename?: string; text?: string }) =>
52
- `${r.filename || ''}|${r.text || ''}`
53
- ).join('\n');
54
- archive.append(csvContent, { name: 'metadata.csv' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
56
- } catch (e) {
57
- console.warn('Could not generate metadata.csv', e);
58
- }
59
 
60
- try {
61
- archive.directory(datasetDir, false);
62
  archive.finalize();
63
  } catch (error) {
64
- console.error('Error finalizing archive:', error);
65
  controller.error(error);
66
  }
67
  }
68
  });
69
 
 
 
70
  return new NextResponse(stream, {
71
  headers: {
72
  'Content-Type': 'application/zip',
73
- 'Content-Disposition': `attachment; filename="${safeDatasetName}.zip"`
74
  }
75
  });
76
  }
 
2
  import { promises as fs } from 'fs';
3
  import path from 'path';
4
  import archiver from 'archiver';
5
+ import { getDataDir, getAudioPath, getTranscriptionsPath, getMetadataPath } from '@/lib/dataPath';
6
 
7
  export async function GET(request: Request) {
8
  const { searchParams } = new URL(request.url);
9
+ const speakerId = searchParams.get('speaker_id');
10
 
11
+ // Get the base data directory
12
+ const dataDir = getDataDir();
 
 
 
 
 
13
 
14
  try {
15
+ await fs.access(dataDir);
16
  } catch {
17
+ return NextResponse.json({ error: 'Data directory not found' }, { status: 404 });
18
  }
19
 
20
  const archive = archiver('zip', {
21
+ zlib: { level: 9 }
22
  });
23
 
24
  const stream = new ReadableStream({
 
36
  controller.error(err);
37
  });
38
 
 
39
  try {
40
+ // Generate metadata.csv for LJSpeech compatibility
41
+ const metadataPath = path.join(getMetadataPath(), 'dataset_info.json');
42
+ try {
43
+ const metadataContent = await fs.readFile(metadataPath, 'utf-8');
44
+ const metadata = JSON.parse(metadataContent);
45
+
46
+ if (metadata.recordings && Array.isArray(metadata.recordings)) {
47
+ const csvContent = metadata.recordings.map((r: { filename?: string; text?: string }) =>
48
+ `${r.filename || ''}|${r.text || ''}`
49
+ ).join('\n');
50
+ archive.append(csvContent, { name: 'metadata.csv' });
51
+ }
52
+ } catch {
53
+ console.warn('Could not generate metadata.csv');
54
+ }
55
+
56
+ // If speaker_id is provided, only export that speaker's data
57
+ if (speakerId) {
58
+ const audioDir = getAudioPath(speakerId);
59
+ const textDir = getTranscriptionsPath(speakerId);
60
+
61
+ try {
62
+ await fs.access(audioDir);
63
+ archive.directory(audioDir, `audio/${speakerId}`);
64
+ } catch {
65
+ // No audio for this speaker
66
+ }
67
+
68
+ try {
69
+ await fs.access(textDir);
70
+ archive.directory(textDir, `transcriptions/${speakerId}`);
71
+ } catch {
72
+ // No transcriptions for this speaker
73
+ }
74
+ } else {
75
+ // Export all data
76
+ const audioDir = getAudioPath();
77
+ const textDir = getTranscriptionsPath();
78
+
79
+ try {
80
+ await fs.access(audioDir);
81
+ archive.directory(audioDir, 'audio');
82
+ } catch {
83
+ // No audio folder
84
+ }
85
+
86
+ try {
87
+ await fs.access(textDir);
88
+ archive.directory(textDir, 'transcriptions');
89
+ } catch {
90
+ // No transcriptions folder
91
+ }
92
  }
 
 
 
93
 
 
 
94
  archive.finalize();
95
  } catch (error) {
96
+ console.error('Error creating archive:', error);
97
  controller.error(error);
98
  }
99
  }
100
  });
101
 
102
+ const filename = speakerId ? `dataset_${speakerId}.zip` : 'dataset_full.zip';
103
+
104
  return new NextResponse(stream, {
105
  headers: {
106
  'Content-Type': 'application/zip',
107
+ 'Content-Disposition': `attachment; filename="${filename}"`
108
  }
109
  });
110
  }
src/app/globals.css CHANGED
@@ -82,4 +82,44 @@
82
 
83
  .glass-card {
84
  @apply bg-card/50 backdrop-blur-md border border-border/50 shadow-xl;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  }
 
82
 
83
  .glass-card {
84
  @apply bg-card/50 backdrop-blur-md border border-border/50 shadow-xl;
85
+ }
86
+
87
+ /* Form Elements */
88
+ .label {
89
+ @apply block text-sm font-medium text-foreground mb-1.5;
90
+ }
91
+
92
+ .input {
93
+ @apply w-full px-4 py-2.5 rounded-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all;
94
+ }
95
+
96
+ textarea.input {
97
+ @apply min-h-[100px] resize-y;
98
+ }
99
+
100
+ /* Buttons */
101
+ .btn {
102
+ @apply px-4 py-2.5 rounded-lg font-medium transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-background disabled:opacity-50 disabled:cursor-not-allowed;
103
+ }
104
+
105
+ .btn-primary {
106
+ @apply bg-primary text-primary-foreground hover:bg-primary/90 focus:ring-primary;
107
+ }
108
+
109
+ .btn-secondary {
110
+ @apply bg-secondary text-secondary-foreground hover:bg-secondary/80 focus:ring-secondary;
111
+ }
112
+
113
+ .btn-ghost {
114
+ @apply bg-transparent hover:bg-secondary text-foreground;
115
+ }
116
+
117
+ /* Container */
118
+ .container {
119
+ @apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8;
120
+ }
121
+
122
+ /* Sentence Display */
123
+ .sentence-display {
124
+ @apply text-2xl md:text-3xl font-medium leading-relaxed text-center py-8 px-4;
125
  }