Skip to content

Commit 6e0cb7b

Browse files
committed
Add CLOUDFLARE_AI_GATEWAY_URL environment variable and update generateAudio function in openai.service.ts
1 parent f53931f commit 6e0cb7b

File tree

11 files changed

+159
-22
lines changed

11 files changed

+159
-22
lines changed

Diff for: apps/api/src/bindings.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export type Bindings = {
22
DB: D1Database;
33
CLOUDFLARE_ACCOUNT_ID: string;
44
CLOUDFLARE_API_TOKEN: string;
5+
CLOUDFLARE_AI_GATEWAY_URL: string;
56
OPENAI_API_KEY: string;
67
AI: any;
78
BUCKET: R2Bucket;

Diff for: apps/api/src/index.ts

+47-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import createSimulation from '@src/routes/createSimulation';
99
import createQuestion from '@src/routes/createQuestion';
1010
import createFeedback from '@src/routes/createFeedback';
1111
import createTranscript from '@src/routes/createTranscript';
12+
import { App } from "@src/types";
1213

13-
const app = new OpenAPIHono();
14+
const app = new OpenAPIHono<App>();
1415
app.use("*", cors());
1516
app.use('*', prettyJSON());
1617
app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404));
@@ -22,6 +23,51 @@ app.route('/api/v1/questions', createQuestion);
2223
app.route('/api/v1/feedback', createFeedback);
2324
app.route('/api/v1/transcript', createTranscript);
2425

