├── shush ├── bun.lockb ├── .eslintrc.json ├── .env.example ├── app │ ├── favicon.ico │ ├── tryit │ │ ├── [call_id] │ │ │ ├── reqFail.tsx │ │ │ ├── loading.tsx │ │ │ ├── reqProgress.tsx │ │ │ └── page.tsx │ │ └── page.tsx │ ├── layout.tsx │ ├── globals.css │ └── page.tsx ├── next.config.js ├── postcss.config.js ├── lib │ ├── utils.ts │ └── hooks │ │ └── use-copy-to-clipboard.tsx ├── components │ ├── footer.tsx │ ├── share-url.tsx │ ├── python-logo.tsx │ ├── ui │ │ ├── button.tsx │ │ ├── tabs.tsx │ │ ├── codeblock.tsx │ │ └── dialog.tsx │ ├── data-viewer.tsx │ ├── navbar.tsx │ ├── stats.tsx │ ├── audio-submit.tsx │ ├── waveform.tsx │ └── code-host.tsx ├── components.json ├── .gitignore ├── public │ ├── vercel.svg │ └── next.svg ├── tsconfig.json ├── package.json ├── README.md └── tailwind.config.js ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md └── modal ├── modal_app.py └── shush.py /shush/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arihanv/Shush/HEAD/shush/bun.lockb -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /shush/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/babel","next/core-web-vitals"] 3 | } 4 | -------------------------------------------------------------------------------- /shush/.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_MODAL_URL=https://[ORG_NAME]--[STUB_NAME]-entrypoint.modal.run -------------------------------------------------------------------------------- /shush/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arihanv/Shush/HEAD/shush/app/favicon.ico -------------------------------------------------------------------------------- /shush/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /shush/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /shush/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 | -------------------------------------------------------------------------------- /shush/app/tryit/[call_id]/reqFail.tsx: -------------------------------------------------------------------------------- 1 | export default function ReqFail() { 2 | return ( 3 |
4 |
5 | Failed to fetch response 6 |
7 |
8 | ) 9 | } -------------------------------------------------------------------------------- /shush/components/footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import React from 'react' 3 | 4 | export default function Footer() { 5 | return ( 6 | Made With ❤️ by Arihan Varanasi 7 | 8 | ) 9 | } -------------------------------------------------------------------------------- /shush/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /shush/.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*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /shush/app/tryit/[call_id]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Loader2 } from "lucide-react"; 2 | import React from "react"; 3 | 4 | export default function Loading() { 5 | return ( 6 |
7 |
8 |
9 | 10 |
{" "} 11 | Checking Up On Your Function Call 12 |
13 |
14 | ); 15 | } 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*.local 30 | shush/.env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | # python 40 | __pycache__/ -------------------------------------------------------------------------------- /shush/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shush/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /shush/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { GeistSans } from "geist/font/sans"; 3 | import "./globals.css"; 4 | import Navbar from "@/components/navbar"; 5 | import { Toaster } from 'sonner' 6 | 7 | export const metadata: Metadata = { 8 | title: "Shush", 9 | description: 10 | "A demo showcasing the power of WhisperV3 with enhancements from Insanely-Fast-Whisper", 11 | }; 12 | 13 | export default function RootLayout({ 14 | children, 15 | }: { 16 | children: React.ReactNode; 17 | }) { 18 | return ( 19 | 20 | 21 | 22 | 23 | {children} 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /shush/app/tryit/[call_id]/reqProgress.tsx: -------------------------------------------------------------------------------- 1 | import { Loader2 } from "lucide-react"; 2 | 3 | export default function ReqProgress() { 4 | return ( 5 |
6 |
7 |
8 |
9 | 10 |
{" "} 11 | Function Call Is In Progress 12 |
13 |
14 | You can stay on this page or come back later to the same url 15 |
16 |
17 |
18 | ) 19 | } -------------------------------------------------------------------------------- /shush/lib/hooks/use-copy-to-clipboard.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | 5 | export interface useCopyToClipboardProps { 6 | timeout?: number 7 | } 8 | 9 | export function useCopyToClipboard({ 10 | timeout = 2000 11 | }: useCopyToClipboardProps) { 12 | const [isCopied, setIsCopied] = React.useState(false) 13 | 14 | const copyToClipboard = (value: string) => { 15 | if (typeof window === 'undefined' || !navigator.clipboard?.writeText) { 16 | return 17 | } 18 | 19 | if (!value) { 20 | return 21 | } 22 | 23 | navigator.clipboard.writeText(value).then(() => { 24 | setIsCopied(true) 25 | 26 | setTimeout(() => { 27 | setIsCopied(false) 28 | }, timeout) 29 | }) 30 | } 31 | 32 | return { isCopied, copyToClipboard } 33 | } 34 | -------------------------------------------------------------------------------- /shush/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 240 10% 3.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 240 10% 3.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 240 10% 3.9%; 13 | --primary: 240 5.9% 10%; 14 | --primary-foreground: 0 0% 98%; 15 | --secondary: 240 4.8% 95.9%; 16 | --secondary-foreground: 240 5.9% 10%; 17 | --muted: 240 4.8% 95.9%; 18 | --muted-foreground: 240 3.8% 46.1%; 19 | --accent: 240 4.8% 95.9%; 20 | --accent-foreground: 240 5.9% 10%; 21 | --destructive: 0 72.22% 50.59%; 22 | --destructive-foreground: 0 0% 98%; 23 | --border: 240 5.9% 90%; 24 | --input: 240 5.9% 90%; 25 | --ring: 240 5% 64.9%; 26 | --radius: 0.5rem; 27 | } 28 | } 29 | 30 | @layer base { 31 | * { 32 | @apply border-border; 33 | } 34 | body { 35 | @apply bg-background text-foreground; 36 | } 37 | } 38 | 39 | html{ 40 | scroll-behavior: smooth; 41 | } -------------------------------------------------------------------------------- /shush/components/share-url.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { usePathname } from "next/navigation"; 5 | import { useCopyToClipboard } from "@/lib/hooks/use-copy-to-clipboard"; 6 | import { Check, Copy } from "lucide-react"; 7 | 8 | type Props = { 9 | host: string; 10 | call_id: string; 11 | }; 12 | 13 | export default function ShareUrl({ host, call_id }: Props) { 14 | const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }); 15 | const onCopy = () => { 16 | if (isCopied) return; 17 | copyToClipboard(`${host}/${call_id}`); 18 | }; 19 | return ( 20 |
21 |
22 |           {host}/{call_id}
23 |       
24 | 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 arihanv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /shush/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shush", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@radix-ui/react-dialog": "^1.0.5", 13 | "@radix-ui/react-icons": "^1.3.0", 14 | "@radix-ui/react-slot": "^1.0.2", 15 | "@radix-ui/react-tabs": "^1.0.4", 16 | "class-variance-authority": "^0.7.0", 17 | "clsx": "^2.0.0", 18 | "geist": "^1.1.0", 19 | "lucide-react": "^0.292.0", 20 | "next": "14.0.3", 21 | "react": "^18", 22 | "react-dom": "^18", 23 | "react-syntax-highlighter": "^15.5.0", 24 | "sonner": "^1.2.0", 25 | "tailwind-merge": "^2.0.0", 26 | "tailwindcss-animate": "^1.0.7", 27 | "wavesurfer.js": "^7.4.6" 28 | }, 29 | "devDependencies": { 30 | "@tailwindcss/typography": "^0.5.10", 31 | "@types/node": "^20", 32 | "@types/react": "^18", 33 | "@types/react-dom": "^18", 34 | "autoprefixer": "^10.0.1", 35 | "eslint": "^8", 36 | "eslint-config-next": "14.0.3", 37 | "postcss": "^8", 38 | "tailwindcss": "^3.3.0", 39 | "typescript": "^5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /shush/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shush/README.md: -------------------------------------------------------------------------------- 1 | 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). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | 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. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shush 2 | 3 | 4 | https://github.com/arihanv/Shush/assets/63890951/6e675260-c29a-4fd4-8ba5-0b70549f0bcd 5 | 6 | 7 | 8 | 9 | 10 | Shush is an app that deploys a WhisperV3 model with Flash Attention v2 on Modal and makes requests to it via a NextJS app. The essential **goal** of this app is to provide a full-stack demo to those interested in running high-performance models and reliable APIs on demand with auto-scaling. 11 | 12 | This is a demo app built with [Next.js](https://nextjs.org/) (Frontend) + [Modal](https://modal.com/) (Backend). 13 | 14 | # Set Up 15 | Visit [modal.com](https://modal.com/) and create a free account. Then follow the instructions to install the Modal python package and authenticate in your CLI. 16 | 17 | ## Deploy backend 18 | We will be using Modal to deploy and serve [WhisperV3](https://github.com/openai/whisper), an audio transcription model built by OpenAI. 19 | 20 | Execute the following commands in your terminal: 21 | ``` 22 | cd modal 23 | modal deploy shush.py 24 | ``` 25 | This is should give you a url in the form: `https://[ORG_NAME]--[STUB_NAME]-entrypoint.modal.run` 26 | 27 | ## Deploy Frontend 28 | Now let's run the NextJS app. After going back to the root of the repo, execute the following commands: 29 | ``` 30 | cd shush 31 | ``` 32 | Now create a `.env` file and add the url we got from Modal (view `.env.example` for reference) 33 | 34 | Then we can just do: 35 | ``` 36 | bun i 37 | bun run dev 38 | ``` 39 | 40 | And that's it! Open http://localhost:3000/ in your browser and test the app + model out! 41 | -------------------------------------------------------------------------------- /shush/components/python-logo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type Props = { 4 | height: number; 5 | className?: string; 6 | }; 7 | 8 | export default function PythonLogo({ height, className }: Props) { 9 | return ( 10 | 11 | 16 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /shush/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { buttonVariants } from "@/components/ui/button"; 3 | import { twMerge } from "tailwind-merge"; 4 | import CodeHost from "@/components/code-host"; 5 | import Footer from "@/components/footer"; 6 | 7 | export default function Home() { 8 | return ( 9 |
10 |
11 |

12 | Transcribe audio in minutes with WhisperV3 13 |

14 | 19 | Accelerated by Flash Attention v2 +{" "} 20 | Transformers 21 | 22 |
23 | 24 | Host it yourself 25 | 26 | 27 | Try it out 28 | 29 |
30 |
31 |
35 | 36 |
37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /shush/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /shush/components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TabsPrimitive from "@radix-ui/react-tabs" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Tabs = TabsPrimitive.Root 9 | 10 | const TabsList = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, ...props }, ref) => ( 14 | 22 | )) 23 | TabsList.displayName = TabsPrimitive.List.displayName 24 | 25 | const TabsTrigger = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 37 | )) 38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName 39 | 40 | const TabsContent = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 | 52 | )) 53 | TabsContent.displayName = TabsPrimitive.Content.displayName 54 | 55 | export { Tabs, TabsList, TabsTrigger, TabsContent } 56 | -------------------------------------------------------------------------------- /shush/app/tryit/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useRef, useEffect } from "react"; 4 | import Waveform from "@/components/waveform"; 5 | import Stats from "@/components/stats"; 6 | import { File, Upload } from "lucide-react"; 7 | import AudioSubmit from "@/components/audio-submit"; 8 | 9 | export default function TryIt() { 10 | const [file, setFile] = useState(); 11 | const fileInputRef = useRef(null); 12 | 13 | useEffect(() => { 14 | if (fileInputRef.current) { 15 | fileInputRef.current.value = ""; 16 | } 17 | }, [file]); 18 | 19 | return ( 20 |
21 |
22 |
23 | Upload an mp3 file 24 |
25 | {file && ( 26 |
27 |

28 | 29 |

30 | 31 |
32 | )} 33 |
fileInputRef.current?.click()} 35 | className="cursor-pointer justify-center" 36 | > 37 | {!file ? ( 38 |
39 | Select a File 40 |
41 | ) : ( 42 |
43 | Change File 44 |
45 | )} 46 | setFile(e.target.files?.[0])} 52 | /> 53 |
54 |
55 | 56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /shush/components/data-viewer.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 5 | 6 | type Props = { 7 | data: any; 8 | }; 9 | 10 | export default function DataViewer({ data }: Props) { 11 | return ( 12 |
13 | 14 |
15 | 16 | Text 17 | Timestamps 18 | JSON 19 | 20 |
21 | Finished on a NVIDIA A10G in{" "} 22 | ~{data[1].toFixed(2)}s 23 |
24 |
25 | 29 | {data[0].text} 30 | 31 | 35 | {data[0].chunks.map((chunk: any, index: number) => ( 36 |
37 |

{chunk.text}

38 |

39 | {chunk.timestamp[0]}s - {chunk.timestamp[1]}s 40 |

41 |
42 | ))} 43 |
44 | 48 |
{JSON.stringify(data[0], null, 2)}
49 |
50 |
51 |
52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /shush/app/tryit/[call_id]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import DataViewer from "@/components/data-viewer"; 4 | import React, { useEffect, useState } from "react"; 5 | import ShareUrl from "@/components/share-url"; 6 | import ReqFail from "./reqFail"; 7 | import ReqProgress from "./reqProgress"; 8 | import Loading from "./loading"; 9 | 10 | export default function Page({ params }: { params: { call_id: string } }) { 11 | const [data, setData] = useState(); 12 | const [status, setStatus] = useState(); 13 | 14 | useEffect(() => { 15 | let timeoutId: NodeJS.Timeout; 16 | const formData = new FormData(); 17 | formData.append("call_id", params.call_id); 18 | const fetchData = async () => { 19 | fetch(`${process.env.NEXT_PUBLIC_MODAL_URL}/call_id`, { 20 | method: "POST", 21 | body: formData, 22 | }) 23 | .then((response) => { 24 | setStatus(response.status); 25 | if (response.status == 202) { 26 | timeoutId = setTimeout(fetchData, 10000); 27 | } else { 28 | return response.json(); 29 | } 30 | }).then((data) => { 31 | setData(data); 32 | } 33 | ) 34 | 35 | .catch((error) => { 36 | setStatus(500); 37 | console.error("Error:", error); 38 | }); 39 | }; 40 | fetchData(); 41 | return () => clearTimeout(timeoutId); 42 | }, []); 43 | 44 | if (status == 202) { 45 | return ; 46 | } 47 | 48 | if (status == 500) { 49 | return ; 50 | } 51 | 52 | if (!data) { 53 | return ; 54 | } 55 | 56 | if (data) { 57 | return ( 58 | <> 59 |
60 | 61 |
62 |
63 | Share 64 |
65 | 74 |
75 |
76 | 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /shush/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{ts,tsx}', 6 | './components/**/*.{ts,tsx}', 7 | './app/**/*.{ts,tsx}', 8 | './src/**/*.{ts,tsx}', 9 | ], 10 | theme: { 11 | container: { 12 | center: true, 13 | padding: "2rem", 14 | screens: { 15 | "2xl": "1400px", 16 | }, 17 | }, 18 | extend: { 19 | colors: { 20 | border: "hsl(var(--border))", 21 | input: "hsl(var(--input))", 22 | ring: "hsl(var(--ring))", 23 | background: "hsl(var(--background))", 24 | foreground: "hsl(var(--foreground))", 25 | primary: { 26 | DEFAULT: "hsl(var(--primary))", 27 | foreground: "hsl(var(--primary-foreground))", 28 | }, 29 | secondary: { 30 | DEFAULT: "hsl(var(--secondary))", 31 | foreground: "hsl(var(--secondary-foreground))", 32 | }, 33 | destructive: { 34 | DEFAULT: "hsl(var(--destructive))", 35 | foreground: "hsl(var(--destructive-foreground))", 36 | }, 37 | muted: { 38 | DEFAULT: "hsl(var(--muted))", 39 | foreground: "hsl(var(--muted-foreground))", 40 | }, 41 | accent: { 42 | DEFAULT: "hsl(var(--accent))", 43 | foreground: "hsl(var(--accent-foreground))", 44 | }, 45 | popover: { 46 | DEFAULT: "hsl(var(--popover))", 47 | foreground: "hsl(var(--popover-foreground))", 48 | }, 49 | card: { 50 | DEFAULT: "hsl(var(--card))", 51 | foreground: "hsl(var(--card-foreground))", 52 | }, 53 | }, 54 | borderRadius: { 55 | lg: "var(--radius)", 56 | md: "calc(var(--radius) - 2px)", 57 | sm: "calc(var(--radius) - 4px)", 58 | }, 59 | keyframes: { 60 | "accordion-down": { 61 | from: { height: 0 }, 62 | to: { height: "var(--radix-accordion-content-height)" }, 63 | }, 64 | "accordion-up": { 65 | from: { height: "var(--radix-accordion-content-height)" }, 66 | to: { height: 0 }, 67 | }, 68 | }, 69 | animation: { 70 | "accordion-down": "accordion-down 0.2s ease-out", 71 | "accordion-up": "accordion-up 0.2s ease-out", 72 | }, 73 | }, 74 | }, 75 | plugins: [require("tailwindcss-animate"), require('@tailwindcss/typography')], 76 | } -------------------------------------------------------------------------------- /shush/components/navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Ear } from "lucide-react"; 3 | import Link from "next/link"; 4 | 5 | type Props = {}; 6 | 7 | export default function Navbar({}: Props) { 8 | return ( 9 |
10 | 11 | Shush 12 | 13 | 14 |
15 | Get the code{" "} 16 |
17 | 18 | 19 | 20 | 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /shush/components/stats.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React from "react"; 4 | import { Loader2 } from "lucide-react"; 5 | import { useEffect, useState } from "react"; 6 | 7 | type ModalStats = { 8 | backlog: number; 9 | num_active_runners: number; 10 | num_total_runners: number; 11 | }; 12 | 13 | export default function Stats() { 14 | const [stats, setStats] = useState(); 15 | useEffect(() => { 16 | let timeoutId: NodeJS.Timeout; 17 | const fetchData = async () => { 18 | fetch(`${process.env.NEXT_PUBLIC_MODAL_URL}/stats`, { 19 | method: "GET", 20 | }) 21 | .then((response) => response.json()) 22 | .then((data) => { 23 | setStats(data); 24 | }) 25 | .catch((error) => { 26 | console.error("Error:", error); 27 | }) 28 | .finally(() => { 29 | timeoutId = setTimeout(fetchData, 2000); 30 | }); 31 | }; 32 | fetchData(); 33 | return () => clearTimeout(timeoutId); 34 | }, []); 35 | return ( 36 |
37 | {stats ? ( 38 | <> 39 |
40 |
{stats.backlog} Files in Backlog
{" "} 41 | {stats.num_active_runners == 0 && stats.backlog > 0 ? ( 42 |
43 |
44 | 45 |
46 |
Cold Starting
47 |
48 | ) : ( 49 |
{stats.num_active_runners} Runners Online
50 | )} 51 |
52 |
53 |
54 |
GPU
{" "} 55 |
A10G
56 |
57 |
58 |
Precision
{" "} 59 |
fp16
60 |
61 |
62 |
Optimizer
{" "} 63 |
flash-attn 2
64 |
65 |
66 | 67 | ) : ( 68 |
69 | {" "} 70 |
71 | 72 |
73 |
74 | )} 75 |
76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /shush/components/audio-submit.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Button, buttonVariants } from "@/components/ui/button"; 3 | import { toast } from "sonner"; 4 | import { twMerge } from "tailwind-merge"; 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogDescription, 9 | DialogFooter, 10 | DialogHeader, 11 | DialogTitle, 12 | } from "@/components/ui/dialog"; 13 | import Link from "next/link"; 14 | import ShareUrl from "./share-url"; 15 | 16 | type Props = { 17 | setFile: React.Dispatch>; 18 | file: File; 19 | }; 20 | 21 | export default function AudioSubmit({ setFile, file }: Props) { 22 | const [submitted, setSubmitted] = useState(false); 23 | const [open, setOpen] = useState(false); 24 | const [call_id, setCall_id] = useState(""); 25 | async function submitAudio() { 26 | const formData = new FormData(); 27 | formData.append("audio", file); 28 | const promise = () => 29 | fetch(`${process.env.NEXT_PUBLIC_MODAL_URL}/transcribe`, { 30 | method: "POST", 31 | body: formData, 32 | }).then((response) => response.json()); 33 | 34 | toast.promise(promise, { 35 | loading: "Sending Your File to the Server", 36 | success: (data) => { 37 | setOpen(true); 38 | setCall_id(data) 39 | return "Received Call ID: " + data; 40 | }, 41 | error: "Failed to send file", 42 | }); 43 | } 44 | 45 | return ( 46 | <> 47 | 48 | 49 | 50 | Successfully Sent File ! 51 | 52 | The transcription will be available at this link. You can go there right now or check back later. 53 |
54 | 55 |
56 |
57 | 58 |
59 | 60 | Go to the Link 61 | 62 | 73 |
74 |
75 |
76 |
77 |
78 | 86 | 87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /modal/modal_app.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is the base modal app that will be used to generate the modal. View the instruction in the README.md 3 | or visit this website for more information: https://shush.arihanv.com/#host 4 | """ 5 | 6 | from modal import Image, App, method, asgi_app, enter 7 | from fastapi import Request, FastAPI 8 | import tempfile 9 | import time 10 | 11 | MODEL_DIR = "/model" 12 | 13 | web_app = FastAPI() 14 | 15 | 16 | def download_model(): 17 | from huggingface_hub import snapshot_download 18 | 19 | snapshot_download("openai/whisper-large-v3", local_dir=MODEL_DIR) 20 | 21 | 22 | image = ( 23 | Image.from_registry("nvidia/cuda:12.1.0-cudnn8-devel-ubuntu22.04", add_python="3.9") 24 | .apt_install("git", "ffmpeg") 25 | .pip_install( 26 | "transformers", 27 | "ninja", 28 | "packaging", 29 | "wheel", 30 | "torch", 31 | "hf-transfer~=0.1", 32 | "ffmpeg-python", 33 | ) 34 | .run_commands("python -m pip install flash-attn --no-build-isolation", gpu="A10G") 35 | .env({"HF_HUB_ENABLE_HF_TRANSFER": "1"}) 36 | .run_function( 37 | download_model, 38 | ) 39 | ) 40 | 41 | app = App("whisper-v3-demo", image=image) 42 | 43 | 44 | @app.cls( 45 | gpu="A10G", 46 | allow_concurrent_inputs=80, 47 | container_idle_timeout=40, 48 | ) 49 | class WhisperV3: 50 | @enter() 51 | def setup(self): 52 | import torch 53 | from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline 54 | 55 | self.device = "cuda" if torch.cuda.is_available() else "cpu" 56 | self.torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32 57 | model = AutoModelForSpeechSeq2Seq.from_pretrained( 58 | MODEL_DIR, 59 | torch_dtype=self.torch_dtype, 60 | use_safetensors=True, 61 | use_flash_attention_2=True, 62 | ) 63 | processor = AutoProcessor.from_pretrained(MODEL_DIR) 64 | model.to(self.device) 65 | self.pipe = pipeline( 66 | "automatic-speech-recognition", 67 | model=model, 68 | tokenizer=processor.tokenizer, 69 | feature_extractor=processor.feature_extractor, 70 | max_new_tokens=128, 71 | chunk_length_s=30, 72 | batch_size=24, 73 | return_timestamps=True, 74 | torch_dtype=self.torch_dtype, 75 | model_kwargs={"use_flash_attention_2": True}, 76 | device=0, 77 | ) 78 | 79 | @method() 80 | def generate(self, audio: bytes): 81 | fp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") 82 | fp.write(audio) 83 | fp.close() 84 | start = time.time() 85 | output = self.pipe( 86 | fp.name, chunk_length_s=30, batch_size=24, return_timestamps=True 87 | ) 88 | elapsed = time.time() - start 89 | return output, elapsed 90 | 91 | 92 | @app.function() 93 | @web_app.post("/") 94 | async def transcribe(request: Request): 95 | form = await request.form() 96 | audio = await form["audio"].read() 97 | output, elapsed = WhisperV3().generate.remote(audio) 98 | return output, elapsed 99 | 100 | 101 | @app.function() 102 | @asgi_app() 103 | def entrypoint(): 104 | return web_app 105 | -------------------------------------------------------------------------------- /shush/components/ui/codeblock.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | // @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Markdown/CodeBlock.tsx 3 | 4 | "use client"; 5 | 6 | import { FC, memo } from "react"; 7 | import PythonLogo from "../python-logo"; 8 | import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; 9 | import { coldarkCold } from "react-syntax-highlighter/dist/cjs/styles/prism"; 10 | 11 | import { useCopyToClipboard } from "@/lib/hooks/use-copy-to-clipboard"; 12 | 13 | import { Check, Copy, Download } from "lucide-react"; 14 | 15 | interface Props { 16 | language: string; 17 | value: string; 18 | fileName: string; 19 | } 20 | 21 | interface languageMap { 22 | [key: string]: string | undefined; 23 | } 24 | 25 | export const programmingLanguages: languageMap = { 26 | javascript: ".js", 27 | python: ".py", 28 | java: ".java", 29 | c: ".c", 30 | cpp: ".cpp", 31 | "c++": ".cpp", 32 | "c#": ".cs", 33 | ruby: ".rb", 34 | php: ".php", 35 | swift: ".swift", 36 | "objective-c": ".m", 37 | kotlin: ".kt", 38 | typescript: ".ts", 39 | go: ".go", 40 | perl: ".pl", 41 | rust: ".rs", 42 | scala: ".scala", 43 | haskell: ".hs", 44 | lua: ".lua", 45 | shell: ".sh", 46 | sql: ".sql", 47 | html: ".html", 48 | css: ".css", 49 | }; 50 | 51 | const CodeBlock: FC = memo(({ language, value, fileName }) => { 52 | const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }); 53 | 54 | const downloadAsFile = () => { 55 | if (typeof window === "undefined") { 56 | return; 57 | } 58 | 59 | const blob = new Blob([value], { type: "text/plain" }); 60 | const url = URL.createObjectURL(blob); 61 | const link = document.createElement("a"); 62 | link.download = fileName; 63 | link.href = url; 64 | link.style.display = "none"; 65 | document.body.appendChild(link); 66 | link.click(); 67 | document.body.removeChild(link); 68 | URL.revokeObjectURL(url); 69 | }; 70 | 71 | const onCopy = () => { 72 | if (isCopied) return; 73 | copyToClipboard(value); 74 | }; 75 | 76 | return ( 77 |
78 |
79 |
80 |
81 | {language == "python" && } {fileName}{" "} 82 |
{" "} 83 | 84 |
85 |
86 | 90 | 96 |
97 |
98 | 116 | {value} 117 | 118 |
119 | ); 120 | }); 121 | CodeBlock.displayName = "CodeBlock"; 122 | 123 | export { CodeBlock }; 124 | -------------------------------------------------------------------------------- /modal/shush.py: -------------------------------------------------------------------------------- 1 | from modal import ( 2 | Image, 3 | App, 4 | method, 5 | asgi_app, 6 | functions, 7 | enter, 8 | ) 9 | from fastapi import Request, FastAPI, responses 10 | from fastapi.middleware.cors import CORSMiddleware 11 | import tempfile 12 | 13 | MODEL_DIR = "/model" 14 | 15 | web_app = FastAPI() 16 | 17 | web_app.add_middleware( 18 | CORSMiddleware, 19 | allow_origins=["*"], 20 | allow_credentials=True, 21 | allow_methods=["*"], 22 | allow_headers=["*"], 23 | ) 24 | 25 | 26 | def download_model(): 27 | from huggingface_hub import snapshot_download 28 | 29 | snapshot_download("openai/whisper-large-v3", local_dir=MODEL_DIR) 30 | 31 | 32 | image = ( 33 | Image.from_registry("nvidia/cuda:12.1.0-cudnn8-devel-ubuntu22.04", add_python="3.9") 34 | .apt_install("git", "ffmpeg") 35 | .pip_install( 36 | "transformers", 37 | "ninja", 38 | "packaging", 39 | "wheel", 40 | "torch", 41 | "hf-transfer~=0.1", 42 | "ffmpeg-python", 43 | ) 44 | .run_commands("python -m pip install flash-attn --no-build-isolation", gpu="A10G") 45 | .env({"HF_HUB_ENABLE_HF_TRANSFER": "1"}) 46 | .run_function( 47 | download_model, 48 | ) 49 | ) 50 | 51 | app = App("whisper-v3") 52 | 53 | 54 | @app.cls( 55 | image=image, 56 | gpu="A10G", 57 | allow_concurrent_inputs=80, 58 | container_idle_timeout=40, 59 | ) 60 | class WhisperV3: 61 | @enter() 62 | def setup(self): 63 | import torch 64 | from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline 65 | 66 | self.device = "cuda" if torch.cuda.is_available() else "cpu" 67 | self.torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32 68 | model = AutoModelForSpeechSeq2Seq.from_pretrained( 69 | MODEL_DIR, 70 | torch_dtype=self.torch_dtype, 71 | use_safetensors=True, 72 | use_flash_attention_2=True, 73 | ) 74 | processor = AutoProcessor.from_pretrained(MODEL_DIR) 75 | model.to(self.device) 76 | self.pipe = pipeline( 77 | "automatic-speech-recognition", 78 | model=model, 79 | tokenizer=processor.tokenizer, 80 | feature_extractor=processor.feature_extractor, 81 | max_new_tokens=128, 82 | chunk_length_s=30, 83 | batch_size=24, 84 | return_timestamps=True, 85 | torch_dtype=self.torch_dtype, 86 | model_kwargs={"use_flash_attention_2": True}, 87 | device=0, 88 | ) 89 | 90 | @method() 91 | def generate(self, audio: bytes): 92 | import time 93 | 94 | fp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") 95 | fp.write(audio) 96 | fp.close() 97 | start = time.time() 98 | output = self.pipe( 99 | fp.name, chunk_length_s=30, batch_size=24, return_timestamps=True 100 | ) 101 | elapsed = time.time() - start 102 | return output, elapsed 103 | 104 | 105 | @web_app.post("/transcribe") 106 | async def transcribe(request: Request): 107 | print("Received a request from", request.client) 108 | form = await request.form() 109 | file_content = await form["audio"].read() 110 | model = WhisperV3() 111 | call = model.generate.spawn(file_content) 112 | return call.object_id 113 | 114 | 115 | @web_app.get("/stats") 116 | def stats(request: Request): 117 | print("Received a request from", request.client) 118 | model = WhisperV3() 119 | f = model.generate 120 | return f.get_current_stats() 121 | 122 | 123 | @web_app.post("/call_id") 124 | async def get_completion(request: Request): 125 | form = await request.form() 126 | call_id = form["call_id"] 127 | f = functions.FunctionCall.from_id(call_id) 128 | try: 129 | result = f.get(timeout=0) 130 | except TimeoutError: 131 | return responses.JSONResponse( 132 | content="Result did not finish processing.", status_code=202 133 | ) 134 | return result 135 | 136 | 137 | @app.function(allow_concurrent_inputs=4) 138 | @asgi_app() 139 | def entrypoint(): 140 | return web_app 141 | -------------------------------------------------------------------------------- /shush/components/waveform.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import WaveSurfer from "wavesurfer.js"; 3 | import { Loader2, Pause, Play } from "lucide-react"; 4 | import { clsx } from "clsx"; 5 | 6 | type Props = { 7 | file: File; 8 | }; 9 | 10 | type TrackMetadata = { 11 | duration: number; 12 | name: string; 13 | size: number; 14 | }; 15 | 16 | const Waveform = ({ file }: Props) => { 17 | const waveform = useRef(null); 18 | const track = useRef(null); 19 | const [mounted, setMounted] = useState(false); 20 | const [metadata, setMetadata] = useState({ 21 | duration: 0, 22 | name: "", 23 | size: 0, 24 | }); 25 | const [currentTime, setCurrentTime] = useState(0); 26 | const [play, setPlay] = useState(false); 27 | 28 | useEffect(() => { 29 | if (waveform.current) { 30 | track.current = WaveSurfer.create({ 31 | container: waveform.current, 32 | waveColor: "rgb(74 222 128)", 33 | progressColor: "#f3f4f6", 34 | url: URL.createObjectURL(file), 35 | barGap: 1, 36 | barWidth: 3, 37 | barRadius: 3, 38 | cursorColor: "rgb(75 85 99)", 39 | cursorWidth: 3, 40 | }); 41 | track.current.on("ready", () => { 42 | setMounted(true); 43 | setMetadata((metadata) => { 44 | metadata.duration = track.current?.getDuration() || 0; 45 | metadata.name = file.name; 46 | metadata.size = file.size; 47 | return metadata; 48 | }); 49 | }); 50 | track.current.on("finish", () => { 51 | setPlay(false); 52 | }); 53 | 54 | return () => { 55 | if (track.current) { 56 | track.current.destroy(); 57 | setMounted(false); 58 | } 59 | }; 60 | } 61 | }, [file]); 62 | 63 | useEffect(() => { 64 | if (track.current) { 65 | track.current.on("interaction", () => { 66 | setCurrentTime(track.current?.getCurrentTime() || 0); 67 | }); 68 | } 69 | }, [track.current]); 70 | 71 | useEffect(() => { 72 | const intervalId = setInterval(() => { 73 | if (track.current && track.current.isPlaying()) { 74 | setCurrentTime(track.current.getCurrentTime()); 75 | } 76 | }, 100); 77 | return () => clearInterval(intervalId); 78 | }, []); 79 | 80 | return ( 81 | <> 82 |
83 |
89 |
90 | 91 |
92 |
93 |
99 |
100 |
{file.name}
101 |
{(metadata.size / 1000).toFixed(2)} kb
102 |
103 |
104 |
105 |
106 | {currentTime.toFixed(2)}s 107 |
108 | 115 |
116 | {metadata.duration.toFixed(2)}s 117 |
118 |
119 |
120 |
121 | 122 | ); 123 | }; 124 | 125 | export default Waveform; 126 | -------------------------------------------------------------------------------- /shush/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DialogPrimitive from "@radix-ui/react-dialog" 5 | import { Cross2Icon } from "@radix-ui/react-icons" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Dialog = DialogPrimitive.Root 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger 12 | 13 | const DialogPortal = DialogPrimitive.Portal 14 | 15 | const DialogClose = DialogPrimitive.Close 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )) 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )) 54 | DialogContent.displayName = DialogPrimitive.Content.displayName 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
67 | ) 68 | DialogHeader.displayName = "DialogHeader" 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
81 | ) 82 | DialogFooter.displayName = "DialogFooter" 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )) 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )) 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogTrigger, 116 | DialogClose, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | } 123 | -------------------------------------------------------------------------------- /shush/components/code-host.tsx: -------------------------------------------------------------------------------- 1 | import { CodeBlock } from "./ui/codeblock"; 2 | import Link from "next/link"; 3 | 4 | import React from "react"; 5 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 6 | // import Markdown from "./markdown"; 7 | 8 | export default function CodeHost() { 9 | return ( 10 |
11 | 12 | 13 | Deploy on Modal 14 | Run Locally 15 | 16 | 17 |
18 |

