Merge branch 'main' of https://github.com/aki-008/prepAI into frontend
Browse files- .gitignore → Backend/.gitignore +0 -0
- .pre-commit-config.yaml → Backend/.pre-commit-config.yaml +0 -0
- Readme.md → Backend/Readme.md +0 -0
- Backend/app/config.py +7 -8
- requiremnts.txt → Backend/requiremnts.txt +0 -0
- Frontend/src/components/auth/SignIn.tsx +14 -8
- ai/stt/stt.ipynb +0 -0
- test/voice.py +62 -0
.gitignore → Backend/.gitignore
RENAMED
|
File without changes
|
.pre-commit-config.yaml → Backend/.pre-commit-config.yaml
RENAMED
|
File without changes
|
Readme.md → Backend/Readme.md
RENAMED
|
File without changes
|
Backend/app/config.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
-
import os
|
| 2 |
from pydantic_settings import BaseSettings
|
| 3 |
|
| 4 |
class Settings(BaseSettings):
|
|
|
|
|
|
|
| 5 |
DATABASE_URL: str = os.getenv(
|
| 6 |
"DATABASE_URL",
|
| 7 |
"postgresql+asyncpg://postgres:mysecretpassword@localhost:5432/studentdb"
|
|
@@ -11,17 +12,15 @@ class Settings(BaseSettings):
|
|
| 11 |
SECRET_KEY: str = os.getenv("SECRET_KEY", "production")
|
| 12 |
ALGORITHM: str = "HS256"
|
| 13 |
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
| 14 |
-
|
| 15 |
-
# Application
|
| 16 |
APP_NAME: str = "Student Management API"
|
| 17 |
APP_VERSION: str = "1.0.0"
|
| 18 |
APP_DESCRIPTION: str = "FastAPI + PostgreSQL with SQLAlchemy async"
|
| 19 |
-
|
| 20 |
-
# CORS
|
| 21 |
CORS_ORIGINS: list = ["*"]
|
| 22 |
-
|
| 23 |
class Config:
|
| 24 |
env_file = ".env"
|
| 25 |
-
|
| 26 |
|
| 27 |
-
settings = Settings()
|
|
|
|
|
|
|
| 1 |
from pydantic_settings import BaseSettings
|
| 2 |
|
| 3 |
class Settings(BaseSettings):
|
| 4 |
+
DATABASE_URL: str
|
| 5 |
+
SECRET_KEY: str
|
| 6 |
DATABASE_URL: str = os.getenv(
|
| 7 |
"DATABASE_URL",
|
| 8 |
"postgresql+asyncpg://postgres:mysecretpassword@localhost:5432/studentdb"
|
|
|
|
| 12 |
SECRET_KEY: str = os.getenv("SECRET_KEY", "production")
|
| 13 |
ALGORITHM: str = "HS256"
|
| 14 |
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
| 15 |
+
|
|
|
|
| 16 |
APP_NAME: str = "Student Management API"
|
| 17 |
APP_VERSION: str = "1.0.0"
|
| 18 |
APP_DESCRIPTION: str = "FastAPI + PostgreSQL with SQLAlchemy async"
|
| 19 |
+
|
|
|
|
| 20 |
CORS_ORIGINS: list = ["*"]
|
| 21 |
+
|
| 22 |
class Config:
|
| 23 |
env_file = ".env"
|
| 24 |
+
extra = "ignore" # optional
|
| 25 |
|
| 26 |
+
settings = Settings()
|
requiremnts.txt → Backend/requiremnts.txt
RENAMED
|
File without changes
|
Frontend/src/components/auth/SignIn.tsx
CHANGED
|
@@ -8,9 +8,13 @@ interface SignInProps {
|
|
| 8 |
onAuthSuccess: () => void;
|
| 9 |
}
|
| 10 |
|
| 11 |
-
const SignIn: React.FC<SignInProps> = ({
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
const [password, setPassword] = useState("");
|
| 15 |
const [error, setError] = useState("");
|
| 16 |
|
|
@@ -20,7 +24,7 @@ const SignIn: React.FC<SignInProps> = ({ onClose, onSwitchToSignUp, onAuthSucces
|
|
| 20 |
|
| 21 |
try {
|
| 22 |
const res = await API.post("/auth/login", {
|
| 23 |
-
|
| 24 |
password,
|
| 25 |
});
|
| 26 |
|
|
@@ -47,8 +51,8 @@ const SignIn: React.FC<SignInProps> = ({ onClose, onSwitchToSignUp, onAuthSucces
|
|
| 47 |
<input
|
| 48 |
type="string"
|
| 49 |
placeholder="username or email"
|
| 50 |
-
value={
|
| 51 |
-
onChange={(e) =>
|
| 52 |
className="w-full pl-10 py-3 bg-slate-800 border border-slate-700 text-white rounded-lg"
|
| 53 |
required
|
| 54 |
/>
|
|
@@ -78,10 +82,12 @@ const SignIn: React.FC<SignInProps> = ({ onClose, onSwitchToSignUp, onAuthSucces
|
|
| 78 |
|
| 79 |
<p className="mt-8 text-center text-sm text-gray-400">
|
| 80 |
Don’t have an account?
|
| 81 |
-
<button onClick={onSwitchToSignUp} className="ml-2 text-blue-400">
|
|
|
|
|
|
|
| 82 |
</p>
|
| 83 |
</>
|
| 84 |
);
|
| 85 |
};
|
| 86 |
|
| 87 |
-
export default SignIn;
|
|
|
|
| 8 |
onAuthSuccess: () => void;
|
| 9 |
}
|
| 10 |
|
| 11 |
+
const SignIn: React.FC<SignInProps> = ({
|
| 12 |
+
onClose,
|
| 13 |
+
onSwitchToSignUp,
|
| 14 |
+
onAuthSuccess,
|
| 15 |
+
}) => {
|
| 16 |
+
const [email, setEmail] = useState("");
|
| 17 |
+
// const [username, setUserName] = useState("");
|
| 18 |
const [password, setPassword] = useState("");
|
| 19 |
const [error, setError] = useState("");
|
| 20 |
|
|
|
|
| 24 |
|
| 25 |
try {
|
| 26 |
const res = await API.post("/auth/login", {
|
| 27 |
+
email: email, // 🔥 FastAPI expects `username` (OAuth2PasswordRequestForm)
|
| 28 |
password,
|
| 29 |
});
|
| 30 |
|
|
|
|
| 51 |
<input
|
| 52 |
type="string"
|
| 53 |
placeholder="username or email"
|
| 54 |
+
value={email}
|
| 55 |
+
onChange={(e) => setEmail(e.target.value)}
|
| 56 |
className="w-full pl-10 py-3 bg-slate-800 border border-slate-700 text-white rounded-lg"
|
| 57 |
required
|
| 58 |
/>
|
|
|
|
| 82 |
|
| 83 |
<p className="mt-8 text-center text-sm text-gray-400">
|
| 84 |
Don’t have an account?
|
| 85 |
+
<button onClick={onSwitchToSignUp} className="ml-2 text-blue-400">
|
| 86 |
+
Sign Up
|
| 87 |
+
</button>
|
| 88 |
</p>
|
| 89 |
</>
|
| 90 |
);
|
| 91 |
};
|
| 92 |
|
| 93 |
+
export default SignIn;
|
ai/stt/stt.ipynb
ADDED
|
File without changes
|
test/voice.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sounddevice as sd
|
| 2 |
+
import numpy as np
|
| 3 |
+
from faster_whisper import WhisperModel
|
| 4 |
+
import ollama
|
| 5 |
+
import subprocess
|
| 6 |
+
import time
|
| 7 |
+
|
| 8 |
+
# --- Configuration ---
|
| 9 |
+
# STT Model: 'tiny' or 'base' are fast; 'small' is accurate.
|
| 10 |
+
stt_model = WhisperModel("base.en", device="cuda", compute_type="float16")
|
| 11 |
+
|
| 12 |
+
def record_audio(duration=5, samplerate=16000):
|
| 13 |
+
"""Simple recorder - in production, use VAD (Voice Activity Detection) to stop automatically."""
|
| 14 |
+
print("Listening...")
|
| 15 |
+
recording = sd.rec(int(duration * samplerate), samplerate=samplerate, channels=1, dtype='float32')
|
| 16 |
+
sd.wait()
|
| 17 |
+
return recording.flatten()
|
| 18 |
+
|
| 19 |
+
def transcribe(audio_data):
|
| 20 |
+
"""Convert Audio to Text"""
|
| 21 |
+
segments, info = stt_model.transcribe(audio_data, beam_size=5)
|
| 22 |
+
text = " ".join([segment.text for segment in segments])
|
| 23 |
+
return text.strip()
|
| 24 |
+
|
| 25 |
+
def generate_response(prompt):
|
| 26 |
+
"""Send text to Ollama (The Brain)"""
|
| 27 |
+
response = ollama.chat(model='llama3.2', messages=[
|
| 28 |
+
{'role': 'user', 'content': prompt},
|
| 29 |
+
])
|
| 30 |
+
return response['message']['content']
|
| 31 |
+
|
| 32 |
+
def speak(text):
|
| 33 |
+
"""Convert Text to Audio (The Mouth) using Piper"""
|
| 34 |
+
# Assuming you have the piper binary and a voice model downloaded
|
| 35 |
+
# Command line: echo "text" | piper --model en_US-lessac-medium.onnx --output_file output.wav
|
| 36 |
+
|
| 37 |
+
# For this example, we'll use a generic speak command for macOS/Linux
|
| 38 |
+
# (Replace with Piper or pyttsx3 for true local/cross-platform)
|
| 39 |
+
subprocess.call(["say", text])
|
| 40 |
+
|
| 41 |
+
# --- Main Loop ---
|
| 42 |
+
def main():
|
| 43 |
+
print("Voice Agent Started (Ctrl+C to stop)")
|
| 44 |
+
while True:
|
| 45 |
+
# 1. Listen
|
| 46 |
+
audio_data = record_audio(duration=4) # Fixed duration for simplicity
|
| 47 |
+
|
| 48 |
+
# 2. Transcribe
|
| 49 |
+
user_text = transcribe(audio_data)
|
| 50 |
+
if not user_text: continue
|
| 51 |
+
|
| 52 |
+
print(f"You: {user_text}")
|
| 53 |
+
|
| 54 |
+
# 3. Think
|
| 55 |
+
ai_response = generate_response(user_text)
|
| 56 |
+
print(f"AI: {ai_response}")
|
| 57 |
+
|
| 58 |
+
# 4. Speak
|
| 59 |
+
speak(ai_response)
|
| 60 |
+
|
| 61 |
+
if __name__ == "__main__":
|
| 62 |
+
main()
|