├── demo.mp4 ├── next.config.mjs ├── .prettierrc ├── postcss.config.mjs ├── middleware.ts ├── lib ├── utils.ts ├── redis.server.ts └── storage.server.ts ├── components.json ├── .env.example ├── .gitignore ├── .prettierignore ├── tsconfig.json ├── app ├── layout.tsx ├── api │ ├── get │ │ └── route.ts │ ├── history │ │ └── route.ts │ ├── upload │ │ └── route.ts │ ├── schedule │ │ └── route.ts │ └── transcribe │ │ └── route.ts ├── globals.css └── page.tsx ├── components └── ui │ ├── toaster.tsx │ ├── input.tsx │ ├── tooltip.tsx │ ├── button.tsx │ ├── use-toast.ts │ └── toast.tsx ├── package.json ├── tailwind.config.ts └── README.md /demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/transcriber/master/demo.mp4 -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | export default nextConfig 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "printWidth": 180, 4 | "singleQuote": true, 5 | "plugins": ["prettier-plugin-tailwindcss"] 6 | } 7 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { clerkMiddleware } from '@clerk/nextjs/server' 2 | 3 | export default clerkMiddleware() 4 | 5 | export const config = { 6 | matcher: ['/((?!.*\\..*|_next).*)'], 7 | } 8 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /lib/redis.server.ts: -------------------------------------------------------------------------------- 1 | import { Redis } from '@upstash/redis' 2 | 3 | const redis = new Redis({ 4 | url: process.env.UPSTASH_REDIS_REST_URL, 5 | token: process.env.UPSTASH_REDIS_REST_TOKEN, 6 | }) 7 | 8 | export default redis 9 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | FIREWORKS_API_KEY="..." 2 | AWS_KEY_ID="..." 3 | AWS_REGION_NAME="auto" 4 | AWS_S3_BUCKET_NAME="..." 5 | AWS_SECRET_ACCESS_KEY="..." 6 | CLOUDFLARE_R2_ACCOUNT_ID="..." 7 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_..." 8 | CLERK_SECRET_KEY="sk_test_..." 9 | QSTASH_TOKEN="..." 10 | QSTASH_CURRENT_SIGNING_KEY="sig_..." 11 | QSTASH_NEXT_SIGNING_KEY="sig_..." 12 | DEPLOYMENT_URL="https://...vercel.app" 13 | UPSTASH_REDIS_REST_URL="https://...upstash.io" 14 | UPSTASH_REDIS_REST_TOKEN="..." 15 | RANDOM_SEPERATOR="4444Kdav" 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | **/*.md* 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Toaster } from '@/components/ui/toaster' 2 | import { ClerkProvider } from '@clerk/nextjs' 3 | import type { Metadata } from 'next' 4 | import { Inter } from 'next/font/google' 5 | import './globals.css' 6 | 7 | const inter = Inter({ subsets: ['latin'] }) 8 | 9 | export const metadata: Metadata = { 10 | title: 'Create Next App', 11 | description: 'Generated by create next app', 12 | } 13 | 14 | export default function RootLayout({ 15 | children, 16 | }: Readonly<{ 17 | children: React.ReactNode 18 | }>) { 19 | return ( 20 | 21 |
22 |4 | Introduction · 5 | One-click Deploy · 6 | Tech Stack + Features · 7 | Author 8 |
9 | 10 | ## Introduction 11 | 12 | In this repository, you will find the code for a scheduled audio transcription system, built using Upstash QStash for task scheduling and Fireworks AI for transcription. You will also learn techniques for secure file uploads to Cloudflare R2, user authentication with Clerk, and data storage with Upstash Redis. 13 | 14 | ## Demo 15 | 16 | https://github.com/user-attachments/assets/9d1484be-2a69-4659-8873-5f8a2f3eb8eb 17 | 18 | ## One-click Deploy 19 | 20 | You can deploy this template to Vercel with the button below: 21 | 22 | [](https://vercel.com/new/clone?repository-url=https://github.com/upstash/transcriber&env=FIREWORKS_API_KEY,AWS_KEY_ID,AWS_REGION_NAME,AWS_S3_BUCKET_NAME,AWS_SECRET_ACCESS_KEY,CLOUDFLARE_R2_ACCOUNT_ID,NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,CLERK_SECRET_KEY) 23 | 24 | ## Tech Stack + Features 25 | 26 | ### Frameworks 27 | 28 | - [Next.js](https://nextjs.org/) – React framework for building performant apps with the best developer experience. 29 | - [Clerk](https://clerk.dev/) – Clerk is a complete suite of embeddable UIs, flexible APIs, and admin dashboards to authenticate and manage your users. 30 | 31 | ### Platforms 32 | 33 | - [Vercel](https://vercel.com/) – Easily preview & deploy changes with git. 34 | - [Upstash](https://upstash.com) - Serverless database platform. You are going to use Upstash Vector for storing vector embeddings and metadata, and Upstash Redis for storing per user chat history. 35 | - [Fireworks](https://fireworks.ai/) - A generative AI inference platform to run and customize models with speed and production-readiness. 36 | 37 | ### UI 38 | 39 | - [Tailwind CSS](https://tailwindcss.com/) – Utility-first CSS framework for rapid UI development. 40 | - [Radix](https://www.radix-ui.com/) – Primitives like modal, popover, etc. to build a stellar user experience. 41 | - [Lucide](https://lucide.dev/) – Beautifully simple, pixel-perfect icons. 42 | - [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) – Optimize custom fonts and remove external network requests for improved performance. 43 | 44 | ### Code Quality 45 | 46 | - [TypeScript](https://www.typescriptlang.org/) – Static type checker for end-to-end typesafety 47 | - [Prettier](https://prettier.io/) – Opinionated code formatter for consistent code style 48 | 49 | ## Author 50 | 51 | - Rishi Raj Jain ([@rishi_raj_jain_](https://twitter.com/rishi_raj_jain_)) 52 | -------------------------------------------------------------------------------- /components/ui/use-toast.ts: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | // Inspired by react-hot-toast library 4 | import type { ToastActionElement, ToastProps } from '@/components/ui/toast' 5 | import * as React from 'react' 6 | 7 | const TOAST_LIMIT = 1 8 | const TOAST_REMOVE_DELAY = 1000000 9 | 10 | type ToasterToast = ToastProps & { 11 | id: string 12 | title?: React.ReactNode 13 | description?: React.ReactNode 14 | action?: ToastActionElement 15 | } 16 | 17 | const actionTypes = { 18 | ADD_TOAST: 'ADD_TOAST', 19 | UPDATE_TOAST: 'UPDATE_TOAST', 20 | DISMISS_TOAST: 'DISMISS_TOAST', 21 | REMOVE_TOAST: 'REMOVE_TOAST', 22 | } as const 23 | 24 | let count = 0 25 | 26 | function genId() { 27 | count = (count + 1) % Number.MAX_SAFE_INTEGER 28 | return count.toString() 29 | } 30 | 31 | type ActionType = typeof actionTypes 32 | 33 | type Action = 34 | | { 35 | type: ActionType['ADD_TOAST'] 36 | toast: ToasterToast 37 | } 38 | | { 39 | type: ActionType['UPDATE_TOAST'] 40 | toast: Partial