19 | One of the easiest and cheapest way to host an on-demand API is 20 | via{" "} 21 | 26 | Modal 27 | 28 | . 29 |

30 |

31 | Once you create a free account and download the Python client, 32 | copy or download the base code below. It's on github too:{" "} 33 | 38 | Code 39 | 40 |

41 |
42 |
43 |
44 | 141 |
142 |
143 |
144 |

145 | After authenticating with the Modal CLI, run this in your 146 | terminal: 147 |

148 |
149 |               
150 |                 $
151 |                 {"modal deploy modal_app.py"}
152 |               
153 |             
154 |
155 |
156 |

{`Now you can make requests! Remember to fill in the missing info:`}

157 |
158 |               
159 |                 $
160 |                 {`curl -X POST -F "audio=@" https://--whisper-v3-demo-entrypoint.modal.run`}
161 |               
162 |             
163 |
164 |
165 | 166 |
167 |

168 | Run WhisperV3 easily with 173 | Insanely Fast Whisper 174 | 175 | . 176 |

177 |

178 | Install insanely-fast-whisper with pipx 179 |

180 |
181 |               
182 |                 $pipx install
183 |                 insanely-fast-whisper
184 |               
185 |             
186 |

187 | Run inference with flash attention v2. Requires 188 | Amphere GPUs (A10G, A100, etc.) View the full requirements at the 189 | pypi page:{" "} 190 | 195 | flash-attn 196 | 197 |

198 |
199 |               
200 |                 $
201 |                 {
202 |                   "insanely-fast-whisper --file-name  --flash True"
203 |                 }
204 |               
205 |             
206 |

207 | If you don't have an Amphere GPU, you can still run WhisperV3 208 | without flash attention. 209 |

210 |
211 |               
212 |                 $
213 |                 {"insanely-fast-whisper --file-name "}
214 |               
215 |             
216 |
217 |
218 |
219 |
220 | ); 221 | } 222 | --------------------------------------------------------------------------------