-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: initial commit working next.js demo
- Loading branch information
1 parent
bca937c
commit a3faf0f
Showing
18 changed files
with
3,164 additions
and
116 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,71 @@ | ||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). | ||
# Live audio Next.js Starter | ||
|
||
## Getting Started | ||
This sample demonstrates interacting with Deepgram from Next.js to transcribe your microphone audio. It uses the Deepgram JavaScript SDK. This was originally a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). | ||
|
||
First, run the development server: | ||
## Sign-up to Deepgram | ||
|
||
Before you start, it's essential to generate a Deepgram API key to use in this project. [Sign-up now for Deepgram](https://console.deepgram.com/signup). | ||
|
||
## Quickstart | ||
|
||
### Manual | ||
|
||
Follow these steps to get started with this starter application. | ||
|
||
#### Clone the repository | ||
|
||
Go to GitHub and [clone the repository](https://github.com/deepgram-starters/live-nextjs-starter). | ||
|
||
#### Install dependencies | ||
|
||
Install the project dependencies. | ||
|
||
```bash | ||
npm install | ||
``` | ||
|
||
#### Edit the config file | ||
|
||
Copy the code from `sample.env.local` and create a new file called `.env.local`. Paste in the code and enter your API key you generated in the [Deepgram console](https://console.deepgram.com/). | ||
|
||
```bash | ||
DEEPGRAM_API_KEY=%api_key% | ||
``` | ||
|
||
#### Run the application | ||
|
||
Once running, you can [access the application in your browser](http://localhost:3000). | ||
|
||
```bash | ||
npm run dev | ||
# or | ||
yarn dev | ||
# or | ||
pnpm dev | ||
# or | ||
bun dev | ||
``` | ||
|
||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. | ||
## What is Deepgram? | ||
|
||
Deepgram is an AI speech platform which specializes in (NLU) Natural Language Understanding features and Transcription. It can help get the following from your audio. | ||
|
||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. | ||
- [Speaker diarization](https://deepgram.com/product/speech-understanding/) | ||
- [Language detection](https://deepgram.com/product/speech-understanding/) | ||
- [Summarization](https://deepgram.com/product/speech-understanding/) | ||
- [Topic detection](https://deepgram.com/product/speech-understanding/) | ||
- [Language translation](https://deepgram.com/product/speech-understanding/) | ||
- [Sentiment analysis](https://deepgram.com/product/speech-understanding/) | ||
- [Entity detection](https://deepgram.com/product/speech-understanding/) | ||
- [Transcription](https://deepgram.com/product/transcription/) | ||
- [Redaction](https://deepgram.com/product/transcription/) | ||
|
||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. | ||
## Create a Free Deepgram Account | ||
|
||
## Learn More | ||
Before you start, it's essential to generate a Deepgram API key to use in our starter applications. [Sign-up now for Deepgram](https://console.deepgram.com/signup). | ||
|
||
To learn more about Next.js, take a look at the following resources: | ||
## Issue Reporting | ||
|
||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. | ||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. | ||
If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Security Policy](./SECURITY.md) details the procedure for contacting Deepgram. | ||
|
||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! | ||
## Author | ||
|
||
## Deploy on Vercel | ||
[Deepgram](https://deepgram.com) | ||
|
||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. | ||
## License | ||
|
||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. | ||
This project is licensed under the MIT license. See the [LICENSE](./LICENSE) file for more info. |
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,39 @@ | ||
import { DeepgramError, createClient } from "@deepgram/sdk"; | ||
import { NextResponse } from "next/server"; | ||
|
||
export async function GET(request: Request) { | ||
// 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: 10, | ||
}); | ||
|
||
if (newKeyError) { | ||
return NextResponse.json(newKeyError); | ||
} | ||
|
||
return NextResponse.json({ ...newKeyResult, url }); | ||
} |
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,14 @@ | ||
"use client"; | ||
|
||
import { useTranscriptionContext } from "@/context/transcription"; | ||
import { useState } from "react"; | ||
|
||
export default function Captions() { | ||
const { transcription } = useTranscriptionContext(); | ||
|
||
return ( | ||
<div className="captions" id="captions"> | ||
<span>{transcription ?? "Captions by Deepgram"}</span> | ||
</div> | ||
); | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,180 @@ | ||
"use client"; | ||
|
||
import { | ||
CreateProjectKeyResponse, | ||
LiveClient, | ||
LiveTranscriptionEvents, | ||
createClient, | ||
} from "@deepgram/sdk"; | ||
import { useState, useEffect, useCallback } from "react"; | ||
import { useQueue } from "@uidotdev/usehooks"; | ||
import Dg from "./dg.svg"; | ||
import Recording from "./recording.svg"; | ||
import Image from "next/image"; | ||
|
||
export default function Microphone() { | ||
const { add, remove, first, size, queue } = useQueue<any>([]); | ||
const [apiKey, setApiKey] = useState<CreateProjectKeyResponse | null>(); | ||
const [connection, setConnection] = useState<LiveClient | null>(); | ||
const [isListening, setListening] = useState(false); | ||
const [isLoadingKey, setLoadingKey] = useState(true); | ||
const [isLoading, setLoading] = useState(true); | ||
const [isProcessing, setProcessing] = useState(false); | ||
const [micOpen, setMicOpen] = useState(false); | ||
const [microphone, setMicrophone] = useState<MediaRecorder | null>(); | ||
const [userMedia, setUserMedia] = useState<MediaStream | null>(); | ||
const [caption, setCaption] = useState<string | null>(); | ||
|
||
const toggleMicrophone = useCallback(async () => { | ||
if (microphone && userMedia) { | ||
setUserMedia(null); | ||
setMicrophone(null); | ||
|
||
microphone.stop(); | ||
} else { | ||
const userMedia = await navigator.mediaDevices.getUserMedia({ | ||
audio: true, | ||
}); | ||
|
||
const microphone = new MediaRecorder(userMedia); | ||
microphone.start(500); | ||
|
||
microphone.onstart = () => { | ||
setMicOpen(true); | ||
}; | ||
|
||
microphone.onstop = () => { | ||
setMicOpen(false); | ||
}; | ||
|
||
microphone.ondataavailable = (e) => { | ||
add(e.data); | ||
}; | ||
|
||
setUserMedia(userMedia); | ||
setMicrophone(microphone); | ||
} | ||
}, [add, microphone, userMedia]); | ||
|
||
useEffect(() => { | ||
if (!apiKey) { | ||
console.log("getting a new api key"); | ||
fetch("/api", { cache: "no-store" }) | ||
.then((res) => res.json()) | ||
.then((object) => { | ||
if (!("key" in object)) throw new Error("No api key returned"); | ||
|
||
setApiKey(object); | ||
setLoadingKey(false); | ||
}) | ||
.catch((e) => { | ||
console.error(e); | ||
}); | ||
} | ||
}, [apiKey]); | ||
|
||
useEffect(() => { | ||
if (apiKey && "key" in apiKey) { | ||
console.log("connecting to deepgram"); | ||
const deepgram = createClient(apiKey?.key ?? ""); | ||
const connection = deepgram.listen.live({ | ||
model: "nova", | ||
interim_results: true, | ||
}); | ||
|
||
connection.on(LiveTranscriptionEvents.Open, () => { | ||
console.log("connection established"); | ||
setListening(true); | ||
}); | ||
|
||
connection.on(LiveTranscriptionEvents.Close, () => { | ||
console.log("connection closed"); | ||
setListening(false); | ||
setApiKey(null); | ||
setConnection(null); | ||
}); | ||
|
||
connection.on(LiveTranscriptionEvents.Transcript, (data) => { | ||
const words = data.channel.alternatives[0].words; | ||
const caption = words | ||
.map((word: any) => word.punctuated_word ?? word.word) | ||
.join(" "); | ||
if (caption !== "") { | ||
setCaption(caption); | ||
} | ||
}); | ||
|
||
setConnection(connection); | ||
setLoading(false); | ||
} | ||
}, [apiKey]); | ||
|
||
useEffect(() => { | ||
const processQueue = async () => { | ||
if (size > 0 && !isProcessing) { | ||
setProcessing(true); | ||
|
||
if (isListening) { | ||
const blob = first; | ||
connection?.send(blob); | ||
remove(); | ||
} | ||
|
||
const waiting = setTimeout(() => { | ||
clearTimeout(waiting); | ||
setProcessing(false); | ||
}, 250); | ||
} | ||
}; | ||
|
||
processQueue(); | ||
}, [connection, queue, remove, first, size, isProcessing, isListening]); | ||
|
||
if (isLoadingKey) return "Loading temporary API key..."; | ||
if (isLoading) return "Loading the app..."; | ||
|
||
return ( | ||
<div className="w-full relative"> | ||
<div className="mt-10 flex flex-col align-middle items-center"> | ||
<Image | ||
src="/click.png" | ||
width="168" | ||
height="129" | ||
alt="Deepgram Logo" | ||
priority | ||
/> | ||
<button className="w-24 h-24" onClick={() => toggleMicrophone()}> | ||
<Recording | ||
width="96" | ||
height="96" | ||
className={ | ||
`cursor-pointer` + !!userMedia && !!microphone && micOpen | ||
? "fill-red-400 drop-shadow-glowRed" | ||
: "fill-gray-600" | ||
} | ||
/> | ||
</button> | ||
<div className="mt-20 uppercase p-6 text-xl text-center"> | ||
{caption ?? "Captions by Deepgram"} | ||
</div> | ||
</div> | ||
<div | ||
className="z-20 text-white flex shrink-0 grow-0 justify-around items-center | ||
fixed bottom-0 right-5 rounded-lg mr-1 mb-5 lg:mr-5 lg:mb-5 xl:mr-10 xl:mb-10 gap-5" | ||
> | ||
<span className="text-sm text-gray-400"> | ||
{isListening | ||
? "Deepgram connection open!" | ||
: "Deepgram is connecting..."} | ||
</span> | ||
<Dg | ||
width="30" | ||
height="30" | ||
className={ | ||
isListening ? "fill-white drop-shadow-glowBlue" : "fill-gray-600" | ||
} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.