diff --git a/package.json b/package.json index 75ecf11..68086fa 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "openai": "^4.80.1", "openai-whisper": "^1.0.2", "react": "^19.0.0", - "react-day-picker": "^8.10.1", + "react-day-picker": "^9.5.0", "react-dom": "^19.0.0", "recharts": "^2.15.0", "svix": "^1.45.1", diff --git a/src/app/api/transcribe/app.js b/src/app/api/transcribe/app.js new file mode 100644 index 0000000..7c0a0ab --- /dev/null +++ b/src/app/api/transcribe/app.js @@ -0,0 +1,127 @@ +import fs, {unlinkSync, existsSync } from 'fs'; +import path from 'path'; +import { config } from 'dotenv'; +import formidable from 'formidable'; +import { AxiosError } from 'axios'; +import OpenAI from 'openai'; + +// Load environment variables +config(); + +const OPENAI_API_KEY = process.env.OPENAI_API_KEY; + +if (!OPENAI_API_KEY) { + throw new Error('OpenAI API key is missing. Set OPENAI_API_KEY in your .env.local file.'); +} + +// Initialize OpenAI client +const openaiClient = new OpenAI({ + apiKey: OPENAI_API_KEY, +}); + +export const apiconfig = { + api: { + bodyParser: false, // Disable default body parsing + }, +}; + +// Helper to parse multipart form data +async function parseMultipartForm(req) { + const form = formidable({ + multiples: false, // Single file upload + uploadDir: '/tmp', // Temporary directory + keepExtensions: true, + maxFileSize: 50 * 1024 * 1024, // 50 MB + }); + + return new Promise((resolve, reject) => { + form.parse(req, (err, fields, files) => { + if (err) { + reject(err); + return; + } + + const file = files.file; + if (!file) { + reject(new Error('No file found in the upload.')); + return; + } + + resolve({ + filePath: file.filepath, + originalFilename: file.originalFilename || 'unknown', + }); + }); + }); +} + +// Main handler +export default async function handler(req, res) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed. Use POST.' }); + } + + let filePath = null; + + try { + // Parse file upload + const { filePath: tempFilePath, originalFilename } = await parseMultipartForm(req); + filePath = tempFilePath; + + // Log file details + console.log('Uploaded file path:', filePath); + console.log('Original filename:', originalFilename); + + // Validate file extension + const allowedExtensions = ['mp3', 'wav', 'm4a']; + const fileExtension = path.extname(originalFilename).toLowerCase().replace('.', ''); + if (!allowedExtensions.includes(fileExtension)) { + unlinkSync(filePath); + return res.status(400).json({ + error: `Invalid file format. Only ${allowedExtensions.join(', ')} are supported.`, + }); + } + + // Create file stream + const audio_file = fs.createReadStream(filePath); + + // Step 2: Get transcription from OpenAI Whisper model + console.log('Requesting transcription...'); + const transcription = await openaiClient.audio.transcriptions.create({ + file: audio_file, + model: 'whisper-1', + response_format: 'text', + }); + + // Clean up temporary file + if (filePath && existsSync(filePath)) { + unlinkSync(filePath); + } + + // Send response back to client + return res.status(200).json({ transcription: transcription.text }); + } catch (error) { + console.error('Error during transcription:', error); + + if (error instanceof AxiosError) { + console.error('OpenAI API error:', error.response?.data || error.message); + return res.status(error.response?.status || 500).json({ + error: error.response?.data?.error?.message || 'OpenAI API Error.', + }); + } + + return res.status(500).json({ + error: 'An unexpected error occurred.', + }); + } finally { + if (filePath) { + try { + if (existsSync(filePath)) { + unlinkSync(filePath); + } + } catch (err) { + console.error('Failed to clean up temporary file:', err); + } + } + } +} diff --git a/src/app/api/transcribe/app.ts b/src/app/api/transcribe/app.ts deleted file mode 100644 index b5b807a..0000000 --- a/src/app/api/transcribe/app.ts +++ /dev/null @@ -1,128 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { createReadStream, unlinkSync } from "fs"; -import path from "path"; -import { IncomingMessage } from "http"; -import { config } from "dotenv"; -import formidable, { File } from "formidable"; -import { AxiosError } from "axios"; -import { OpenAI } from 'openai'; - -// Load environment variables -config(); - -const OPENAI_API_KEY = process.env.OPENAI_API_KEY; - -if (!OPENAI_API_KEY) { - throw new Error("OpenAI API key is missing. Set OPENAI_API_KEY in your .env.local file."); -} - -// Initialize OpenAI client -const openaiClient = new OpenAI({ - apiKey: OPENAI_API_KEY, -}); - -export const apiconfig = { - api: { - bodyParser: false, // Disable default body parsing - }, -}; - -// Helper to parse multipart form data -async function parseMultipartForm(req: IncomingMessage): Promise<{ filePath: string; originalFilename: string }> { - const form = formidable({ - multiples: false, // Single file upload - uploadDir: "/tmp", // Temporary directory - keepExtensions: true, - maxFileSize: 50 * 1024 * 1024, // 50 MB - }); - - return new Promise((resolve, reject) => { - form.parse(req, (err, fields, files) => { - if (err) { - reject(err); - return; - } - - const file = files.file as File | undefined; - if (!file) { - reject(new Error("No file found in the upload.")); - return; - } - - resolve({ - filePath: file.filepath, - originalFilename: file.originalFilename || "unknown", - }); - }); - }); -} - -// Main handler -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method !== "POST") { - return res.status(405).json({ error: "Method not allowed. Use POST." }); - } - - let filePath: string | null = null; - - try { - // Parse file upload - const { filePath: tempFilePath, originalFilename } = await parseMultipartForm(req); - filePath = tempFilePath; - - // Log file details - console.log("Uploaded file path:", filePath); - console.log("Original filename:", originalFilename); - - // Validate file extension - const allowedExtensions = ["mp3", "wav", "m4a"]; - const fileExtension = path.extname(originalFilename).toLowerCase().replace(".", ""); - if (!allowedExtensions.includes(fileExtension)) { - unlinkSync(filePath); - return res.status(400).json({ - error: `Invalid file format. Only ${allowedExtensions.join(", ")} are supported.`, - }); - } - - // Create file stream - const audioFile = createReadStream(filePath); - console.log("File stream created for:", audioFile.path); - - // Send to OpenAI Whisper API - console.log("Sending file to OpenAI Whisper..."); - const response = await (openaiClient as any).createCompletion({ - model: "whisper-1", - file: audioFile, - }); - - console.log("OpenAI response:", response.data); - - // Clean up temporary file - unlinkSync(filePath); - - // Send response back to client - return res.status(200).json({ transcription: response.data.text }); - } catch (error) { - console.error("Error during transcription:", error); - - if (error instanceof AxiosError) { - console.error("OpenAI API error:", error.response?.data || error.message); - return res.status(error.response?.status || 500).json({ - error: error.response?.data.error?.message || "OpenAI API Error.", - }); - } - - return res.status(500).json({ - error: "An unexpected error occurred.", - }); - } finally { - if (filePath) { - try { - unlinkSync(filePath); - } catch (err) { - console.error("Failed to clean up temporary file:", err); - } - } - } -} - diff --git a/tsconfig.json b/tsconfig.json index d6794a3..9f97d0a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,6 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app/api/connectDB.js", "src/lib/utils.js", "src/app/(web)/account/page.jsx", "src/app/(panels)/suite/patient/account/page.jsx", "src/app/(panels)/suite/patient/dashboard/MedicationTable.jsx", "src/app/(panels)/suite/patient/dashboard/page.jsx"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app/api/connectDB.js", "src/lib/utils.js", "src/app/(web)/account/page.jsx", "src/app/(panels)/suite/patient/account/page.jsx", "src/app/(panels)/suite/patient/dashboard/MedicationTable.jsx", "src/app/(panels)/suite/patient/dashboard/page.jsx", "src/app/api/transcribe/app.js"], "exclude": ["node_modules"] }