Skip to content

Commit

Permalink
feat: add ai service with deepgram and openai (#17)
Browse files Browse the repository at this point in the history
feat: add DEEPGRAM and OPENAI keys to env

feat: init transcribeAudio with deepgram

feat: add openai

feat: add summarizeTranscript via OPENAI

refactor: use `@env` to access openai key

feat: add generateEpisodeContent
  • Loading branch information
altaywtf authored Dec 13, 2023
1 parent 0390abb commit a2dc046
Show file tree
Hide file tree
Showing 8 changed files with 361 additions and 18 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ NEXT_PUBLIC_SUPABASE_URL=

SUPABASE_SERVICE_ROLE_KEY=
SUPABASE_URL=

DEEPGRAM_API_KEY=
OPENAI_API_KEY=
2 changes: 2 additions & 0 deletions env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ const getGitSha = () => {
};

const envSchema = z.object({
DEEPGRAM_API_KEY: z.string().min(1),
GIT_SHA: z.string().min(1).default(getGitSha()),
NEXT_PUBLIC_PODCAST_INDEX_API_KEY: z.string().min(1),
NEXT_PUBLIC_PODCAST_INDEX_SECRET: z.string().min(1),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
NEXT_PUBLIC_SUPABASE_URL: z.string().min(1),
OPENAI_API_KEY: z.string().min(1),
SUPABASE_SERVICE_ROLE_KEY: z.string().min(1),
SUPABASE_URL: z.string().min(1),
});
Expand Down
16 changes: 16 additions & 0 deletions lib/services/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ const getUserId = async () => {
return userQuery.data.user.id;
};

export const getAccountId = async () => {
const supabase = createSupabaseServerClient(cookies());

const accountQuery = await supabase
.from('account')
.select('id')
.eq('user_id', await getUserId())
.single();

if (accountQuery.error) {
throw new Error(accountQuery.error.message);
}

return accountQuery.data.id;
};

export const fetchAccountAICredits = async () => {
const supabase = createSupabaseServerClient(cookies());

Expand Down
25 changes: 25 additions & 0 deletions lib/services/ai/deepgram.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { env } from '@/env.mjs';
import { createClient as createDeepgramClient } from '@deepgram/sdk';

const deepgram = createDeepgramClient(env.DEEPGRAM_API_KEY);

export const transcribeAudio = async ({ fileURL }: { fileURL: string }) => {
const { result } = await deepgram.listen.prerecorded.transcribeUrl(
{
url: fileURL,
},
{
diarize: true,
model: 'nova-2',
smart_format: true,
},
);

const transcript = result?.results.channels[0]?.alternatives[0]?.transcript;

if (!transcript) {
throw new Error('No transcript found');
}

return transcript;
};
67 changes: 67 additions & 0 deletions lib/services/ai/generate-episode-content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { Tables } from '@/types/supabase/database';

import {
getAccountId,
updateAccountAICredits,
validateAccountAICredits,
} from '../account';
import { createSupabaseServiceClient } from '../supabase/service';
import { transcribeAudio } from './deepgram';
import { summarizeEpisodeTranscript } from './openai';

export const generateEpisodeContent = async ({
episodeId,
}: {
episodeId: Tables<'episode'>['id'];
}) => {
const initialAiCredits = await validateAccountAICredits();
const updatedAiCredits = await updateAccountAICredits(initialAiCredits - 1);

try {
const supabase = createSupabaseServiceClient();

const episodeQuery = await supabase
.from('episode')
.select('title, audio_url')
.eq('id', episodeId)
.single();

if (episodeQuery.error) {
throw new Error(episodeQuery.error.message);
}

const transcript = await transcribeAudio({
fileURL: episodeQuery.data.audio_url,
});

const summary = await summarizeEpisodeTranscript({
title: episodeQuery.data.title,
transcript,
});

const createEpisodeContentQuery = await supabase
.from('episode_content')
.upsert(
{
episode: episodeId,
text_summary: summary,
transcription: transcript,
user: await getAccountId(),
},
{
onConflict: 'episode',
},
)
.select('*')
.single();

if (createEpisodeContentQuery.error) {
throw new Error(createEpisodeContentQuery.error.message);
}

return createEpisodeContentQuery.data;
} catch (error) {
await updateAccountAICredits(updatedAiCredits + 1);
throw error;
}
};
28 changes: 28 additions & 0 deletions lib/services/ai/openai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { env } from '@/env.mjs';
import { OpenAI } from 'openai';

const openai = new OpenAI({ apiKey: env.OPENAI_API_KEY });

export const summarizeEpisodeTranscript = async ({
title,
transcript,
}: {
title: string;
transcript: string;
}) => {
const response = await openai.chat.completions.create({
messages: [
{
content: `Summarize the following transcript from a podcast episode titled as ${title}`,
role: 'system',
},
{
content: transcript,
role: 'system',
},
],
model: 'gpt-3.5-turbo-1106',
});

return response.choices[0].message.content;
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
},
"prettier": "@vercel/style-guide/prettier",
"dependencies": {
"@deepgram/sdk": "^3.0.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/themes": "^2.0.2",
"@supabase/ssr": "^0.0.10",
"@supabase/supabase-js": "^2.39.0",
"next": "14.0.4",
"next-themes": "1.0.0-beta.0",
"ofetch": "^1.3.3",
"openai": "^4.20.1",
"react": "^18",
"react-dom": "^18",
"zod": "^3.22.4"
Expand Down
Loading

0 comments on commit a2dc046

Please sign in to comment.