26+
/*
27+
import { Ai } from '@cloudflare/ai';
28+
29+
app.post('/api/v1/demo1', async (c) => {
30+
const ai = new Ai(c.env.AI);
31+
const arrayBuffer = await c.req.arrayBuffer();
32+
const audio = [...new Uint8Array(arrayBuffer)];
33+
const { text } = await ai.run("@cf/openai/whisper", {audio });
34+
return c.json({text});
35+
});
36+
37+
app.post('/api/v1/demo2', async (c) => {
38+
const ai = new Ai(c.env.AI);
39+
const data = await c.req.formData();
40+
const file = data.get('file') as unknown as File;
41+
const arrayBuffer = await file.arrayBuffer();
42+
const audio = [...new Uint8Array(arrayBuffer)];
43+
const { text } = await ai.run("@cf/openai/whisper", { audio });
44+
return c.json({text});
45+
});
46+
47+
app.post('/api/v1/demo3', async (c) => {
48+
const data = await c.req.formData();
49+
const file = data.get('file') as unknown as File;
50+
const text = await generateTranscription(file, c.env.OPENAI_API_KEY);
51+
return c.json({text});
52+
});
53+
54+
app.post('/api/v1/demo4', async (c) => {
55+
const data = await c.req.formData();
56+
const file = data.get('file') as unknown as File;
57+
const arrayBuffer = await file.arrayBuffer();
58+
const response = await fetch('https://gateway.ai.cloudflare.com/v1/b2bb1719bede14df8732870a3974b263/gateway/workers-ai/@cf/openai/whisper', {
59+
method: 'POST',
60+
headers: {
61+
'Authorization': `Bearer ${c.env.CLOUDFLARE_API_TOKEN}`,
62+
'Content-Type': 'application/octet-stream',
63+
},
64+
body: arrayBuffer,
65+
});
66+
const result = await response.json();
67+
return c.json({result});
68+
});
69+
*/
70+
2571
app.get("/", swaggerUI({ url: "/docs" }));
2672
app.doc("/docs", {
2773
info: {

Diff for: apps/api/src/routes/createQuestion.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ app.openapi(route, async (c) => {
4545
cloudflareApiToken: c.env.CLOUDFLARE_API_TOKEN,
4646
}, c.env.DB);
4747

48-
const filename = await generateAudio(response, c.env.OPENAI_API_KEY, c.env.BUCKET);
48+
const filename = await generateAudio(response, c.env.OPENAI_API_KEY, c.env.CLOUDFLARE_AI_GATEWAY_URL, c.env.BUCKET);
4949

5050
return c.json({
5151
id: `${Date.now()}`,

Diff for: apps/api/src/routes/createTranscript.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { OpenAPIHono, createRoute } from "@hono/zod-openapi";
22
import { MessageSchema } from '@src/dtos/message.dto';
3-
import { generateTranscription } from '@src/services/whisper.service';
3+
import { generateTranscription } from '@src/services/openai.service';
44
import { App } from "@src/types";
55

66
const app = new OpenAPIHono<App>();
@@ -23,9 +23,9 @@ const route = createRoute({
2323
});
2424

2525
app.openapi(route, async (c) => {
26-
const body = await c.req.parseBody();
27-
const file = body.file as File;
28-
const answer = await generateTranscription(file, c.env.AI);
26+
const data = await c.req.formData();
27+
const file = data.get('file') as unknown as File;
28+
const answer = await generateTranscription(file, c.env.OPENAI_API_KEY, c.env.CLOUDFLARE_AI_GATEWAY_URL);
2929

3030
return c.json({
3131
id: `${Date.now()}`,

Diff for: apps/api/src/services/openai.service.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import OpenAI from "openai";
22

3-
export const generateTranscription = async (file: File, key: string) => {
4-
const openai = new OpenAI({
5-
apiKey: key,
6-
});
3+
export const generateTranscription = async (file: File, apiKey: string, baseURL: string) => {
4+
const openai = new OpenAI({ apiKey, baseURL: `${baseURL}/openai`});
75
console.log('name', file.name);
86
console.log('type', file.type);
97
const transcription = await openai.audio.transcriptions.create({
@@ -14,10 +12,8 @@ export const generateTranscription = async (file: File, key: string) => {
1412
return transcription.text;
1513
}
1614

17-
export const generateAudio = async (input: string, key: string, bucket: R2Bucket) => {
18-
const openai = new OpenAI({
19-
apiKey: key,
20-
});
15+
export const generateAudio = async (input: string, apiKey: string, baseURL: string, bucket: R2Bucket) => {
16+
const openai = new OpenAI({ apiKey, baseURL: `${baseURL}/openai`});
2117
const file = await openai.audio.speech.create({
2218
model: "tts-1",
2319
voice: "alloy",

Diff for: apps/webapp/src/app/app.routes.ts

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export const routes: Routes = [
1313
path: 'simulator/:id',
1414
loadComponent: () => import('./pages/simulator/simulator.component')
1515
},
16+
{
17+
path: 'demo',
18+
loadComponent: () => import('./pages/demo/demo.component')
19+
},
1620
{
1721
path: '**',
1822
redirectTo: ''

Diff for: apps/webapp/src/app/components/modal-recording/modal-recording.component.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class ModalRecordingComponent implements AfterViewInit {
3131
@ViewChild('videoRta', { static: false }) videoRta!: ElementRef<HTMLVideoElement>;
3232
private mediaRecorder!: MediaRecorder;
3333
status = signal<'init' | 'recording' | 'success' | 'processing' | 'streaming'>('init');
34-
file = signal<File | null>(null);
34+
file = signal<Blob | null>(null);
3535
intervalId = signal<number | null>(null);
3636
duration = signal<number>(0);
3737
formatDuration = computed(() => {
@@ -77,11 +77,9 @@ export class ModalRecordingComponent implements AfterViewInit {
7777
previewVideo() {
7878
const chunks = this.recordedChunks();
7979
const blob = new Blob(chunks, { type: 'video/webm' });
80-
const filename = `${Date.now()}.webm`;
81-
const file = new File([blob], filename, { type: blob.type, lastModified: Date.now() });
82-
const url = URL.createObjectURL(file);
80+
const url = URL.createObjectURL(blob);
8381
this.status.set('success');
84-
this.file.set(file);
82+
this.file.set(blob);
8583
this.videoRta.nativeElement.src = url;
8684
this.cdRef.detectChanges();
8785
}

Diff for: apps/webapp/src/app/pages/demo/demo.component.html

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<div class="p-10">
2+
<p>
3+
<button class="btn btn-large btn-primary" type="button" (click)="startRecording()">start</button>
4+
</p>
5+
<p>
6+
<button class="btn btn-large" type="button" (click)="stopRecording()">stop</button>
7+
</p>
8+
@if(url()) {
9+
<p>
10+
<video controls>
11+
<source [src]="url()" type="video/webm">
12+
</video>
13+
</p>
14+
}
15+
16+
</div>

Diff for: apps/webapp/src/app/pages/demo/demo.component.ts

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Component, inject, signal } from '@angular/core';
2+
import { HttpClient } from '@angular/common/http';
3+
import { environment } from '@env/environment';
4+
5+
@Component({
6+
selector: 'app-demo',
7+
standalone: true,
8+
imports: [],
9+
templateUrl: './demo.component.html',
10+
styles: ``
11+
})
12+
export default class DemoComponent {
13+
recordedChunks = signal<Blob[]>([]);
14+
url = signal<string | null>(null);
15+
private mediaRecorder!: MediaRecorder;
16+
17+
private http = inject(HttpClient);
18+
19+
async startRecording() {
20+
const stream = await navigator.mediaDevices.getUserMedia({
21+
video: true,
22+
audio: true,
23+
});
24+
this.mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm;codecs=vp9,opus' });
25+
this.mediaRecorder.ondataavailable = (event) => {
26+
if (event.data.size > 0) {
27+
this.recordedChunks.update((chunks) => [...chunks, event.data]);
28+
}
29+
};
30+
this.mediaRecorder.start();
31+
}
32+
33+
stopRecording() {
34+
this.mediaRecorder.stop();
35+
setTimeout(() => {
36+
this.processRecording();
37+
}, 300);
38+
}
39+
40+
processRecording() {
41+
const chunks = this.recordedChunks();
42+
console.log(chunks);
43+
const blob = new Blob(chunks, { type: 'video/webm' });
44+
const url = URL.createObjectURL(blob);
45+
this.url.set(url);
46+
this.requestOpenAI(blob).subscribe((rta) => {
47+
console.log(rta);
48+
this.recordedChunks.set([]);
49+
this.url.set(null);
50+
});
51+
}
52+
53+
requestBlob(file: Blob) {
54+
return this.http.post(`${environment.apiUrl}/demo1`, file);
55+
}
56+
57+
requestFormData(file: Blob) {
58+
const formData = new FormData();
59+
formData.append('file', file, 'file.webm');
60+
return this.http.post(`${environment.apiUrl}/demo2`, formData);
61+
}
62+
63+
requestOpenAI(file: Blob) {
64+
const formData = new FormData();
65+
formData.append('file', file, 'file.webm');
66+
return this.http.post(`${environment.apiUrl}/demo3`, formData);
67+
}
68+
69+
requestGateway(file: Blob) {
70+
const formData = new FormData();
71+
formData.append('file', file, 'file.webm');
72+
return this.http.post(`${environment.apiUrl}/demo4`, formData);
73+
}
74+
75+
}

Diff for: apps/webapp/src/app/pages/simulator/simulator.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export default class SimulatorComponent implements OnInit {
6363
});
6464
}
6565

66-
createTranscript(file: File, question: string) {
66+
createTranscript(file: Blob, question: string) {
6767
this.mode.set('loading');
6868
this.apiService.createTranscript(file).subscribe({
6969
next: (newMessage) => {

Diff for: apps/webapp/src/app/services/api.service.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ export class ApiService {
3030
});
3131
}
3232

33-
createTranscript(file: File) {
33+
createTranscript(file: Blob) {
3434
const formData = new FormData();
35-
formData.append('file', file);
35+
const filename = `file-${Date.now()}.webm`;
36+
formData.append('file', file, filename);
3637
return this.http.post<Message>(`${environment.apiUrl}/transcript`, formData)
3738
.pipe(
3839
map((response) => ({

0 commit comments

Comments
 (0)