forked from ansh/template-2
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
create deepgram & microphone helpers for the template
- Loading branch information
Showing
5 changed files
with
268 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { DeepgramError, createClient } from "@deepgram/sdk"; | ||
import { NextResponse, type NextRequest } from "next/server"; | ||
|
||
export const revalidate = 0; | ||
|
||
export async function GET(request: NextRequest) { | ||
// exit early so we don't request 70000000 keys while in devmode | ||
if (process.env.DEEPGRAM_ENV === "development") { | ||
return NextResponse.json({ | ||
key: process.env.DEEPGRAM_API_KEY ?? "", | ||
}); | ||
} | ||
|
||
// gotta use the request object to invalidate the cache every request :vomit: | ||
const url = request.url; | ||
const deepgram = createClient(process.env.DEEPGRAM_API_KEY ?? ""); | ||
|
||
let { result: projectsResult, error: projectsError } = await deepgram.manage.getProjects(); | ||
|
||
if (projectsError) { | ||
return NextResponse.json(projectsError); | ||
} | ||
|
||
const project = projectsResult?.projects[0]; | ||
|
||
if (!project) { | ||
return NextResponse.json( | ||
new DeepgramError("Cannot find a Deepgram project. Please create a project first.") | ||
); | ||
} | ||
|
||
let { result: newKeyResult, error: newKeyError } = await deepgram.manage.createProjectKey( | ||
project.project_id, | ||
{ | ||
comment: "Temporary API key", | ||
scopes: ["usage:write"], | ||
tags: ["next.js"], | ||
time_to_live_in_seconds: 60, | ||
} | ||
); | ||
|
||
if (newKeyError) { | ||
return NextResponse.json(newKeyError); | ||
} | ||
|
||
const response = NextResponse.json({ ...newKeyResult, url }); | ||
response.headers.set("Surrogate-Control", "no-store"); | ||
response.headers.set( | ||
"Cache-Control", | ||
"s-maxage=0, no-store, no-cache, must-revalidate, proxy-revalidate" | ||
); | ||
response.headers.set("Expires", "0"); | ||
|
||
return response; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
"use client"; | ||
|
||
import { | ||
createClient, | ||
LiveClient, | ||
SOCKET_STATES, | ||
LiveTranscriptionEvents, | ||
type LiveSchema, | ||
type LiveTranscriptionEvent, | ||
} from "@deepgram/sdk"; | ||
|
||
import { createContext, useContext, useState, ReactNode, FunctionComponent } from "react"; | ||
|
||
interface DeepgramContextType { | ||
connection: LiveClient | null; | ||
connectToDeepgram: (options: LiveSchema, endpoint?: string) => Promise<void>; | ||
disconnectFromDeepgram: () => void; | ||
connectionState: SOCKET_STATES; | ||
} | ||
|
||
const DeepgramContext = createContext<DeepgramContextType | undefined>(undefined); | ||
|
||
interface DeepgramContextProviderProps { | ||
children: ReactNode; | ||
} | ||
|
||
const getApiKey = async (): Promise<string> => { | ||
const response = await fetch("/api/authenticate", { cache: "no-store" }); | ||
const result = await response.json(); | ||
return result.key; | ||
}; | ||
|
||
const DeepgramContextProvider: FunctionComponent<DeepgramContextProviderProps> = ({ children }) => { | ||
const [connection, setConnection] = useState<LiveClient | null>(null); | ||
const [connectionState, setConnectionState] = useState<SOCKET_STATES>(SOCKET_STATES.closed); | ||
|
||
/** | ||
* Connects to the Deepgram speech recognition service and sets up a live transcription session. | ||
* | ||
* @param options - The configuration options for the live transcription session. | ||
* @param endpoint - The optional endpoint URL for the Deepgram service. | ||
* @returns A Promise that resolves when the connection is established. | ||
*/ | ||
const connectToDeepgram = async (options: LiveSchema, endpoint?: string) => { | ||
const key = await getApiKey(); | ||
const deepgram = createClient(key); | ||
|
||
const conn = deepgram.listen.live(options, endpoint); | ||
|
||
conn.addListener(LiveTranscriptionEvents.Open, () => { | ||
setConnectionState(SOCKET_STATES.open); | ||
}); | ||
|
||
conn.addListener(LiveTranscriptionEvents.Close, () => { | ||
setConnectionState(SOCKET_STATES.closed); | ||
}); | ||
|
||
setConnection(conn); | ||
}; | ||
|
||
const disconnectFromDeepgram = async () => { | ||
if (connection) { | ||
connection.finish(); | ||
setConnection(null); | ||
} | ||
}; | ||
|
||
return ( | ||
<DeepgramContext.Provider | ||
value={{ | ||
connection, | ||
connectToDeepgram, | ||
disconnectFromDeepgram, | ||
connectionState, | ||
}} | ||
> | ||
{children} | ||
</DeepgramContext.Provider> | ||
); | ||
}; | ||
|
||
// Use the useDeepgram hook to access the deepgram context and use the deepgram in any component. | ||
// This allows you to connect to the deepgram and disconnect from the deepgram via a socket. | ||
// Make sure to wrap your application in a DeepgramContextProvider to use the deepgram. | ||
function useDeepgram(): DeepgramContextType { | ||
const context = useContext(DeepgramContext); | ||
if (context === undefined) { | ||
throw new Error("useDeepgram must be used within a DeepgramContextProvider"); | ||
} | ||
return context; | ||
} | ||
|
||
export { | ||
DeepgramContextProvider, | ||
useDeepgram, | ||
SOCKET_STATES, | ||
LiveTranscriptionEvents, | ||
type LiveTranscriptionEvent, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
"use client"; | ||
|
||
import { createContext, useCallback, useContext, useState, ReactNode } from "react"; | ||
|
||
interface MicrophoneContextType { | ||
microphone: MediaRecorder | null; | ||
startMicrophone: () => void; | ||
stopMicrophone: () => void; | ||
setupMicrophone: () => void; | ||
microphoneState: MicrophoneState | null; | ||
} | ||
|
||
export enum MicrophoneEvents { | ||
DataAvailable = "dataavailable", | ||
Error = "error", | ||
Pause = "pause", | ||
Resume = "resume", | ||
Start = "start", | ||
Stop = "stop", | ||
} | ||
|
||
export enum MicrophoneState { | ||
NotSetup = -1, | ||
SettingUp = 0, | ||
Ready = 1, | ||
Opening = 2, | ||
Open = 3, | ||
Error = 4, | ||
Pausing = 5, | ||
Paused = 6, | ||
} | ||
|
||
const MicrophoneContext = createContext<MicrophoneContextType | undefined>(undefined); | ||
|
||
interface MicrophoneContextProviderProps { | ||
children: ReactNode; | ||
} | ||
|
||
const MicrophoneContextProvider: React.FC<MicrophoneContextProviderProps> = ({ children }) => { | ||
const [microphoneState, setMicrophoneState] = useState<MicrophoneState>(MicrophoneState.NotSetup); | ||
const [microphone, setMicrophone] = useState<MediaRecorder | null>(null); | ||
|
||
const setupMicrophone = async () => { | ||
setMicrophoneState(MicrophoneState.SettingUp); | ||
|
||
try { | ||
const userMedia = await navigator.mediaDevices.getUserMedia({ | ||
audio: { | ||
noiseSuppression: true, | ||
echoCancellation: true, | ||
}, | ||
}); | ||
|
||
const microphone = new MediaRecorder(userMedia); | ||
|
||
setMicrophoneState(MicrophoneState.Ready); | ||
setMicrophone(microphone); | ||
} catch (err: any) { | ||
console.error(err); | ||
|
||
throw err; | ||
} | ||
}; | ||
|
||
const stopMicrophone = useCallback(() => { | ||
setMicrophoneState(MicrophoneState.Pausing); | ||
|
||
if (microphone?.state === "recording") { | ||
microphone.pause(); | ||
setMicrophoneState(MicrophoneState.Paused); | ||
} | ||
}, [microphone]); | ||
|
||
const startMicrophone = useCallback(() => { | ||
setMicrophoneState(MicrophoneState.Opening); | ||
|
||
if (microphone?.state === "paused") { | ||
microphone.resume(); | ||
} else { | ||
microphone?.start(250); | ||
} | ||
|
||
setMicrophoneState(MicrophoneState.Open); | ||
}, [microphone]); | ||
|
||
return ( | ||
<MicrophoneContext.Provider | ||
value={{ | ||
microphone, | ||
startMicrophone, | ||
stopMicrophone, | ||
setupMicrophone, | ||
microphoneState, | ||
}} | ||
> | ||
{children} | ||
</MicrophoneContext.Provider> | ||
); | ||
}; | ||
|
||
// Use the useMicrophone hook to access the microphone context and use the microphone in any component. | ||
// Make sure to wrap your application in a MicrophoneContextProvider to use the microphone. | ||
function useMicrophone(): MicrophoneContextType { | ||
const context = useContext(MicrophoneContext); | ||
|
||
if (context === undefined) { | ||
throw new Error("useMicrophone must be used within a MicrophoneContextProvider"); | ||
} | ||
|
||
return context; | ||
} | ||
|
||
export { MicrophoneContextProvider, useMicrophone }; |