Merge remote-tracking branch 'refs/remotes/origin/main'
This commit is contained in:
199
README.md
199
README.md
@@ -1,36 +1,191 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
<!-- Improved compatibility of back to top link: See: https://github.com/othneildrew/Best-README-Template/pull/73 -->
|
||||
<a id="readme-top"></a>
|
||||
<!--
|
||||
*** Thanks for checking out the Best-README-Template. If you have a suggestion
|
||||
*** that would make this better, please fork the repo and create a pull request
|
||||
*** or simply open an issue with the tag "enhancement".
|
||||
*** Don't forget to give the project a star!
|
||||
*** Thanks again! Now go create something AMAZING! :D
|
||||
-->
|
||||
|
||||
|
||||
|
||||
<!-- PROJECT SHIELDS -->
|
||||
<!--
|
||||
*** I'm using markdown "reference style" links for readability.
|
||||
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
|
||||
*** See the bottom of this document for the declaration of the reference variables
|
||||
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
|
||||
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
|
||||
-->
|
||||
[![Contributors][contributors-shield]][contributors-url]
|
||||
[![Stargazers][stars-shield]][stars-url]
|
||||
[![Issues][issues-shield]][issues-url]
|
||||
[![Apache License][license-shield]][license-url]
|
||||
|
||||
|
||||
|
||||
<!-- PROJECT LOGO -->
|
||||
<br />
|
||||
<div align="center">
|
||||
<a href="https://github.com/GamerBoss101/HoyaHax2025">
|
||||
<img src="/public/HoyaHax2025-icon.svg" alt="Logo" width="80" height="80">
|
||||
</a>
|
||||
|
||||
<h3 align="center">PatSafe</h3>
|
||||
|
||||
<p align="center">
|
||||
Bridging the gap between doctors and patients for seamless post-discharge care.
|
||||
<br />
|
||||
<br />
|
||||
<a href="https://www.patsafe.co">Visit</a>
|
||||
·
|
||||
<a href="https://github.com/GamerBoss101/HoyaHax2025/issues/new?labels=bug&template=bug-report---.md">Report Bug</a>
|
||||
·
|
||||
<a href="https://github.com/GamerBoss101/HoyaHax2025/issues/new?labels=enhancement&template=feature-request---.md">Request Feature</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- ABOUT THE PROJECT -->
|
||||
## About the Project
|
||||
|
||||
PatSafe is a web-based application that connects discharged patients with their doctors for post-discharge care. It features a doctor’s dashboard where they can update patient information, provide medication instructions, and track patient progress. Patients can report their symptoms, recovery status, and medication adherence, which is then updated in real time for their doctor to review. Additionally, PatSafe includes an AI-powered chatbot that allows patients to ask medical questions and report symptoms, helping doctors get insights into the patient’s condition remotely. The chatbot also aids in tracking medication adherence and offering information for diagnoses and prescriptions.
|
||||
|
||||
### How does it work?
|
||||
|
||||
PatSafe was built using **Next.js** and **React.js** for the frontend, while the backend is powered by **MongoDB** and **Clerk** for authentication. The platform utilizes APIs for real-time updates and features such as medication tracking and symptom reporting. We integrated a chatbot powered by **Hugging Face models** for natural language processing, allowing users to interact conversationally and gather helpful medical information.
|
||||
|
||||
|
||||
|
||||
|
||||
### Built With
|
||||
|
||||
* [![Next.js][Next.js]][Next-url]
|
||||
* [![React][React.js]][React-url]
|
||||
* [![Tailwind][Tailwind]][Tailwind-url]
|
||||
* [![Clerk][Clerk]][Clerk-url]
|
||||
* [![MongoDB][MongoDB]][MongoDB-url]
|
||||
* [![HuggingFace][HuggingFace]][HuggingFace-url]
|
||||
* [![OpenAI][OpenAI]][OpenAI]
|
||||
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- GETTING STARTED -->
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
Here are the steps to run the project locally if you want to develop your own project.
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
### Prerequisites
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
* pnpm
|
||||
```sh
|
||||
pnpm self-update
|
||||
```
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
### Installation
|
||||
|
||||
## Learn More
|
||||
1. Fork the repository and set it up as a project on Vercel or another hosting platform
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
2. Install packages
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. You can run the website locally with
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
or, if hosting on Vercel, with
|
||||
```sh
|
||||
vercel dev
|
||||
```
|
||||
|
||||
- [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.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
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.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
|
||||
|
||||
|
||||
<!-- LICENSE -->
|
||||
## License
|
||||
|
||||
Distributed under the Apache 2.0 License. See `LICENSE.txt` for more information.
|
||||
|
||||
|
||||
* [Best README Template](https://github.com/othneildrew/Best-README-Template)
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- MARKDOWN LINKS & IMAGES -->
|
||||
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/GamerBoss101/HoyaHax2025.svg?style=for-the-badge
|
||||
[contributors-url]: https://github.com/GamerBoss101/HoyaHax2025/graphs/contributors
|
||||
[forks-shield]: https://img.shields.io/github/forks/GamerBoss101/HoyaHax2025.svg?style=for-the-badge
|
||||
[forks-url]: https://github.com/GamerBoss101/HoyaHax2025/network/members
|
||||
[stars-shield]: https://img.shields.io/github/stars/GamerBoss101/HoyaHax2025.svg?style=for-the-badge
|
||||
[stars-url]: https://github.com/GamerBoss101/HoyaHax2025/stargazers
|
||||
[issues-shield]: https://img.shields.io/github/issues/GamerBoss101/HoyaHax2025.svg?style=for-the-badge
|
||||
[issues-url]: https://github.com/GamerBoss101/HoyaHax2025/issues
|
||||
[license-shield]: https://img.shields.io/github/license/GamerBoss101/HoyaHax2025.svg?style=for-the-badge
|
||||
[license-url]: https://github.com/GamerBoss101/HoyaHax2025/blob/master/LICENSE.txt
|
||||
[linkedin-shield]: https://img.shields.io/badge/LinkedIn-0A66C2.svg?style=for-the-badge&logo=linkedin&logoColor=white
|
||||
[linkedin-url-joseph]: https://linkedin.com/in/joseph-j-helfenbein
|
||||
[product-screenshot]: images/screenshot.png
|
||||
[Next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white
|
||||
[Next-url]: https://nextjs.org/
|
||||
[React.js]: https://img.shields.io/badge/React.js-20232A?style=for-the-badge&logo=react&logoColor=61DAFB
|
||||
[React-url]: https://reactjs.org/
|
||||
[Vue.js]: https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs&logoColor=4FC08D
|
||||
[Vue-url]: https://vuejs.org/
|
||||
[Angular.io]: https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white
|
||||
[Angular-url]: https://angular.io/
|
||||
[Svelte.dev]: https://img.shields.io/badge/Svelte-4A4A55?style=for-the-badge&logo=svelte&logoColor=FF3E00
|
||||
[Svelte-url]: https://svelte.dev/
|
||||
[Laravel.com]: https://img.shields.io/badge/Laravel-FF2D20?style=for-the-badge&logo=laravel&logoColor=white
|
||||
[Laravel-url]: https://laravel.com
|
||||
[Bootstrap.com]: https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white
|
||||
[Bootstrap-url]: https://getbootstrap.com
|
||||
[JQuery.com]: https://img.shields.io/badge/jQuery-0769AD?style=for-the-badge&logo=jquery&logoColor=white
|
||||
[JQuery-url]: https://jquery.com
|
||||
[Expo]: https://img.shields.io/badge/expo-000000?style=for-the-badge&logo=expo&logoColor=white
|
||||
[Expo-url]: https://expo.dev/
|
||||
[Flask]: https://img.shields.io/badge/flask-4590A1?logo=flask&style=for-the-badge&logoColor=white
|
||||
[Flask-url]: https://flask.palletsprojects.com/en/3.0.x/
|
||||
[JavaScript]: https://img.shields.io/badge/javascript-yellow?logo=javascript&style=for-the-badge&logoColor=white
|
||||
[JavaScript-url]: https://developer.oracle.com/languages/javascript.html
|
||||
[ThreeJS]: https://img.shields.io/badge/three.js-black?logo=three.js&style=for-the-badge&logoColor=white
|
||||
[ThreeJS-url]: https://threejs.org/
|
||||
[TypeScript]: https://img.shields.io/badge/typescript-3178C6?logo=typescript&style=for-the-badge&logoColor=white
|
||||
[TypeScript-url]: https://www.typescriptlang.org/
|
||||
[Python]: https://img.shields.io/badge/python-3776AB?style=for-the-badge&logo=python&logoColor=white
|
||||
[Python-url]: https://www.python.org/
|
||||
[Amazon-RDS]: https://img.shields.io/badge/amazon%20rds-527FFF?style=for-the-badge&logo=amazon%20rds&logoColor=white
|
||||
[Amazon-RDS-url]: https://aws.amazon.com/rds/
|
||||
[Cloudflare]: https://img.shields.io/badge/cloudflare%20workers-F38020?style=for-the-badge&logo=cloudflare%20workers&logoColor=white
|
||||
[Cloudflare-url]: https://workers.cloudflare.com/
|
||||
[Vercel]: https://img.shields.io/badge/vercel-000000?logo=vercel&style=for-the-badge&logoColor=white
|
||||
[Vercel-url]: https://www.vercel.com/
|
||||
[Supabase]: https://img.shields.io/badge/supabase-3FCF8E?logo=supabase&style=for-the-badge&logoColor=white
|
||||
[Supabase-url]: https://supabase.com/
|
||||
[Clerk]: https://img.shields.io/badge/clerk-6C47FF?logo=clerk&style=for-the-badge&logoColor=white
|
||||
[Clerk-url]: https://clerk.com/
|
||||
[Tailwind]: https://img.shields.io/badge/tailwind%20css-06B6D4?logo=tailwindcss&style=for-the-badge&logoColor=white
|
||||
[Tailwind-url]: https://tailwindcss.com/
|
||||
[MongoDB]: https://img.shields.io/badge/mongodb-47A248?logo=mongodb&style=for-the-badge&logoColor=white
|
||||
[MongoDB-url]: https://www.mongodb.com/
|
||||
[HuggingFace]: https://img.shields.io/badge/huggingface-FFD21E?logo=huggingface&style=for-the-badge&logoColor=white
|
||||
[HuggingFace-url]: https://huggingface.co/
|
||||
[OpenAI]: https://img.shields.io/badge/openai%20api-412991?logo=openai&style=for-the-badge&logoColor=white
|
||||
[OpenAI-url]: [https://huggingface.co/](https://openai.com/)
|
||||
|
||||
@@ -7,17 +7,9 @@ import { Button } from '@/components/ui/button';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardHeader, CardContent } from '@/components/ui/card';
|
||||
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
|
||||
import { PersonForm } from './PatientForm';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
const AccountPage = () => {
|
||||
const { user } = useUser();
|
||||
const [userData, setUserData] = useState(null);
|
||||
const [patients, setPatients] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
@@ -72,33 +64,6 @@ const AccountPage = () => {
|
||||
<Button onClick={handleRoleChange} className="mb-4">
|
||||
Change role to {userData.role === 'patient' ? 'caregiver' : 'patient'}
|
||||
</Button>
|
||||
|
||||
{userData.role === 'caregiver' && (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Patients</h2>
|
||||
<ul className="mb-4">
|
||||
{patients.map(patient => (
|
||||
<Collapsible key={patient.id}>
|
||||
<div className="flex items-center justify-between p-2 bg-gray-100 dark:bg-neutral-800 rounded-t-lg">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">{patient.name}</h2>
|
||||
<p className="text-sm text-gray-400">{patient.role}</p>
|
||||
</div>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
<CollapsibleContent className="p-4 border border-t-0 rounded-b-lg">
|
||||
<PersonForm person={patient} />
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
33
src/app/(panels)/suite/doctor/dashboard/AppList.jsx
Normal file
33
src/app/(panels)/suite/doctor/dashboard/AppList.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
|
||||
export function AppointmentList({ appointments }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableCaption>A list of your upcoming appointments.</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">ID</TableHead>
|
||||
<TableHead>Date</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{appointments.map((appointment) => {
|
||||
return (
|
||||
<TableRow key={appointment.id}>
|
||||
<TableCell className="font-medium">{appointment.id}</TableCell>
|
||||
<TableCell>{new Date(appointment.date).toLocaleString()}</TableCell>
|
||||
<TableCell>{appointment.status}</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
"use client"
|
||||
|
||||
import { PatientTable } from "./PatientTable"
|
||||
import { AppointmentList } from "./AppList"
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import { useUser } from '@clerk/nextjs';
|
||||
|
||||
import React from "react";
|
||||
|
||||
|
||||
export default function Dashboard() {
|
||||
|
||||
const router = useRouter();
|
||||
const { user } = useUser();
|
||||
const [userData, setUserData] = useState(null);
|
||||
const { user } = useUser();
|
||||
const [userData, setUserData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
axios.get(`/api/user?userId=${user.id}`).then(response => {
|
||||
setUserData(response.data);
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
axios.get(`/api/user?userId=${user.id}`).then(response => {
|
||||
setUserData(response.data);
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
if (userData) {
|
||||
if (userData) {
|
||||
if (userData.role != "caregiver") {
|
||||
router.push("/suite/patient/dashboard");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const patients = [
|
||||
{ id: 1, name: "John Doe", age: 30, lastVisit: "2024-10-01" },
|
||||
@@ -34,11 +37,21 @@ export default function Dashboard() {
|
||||
{ id: 3, name: "Sam Johnson", age: 40, lastVisit: "2024-10-05" },
|
||||
];
|
||||
|
||||
|
||||
const appointments = [
|
||||
{ id: 1, patientId: 1, date: "2025-01-27T09:00:00", status: "Scheduled" },
|
||||
{ id: 2, patientId: 2, date: "2025-01-27T10:30:00", status: "Scheduled" },
|
||||
{ id: 3, patientId: 3, date: "2025-01-27T14:00:00", status: "Scheduled" },
|
||||
{ id: 4, patientId: 4, date: "2025-01-28T11:00:00", status: "Scheduled" },
|
||||
{ id: 5, patientId: 5, date: "2025-01-28T15:30:00", status: "Scheduled" },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="container mx-auto">
|
||||
<h1 className="text-3xl font-semibold mb-6">Dashboard</h1>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="h-20 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<PatientTable data={patients} />
|
||||
<AppointmentList appointments={appointments} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
76
src/app/(panels)/suite/doctor/patient/page.jsx
Normal file
76
src/app/(panels)/suite/doctor/patient/page.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import axios from "axios";
|
||||
import { useUser } from "@clerk/nextjs";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import PersonForm from "@/components/PersonForm";
|
||||
import { Card } from "@/components/ui/card";
|
||||
|
||||
|
||||
export default function Dashboard() {
|
||||
|
||||
const router = useRouter();
|
||||
const { user } = useUser();
|
||||
const [userData, setUserData] = useState(null);
|
||||
const [patients, setPatients] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
axios.get(`/api/user?userId=${user.id}`).then(response => {
|
||||
setUserData(response.data);
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
if (userData) {
|
||||
if (userData.role != "caregiver") {
|
||||
router.push("/suite/patient/dashboard");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto">
|
||||
<h1 className="text-3xl font-semibold mb-6">Patients</h1>
|
||||
<div className="h-20 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<CardContent>
|
||||
{userData.role === 'caregiver' && (
|
||||
<div>
|
||||
<ul className="mb-4">
|
||||
{patients.map(patient => (
|
||||
<Collapsible key={patient.id}>
|
||||
<div className="flex items-center justify-between p-2 bg-gray-100 dark:bg-neutral-800 rounded-t-lg">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">{patient.name}</h2>
|
||||
<p className="text-sm text-gray-400">{patient.role}</p>
|
||||
</div>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
<CollapsibleContent className="p-4 border border-t-0 rounded-b-lg">
|
||||
<PersonForm person={patient} />
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,156 +1,174 @@
|
||||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
//import Hero1 from '@/components/Hero1'
|
||||
//IMPORT THE HERO1 FUNCTION TO MAKE THE TRANSCRIBE PAGE LOOK BETTER
|
||||
import React, { useState, useRef } from "react";
|
||||
// import axios from "axios";
|
||||
|
||||
import { AlertCircle } from "lucide-react"
|
||||
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "@/components/ui/alert"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
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 [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[]>([]);
|
||||
|
||||
// Handle file selection
|
||||
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);
|
||||
}
|
||||
};
|
||||
// Handle file selection
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle file transcription
|
||||
const handleTranscription = async (audioFile: File) => {
|
||||
if (!audioFile) {
|
||||
alert("No audio file to transcribe!");
|
||||
return;
|
||||
}
|
||||
// Handle file transcription
|
||||
const handleTranscription = async (audioFile: File) => {
|
||||
if (!audioFile) {
|
||||
alert("No audio file to transcribe!");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Starting transcription for:", audioFile.name);
|
||||
console.log("Starting transcription for:", audioFile.name);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", audioFile);
|
||||
console.log(audioFile);
|
||||
setLoading(true);
|
||||
setError(null); // Clear previous errors
|
||||
try {
|
||||
let response = await fetch("/api/transcribe", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
}
|
||||
})
|
||||
const formData = new FormData();
|
||||
formData.append("file", audioFile);
|
||||
setLoading(true);
|
||||
setError(null); // Clear previous errors
|
||||
try {
|
||||
let response = await fetch("/api/transcribe", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
}
|
||||
})
|
||||
|
||||
response = await response.json();
|
||||
console.log("Transcription response:", response);
|
||||
console.log("! response:", response);
|
||||
response = await response.json();
|
||||
console.log("@ response:", response);
|
||||
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
// Start recording audio
|
||||
const startRecording = async () => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
console.log("Microphone access granted.");
|
||||
// Start recording audio
|
||||
const startRecording = async () => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
console.log("Microphone access granted.");
|
||||
|
||||
mediaRecorderRef.current = new MediaRecorder(stream);
|
||||
audioChunksRef.current = []; // Reset audio chunks
|
||||
mediaRecorderRef.current = new MediaRecorder(stream);
|
||||
audioChunksRef.current = []; // Reset audio chunks
|
||||
|
||||
mediaRecorderRef.current.ondataavailable = (event) => {
|
||||
if (event.data.size > 0) {
|
||||
console.log("Audio chunk received:", event.data);
|
||||
audioChunksRef.current.push(event.data);
|
||||
}
|
||||
};
|
||||
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" });
|
||||
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);
|
||||
console.log("Recording stopped. Blob created:", audioBlob);
|
||||
|
||||
setFile(audioFile); // Save the recorded file
|
||||
setTranscription("Processing transcription for recorded audio...");
|
||||
await handleTranscription(audioFile); // Automatically transcribe
|
||||
};
|
||||
setFile(audioFile); // Save the recorded file
|
||||
setTranscription("Processing transcription for recorded audio...");
|
||||
await handleTranscription(audioFile); // Automatically transcribe
|
||||
};
|
||||
|
||||
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.");
|
||||
}
|
||||
};
|
||||
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.");
|
||||
}
|
||||
};
|
||||
|
||||
// Stop recording audio
|
||||
const stopRecording = () => {
|
||||
if (mediaRecorderRef.current) {
|
||||
console.log("Stopping recording...");
|
||||
mediaRecorderRef.current.stop();
|
||||
setRecording(false);
|
||||
}
|
||||
};
|
||||
// Stop recording audio
|
||||
const stopRecording = () => {
|
||||
if (mediaRecorderRef.current) {
|
||||
console.log("Stopping recording...");
|
||||
mediaRecorderRef.current.stop();
|
||||
setRecording(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>
|
||||
<center>
|
||||
Audio Transcription
|
||||
</center>
|
||||
</h1>
|
||||
<div>
|
||||
<h2>Upload or Record Audio</h2>
|
||||
|
||||
<input type="file" accept="audio/*" onChange={handleFileChange} />
|
||||
<button onClick={() => file && handleTranscription(file)} disabled={loading || !file}>
|
||||
{loading ? "Transcribing..." : "Transcribe"}
|
||||
</button>
|
||||
</div>
|
||||
return (
|
||||
<section className="mx-auto my-auto bg-white dark:bg-neutral-950 h-screen w-1/2">
|
||||
<h1 className="text-4xl font-bold text-center mb-8">
|
||||
<center>
|
||||
Audio Transcription
|
||||
</center>
|
||||
</h1>
|
||||
<br />
|
||||
<div>
|
||||
<h2>Upload or Record Audio</h2>
|
||||
|
||||
<div>
|
||||
<h2>Record Audio</h2>
|
||||
{!recording ? (
|
||||
<button onClick={startRecording}>Start Recording</button>
|
||||
) : (
|
||||
<button onClick={stopRecording} disabled={!recording}>
|
||||
Stop Recording
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Input type="file" accept="audio/*" onChange={handleFileChange} />
|
||||
<Button className="my-4" onClick={() => file && handleTranscription(file)} disabled={loading || !file}>
|
||||
{loading ? "Transcribing..." : "Transcribe"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Transcription:</h2>
|
||||
{loading ? (
|
||||
<p>Processing transcription...</p>
|
||||
) : transcription ? (
|
||||
<p>{transcription}</p>
|
||||
) : (
|
||||
<p>No transcription available yet.</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h2>Record Audio</h2>
|
||||
{!recording ? (
|
||||
<Button onClick={startRecording}>Start Recording</Button>
|
||||
) : (
|
||||
<Button onClick={stopRecording} disabled={!recording}>Stop Recording</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div style={{ color: "red" }}>
|
||||
<strong>Error:</strong> {error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
<Card className="mt-4 bg-neutral-200 dark:bg-neutral-800">
|
||||
<CardHeader>
|
||||
<CardTitle>Transcription Result</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent>
|
||||
{loading
|
||||
? "Processing transcription..."
|
||||
: transcription
|
||||
? "Your transcription is ready"
|
||||
: "No transcription available yet"}
|
||||
</CardContent>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>
|
||||
<strong>Error:</strong> {error}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default AudioTranscriber;
|
||||
@@ -182,179 +200,179 @@ const AudioTranscriber: React.FC = () => {
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
if (!audioFile) {
|
||||
setError("No audio file to transcribe!")
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Starting transcription for:", audioFile.name)
|
||||
console.log("Starting transcription for:", audioFile.name)
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append("file", audioFile)
|
||||
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",
|
||||
},
|
||||
})
|
||||
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)
|
||||
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)
|
||||
}
|
||||
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.")
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
console.log("Microphone access granted.")
|
||||
|
||||
mediaRecorderRef.current = new MediaRecorder(stream)
|
||||
audioChunksRef.current = []
|
||||
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.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" })
|
||||
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)
|
||||
console.log("Recording stopped. Blob created:", audioBlob)
|
||||
|
||||
setFile(audioFile)
|
||||
setTranscription("Processing transcription for recorded audio...")
|
||||
await handleTranscription(audioFile)
|
||||
}
|
||||
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.")
|
||||
}
|
||||
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)
|
||||
}
|
||||
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>
|
||||
<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 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>
|
||||
<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>
|
||||
{error && (
|
||||
<Alert variant="destructive" className="mt-4">
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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", "src/app/api/transcribe/route.js", "src/components/ui/calendar.jsx", "src/app/(web)/page.jsx", "src/app/(web)/transcribe/page.jsx", "src/app/(web)/login/page.jsx", "src/app/(panels)/suite/layout.jsx", "src/app/(panels)/suite/doctor/dashboard/page.jsx", "src/app/(panels)/suite/patient/chat/page.jsx", "src/app/api/chat/route.js"],
|
||||
"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/route.js", "src/components/ui/calendar.jsx", "src/app/(web)/page.jsx", "src/app/(web)/transcribe/page.jsx", "src/app/(web)/login/page.jsx", "src/app/(panels)/suite/layout.jsx", "src/app/(panels)/suite/doctor/dashboard/page.jsx", "src/app/(panels)/suite/patient/chat/page.jsx", "src/app/(panels)/suite/doctor/dashboard/AppList.jsx", "src/app/(panels)/suite/doctor/patient/page.jsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user