Skip to content

Commit 93ccff6

Browse files
author
Aleksander Grygier
committed
feat: Improvements
1 parent 554e192 commit 93ccff6

File tree

5 files changed

+72
-89
lines changed

5 files changed

+72
-89
lines changed

apps/web/src/lib/components/RecordingTile.svelte

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
import { browser } from '$app/environment';
66
import transcribeRecording from '$lib/methods/transcribe-recording';
77
import LoadingSpinner from './LoadingSpinner.svelte';
8+
import type { Transcription } from '$lib/types';
89
910
interface RecordingTileProps {
1011
blob: Blob;
1112
deleteRecording: () => void;
1213
id: string;
1314
name: string;
1415
savedRecordings?: any[];
15-
transcription?: string;
16-
url: string;
16+
transcription?: Transcription;
1717
}
1818
1919
let {
@@ -22,11 +22,13 @@
2222
id,
2323
name,
2424
savedRecordings = $bindable([]),
25-
transcription,
26-
url
25+
transcription
2726
}: RecordingTileProps = $props();
2827
let isPlaying = $state(false);
2928
let isTranscribing = $state(false);
29+
30+
const recordingUrl = URL.createObjectURL(blob);
31+
3032
let waveformContainer: HTMLElement;
3133
let wavesurfer: WaveSurfer;
3234
let progressColor = $state(
@@ -53,7 +55,7 @@
5355
backend: 'MediaElement' // Ensures browser-native audio handling
5456
});
5557
56-
wavesurfer.load(url);
58+
wavesurfer.load(recordingUrl);
5759
5860
wavesurfer.on('play', () => {
5961
isPlaying = true;
@@ -66,7 +68,7 @@
6668
wavesurfer.on('finish', () => {
6769
isPlaying = false;
6870
69-
wavesurfer.load(url);
71+
wavesurfer.load(recordingUrl);
7072
});
7173
}
7274
@@ -83,7 +85,7 @@
8385
{#if transcription}
8486
<div class="transcription">
8587
<div class="inner">
86-
{transcription}
88+
{transcription.text}
8789
</div>
8890
</div>
8991
{:else}
@@ -105,9 +107,7 @@
105107

106108
isTranscribing = true;
107109

108-
const transcriptionResult = await transcribeRecording(blob);
109-
110-
transcription = transcriptionResult ? transcriptionResult.text : '';
110+
const transcription = await transcribeRecording(blob);
111111

112112
isTranscribing = false;
113113

@@ -140,7 +140,7 @@
140140
label="Stop"
141141
/>
142142

143-
<Button kind="secondary" download={name} href={url} label="Download"></Button>
143+
<Button kind="secondary" download={name} href={recordingUrl} label="Download"></Button>
144144

145145
<Button kind="danger" onclick={async () => await deleteRecording()} label="Delete" />
146146
</div>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { Transcription } from '$lib/types';
2+
import blobToBase64 from '$lib/utils/blob-to-base64';
3+
4+
export default async function createRecording(recordingUrl: string): Promise<{
5+
id: string;
6+
name: string;
7+
data: string;
8+
transcription: Transcription | undefined;
9+
}> {
10+
const response = await fetch(recordingUrl);
11+
12+
const recordingBlob = await response.blob();
13+
const recordingId = crypto.randomUUID();
14+
15+
return {
16+
id: recordingId,
17+
name: `${new Date().toISOString()}-${recordingId}.webm`,
18+
data: await blobToBase64(recordingBlob),
19+
transcription: undefined
20+
};
21+
}

apps/web/src/lib/types/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type { Transcription } from './transcription';
2+
3+
export type { Transcription };
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface Transcription {
2+
text: string;
3+
vtt: string;
4+
word_count: number;
5+
words: { word: string; start: number; end: number }[];
6+
}

apps/web/src/routes/+page.svelte

Lines changed: 31 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -3,88 +3,32 @@
33
import RecordingTile from '$lib/components/RecordingTile.svelte';
44
import { onMount } from 'svelte';
55
import base64ToBlob from '$lib/utils/base64-to-blob';
6-
import blobToBase64 from '$lib/utils/blob-to-base64';
6+
import type { Transcription } from '$lib/types';
7+
import createRecording from '$lib/methods/create-recording';
78
8-
let recordingBlob: Blob | MediaSource | undefined = $state();
9-
let recordingFileName = $state('');
109
let recordingUrl = $state('');
1110
1211
let savedRecordings: {
12+
data: string;
1313
id: string;
1414
name: string;
15-
url: string;
16-
transcription?: string;
17-
data: string;
15+
transcription?: Transcription;
1816
}[] = $state.raw([]);
1917
20-
async function discardRecording() {
21-
if (!recordingUrl) return alert('No recording to delete');
22-
23-
URL.revokeObjectURL(recordingUrl);
24-
recordingUrl = '';
25-
}
26-
27-
async function saveRecording() {
28-
if (!recordingUrl) return alert('No recording to save');
29-
30-
const response = await fetch(recordingUrl);
31-
recordingBlob = await response.blob();
32-
recordingFileName = `${new Date().toISOString()}-${crypto.randomUUID()}.webm`;
33-
recordingUrl = '';
34-
35-
const base64Data = await blobToBase64(recordingBlob);
36-
37-
savedRecordings = [
38-
{
39-
id: recordingFileName,
40-
name: recordingFileName,
41-
data: base64Data,
42-
transcription: '',
43-
url: URL.createObjectURL(recordingBlob)
44-
},
45-
...savedRecordings
46-
];
47-
}
48-
49-
function deleteRecording(id: string) {
50-
if (confirm('Are you sure you want to delete this recording?')) {
51-
savedRecordings = savedRecordings.filter((rec) => rec.id !== id);
52-
}
53-
}
54-
5518
onMount(() => {
5619
if (localStorage.getItem('savedRecordings')) {
57-
savedRecordings = JSON.parse(`${localStorage.getItem('savedRecordings')}`).map(
58-
(rec: { id: string; name: string; data: string; transcription?: string }) => {
59-
const blob = base64ToBlob(rec.data);
60-
const url = URL.createObjectURL(blob);
61-
return {
62-
id: rec.id,
63-
name: rec.name,
64-
data: rec.data,
65-
transcription: rec.transcription,
66-
url
67-
};
68-
}
69-
);
20+
savedRecordings = JSON.parse(`${localStorage.getItem('savedRecordings')}`);
7021
}
7122
});
7223
7324
$effect(() => {
7425
if (savedRecordings) {
75-
console.log('savedRecordings:', savedRecordings);
76-
77-
localStorage.setItem(
26+
console.log(
7827
'savedRecordings',
79-
JSON.stringify(
80-
savedRecordings.map(({ id, name, data, transcription }) => ({
81-
id,
82-
name,
83-
data,
84-
transcription
85-
}))
86-
)
28+
savedRecordings.map((x) => x.transcription)
8729
);
30+
31+
localStorage.setItem('savedRecordings', JSON.stringify(savedRecordings));
8832
}
8933
});
9034
</script>
@@ -106,26 +50,40 @@
10650
<br />
10751
<strong>It's that simple.</strong>
10852
</h1>
109-
110-
<!-- <p>
111-
The easiest way to instantly get your ideas, interviews and conversations to Google Docs.
112-
</p> -->
11353
</div>
11454

115-
<Recorder {discardRecording} bind:recordingUrl {saveRecording} />
55+
<Recorder
56+
discardRecording={() => {
57+
URL.revokeObjectURL(recordingUrl);
58+
recordingUrl = '';
59+
}}
60+
saveRecording={async () => {
61+
if (!recordingUrl) return alert('No recording to save');
62+
63+
const newRecording = await createRecording(recordingUrl);
64+
65+
recordingUrl = '';
66+
67+
savedRecordings = [newRecording, ...savedRecordings];
68+
}}
69+
bind:recordingUrl
70+
/>
11671

11772
{#if savedRecordings.length > 0}
11873
<section class="recordings">
11974
<ul>
120-
{#each savedRecordings as recording (recording.id)}
75+
{#each savedRecordings as recording ((recording.id, recording.transcription))}
12176
<li>
12277
<RecordingTile
12378
blob={base64ToBlob(recording.data)}
79+
deleteRecording={() => {
80+
if (confirm('Are you sure you want to delete this recording?')) {
81+
savedRecordings = savedRecordings.filter((r) => r.id !== recording.id);
82+
}
83+
}}
12484
id={recording.id}
12585
name={recording.name}
126-
url={recording.url}
12786
transcription={recording.transcription}
128-
deleteRecording={() => deleteRecording(recording.id)}
12987
bind:savedRecordings
13088
/>
13189
</li>
@@ -161,11 +119,6 @@
161119
}
162120
}
163121
164-
.hero p {
165-
font-size: 1.125rem;
166-
line-height: 1.5;
167-
}
168-
169122
h1 {
170123
font-size: 1.875rem;
171124
line-height: 1.5;

0 commit comments

Comments
 (0)