victor HF Staff commited on
Commit
0a6eeac
·
1 Parent(s): 64c473c

transcribe followup

Browse files
src/lib/components/chat/ChatInput.svelte CHANGED
@@ -402,7 +402,7 @@
402
 
403
  {#if $enabledServersCount > 0}
404
  <div
405
- class="ml-2 inline-flex h-8 items-center gap-1.5 rounded-full border border-blue-500/10 bg-blue-600/10 pl-2 pr-1 text-xs font-semibold text-blue-700 dark:bg-blue-600/20 dark:text-blue-400 sm:h-7"
406
  class:grayscale={!modelSupportsTools}
407
  class:opacity-60={!modelSupportsTools}
408
  class:cursor-help={!modelSupportsTools}
 
402
 
403
  {#if $enabledServersCount > 0}
404
  <div
405
+ class="ml-1.5 inline-flex h-8 items-center gap-1.5 rounded-full border border-blue-500/10 bg-blue-600/10 pl-2 pr-1 text-xs font-semibold text-blue-700 dark:bg-blue-600/20 dark:text-blue-400 sm:h-7"
406
  class:grayscale={!modelSupportsTools}
407
  class:opacity-60={!modelSupportsTools}
408
  class:cursor-help={!modelSupportsTools}
src/lib/components/chat/ChatWindow.svelte CHANGED
@@ -653,7 +653,7 @@
653
  {#if transcriptionEnabled}
654
  <button
655
  type="button"
656
- class="btn absolute bottom-2 right-10 mr-1 size-8 self-end rounded-full border bg-white/50 text-gray-500 transition-none hover:bg-gray-50 hover:text-gray-700 dark:border-transparent dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-500 dark:hover:text-white sm:right-9 sm:size-7"
657
  disabled={isReadOnly}
658
  onclick={() => {
659
  isRecording = true;
 
653
  {#if transcriptionEnabled}
654
  <button
655
  type="button"
656
+ class="btn absolute bottom-2 right-10 mr-1.5 size-8 self-end rounded-full border bg-white/50 text-gray-500 transition-none hover:bg-gray-50 hover:text-gray-700 dark:border-transparent dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-500 dark:hover:text-white sm:right-9 sm:size-7"
657
  disabled={isReadOnly}
658
  onclick={() => {
659
  isRecording = true;
src/lib/components/chat/VoiceRecorder.svelte CHANGED
@@ -100,44 +100,52 @@
100
  }
101
  }
102
 
103
- function stopRecording(): Blob | null {
104
- stopVisualization();
105
-
106
- if (mediaRecorder && mediaRecorder.state !== "inactive") {
107
- mediaRecorder.stop();
108
- }
109
-
110
- // Stop all audio tracks
111
- if (mediaStream) {
112
- mediaStream.getTracks().forEach((track) => track.stop());
113
- mediaStream = null;
114
- }
115
-
116
- // Close audio context
117
- if (audioContext) {
118
- audioContext.close();
119
- audioContext = null;
120
- }
121
 
122
- analyser = null;
123
- mediaRecorder = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- if (audioChunks.length === 0) {
126
- return null;
127
- }
 
 
 
 
128
 
129
- // Create blob from chunks
130
- const mimeType = audioChunks[0]?.type || "audio/webm";
131
- return new Blob(audioChunks, { type: mimeType });
132
  }
133
 
134
- function handleCancel() {
135
- stopRecording();
136
  oncancel();
137
  }
138
 
139
- function handleConfirm() {
140
- const audioBlob = stopRecording();
141
  if (audioBlob && audioBlob.size > 0) {
142
  if (isTouchDevice) {
143
  onsend(audioBlob);
@@ -154,6 +162,7 @@
154
  });
155
 
156
  onDestroy(() => {
 
157
  stopRecording();
158
  });
159
  </script>
 
100
  }
101
  }
102
 
103
+ function stopRecording(): Promise<Blob | null> {
104
+ return new Promise((resolve) => {
105
+ stopVisualization();
106
+
107
+ // Stop all audio tracks
108
+ if (mediaStream) {
109
+ mediaStream.getTracks().forEach((track) => track.stop());
110
+ mediaStream = null;
111
+ }
 
 
 
 
 
 
 
 
 
112
 
113
+ // Close audio context
114
+ if (audioContext) {
115
+ audioContext.close();
116
+ audioContext = null;
117
+ }
118
+ analyser = null;
119
+
120
+ if (!mediaRecorder || mediaRecorder.state === "inactive") {
121
+ mediaRecorder = null;
122
+ resolve(
123
+ audioChunks.length > 0
124
+ ? new Blob(audioChunks, { type: audioChunks[0]?.type || "audio/webm" })
125
+ : null
126
+ );
127
+ return;
128
+ }
129
 
130
+ // Wait for final data before resolving
131
+ mediaRecorder.onstop = () => {
132
+ const mimeType = audioChunks[0]?.type || "audio/webm";
133
+ const blob = audioChunks.length > 0 ? new Blob(audioChunks, { type: mimeType }) : null;
134
+ mediaRecorder = null;
135
+ resolve(blob);
136
+ };
137
 
138
+ mediaRecorder.stop();
139
+ });
 
140
  }
141
 
142
+ async function handleCancel() {
143
+ await stopRecording();
144
  oncancel();
145
  }
146
 
147
+ async function handleConfirm() {
148
+ const audioBlob = await stopRecording();
149
  if (audioBlob && audioBlob.size > 0) {
150
  if (isTouchDevice) {
151
  onsend(audioBlob);
 
162
  });
163
 
164
  onDestroy(() => {
165
+ // Fire and forget - cleanup happens but we don't wait
166
  stopRecording();
167
  });
168
  </script>
src/routes/api/transcribe/+server.ts CHANGED
@@ -29,7 +29,10 @@ export async function POST({ request, locals }) {
29
  throw error(401, "Authentication required");
30
  }
31
 
32
- const contentType = request.headers.get("content-type") || "";
 
 
 
33
  const isAllowed = ALLOWED_CONTENT_TYPES.some((type) => contentType.includes(type));
34
 
35
  if (!isAllowed) {
 
29
  throw error(401, "Authentication required");
30
  }
31
 
32
+ const rawContentType = request.headers.get("content-type") || "";
33
+ // Normalize content-type: Safari sends "audio/webm; codecs=opus" (with space)
34
+ // but HF API expects "audio/webm;codecs=opus" (no space)
35
+ const contentType = rawContentType.replace(/;\s+/g, ";");
36
  const isAllowed = ALLOWED_CONTENT_TYPES.some((type) => contentType.includes(type));
37
 
38
  if (!isAllowed) {