This commit is contained in:
suraj.shenoy.b@gmail.com
2025-01-25 21:40:32 -06:00
7 changed files with 488 additions and 23 deletions

View File

@@ -18,6 +18,7 @@
"@radix-ui/react-navigation-menu": "^1.2.4", "@radix-ui/react-navigation-menu": "^1.2.4",
"@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.7", "@radix-ui/react-tooltip": "^1.1.7",
"@tanstack/react-table": "^8.20.6", "@tanstack/react-table": "^8.20.6",
"@vercel/analytics": "^1.4.1", "@vercel/analytics": "^1.4.1",

View File

@@ -1,3 +1,5 @@
/*
"use client" "use client"
import React from 'react'; import React from 'react';
@@ -23,3 +25,60 @@ export default function RootLayout({ children }: { children: React.ReactNode })
</html> </html>
) )
} }
*/
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import "./globals.css"
import { Mic } from "lucide-react"
const inter = Inter({ subsets: ["latin"] })
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
<div className="flex flex-col min-h-screen">
<header className="bg-primary text-primary-foreground shadow-md">
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center space-x-2">
<Mic className="h-6 w-6" />
<h1 className="text-2xl font-bold">PostCare</h1>
</div>
<nav>
<ul className="flex space-x-4">
<li>
<a href="#" className="hover:underline">
Home
</a>
</li>
<li>
<a href="#" className="hover:underline">
About
</a>
</li>
<li>
<a href="#" className="hover:underline">
Contact
</a>
</li>
</ul>
</nav>
</div>
</header>
<main className="flex-grow">{children}</main>
<footer className="bg-muted mt-8">
<div className="container mx-auto px-4 py-6 text-center">
</div>
</footer>
</div>
</body>
</html>
)
}

106
src/app/(web)/page.css Normal file
View File

@@ -0,0 +1,106 @@
/*
"use client"
import { Hero } from "@/components/hero";
import { Facts } from "@/components/facts";
import Link from "next/link";
export default function Home() {
return (
<div className="items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<Hero />
<Facts />
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100vh' }}>
<Link href="/transcribe">
<button>Go to Transcribe Page</button>
</Link>
</div>
</div>
);
}
*/
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--ring: 215 20.2% 65.1%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--ring: 217.2 32.6% 17.5%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -1,22 +0,0 @@
"use client"
import { Hero } from "@/components/hero";
import { Facts } from "@/components/facts";
import Link from "next/link";
export default function Home() {
return (
<div className="items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<Hero />
<Facts />
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100vh' }}>
<Link href="/transcribe">
<button>Go to Transcribe Page</button>
</Link>
</div>
</div>
);
}

View File

@@ -1,3 +1,5 @@
/*
"use client"; "use client";
import React, { useState, useRef } from "react"; import React, { useState, useRef } from "react";
@@ -129,3 +131,208 @@ const AudioTranscriber: React.FC = () => {
}; };
export default AudioTranscriber; export default AudioTranscriber;
*/
"use client"
import type React from "react"
import { useState, useRef } from "react"
import axios from "axios"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Mic, Upload, StopCircle, FileAudio } from "lucide-react"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
const AudioTranscriber: React.FC = () => {
const [file, setFile] = useState<File | null>(null)
const [transcription, setTranscription] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
const [recording, setRecording] = useState(false)
const [error, setError] = useState<string | null>(null)
const mediaRecorderRef = useRef<MediaRecorder | null>(null)
const audioChunksRef = useRef<Blob[]>([])
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files.length > 0) {
setFile(event.target.files[0])
console.log("File selected:", event.target.files[0].name)
}
}
const handleTranscription = async (audioFile: File) => {
if (!audioFile) {
setError("No audio file to transcribe!")
return
}
console.log("Starting transcription for:", audioFile.name)
const formData = new FormData()
formData.append("file", audioFile)
setLoading(true)
setError(null)
try {
const response = await axios.post("http://localhost:8000/transcribe", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
console.log("Transcription response:", response.data)
if (response.data && response.data.transcription) {
setTranscription(response.data.transcription)
} else {
setError("Unexpected response format. Check backend API.")
console.error("Invalid response format:", response.data)
}
} catch (error) {
console.error("Error transcribing audio:", error)
setError("Failed to transcribe audio. Please try again.")
} finally {
setLoading(false)
}
}
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
console.log("Microphone access granted.")
mediaRecorderRef.current = new MediaRecorder(stream)
audioChunksRef.current = []
mediaRecorderRef.current.ondataavailable = (event) => {
if (event.data.size > 0) {
console.log("Audio chunk received:", event.data)
audioChunksRef.current.push(event.data)
}
}
mediaRecorderRef.current.onstop = async () => {
const audioBlob = new Blob(audioChunksRef.current, { type: "audio/mp3" })
const audioFile = new File([audioBlob], "recording.mp3", { type: "audio/mp3" })
console.log("Recording stopped. Blob created:", audioBlob)
setFile(audioFile)
setTranscription("Processing transcription for recorded audio...")
await handleTranscription(audioFile)
}
mediaRecorderRef.current.start()
console.log("Recording started.")
setRecording(true)
} catch (error) {
console.error("Error starting recording:", error)
setError("Failed to start recording. Please check microphone permissions.")
}
}
const stopRecording = () => {
if (mediaRecorderRef.current) {
console.log("Stopping recording...")
mediaRecorderRef.current.stop()
setRecording(false)
}
}
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-4xl font-bold text-center mb-8">Audio Transcriber</h1>
<Card className="mb-8">
<CardHeader>
<CardTitle>Transcribe Audio</CardTitle>
<CardDescription>Upload an audio file or record directly to transcribe</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="upload" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="upload">Upload Audio</TabsTrigger>
<TabsTrigger value="record">Record Audio</TabsTrigger>
</TabsList>
<TabsContent value="upload">
<div className="space-y-4">
<Label htmlFor="audio-file">Select an audio file</Label>
<Input id="audio-file" type="file" accept="audio/*" onChange={handleFileChange} />
<Button
onClick={() => file && handleTranscription(file)}
disabled={loading || !file}
className="w-full"
>
{loading ? "Transcribing..." : "Transcribe"}
<Upload className="ml-2 h-4 w-4" />
</Button>
</div>
</TabsContent>
<TabsContent value="record">
<div className="space-y-4">
<Label>Record audio from your microphone</Label>
{!recording ? (
<Button onClick={startRecording} className="w-full">
Start Recording
<Mic className="ml-2 h-4 w-4" />
</Button>
) : (
<Button onClick={stopRecording} variant="destructive" className="w-full">
Stop Recording
<StopCircle className="ml-2 h-4 w-4" />
</Button>
)}
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Transcription Result</CardTitle>
<CardDescription>
{loading
? "Processing transcription..."
: transcription
? "Your transcription is ready"
: "No transcription available yet"}
</CardDescription>
</CardHeader>
<CardContent>
<Textarea
value={transcription || ""}
readOnly
placeholder="Transcription will appear here"
className="min-h-[200px]"
/>
</CardContent>
{file && (
<CardFooter className="flex justify-between items-center">
<div className="flex items-center">
<FileAudio className="mr-2 h-4 w-4" />
<span className="text-sm text-muted-foreground">{file.name}</span>
</div>
<Button variant="outline" onClick={() => setFile(null)}>
Clear
</Button>
</CardFooter>
)}
</Card>
{error && (
<Alert variant="destructive" className="mt-4">
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
</div>
)
}
export default AudioTranscriber

View File

@@ -0,0 +1,59 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }

View File

@@ -0,0 +1,55 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }