Spaces:
Paused
Paused
Upload 13 files
Browse files- .gitattributes +1 -0
- MetaAIAPI_app.py +137 -0
- README.md +217 -12
- app.py +336 -0
- model_metrics.log +141 -0
- static/android-chrome-192x192.png +0 -0
- static/android-chrome-512x512.png +3 -0
- static/apple-touch-icon.png +0 -0
- static/favicon-16x16.png +0 -0
- static/favicon-32x32.png +0 -0
- static/favicon.ico +0 -0
- static/site.webmanifest +1 -0
- templates/index.html +405 -0
- test_app.py +32 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
static/android-chrome-512x512.png filter=lfs diff=lfs merge=lfs -text
|
MetaAIAPI_app.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, render_template, request, jsonify
|
| 2 |
+
from transformers import pipeline
|
| 3 |
+
from meta_ai_api import MetaAI
|
| 4 |
+
import os
|
| 5 |
+
import re # For text processing
|
| 6 |
+
|
| 7 |
+
# Initialize Meta AI client
|
| 8 |
+
client = MetaAI()
|
| 9 |
+
|
| 10 |
+
# Category list (unchanged)
|
| 11 |
+
categories = [
|
| 12 |
+
"Banking", "Books", "Clothes", "College Admissions", "Cooking",
|
| 13 |
+
"Elementary Education", "Middle School Education", "High School Education", "University Education",
|
| 14 |
+
"Employment", "Finance", "Food", "Gardening", "Homelessness", "Housing", "Jobs", "Investing",
|
| 15 |
+
"Matrimonial", "Brain Medical", "Depression Medical", "Eye Medical", "Hand Medical",
|
| 16 |
+
"Head Medical", "Leg Medical", "Rental", "School", "Shopping",
|
| 17 |
+
"Baseball Sports", "Basketball Sports", "Cricket Sports", "Handball Sports",
|
| 18 |
+
"Jogging Sports", "Hockey Sports", "Running Sports", "Tennis Sports",
|
| 19 |
+
"Stocks", "Travel", "Tourism"
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
# Zero-shot classification pipeline (unchanged)
|
| 23 |
+
classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
|
| 24 |
+
|
| 25 |
+
app = Flask(__name__)
|
| 26 |
+
|
| 27 |
+
@app.route('/')
|
| 28 |
+
def home():
|
| 29 |
+
return render_template('index.html', categories=categories)
|
| 30 |
+
|
| 31 |
+
@app.route('/predict_categories', methods=['POST'])
|
| 32 |
+
def predict_categories():
|
| 33 |
+
data = request.get_json()
|
| 34 |
+
subject = data.get("subject")
|
| 35 |
+
description = data.get("description")
|
| 36 |
+
if not subject or not description:
|
| 37 |
+
return jsonify({"error": "Subject and description required"}), 400
|
| 38 |
+
prompt = f"{subject}. {description}"
|
| 39 |
+
try:
|
| 40 |
+
result = classifier(prompt, categories)
|
| 41 |
+
return jsonify({"predicted_categories": result['labels'][:3]})
|
| 42 |
+
except Exception as e:
|
| 43 |
+
return jsonify({"error": str(e)}), 500
|
| 44 |
+
|
| 45 |
+
@app.route('/generate_answer', methods=['POST'])
|
| 46 |
+
def generate_answer():
|
| 47 |
+
data = request.get_json()
|
| 48 |
+
category = data.get("category")
|
| 49 |
+
question = data.get("question")
|
| 50 |
+
if not category or not question:
|
| 51 |
+
return jsonify({"error": "Category and question required"}), 400
|
| 52 |
+
|
| 53 |
+
# Create a more specific prompt with formatting instructions
|
| 54 |
+
prompt = (
|
| 55 |
+
f"Category: {category}\n"
|
| 56 |
+
f"Question: {question}\n\n"
|
| 57 |
+
"Please provide a detailed and well-structured answer. Format your response as follows:\n"
|
| 58 |
+
"- For any list of items (e.g., websites, tips, steps, examples, or recommendations), use bullet points starting with '-' (e.g., - Item).\n"
|
| 59 |
+
"- Use bold text (e.g., **text**) for headings or key terms, such as section titles or important concepts.\n"
|
| 60 |
+
"- Use line breaks to separate paragraphs or sections for clarity.\n"
|
| 61 |
+
"- Ensure the response is clear, concise, and easy to read.\n"
|
| 62 |
+
"- If the question asks for recommendations, resources, or a list, always present them as bullet points.\n"
|
| 63 |
+
"For example, if asked for websites, format the response like this:\n"
|
| 64 |
+
"**Websites**\n"
|
| 65 |
+
"- Website 1: Description.\n"
|
| 66 |
+
"- Website 2: Description.\n"
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
try:
|
| 70 |
+
# Get the response from Meta AI
|
| 71 |
+
response = client.prompt(prompt)
|
| 72 |
+
raw_answer = response['message'].strip()
|
| 73 |
+
|
| 74 |
+
# Format the response to ensure consistency
|
| 75 |
+
formatted_answer = format_response(raw_answer)
|
| 76 |
+
|
| 77 |
+
return jsonify({"answer": formatted_answer})
|
| 78 |
+
except Exception as e:
|
| 79 |
+
return jsonify({"error": str(e)}), 500
|
| 80 |
+
|
| 81 |
+
def format_response(text):
|
| 82 |
+
"""
|
| 83 |
+
Format the raw AI response to ensure bullet points and structure, even if the AI doesn't follow instructions.
|
| 84 |
+
"""
|
| 85 |
+
# Split the text into lines
|
| 86 |
+
lines = text.split('\n')
|
| 87 |
+
formatted_lines = []
|
| 88 |
+
in_list = False
|
| 89 |
+
|
| 90 |
+
# Patterns to detect list-like content
|
| 91 |
+
list_indicators = [
|
| 92 |
+
r'^(General|Niche|International|Country-Specific|Additional)\b', # Section headings
|
| 93 |
+
r'^(Indeed|LinkedIn|Glassdoor|H1BGrader|H1B Visa Jobs|ImmIhelp)\b', # Website names
|
| 94 |
+
r'^(Network|Check|Stay)\b' # Tips starting with verbs
|
| 95 |
+
]
|
| 96 |
+
|
| 97 |
+
for line in lines:
|
| 98 |
+
line = line.strip()
|
| 99 |
+
if not line:
|
| 100 |
+
if in_list:
|
| 101 |
+
in_list = False
|
| 102 |
+
formatted_lines.append('') # Add a line break after a list
|
| 103 |
+
continue
|
| 104 |
+
|
| 105 |
+
# Check if the line starts a new section (e.g., "General Job Search Websites")
|
| 106 |
+
if any(re.match(pattern, line, re.IGNORECASE) for pattern in list_indicators):
|
| 107 |
+
if in_list:
|
| 108 |
+
in_list = False
|
| 109 |
+
formatted_lines.append('') # Add a line break before a new section
|
| 110 |
+
# Add the section as a bold heading
|
| 111 |
+
formatted_lines.append(f"**{line}**")
|
| 112 |
+
in_list = True
|
| 113 |
+
continue
|
| 114 |
+
|
| 115 |
+
# If we're in a list, format the line as a bullet point
|
| 116 |
+
if in_list and not line.startswith('-'):
|
| 117 |
+
# Split the line into website/tip and description (e.g., "Indeed (link unavailable): Description")
|
| 118 |
+
if ': ' in line:
|
| 119 |
+
parts = line.split(': ', 1)
|
| 120 |
+
item = parts[0].strip()
|
| 121 |
+
description = parts[1].strip() if len(parts) > 1 else ""
|
| 122 |
+
formatted_lines.append(f"- **{item}**: {description}")
|
| 123 |
+
else:
|
| 124 |
+
formatted_lines.append(f"- {line}")
|
| 125 |
+
else:
|
| 126 |
+
# If not in a list, add the line as a paragraph
|
| 127 |
+
if in_list:
|
| 128 |
+
in_list = False
|
| 129 |
+
formatted_lines.append('') # Add a line break after a list
|
| 130 |
+
formatted_lines.append(line)
|
| 131 |
+
|
| 132 |
+
# Join the lines with proper spacing
|
| 133 |
+
formatted_text = '\n'.join(formatted_lines)
|
| 134 |
+
return formatted_text
|
| 135 |
+
|
| 136 |
+
if __name__ == '__main__':
|
| 137 |
+
app.run(debug=True)
|
README.md
CHANGED
|
@@ -1,12 +1,217 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# 🔍 Saayam AI Assistant 🤖
|
| 3 |
+
|
| 4 |
+
Saayam AI Assistant is a web-based application built with Flask that allows users to query various AI models (Meta AI, Gemini, ChatGPT, and groq) for answers across multiple categories (e.g., Jobs, Education, Finance). The application uses zero-shot classification to predict relevant categories for user queries and provides detailed, formatted responses. Additionally, it collects performance metrics (latency, speed, temperature, token counts) to compare the efficiency of each AI model.
|
| 5 |
+
|
| 6 |
+
## 🧠 Features
|
| 7 |
+
- **Multi-Model Support**: Query Meta AI, Gemini, ChatGPT, or groq via a command-line argument.
|
| 8 |
+
- **Category Prediction**: Uses zero-shot classification (`facebook/bart-large-mnli`) to predict relevant categories for user queries.
|
| 9 |
+
- **Formatted Responses**: Responses are structured with bullet points, bold headings, and clear sections for readability.
|
| 10 |
+
- **Performance Metrics**: Measures latency (TTFT/TTLT), speed (tokens/second), temperature, and token counts for each model.
|
| 11 |
+
- **Web Interface**: A user-friendly interface built with Flask, HTML, and JavaScript, with Markdown rendering for responses.
|
| 12 |
+
|
| 13 |
+
## 🔧 Setup Instructions
|
| 14 |
+
|
| 15 |
+
### 1. Create & Activate Conda Environment
|
| 16 |
+
Create a Conda environment with Python 3.10 and activate it:
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
conda create -n saayam-env python=3.10
|
| 20 |
+
conda activate saayam-env
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
### 2. Clone the Repository
|
| 24 |
+
Clone the project repository to your local machine:
|
| 25 |
+
|
| 26 |
+
```bash
|
| 27 |
+
git clone https://github.com/RobuRishabh/Saayam_ai.git
|
| 28 |
+
cd Saayam_ai
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
### 3. Install Requirements
|
| 32 |
+
Install the required Python packages listed in requirements.txt:
|
| 33 |
+
|
| 34 |
+
```bash
|
| 35 |
+
pip install -r requirements.txt
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
Note: Ensure you have the following packages in your requirements.txt:
|
| 39 |
+
|
| 40 |
+
```
|
| 41 |
+
flask
|
| 42 |
+
transformers
|
| 43 |
+
meta-ai-api
|
| 44 |
+
google-generativeai
|
| 45 |
+
openai
|
| 46 |
+
groq
|
| 47 |
+
python-dotenv
|
| 48 |
+
tiktoken
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
### 4. Set Up Environment Variables
|
| 52 |
+
Create a .env file in the project root directory and add your API keys for Gemini, ChatGPT, and groq:
|
| 53 |
+
|
| 54 |
+
```
|
| 55 |
+
GEMINI_API_KEY=your_gemini_api_key
|
| 56 |
+
OPENAI_API_KEY=your_openai_api_key
|
| 57 |
+
GROQ_API_KEY=your_groq_api_key
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
Note: Meta AI doesn’t require an API key in this setup (uses meta-ai-api library).
|
| 61 |
+
|
| 62 |
+
### 5. Run the Application
|
| 63 |
+
Run the application with a specific AI model using the --model argument. The available models are meta_ai, gemini, openai, and groq.
|
| 64 |
+
|
| 65 |
+
Meta AI:
|
| 66 |
+
```bash
|
| 67 |
+
python app.py --model meta_ai
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
Gemini:
|
| 71 |
+
```bash
|
| 72 |
+
python app.py --model gemini
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
ChatGPT (OpenAI):
|
| 76 |
+
```bash
|
| 77 |
+
python app.py --model openai
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
groq:
|
| 81 |
+
```bash
|
| 82 |
+
python app.py --model groq
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
After running the application, open your browser and navigate to http://127.0.0.1:5000 to access the Saayam AI Assistant interface.
|
| 86 |
+
|
| 87 |
+
## 📁 Project Structure
|
| 88 |
+
|
| 89 |
+
```
|
| 90 |
+
Saayam_ai/
|
| 91 |
+
├── app.py # Main application with multi-model support and metrics
|
| 92 |
+
├── MetaAIAPI_app.py # Meta AI-only version (simpler implementation)
|
| 93 |
+
├── templates/
|
| 94 |
+
│ └── index.html # Frontend HTML template
|
| 95 |
+
├── static/
|
| 96 |
+
│ ├── apple-touch-icon.png
|
| 97 |
+
│ ├── favicon-16x16.png
|
| 98 |
+
│ ├── favicon-32x32.png
|
| 99 |
+
│ ├── favicon.ico
|
| 100 |
+
│ └── site.webmanifest # Web manifest for favicon and icons
|
| 101 |
+
├── requirements.txt # Python dependencies
|
| 102 |
+
├── .env # Environment variables (API keys)
|
| 103 |
+
├── model_metrics.log # Log file for performance metrics
|
| 104 |
+
└── .gitignore # Git ignore file
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
## 📊 API Performance Evaluation
|
| 108 |
+
The application collects performance metrics for each AI model, including latency, speed, temperature, and token counts. The metrics were evaluated using the query "Suggest me good job searching websites for international students" in the "Jobs" category.
|
| 109 |
+
|
| 110 |
+
### Performance Metrics
|
| 111 |
+
|
| 112 |
+
**Meta AI**:
|
| 113 |
+
- Model: meta_ai
|
| 114 |
+
- Temperature: 0.7 (default)
|
| 115 |
+
- Time to First Token (TTFT): 15.185 seconds
|
| 116 |
+
- Total Response Time (TTLT): 15.185 seconds
|
| 117 |
+
- Speed: 20.81 tokens/second
|
| 118 |
+
- Input Tokens: 127
|
| 119 |
+
- Output Tokens: 316
|
| 120 |
+
|
| 121 |
+
**Gemini**:
|
| 122 |
+
- Model: gemini
|
| 123 |
+
- Temperature: 0.7
|
| 124 |
+
- Time to First Token (TTFT): 4.515 seconds
|
| 125 |
+
- Total Response Time (TTLT): 4.515 seconds
|
| 126 |
+
- Speed: 100.32 tokens/second
|
| 127 |
+
- Input Tokens: 127
|
| 128 |
+
- Output Tokens: 453
|
| 129 |
+
|
| 130 |
+
**ChatGPT (OpenAI)**:
|
| 131 |
+
- Model: openai
|
| 132 |
+
- Temperature: 0.7
|
| 133 |
+
- Time to First Token (TTFT): 4.619 seconds
|
| 134 |
+
- Total Response Time (TTLT): 4.619 seconds
|
| 135 |
+
- Speed: 81.83 tokens/second
|
| 136 |
+
- Input Tokens: 176
|
| 137 |
+
- Output Tokens: 378
|
| 138 |
+
|
| 139 |
+
**groq**:
|
| 140 |
+
- Model: groq
|
| 141 |
+
- Temperature: 0.7
|
| 142 |
+
- Time to First Token (TTFT): 0.856 seconds
|
| 143 |
+
- Total Response Time (TTLT): 0.856 seconds
|
| 144 |
+
- Speed: 630.66 tokens/second
|
| 145 |
+
- Input Tokens: 127
|
| 146 |
+
- Output Tokens: 540
|
| 147 |
+
|
| 148 |
+
### Cost Analysis
|
| 149 |
+
|
| 150 |
+
- **Meta AI**: Free (unofficial API), but may have rate limits or reliability issues.
|
| 151 |
+
- **Gemini**: Free tier available, with paid plans for higher usage.
|
| 152 |
+
- **ChatGPT**: Pay-per-use ($0.002 per 1K tokens for gpt-3.5-turbo).
|
| 153 |
+
- **groq**: Free tier available, with paid plans for higher usage.
|
| 154 |
+
|
| 155 |
+
### Limitations
|
| 156 |
+
|
| 157 |
+
- **Meta AI**: Slow, low speed, lacks temperature control.
|
| 158 |
+
- **Gemini**: Moderate performance, potential tokenization differences.
|
| 159 |
+
- **ChatGPT**: Reliable, slightly slower than groq.
|
| 160 |
+
- **groq**: Fastest, high output token count (verbosity).
|
| 161 |
+
|
| 162 |
+
## ⚖️ Comparison with Alternative Solutions
|
| 163 |
+
|
| 164 |
+
| Model | Speed (tokens/s) | TTLT (s) | Cost | Quality |
|
| 165 |
+
|-----------|------------------|----------|--------------|------------------------|
|
| 166 |
+
| Meta AI | 20.81 | 15.185 | Free | Least consistent |
|
| 167 |
+
| Gemini | 100.32 | 4.515 | Free tier | Moderate consistency |
|
| 168 |
+
| ChatGPT | 81.83 | 4.619 | Pay-per-use | Highly consistent |
|
| 169 |
+
| groq | 630.66 | 0.856 | Free tier | Practical, fast |
|
| 170 |
+
|
| 171 |
+
## 🛠️ Proof-of-Concept Implementation
|
| 172 |
+
|
| 173 |
+
### Overview
|
| 174 |
+
Flask-based web app to query 4 models, classify categories, format answers, and log metrics.
|
| 175 |
+
|
| 176 |
+
### Backend (`app.py`)
|
| 177 |
+
- Flask app, handles routes `/predict_categories` and `/generate_answer`
|
| 178 |
+
- Model passed using `--model` CLI argument
|
| 179 |
+
- Collects and logs metrics (TTFT, TTLT, token counts, temperature)
|
| 180 |
+
|
| 181 |
+
### Frontend (`index.html`)
|
| 182 |
+
- HTML + JavaScript interface
|
| 183 |
+
- Markdown rendering using marked.js
|
| 184 |
+
- Submits subject, description, category
|
| 185 |
+
- Displays response + metrics
|
| 186 |
+
|
| 187 |
+
### Example
|
| 188 |
+
```bash
|
| 189 |
+
python app.py --model groq
|
| 190 |
+
```
|
| 191 |
+
Visit [http://127.0.0.1:5000](http://127.0.0.1:5000)
|
| 192 |
+
|
| 193 |
+
## 📈 Analysis and Recommendations
|
| 194 |
+
|
| 195 |
+
- **Fastest**: groq (0.856s TTLT, 630.66 tokens/s)
|
| 196 |
+
- **Most Consistent**: ChatGPT
|
| 197 |
+
- **Cost-Effective**: Gemini & groq
|
| 198 |
+
- **Slowest**: Meta AI
|
| 199 |
+
|
| 200 |
+
### Recommendations
|
| 201 |
+
|
| 202 |
+
- Use **groq** for real-time speed
|
| 203 |
+
- Use **ChatGPT** for reliability & consistency
|
| 204 |
+
- Use **Gemini** for cost-conscious performance
|
| 205 |
+
- Avoid **Meta AI** for production
|
| 206 |
+
|
| 207 |
+
## 🚀 Future Improvements
|
| 208 |
+
|
| 209 |
+
- Enable streaming for better TTFT
|
| 210 |
+
- Add cosine similarity for response sensitivity
|
| 211 |
+
- Load testing (e.g., locust)
|
| 212 |
+
- Caching frequent queries to save cost
|
| 213 |
+
|
| 214 |
+
## 🙌 Acknowledgments
|
| 215 |
+
|
| 216 |
+
Built with ❤️ using Flask, Transformers, and AI APIs.
|
| 217 |
+
Special thanks to the open-source contributors of meta-ai-api.
|
app.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, render_template, request, jsonify
|
| 2 |
+
from transformers import pipeline
|
| 3 |
+
from meta_ai_api import MetaAI
|
| 4 |
+
import google.generativeai as genai # Gemini API
|
| 5 |
+
from openai import OpenAI # OpenAI API
|
| 6 |
+
from groq import Groq # groq API
|
| 7 |
+
from huggingface_hub import InferenceClient # Hugging Face API
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
import os
|
| 10 |
+
import re
|
| 11 |
+
import argparse
|
| 12 |
+
import time
|
| 13 |
+
import tiktoken
|
| 14 |
+
import logging
|
| 15 |
+
import transformers
|
| 16 |
+
import tempfile
|
| 17 |
+
|
| 18 |
+
# Suppress transformers logging to reduce clutter
|
| 19 |
+
transformers.logging.set_verbosity_error()
|
| 20 |
+
|
| 21 |
+
# Load environment variables from .env
|
| 22 |
+
load_dotenv()
|
| 23 |
+
|
| 24 |
+
# Set up logging to a file for metrics
|
| 25 |
+
logging.basicConfig(
|
| 26 |
+
filename='model_metrics.log',
|
| 27 |
+
level=logging.INFO,
|
| 28 |
+
format='%(asctime)s - %(message)s'
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
# Ensure Flask's logger outputs to the console
|
| 32 |
+
console_handler = logging.StreamHandler()
|
| 33 |
+
console_handler.setLevel(logging.INFO)
|
| 34 |
+
werkzeug_logger = logging.getLogger('werkzeug')
|
| 35 |
+
werkzeug_logger.addHandler(console_handler)
|
| 36 |
+
|
| 37 |
+
# Parse command-line arguments
|
| 38 |
+
parser = argparse.ArgumentParser(description="Run Saayam AI Assistant with a specific AI model.")
|
| 39 |
+
parser.add_argument(
|
| 40 |
+
"--model",
|
| 41 |
+
type=str,
|
| 42 |
+
choices=["meta_ai", "gemini", "openai", "groq"],
|
| 43 |
+
default="meta_ai",
|
| 44 |
+
help="Choose the AI model to use: meta_ai, gemini, openai, or groq"
|
| 45 |
+
)
|
| 46 |
+
args = parser.parse_args()
|
| 47 |
+
selected_model = args.model
|
| 48 |
+
|
| 49 |
+
# Default temperature for models that support it
|
| 50 |
+
DEFAULT_TEMPERATURE = 0.7
|
| 51 |
+
|
| 52 |
+
# Initialize the chosen AI client
|
| 53 |
+
if selected_model == "meta_ai":
|
| 54 |
+
ai_client = MetaAI()
|
| 55 |
+
elif selected_model == "gemini":
|
| 56 |
+
gemini_api_key = os.getenv("GEMINI_API_KEY")
|
| 57 |
+
genai.configure(api_key=gemini_api_key)
|
| 58 |
+
ai_client = genai.GenerativeModel('gemini-1.5-flash')
|
| 59 |
+
elif selected_model == "openai":
|
| 60 |
+
ai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
| 61 |
+
elif selected_model == "groq":
|
| 62 |
+
groq_api_key = os.getenv("GROQ_API_KEY")
|
| 63 |
+
ai_client = Groq(api_key=groq_api_key)
|
| 64 |
+
|
| 65 |
+
# Initialize Hugging Face client for Speech-to-Text
|
| 66 |
+
hf_client = InferenceClient(token=os.getenv("HF_API_KEY"))
|
| 67 |
+
|
| 68 |
+
# Category list (unchanged)
|
| 69 |
+
categories = [
|
| 70 |
+
"Banking", "Books", "Clothes", "College Admissions", "Cooking",
|
| 71 |
+
"Elementary Education", "Middle School Education", "High School Education", "University Education",
|
| 72 |
+
"Employment", "Finance", "Food", "Gardening", "Homelessness", "Housing", "Jobs", "Investing",
|
| 73 |
+
"Matrimonial", "Brain Medical", "Depression Medical", "Eye Medical", "Hand Medical",
|
| 74 |
+
"Head Medical", "Leg Medical", "Rental", "School", "Shopping",
|
| 75 |
+
"Baseball Sports", "Basketball Sports", "Cricket Sports", "Handball Sports",
|
| 76 |
+
"Jogging Sports", "Hockey Sports", "Running Sports", "Tennis Sports",
|
| 77 |
+
"Stocks", "Travel", "Tourism"
|
| 78 |
+
]
|
| 79 |
+
|
| 80 |
+
# Zero-shot classification pipeline (unchanged)
|
| 81 |
+
classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
|
| 82 |
+
|
| 83 |
+
# Initialize tokenizer for token counting (for OpenAI models)
|
| 84 |
+
try:
|
| 85 |
+
openai_tokenizer = tiktoken.encoding_for_model("gpt-3.5-turbo")
|
| 86 |
+
except:
|
| 87 |
+
openai_tokenizer = None # Fallback if tiktoken fails
|
| 88 |
+
|
| 89 |
+
app = Flask(__name__)
|
| 90 |
+
|
| 91 |
+
@app.route('/')
|
| 92 |
+
def home():
|
| 93 |
+
return render_template('index.html', categories=categories)
|
| 94 |
+
|
| 95 |
+
@app.route('/transcribe', methods=['POST'])
|
| 96 |
+
def transcribe_audio():
|
| 97 |
+
"""
|
| 98 |
+
Transcribe audio using Whisper Large V3 Turbo via Hugging Face Inference API.
|
| 99 |
+
"""
|
| 100 |
+
if 'audio' not in request.files:
|
| 101 |
+
return jsonify({"error": "No audio file provided"}), 400
|
| 102 |
+
|
| 103 |
+
audio_file = request.files['audio']
|
| 104 |
+
temp_file_path = None
|
| 105 |
+
|
| 106 |
+
try:
|
| 107 |
+
# Read the audio bytes
|
| 108 |
+
audio_bytes = audio_file.read()
|
| 109 |
+
|
| 110 |
+
# Detect if it's video/webm and use .ogg instead to force audio interpretation
|
| 111 |
+
content_type = audio_file.content_type or 'audio/webm'
|
| 112 |
+
|
| 113 |
+
# Force .ogg extension to avoid video/webm interpretation
|
| 114 |
+
if 'webm' in content_type:
|
| 115 |
+
suffix = '.ogg'
|
| 116 |
+
elif 'wav' in content_type:
|
| 117 |
+
suffix = '.wav'
|
| 118 |
+
elif 'ogg' in content_type:
|
| 119 |
+
suffix = '.ogg'
|
| 120 |
+
else:
|
| 121 |
+
suffix = '.webm'
|
| 122 |
+
|
| 123 |
+
# Save to a temporary file
|
| 124 |
+
temp_dir = "/tmp/audio"
|
| 125 |
+
if not os.path.exists(temp_dir):
|
| 126 |
+
os.makedirs(temp_dir)
|
| 127 |
+
|
| 128 |
+
suffix = '.ogg'
|
| 129 |
+
# Use os.path.join for explicit path control in container
|
| 130 |
+
temp_file_path = os.path.join(temp_dir, f"recording_{int(time.time())}{suffix}")
|
| 131 |
+
|
| 132 |
+
with open(temp_file_path, "wb") as f:
|
| 133 |
+
f.write(audio_bytes)
|
| 134 |
+
|
| 135 |
+
# Pass the file path to the Hugging Face client
|
| 136 |
+
result = hf_client.automatic_speech_recognition(
|
| 137 |
+
temp_file_path,
|
| 138 |
+
model="openai/whisper-large-v3-turbo"
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
# Clean up the temporary file
|
| 142 |
+
if temp_file_path and os.path.exists(temp_file_path):
|
| 143 |
+
os.unlink(temp_file_path)
|
| 144 |
+
|
| 145 |
+
# Extract transcribed text
|
| 146 |
+
transcribed_text = ""
|
| 147 |
+
if isinstance(result, dict) and 'text' in result:
|
| 148 |
+
transcribed_text = result['text']
|
| 149 |
+
elif hasattr(result, 'text'):
|
| 150 |
+
transcribed_text = result.text
|
| 151 |
+
elif isinstance(result, str):
|
| 152 |
+
transcribed_text = result
|
| 153 |
+
else:
|
| 154 |
+
transcribed_text = str(result)
|
| 155 |
+
|
| 156 |
+
return jsonify({
|
| 157 |
+
"text": transcribed_text.strip()
|
| 158 |
+
})
|
| 159 |
+
|
| 160 |
+
except Exception as e:
|
| 161 |
+
# Clean up temp file if it exists
|
| 162 |
+
if temp_file_path and os.path.exists(temp_file_path):
|
| 163 |
+
os.unlink(temp_file_path)
|
| 164 |
+
|
| 165 |
+
import traceback
|
| 166 |
+
traceback.print_exc()
|
| 167 |
+
return jsonify({"error": f"Transcription failed: {str(e)}"}), 500
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
@app.route('/predict_categories', methods=['POST'])
|
| 171 |
+
def predict_categories():
|
| 172 |
+
data = request.get_json()
|
| 173 |
+
subject = data.get("subject")
|
| 174 |
+
description = data.get("description")
|
| 175 |
+
if not subject or not description:
|
| 176 |
+
return jsonify({"error": "Subject and description required"}), 400
|
| 177 |
+
prompt = f"{subject}. {description}"
|
| 178 |
+
try:
|
| 179 |
+
result = classifier(prompt, categories)
|
| 180 |
+
return jsonify({"predicted_categories": result['labels'][:3]})
|
| 181 |
+
except Exception as e:
|
| 182 |
+
return jsonify({"error": str(e)}), 500
|
| 183 |
+
|
| 184 |
+
@app.route('/generate_answer', methods=['POST'])
|
| 185 |
+
def generate_answer():
|
| 186 |
+
data = request.get_json()
|
| 187 |
+
category = data.get("category")
|
| 188 |
+
question = data.get("question")
|
| 189 |
+
|
| 190 |
+
if not category or not question:
|
| 191 |
+
return jsonify({"error": "Category and question required"}), 400
|
| 192 |
+
|
| 193 |
+
# Create a prompt with formatting instructions
|
| 194 |
+
prompt = (
|
| 195 |
+
f"Category: {category}\n"
|
| 196 |
+
f"Question: {question}\n\n"
|
| 197 |
+
"Please provide a detailed and well-structured answer. Format your response as follows:\n"
|
| 198 |
+
"- For any list of items (e.g., websites, tips, steps, examples, or recommendations), use bullet points starting with '-' (e.g., - Item).\n"
|
| 199 |
+
"- Use bold text (e.g., **text**) for headings or key terms, such as section titles or important concepts.\n"
|
| 200 |
+
"- Use line breaks to separate paragraphs or sections for clarity.\n"
|
| 201 |
+
"- Ensure the response is clear, concise, and easy to read.\n"
|
| 202 |
+
"- If the question asks for recommendations, resources, or a list, always present them as bullet points.\n"
|
| 203 |
+
"For example, if asked for websites, format the response like this:\n"
|
| 204 |
+
"**Websites**\n"
|
| 205 |
+
"- Website 1: Description.\n"
|
| 206 |
+
"- Website 2: Description.\n"
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
# Count input tokens (approximation for non-OpenAI models)
|
| 210 |
+
if openai_tokenizer:
|
| 211 |
+
input_tokens = len(openai_tokenizer.encode(prompt))
|
| 212 |
+
else:
|
| 213 |
+
input_tokens = len(prompt.split())
|
| 214 |
+
|
| 215 |
+
# Measure latency and generate response
|
| 216 |
+
try:
|
| 217 |
+
start_time = time.time()
|
| 218 |
+
first_token_time = None
|
| 219 |
+
raw_answer = None
|
| 220 |
+
|
| 221 |
+
if selected_model == "meta_ai":
|
| 222 |
+
response = ai_client.prompt(prompt)
|
| 223 |
+
raw_answer = response['message'].strip()
|
| 224 |
+
first_token_time = time.time()
|
| 225 |
+
elif selected_model == "gemini":
|
| 226 |
+
response = ai_client.generate_content(prompt, generation_config={"temperature": DEFAULT_TEMPERATURE})
|
| 227 |
+
raw_answer = response.text.strip()
|
| 228 |
+
first_token_time = time.time()
|
| 229 |
+
elif selected_model == "openai":
|
| 230 |
+
response = ai_client.chat.completions.create(
|
| 231 |
+
model="gpt-3.5-turbo",
|
| 232 |
+
messages=[
|
| 233 |
+
{"role": "system", "content": "You are a helpful assistant."},
|
| 234 |
+
{"role": "user", "content": prompt}
|
| 235 |
+
],
|
| 236 |
+
temperature=DEFAULT_TEMPERATURE
|
| 237 |
+
)
|
| 238 |
+
raw_answer = response.choices[0].message.content.strip()
|
| 239 |
+
first_token_time = time.time()
|
| 240 |
+
elif selected_model == "groq":
|
| 241 |
+
response = ai_client.chat.completions.create(
|
| 242 |
+
model="llama-3.1-8b-instant",
|
| 243 |
+
messages=[
|
| 244 |
+
{"role": "system", "content": "You are a helpful assistant."},
|
| 245 |
+
{"role": "user", "content": prompt}
|
| 246 |
+
],
|
| 247 |
+
temperature=DEFAULT_TEMPERATURE
|
| 248 |
+
)
|
| 249 |
+
raw_answer = response.choices[0].message.content.strip()
|
| 250 |
+
first_token_time = time.time()
|
| 251 |
+
|
| 252 |
+
end_time = time.time()
|
| 253 |
+
|
| 254 |
+
# Calculate latency
|
| 255 |
+
ttft = first_token_time - start_time
|
| 256 |
+
ttlt = end_time - start_time
|
| 257 |
+
|
| 258 |
+
# Format the response
|
| 259 |
+
formatted_answer = format_response(raw_answer)
|
| 260 |
+
|
| 261 |
+
# Count output tokens
|
| 262 |
+
if openai_tokenizer:
|
| 263 |
+
output_tokens = len(openai_tokenizer.encode(raw_answer))
|
| 264 |
+
else:
|
| 265 |
+
output_tokens = len(raw_answer.split())
|
| 266 |
+
|
| 267 |
+
# Calculate speed (tokens per second)
|
| 268 |
+
speed = output_tokens / ttlt if ttlt > 0 else 0
|
| 269 |
+
|
| 270 |
+
# Log metrics
|
| 271 |
+
metrics = {
|
| 272 |
+
"model": selected_model,
|
| 273 |
+
"temperature": DEFAULT_TEMPERATURE if selected_model in ["gemini", "openai", "groq"] else "N/A",
|
| 274 |
+
"ttft_seconds": round(ttft, 3),
|
| 275 |
+
"ttlt_seconds": round(ttlt, 3),
|
| 276 |
+
"speed_tokens_per_second": round(speed, 2),
|
| 277 |
+
"input_tokens": input_tokens,
|
| 278 |
+
"output_tokens": output_tokens
|
| 279 |
+
}
|
| 280 |
+
logging.info(f"Metrics: {metrics}")
|
| 281 |
+
|
| 282 |
+
# Return the answer and metrics
|
| 283 |
+
return jsonify({
|
| 284 |
+
"answer": formatted_answer,
|
| 285 |
+
"metrics": metrics
|
| 286 |
+
})
|
| 287 |
+
except Exception as e:
|
| 288 |
+
return jsonify({"error": str(e)}), 500
|
| 289 |
+
|
| 290 |
+
def format_response(text):
|
| 291 |
+
lines = text.split('\n')
|
| 292 |
+
formatted_lines = []
|
| 293 |
+
in_list = False
|
| 294 |
+
|
| 295 |
+
list_indicators = [
|
| 296 |
+
r'^(General|Niche|International|Country-Specific|Additional)\b',
|
| 297 |
+
r'^(Indeed|LinkedIn|Glassdoor|H1BGrader|H1B Visa Jobs|ImmIhelp)\b',
|
| 298 |
+
r'^(Network|Check|Stay)\b'
|
| 299 |
+
]
|
| 300 |
+
|
| 301 |
+
for line in lines:
|
| 302 |
+
line = line.strip()
|
| 303 |
+
if not line:
|
| 304 |
+
if in_list:
|
| 305 |
+
in_list = False
|
| 306 |
+
formatted_lines.append('')
|
| 307 |
+
continue
|
| 308 |
+
|
| 309 |
+
if any(re.match(pattern, line, re.IGNORECASE) for pattern in list_indicators):
|
| 310 |
+
if in_list:
|
| 311 |
+
in_list = False
|
| 312 |
+
formatted_lines.append('')
|
| 313 |
+
formatted_lines.append(f"**{line}**")
|
| 314 |
+
in_list = True
|
| 315 |
+
continue
|
| 316 |
+
|
| 317 |
+
if in_list and not line.startswith('-'):
|
| 318 |
+
if ': ' in line:
|
| 319 |
+
parts = line.split(': ', 1)
|
| 320 |
+
item = parts[0].strip()
|
| 321 |
+
description = parts[1].strip() if len(parts) > 1 else ""
|
| 322 |
+
formatted_lines.append(f"- **{item}**: {description}")
|
| 323 |
+
else:
|
| 324 |
+
formatted_lines.append(f"- {line}")
|
| 325 |
+
else:
|
| 326 |
+
if in_list:
|
| 327 |
+
in_list = False
|
| 328 |
+
formatted_lines.append('')
|
| 329 |
+
formatted_lines.append(line)
|
| 330 |
+
|
| 331 |
+
formatted_text = '\n'.join(formatted_lines)
|
| 332 |
+
return formatted_text
|
| 333 |
+
|
| 334 |
+
if __name__ == '__main__':
|
| 335 |
+
print(f"Starting Saayam AI Assistant with model: {selected_model}")
|
| 336 |
+
app.run(debug=True, host='127.0.0.1', port=5000)
|
model_metrics.log
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2025-11-20 15:13:05,032 - Xet Storage is enabled for this repo, but the 'hf_xet' package is not installed. Falling back to regular HTTP download. For better performance, install the package with: `pip install huggingface_hub[hf_xet]` or `pip install hf_xet`
|
| 2 |
+
2025-11-20 15:18:21,164 - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
| 3 |
+
* Running on http://127.0.0.1:5000
|
| 4 |
+
2025-11-20 15:18:21,164 - [33mPress CTRL+C to quit[0m
|
| 5 |
+
2025-11-20 15:18:21,167 - * Restarting with stat
|
| 6 |
+
2025-11-20 15:18:35,608 - * Debugger is active!
|
| 7 |
+
2025-11-20 15:18:35,612 - * Debugger PIN: 465-628-698
|
| 8 |
+
2025-11-20 15:18:35,710 - 127.0.0.1 - - [20/Nov/2025 15:18:35] "GET / HTTP/1.1" 200 -
|
| 9 |
+
2025-11-20 15:18:35,965 - 127.0.0.1 - - [20/Nov/2025 15:18:35] "GET /static/favicon.ico HTTP/1.1" 200 -
|
| 10 |
+
2025-11-20 15:18:35,968 - 127.0.0.1 - - [20/Nov/2025 15:18:35] "GET /static/site.webmanifest HTTP/1.1" 200 -
|
| 11 |
+
2025-11-20 15:19:05,508 - Metrics: {'model': 'meta_ai', 'temperature': 'N/A', 'ttft_seconds': 10.035, 'ttlt_seconds': 10.035, 'speed_tokens_per_second': 3.29, 'input_tokens': 115, 'output_tokens': 33}
|
| 12 |
+
2025-11-20 15:19:05,510 - 127.0.0.1 - - [20/Nov/2025 15:19:05] "POST /generate_answer HTTP/1.1" 200 -
|
| 13 |
+
2025-11-20 15:19:08,871 - Metrics: {'model': 'meta_ai', 'temperature': 'N/A', 'ttft_seconds': 3.13, 'ttlt_seconds': 3.13, 'speed_tokens_per_second': 14.7, 'input_tokens': 115, 'output_tokens': 46}
|
| 14 |
+
2025-11-20 15:19:08,872 - 127.0.0.1 - - [20/Nov/2025 15:19:08] "POST /generate_answer HTTP/1.1" 200 -
|
| 15 |
+
2025-11-20 15:19:59,788 - Metrics: {'model': 'meta_ai', 'temperature': 'N/A', 'ttft_seconds': 7.48, 'ttlt_seconds': 7.48, 'speed_tokens_per_second': 26.07, 'input_tokens': 126, 'output_tokens': 195}
|
| 16 |
+
2025-11-20 15:19:59,788 - 127.0.0.1 - - [20/Nov/2025 15:19:59] "POST /generate_answer HTTP/1.1" 200 -
|
| 17 |
+
2025-11-20 15:20:48,377 - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
| 18 |
+
* Running on http://127.0.0.1:5000
|
| 19 |
+
2025-11-20 15:20:48,377 - [33mPress CTRL+C to quit[0m
|
| 20 |
+
2025-11-20 15:20:48,379 - * Restarting with stat
|
| 21 |
+
2025-11-20 15:21:03,705 - * Debugger is active!
|
| 22 |
+
2025-11-20 15:21:03,712 - * Debugger PIN: 465-628-698
|
| 23 |
+
2025-11-20 15:21:03,747 - 127.0.0.1 - - [20/Nov/2025 15:21:03] "GET / HTTP/1.1" 200 -
|
| 24 |
+
2025-11-20 15:21:04,891 - 127.0.0.1 - - [20/Nov/2025 15:21:04] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 25 |
+
2025-11-20 15:23:15,016 - HTTP Request: POST https://api.groq.com/openai/v1/chat/completions "HTTP/1.1 400 Bad Request"
|
| 26 |
+
2025-11-20 15:23:15,017 - 127.0.0.1 - - [20/Nov/2025 15:23:15] "[35m[1mPOST /generate_answer HTTP/1.1[0m" 500 -
|
| 27 |
+
2025-11-20 15:24:09,660 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
|
| 28 |
+
2025-11-20 15:24:10,236 - * Restarting with stat
|
| 29 |
+
2025-11-20 15:24:17,104 - * Debugger is active!
|
| 30 |
+
2025-11-20 15:24:17,106 - * Debugger PIN: 465-628-698
|
| 31 |
+
2025-11-20 15:24:29,466 - HTTP Request: POST https://api.groq.com/openai/v1/chat/completions "HTTP/1.1 404 Not Found"
|
| 32 |
+
2025-11-20 15:24:29,468 - 127.0.0.1 - - [20/Nov/2025 15:24:29] "[35m[1mPOST /generate_answer HTTP/1.1[0m" 500 -
|
| 33 |
+
2025-11-20 15:25:10,940 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
|
| 34 |
+
2025-11-20 15:25:11,864 - * Restarting with stat
|
| 35 |
+
2025-11-20 15:25:20,254 - * Debugger is active!
|
| 36 |
+
2025-11-20 15:25:20,257 - * Debugger PIN: 465-628-698
|
| 37 |
+
2025-11-20 15:26:01,027 - HTTP Request: POST https://api.groq.com/openai/v1/chat/completions "HTTP/1.1 200 OK"
|
| 38 |
+
2025-11-20 15:26:01,071 - Metrics: {'model': 'grok', 'temperature': 0.7, 'ttft_seconds': 1.227, 'ttlt_seconds': 1.227, 'speed_tokens_per_second': 301.62, 'input_tokens': 126, 'output_tokens': 370}
|
| 39 |
+
2025-11-20 15:26:01,075 - 127.0.0.1 - - [20/Nov/2025 15:26:01] "POST /generate_answer HTTP/1.1" 200 -
|
| 40 |
+
2025-11-21 15:12:43,192 - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
| 41 |
+
* Running on http://127.0.0.1:5000
|
| 42 |
+
2025-11-21 15:12:43,192 - [33mPress CTRL+C to quit[0m
|
| 43 |
+
2025-11-21 15:12:43,194 - * Restarting with stat
|
| 44 |
+
2025-11-21 15:12:49,726 - * Debugger is active!
|
| 45 |
+
2025-11-21 15:12:49,734 - * Debugger PIN: 465-628-698
|
| 46 |
+
2025-11-21 15:12:49,781 - 127.0.0.1 - - [21/Nov/2025 15:12:49] "GET / HTTP/1.1" 200 -
|
| 47 |
+
2025-11-21 15:12:49,996 - 127.0.0.1 - - [21/Nov/2025 15:12:49] "GET /static/site.webmanifest HTTP/1.1" 200 -
|
| 48 |
+
2025-11-21 15:13:30,295 - HTTP Request: POST https://api.groq.com/openai/v1/chat/completions "HTTP/1.1 200 OK"
|
| 49 |
+
2025-11-21 15:13:30,341 - Metrics: {'model': 'groq', 'temperature': 0.7, 'ttft_seconds': 1.451, 'ttlt_seconds': 1.451, 'speed_tokens_per_second': 298.44, 'input_tokens': 134, 'output_tokens': 433}
|
| 50 |
+
2025-11-21 15:13:30,342 - 127.0.0.1 - - [21/Nov/2025 15:13:30] "POST /generate_answer HTTP/1.1" 200 -
|
| 51 |
+
2025-11-21 16:41:31,251 - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
| 52 |
+
* Running on http://127.0.0.1:5000
|
| 53 |
+
2025-11-21 16:41:31,252 - [33mPress CTRL+C to quit[0m
|
| 54 |
+
2025-11-21 16:41:31,256 - * Restarting with stat
|
| 55 |
+
2025-11-21 16:41:57,973 - * Debugger is active!
|
| 56 |
+
2025-11-21 16:41:57,983 - * Debugger PIN: 465-628-698
|
| 57 |
+
2025-11-21 16:41:58,031 - 127.0.0.1 - - [21/Nov/2025 16:41:58] "GET / HTTP/1.1" 200 -
|
| 58 |
+
2025-11-21 16:41:58,408 - 127.0.0.1 - - [21/Nov/2025 16:41:58] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 59 |
+
2025-11-21 16:48:14,113 - 127.0.0.1 - - [21/Nov/2025 16:48:14] "GET / HTTP/1.1" 200 -
|
| 60 |
+
2025-11-21 16:48:14,330 - 127.0.0.1 - - [21/Nov/2025 16:48:14] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 61 |
+
2025-11-21 16:48:14,333 - 127.0.0.1 - - [21/Nov/2025 16:48:14] "GET /static/favicon.ico HTTP/1.1" 200 -
|
| 62 |
+
2025-11-21 16:48:23,602 - 127.0.0.1 - - [21/Nov/2025 16:48:23] "[35m[1mPOST /transcribe HTTP/1.1[0m" 500 -
|
| 63 |
+
2025-11-21 16:48:51,478 - 127.0.0.1 - - [21/Nov/2025 16:48:51] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 64 |
+
2025-11-21 16:48:51,483 - 127.0.0.1 - - [21/Nov/2025 16:48:51] "[33mGET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1[0m" 404 -
|
| 65 |
+
2025-11-21 16:48:57,299 - 127.0.0.1 - - [21/Nov/2025 16:48:57] "[35m[1mPOST /transcribe HTTP/1.1[0m" 500 -
|
| 66 |
+
2025-11-21 17:00:33,452 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
|
| 67 |
+
2025-11-21 17:00:34,779 - * Restarting with stat
|
| 68 |
+
2025-11-21 17:05:05,788 - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
| 69 |
+
* Running on http://127.0.0.1:5000
|
| 70 |
+
2025-11-21 17:05:05,789 - [33mPress CTRL+C to quit[0m
|
| 71 |
+
2025-11-21 17:05:05,793 - * Restarting with stat
|
| 72 |
+
2025-11-21 17:05:24,987 - * Debugger is active!
|
| 73 |
+
2025-11-21 17:05:24,993 - * Debugger PIN: 465-628-698
|
| 74 |
+
2025-11-21 17:05:31,454 - 127.0.0.1 - - [21/Nov/2025 17:05:31] "GET / HTTP/1.1" 200 -
|
| 75 |
+
2025-11-21 17:05:31,511 - 127.0.0.1 - - [21/Nov/2025 17:05:31] "[33mGET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1[0m" 404 -
|
| 76 |
+
2025-11-21 17:05:31,753 - 127.0.0.1 - - [21/Nov/2025 17:05:31] "GET /static/favicon.ico HTTP/1.1" 200 -
|
| 77 |
+
2025-11-21 17:05:31,755 - 127.0.0.1 - - [21/Nov/2025 17:05:31] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 78 |
+
2025-11-21 17:05:43,532 - 127.0.0.1 - - [21/Nov/2025 17:05:43] "[35m[1mPOST /transcribe HTTP/1.1[0m" 500 -
|
| 79 |
+
2025-11-21 17:05:50,954 - 127.0.0.1 - - [21/Nov/2025 17:05:50] "GET / HTTP/1.1" 200 -
|
| 80 |
+
2025-11-21 17:05:51,252 - 127.0.0.1 - - [21/Nov/2025 17:05:51] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 81 |
+
2025-11-21 17:05:53,650 - 127.0.0.1 - - [21/Nov/2025 17:05:53] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 82 |
+
2025-11-21 17:05:53,669 - 127.0.0.1 - - [21/Nov/2025 17:05:53] "[33mGET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1[0m" 404 -
|
| 83 |
+
2025-11-21 17:06:07,126 - 127.0.0.1 - - [21/Nov/2025 17:06:07] "[35m[1mPOST /transcribe HTTP/1.1[0m" 500 -
|
| 84 |
+
2025-11-21 17:06:20,044 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
|
| 85 |
+
2025-11-21 17:06:21,979 - * Restarting with stat
|
| 86 |
+
2025-11-21 17:06:53,632 - * Debugger is active!
|
| 87 |
+
2025-11-21 17:06:53,642 - * Debugger PIN: 465-628-698
|
| 88 |
+
2025-11-21 17:06:53,698 - 127.0.0.1 - - [21/Nov/2025 17:06:53] "GET / HTTP/1.1" 200 -
|
| 89 |
+
2025-11-21 17:06:53,756 - 127.0.0.1 - - [21/Nov/2025 17:06:53] "[33mGET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1[0m" 404 -
|
| 90 |
+
2025-11-21 17:06:54,076 - 127.0.0.1 - - [21/Nov/2025 17:06:54] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 91 |
+
2025-11-21 17:06:54,078 - 127.0.0.1 - - [21/Nov/2025 17:06:54] "GET /static/favicon.ico HTTP/1.1" 200 -
|
| 92 |
+
2025-11-21 17:09:47,214 - 127.0.0.1 - - [21/Nov/2025 17:09:47] "[35m[1mPOST /transcribe HTTP/1.1[0m" 500 -
|
| 93 |
+
2025-11-21 17:24:06,935 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
|
| 94 |
+
2025-11-21 17:24:08,306 - * Restarting with stat
|
| 95 |
+
2025-11-21 17:24:30,142 - * Debugger is active!
|
| 96 |
+
2025-11-21 17:24:30,144 - * Debugger PIN: 465-628-698
|
| 97 |
+
2025-11-21 17:24:40,736 - 127.0.0.1 - - [21/Nov/2025 17:24:40] "[35m[1mPOST /transcribe HTTP/1.1[0m" 500 -
|
| 98 |
+
2025-11-21 17:26:37,091 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
|
| 99 |
+
2025-11-21 17:26:38,450 - * Restarting with stat
|
| 100 |
+
2025-11-21 17:26:52,990 - * Debugger is active!
|
| 101 |
+
2025-11-21 17:26:52,997 - * Debugger PIN: 465-628-698
|
| 102 |
+
2025-11-21 17:27:10,347 - 127.0.0.1 - - [21/Nov/2025 17:27:10] "[35m[1mPOST /transcribe HTTP/1.1[0m" 500 -
|
| 103 |
+
2025-11-21 17:27:25,067 - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
| 104 |
+
* Running on http://127.0.0.1:5000
|
| 105 |
+
2025-11-21 17:27:25,067 - [33mPress CTRL+C to quit[0m
|
| 106 |
+
2025-11-21 17:27:25,069 - * Restarting with stat
|
| 107 |
+
2025-11-21 17:27:35,951 - * Debugger is active!
|
| 108 |
+
2025-11-21 17:27:35,956 - * Debugger PIN: 465-628-698
|
| 109 |
+
2025-11-21 17:27:35,985 - 127.0.0.1 - - [21/Nov/2025 17:27:35] "GET / HTTP/1.1" 200 -
|
| 110 |
+
2025-11-21 17:27:36,140 - 127.0.0.1 - - [21/Nov/2025 17:27:36] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 111 |
+
2025-11-21 17:28:38,222 - 127.0.0.1 - - [21/Nov/2025 17:28:38] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 112 |
+
2025-11-21 17:28:38,232 - 127.0.0.1 - - [21/Nov/2025 17:28:38] "[33mGET /.well-known/appspecific/com.chrome.devtools.json HTTP/1.1[0m" 404 -
|
| 113 |
+
2025-11-21 17:28:56,242 - 127.0.0.1 - - [21/Nov/2025 17:28:56] "[35m[1mPOST /transcribe HTTP/1.1[0m" 500 -
|
| 114 |
+
2025-11-21 17:34:01,152 - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
| 115 |
+
* Running on http://127.0.0.1:5000
|
| 116 |
+
2025-11-21 17:34:01,153 - [33mPress CTRL+C to quit[0m
|
| 117 |
+
2025-11-21 17:34:01,155 - * Restarting with stat
|
| 118 |
+
2025-11-21 17:34:14,790 - * Debugger is active!
|
| 119 |
+
2025-11-21 17:34:14,795 - * Debugger PIN: 465-628-698
|
| 120 |
+
2025-11-21 17:34:36,971 - 127.0.0.1 - - [21/Nov/2025 17:34:36] "GET / HTTP/1.1" 200 -
|
| 121 |
+
2025-11-21 17:34:37,154 - 127.0.0.1 - - [21/Nov/2025 17:34:37] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 122 |
+
2025-11-21 17:34:51,570 - 127.0.0.1 - - [21/Nov/2025 17:34:51] "[35m[1mPOST /transcribe HTTP/1.1[0m" 500 -
|
| 123 |
+
2025-11-21 17:34:54,847 - 127.0.0.1 - - [21/Nov/2025 17:34:54] "[35m[1mPOST /transcribe HTTP/1.1[0m" 500 -
|
| 124 |
+
2025-11-21 17:35:02,069 - 127.0.0.1 - - [21/Nov/2025 17:35:02] "[35m[1mPOST /transcribe HTTP/1.1[0m" 500 -
|
| 125 |
+
2025-11-21 17:35:16,704 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
|
| 126 |
+
2025-11-21 17:35:17,645 - * Restarting with stat
|
| 127 |
+
2025-11-21 17:35:31,749 - * Debugger is active!
|
| 128 |
+
2025-11-21 17:35:31,753 - * Debugger PIN: 465-628-698
|
| 129 |
+
2025-11-21 17:36:46,964 - * Detected change in 'C:\\Users\\chaitanyakharche\\OneDrive\\Desktop\\saayam\\ai\\app.py', reloading
|
| 130 |
+
2025-11-21 17:36:47,930 - * Restarting with stat
|
| 131 |
+
2025-11-21 17:37:02,683 - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
| 132 |
+
* Running on http://127.0.0.1:5000
|
| 133 |
+
2025-11-21 17:37:02,683 - [33mPress CTRL+C to quit[0m
|
| 134 |
+
2025-11-21 17:37:02,687 - * Restarting with stat
|
| 135 |
+
2025-11-21 17:37:15,615 - * Debugger is active!
|
| 136 |
+
2025-11-21 17:37:15,617 - * Debugger PIN: 465-628-698
|
| 137 |
+
2025-11-21 17:37:15,714 - 127.0.0.1 - - [21/Nov/2025 17:37:15] "GET / HTTP/1.1" 200 -
|
| 138 |
+
2025-11-21 17:37:15,841 - 127.0.0.1 - - [21/Nov/2025 17:37:15] "[36mGET /static/site.webmanifest HTTP/1.1[0m" 304 -
|
| 139 |
+
2025-11-21 17:37:30,830 - 127.0.0.1 - - [21/Nov/2025 17:37:30] "POST /transcribe HTTP/1.1" 200 -
|
| 140 |
+
2025-11-21 17:37:44,136 - 127.0.0.1 - - [21/Nov/2025 17:37:44] "POST /transcribe HTTP/1.1" 200 -
|
| 141 |
+
2025-11-21 17:38:01,648 - 127.0.0.1 - - [21/Nov/2025 17:38:01] "POST /transcribe HTTP/1.1" 200 -
|
static/android-chrome-192x192.png
ADDED
|
static/android-chrome-512x512.png
ADDED
|
Git LFS Details
|
static/apple-touch-icon.png
ADDED
|
|
static/favicon-16x16.png
ADDED
|
|
static/favicon-32x32.png
ADDED
|
|
static/favicon.ico
ADDED
|
|
static/site.webmanifest
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
templates/index.html
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<title>Saayam AI Assistant</title>
|
| 6 |
+
|
| 7 |
+
<!-- Favicon & Icons -->
|
| 8 |
+
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='apple-touch-icon.png') }}">
|
| 9 |
+
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='favicon-32x32.png') }}">
|
| 10 |
+
<link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='favicon-16x16.png') }}">
|
| 11 |
+
<link rel="manifest" href="{{ url_for('static', filename='site.webmanifest') }}">
|
| 12 |
+
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
| 13 |
+
|
| 14 |
+
<!-- Add marked.js for Markdown rendering -->
|
| 15 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 16 |
+
|
| 17 |
+
<style>
|
| 18 |
+
body {
|
| 19 |
+
font-family: Arial, sans-serif;
|
| 20 |
+
margin: 20px;
|
| 21 |
+
background-color: #f9f9f9;
|
| 22 |
+
}
|
| 23 |
+
.container {
|
| 24 |
+
max-width: 600px;
|
| 25 |
+
margin: auto;
|
| 26 |
+
background: #fff;
|
| 27 |
+
padding: 20px;
|
| 28 |
+
border-radius: 8px;
|
| 29 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
| 30 |
+
}
|
| 31 |
+
h1 {
|
| 32 |
+
text-align: center;
|
| 33 |
+
color: #4CAF50;
|
| 34 |
+
}
|
| 35 |
+
label {
|
| 36 |
+
font-weight: bold;
|
| 37 |
+
margin-top: 10px;
|
| 38 |
+
display: block;
|
| 39 |
+
}
|
| 40 |
+
textarea, select, input, button {
|
| 41 |
+
margin-top: 10px;
|
| 42 |
+
width: 100%;
|
| 43 |
+
padding: 10px;
|
| 44 |
+
border: 1px solid #ccc;
|
| 45 |
+
border-radius: 5px;
|
| 46 |
+
box-sizing: border-box; /* Ensures padding doesn't affect width */
|
| 47 |
+
}
|
| 48 |
+
button {
|
| 49 |
+
background-color: #4CAF50;
|
| 50 |
+
color: white;
|
| 51 |
+
border: none;
|
| 52 |
+
cursor: pointer;
|
| 53 |
+
font-size: 16px;
|
| 54 |
+
}
|
| 55 |
+
button:hover {
|
| 56 |
+
background-color: #45a049;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
/* STT Toolbar Styles */
|
| 60 |
+
.stt-toolbar {
|
| 61 |
+
display: flex;
|
| 62 |
+
gap: 10px;
|
| 63 |
+
margin-top: 5px;
|
| 64 |
+
margin-bottom: 5px;
|
| 65 |
+
align-items: center;
|
| 66 |
+
}
|
| 67 |
+
.stt-select {
|
| 68 |
+
flex-grow: 1;
|
| 69 |
+
margin-top: 0;
|
| 70 |
+
padding: 8px;
|
| 71 |
+
}
|
| 72 |
+
.mic-button {
|
| 73 |
+
width: auto;
|
| 74 |
+
margin-top: 0;
|
| 75 |
+
background-color: #f0f0f0;
|
| 76 |
+
color: #333;
|
| 77 |
+
border: 1px solid #ccc;
|
| 78 |
+
padding: 8px 15px;
|
| 79 |
+
display: flex;
|
| 80 |
+
align-items: center;
|
| 81 |
+
gap: 5px;
|
| 82 |
+
transition: all 0.3s ease;
|
| 83 |
+
}
|
| 84 |
+
.mic-button:hover {
|
| 85 |
+
background-color: #e0e0e0;
|
| 86 |
+
}
|
| 87 |
+
.mic-button.recording {
|
| 88 |
+
background-color: #ffebee;
|
| 89 |
+
color: #d32f2f;
|
| 90 |
+
border-color: #d32f2f;
|
| 91 |
+
animation: pulse 1.5s infinite;
|
| 92 |
+
}
|
| 93 |
+
.mic-icon {
|
| 94 |
+
width: 16px;
|
| 95 |
+
height: 16px;
|
| 96 |
+
fill: currentColor;
|
| 97 |
+
}
|
| 98 |
+
@keyframes pulse {
|
| 99 |
+
0% { box-shadow: 0 0 0 0 rgba(211, 47, 47, 0.4); }
|
| 100 |
+
70% { box-shadow: 0 0 0 10px rgba(211, 47, 47, 0); }
|
| 101 |
+
100% { box-shadow: 0 0 0 0 rgba(211, 47, 47, 0); }
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.predicted-categories {
|
| 105 |
+
margin-top: 10px;
|
| 106 |
+
display: flex;
|
| 107 |
+
gap: 10px;
|
| 108 |
+
flex-wrap: wrap;
|
| 109 |
+
}
|
| 110 |
+
.category-option {
|
| 111 |
+
padding: 10px;
|
| 112 |
+
border: 1px solid #ccc;
|
| 113 |
+
border-radius: 5px;
|
| 114 |
+
background-color: #e7f5e7;
|
| 115 |
+
cursor: pointer;
|
| 116 |
+
}
|
| 117 |
+
.category-option.selected {
|
| 118 |
+
background-color: #4CAF50;
|
| 119 |
+
color: white;
|
| 120 |
+
border: 1px solid #4CAF50;
|
| 121 |
+
}
|
| 122 |
+
.loading-message {
|
| 123 |
+
text-align: center;
|
| 124 |
+
margin-top: 10px;
|
| 125 |
+
font-style: italic;
|
| 126 |
+
color: #888;
|
| 127 |
+
}
|
| 128 |
+
.response {
|
| 129 |
+
margin-top: 20px;
|
| 130 |
+
display: none;
|
| 131 |
+
}
|
| 132 |
+
.response p {
|
| 133 |
+
background: #e7f5e7;
|
| 134 |
+
padding: 10px;
|
| 135 |
+
border: 1px solid #4CAF50;
|
| 136 |
+
border-radius: 5px;
|
| 137 |
+
font-family: Arial, sans-serif;
|
| 138 |
+
}
|
| 139 |
+
.metrics {
|
| 140 |
+
margin-top: 10px;
|
| 141 |
+
padding: 10px;
|
| 142 |
+
background: #f0f0f0;
|
| 143 |
+
border-radius: 5px;
|
| 144 |
+
}
|
| 145 |
+
.metrics p {
|
| 146 |
+
margin: 5px 0;
|
| 147 |
+
}
|
| 148 |
+
</style>
|
| 149 |
+
</head>
|
| 150 |
+
<body>
|
| 151 |
+
<div class="container">
|
| 152 |
+
<h1>Saayam AI Assistant</h1>
|
| 153 |
+
<p><strong>Homepage:</strong><br>Enter a subject and description for your query.<br>
|
| 154 |
+
Optionally, select a category from the dropdown menu.</p>
|
| 155 |
+
|
| 156 |
+
<form id="qa-form">
|
| 157 |
+
<label for="subject">Subject:</label>
|
| 158 |
+
<input id="subject" name="subject" type="text" required />
|
| 159 |
+
|
| 160 |
+
<label for="category">Category (Optional):</label>
|
| 161 |
+
<select id="category" name="category">
|
| 162 |
+
<option value="" disabled selected>Select a category</option>
|
| 163 |
+
</select>
|
| 164 |
+
|
| 165 |
+
<label for="description">Description:</label>
|
| 166 |
+
|
| 167 |
+
<!-- STT Toolbar -->
|
| 168 |
+
<div class="stt-toolbar">
|
| 169 |
+
<select id="stt-language" class="stt-select">
|
| 170 |
+
<option value="auto">Auto-Detect Language</option>
|
| 171 |
+
<option value="en">English</option>
|
| 172 |
+
<option value="zh">Mandarin Chinese</option>
|
| 173 |
+
<option value="hi">Hindi</option>
|
| 174 |
+
<option value="es">Spanish</option>
|
| 175 |
+
<option value="fr">French</option>
|
| 176 |
+
<option value="ar">Arabic</option>
|
| 177 |
+
<option value="bn">Bengali</option>
|
| 178 |
+
<option value="pt">Portuguese</option>
|
| 179 |
+
<option value="ru">Russian</option>
|
| 180 |
+
<option value="ur">Urdu</option>
|
| 181 |
+
<option value="id">Indonesian</option>
|
| 182 |
+
<option value="de">German</option>
|
| 183 |
+
<option value="ja">Japanese</option>
|
| 184 |
+
</select>
|
| 185 |
+
<button type="button" id="mic-btn" class="mic-button" onclick="toggleRecording()">
|
| 186 |
+
<svg class="mic-icon" viewBox="0 0 24 24">
|
| 187 |
+
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
|
| 188 |
+
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
|
| 189 |
+
</svg>
|
| 190 |
+
Record
|
| 191 |
+
</button>
|
| 192 |
+
</div>
|
| 193 |
+
|
| 194 |
+
<textarea id="description" name="description" rows="4" required placeholder="Type or use the microphone to speak..."></textarea>
|
| 195 |
+
|
| 196 |
+
<div id="category-prediction-area"></div>
|
| 197 |
+
|
| 198 |
+
<button type="button" onclick="askQuestion()" style="margin-top: 20px;">Ask</button>
|
| 199 |
+
</form>
|
| 200 |
+
|
| 201 |
+
<div class="response" id="response-container">
|
| 202 |
+
<h3>AI Response:</h3>
|
| 203 |
+
<div id="response-text"></div>
|
| 204 |
+
<div class="metrics" id="metrics-container"></div>
|
| 205 |
+
</div>
|
| 206 |
+
</div>
|
| 207 |
+
|
| 208 |
+
<script>
|
| 209 |
+
const categories = JSON.parse('{{ categories | tojson | safe }}');
|
| 210 |
+
const categoryDropdown = document.getElementById("category");
|
| 211 |
+
const predictionArea = document.getElementById("category-prediction-area");
|
| 212 |
+
let selectedCategory = "";
|
| 213 |
+
|
| 214 |
+
// Populate category dropdown
|
| 215 |
+
categories.forEach(cat => {
|
| 216 |
+
const option = document.createElement("option");
|
| 217 |
+
option.value = cat;
|
| 218 |
+
option.textContent = cat;
|
| 219 |
+
categoryDropdown.appendChild(option);
|
| 220 |
+
});
|
| 221 |
+
|
| 222 |
+
// --- Speech to Text Logic ---
|
| 223 |
+
let mediaRecorder;
|
| 224 |
+
let audioChunks = [];
|
| 225 |
+
const micBtn = document.getElementById('mic-btn');
|
| 226 |
+
const descriptionArea = document.getElementById('description');
|
| 227 |
+
|
| 228 |
+
async function toggleRecording() {
|
| 229 |
+
if (mediaRecorder && mediaRecorder.state === "recording") {
|
| 230 |
+
mediaRecorder.stop();
|
| 231 |
+
} else {
|
| 232 |
+
try {
|
| 233 |
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
| 234 |
+
startRecording(stream);
|
| 235 |
+
} catch (err) {
|
| 236 |
+
console.error("Error accessing microphone:", err);
|
| 237 |
+
alert("Microphone access denied or unavailable.");
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
function startRecording(stream) {
|
| 243 |
+
audioChunks = [];
|
| 244 |
+
let options;
|
| 245 |
+
if (MediaRecorder.isTypeSupported('audio/webm;codecs=opus')) {
|
| 246 |
+
options = { mimeType: 'audio/webm;codecs=opus' };
|
| 247 |
+
} else if (MediaRecorder.isTypeSupported('audio/webm')) {
|
| 248 |
+
options = { mimeType: 'audio/webm' };
|
| 249 |
+
} else {
|
| 250 |
+
options = { mimeType: 'audio/ogg;codecs=opus' };
|
| 251 |
+
}
|
| 252 |
+
mediaRecorder = new MediaRecorder(stream, options);
|
| 253 |
+
|
| 254 |
+
mediaRecorder.ondataavailable = event => {
|
| 255 |
+
audioChunks.push(event.data);
|
| 256 |
+
};
|
| 257 |
+
|
| 258 |
+
mediaRecorder.onstop = async () => {
|
| 259 |
+
// Visual reset
|
| 260 |
+
micBtn.classList.remove("recording");
|
| 261 |
+
micBtn.innerHTML = `
|
| 262 |
+
<svg class="mic-icon" viewBox="0 0 24 24">
|
| 263 |
+
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
|
| 264 |
+
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
|
| 265 |
+
</svg> Record`;
|
| 266 |
+
descriptionArea.placeholder = "Processing audio...";
|
| 267 |
+
|
| 268 |
+
// Prepare file
|
| 269 |
+
const mimeType = mediaRecorder.mimeType || 'audio/webm';
|
| 270 |
+
const audioBlob = new Blob(audioChunks, { type: mimeType });
|
| 271 |
+
const formData = new FormData();
|
| 272 |
+
formData.append("audio", audioBlob);
|
| 273 |
+
|
| 274 |
+
const lang = document.getElementById('stt-language').value;
|
| 275 |
+
formData.append("language", lang);
|
| 276 |
+
|
| 277 |
+
try {
|
| 278 |
+
const response = await fetch('/transcribe', {
|
| 279 |
+
method: 'POST',
|
| 280 |
+
body: formData
|
| 281 |
+
});
|
| 282 |
+
|
| 283 |
+
const data = await response.json();
|
| 284 |
+
|
| 285 |
+
if (data.text) {
|
| 286 |
+
// Append text if content exists, otherwise set it
|
| 287 |
+
const currentText = descriptionArea.value;
|
| 288 |
+
descriptionArea.value = currentText ? currentText + " " + data.text : data.text;
|
| 289 |
+
descriptionArea.placeholder = "Type or use the microphone to speak...";
|
| 290 |
+
} else if (data.error) {
|
| 291 |
+
alert("Transcription failed: " + data.error);
|
| 292 |
+
descriptionArea.placeholder = "Error in transcription.";
|
| 293 |
+
}
|
| 294 |
+
} catch (error) {
|
| 295 |
+
console.error("API Error:", error);
|
| 296 |
+
alert("Failed to connect to transcription server.");
|
| 297 |
+
descriptionArea.placeholder = "Connection failed.";
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
// Stop all tracks to release mic
|
| 301 |
+
stream.getTracks().forEach(track => track.stop());
|
| 302 |
+
};
|
| 303 |
+
|
| 304 |
+
mediaRecorder.start();
|
| 305 |
+
micBtn.classList.add("recording");
|
| 306 |
+
micBtn.innerHTML = "Listening... (Click to Stop)";
|
| 307 |
+
descriptionArea.placeholder = "Listening...";
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
// --- Existing Logic ---
|
| 311 |
+
|
| 312 |
+
function askQuestion() {
|
| 313 |
+
const subject = document.getElementById("subject").value;
|
| 314 |
+
const category = document.getElementById("category").value || selectedCategory;
|
| 315 |
+
const description = document.getElementById("description").value;
|
| 316 |
+
|
| 317 |
+
if (!subject || !description) {
|
| 318 |
+
alert("Please fill out all required fields.");
|
| 319 |
+
return;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
// Basic prediction logic if category missing
|
| 323 |
+
if (!category) {
|
| 324 |
+
predictionArea.innerHTML = '<p class="loading-message">Predicting categories...</p>';
|
| 325 |
+
fetch('/predict_categories', {
|
| 326 |
+
method: 'POST',
|
| 327 |
+
headers: { 'Content-Type': 'application/json' },
|
| 328 |
+
body: JSON.stringify({ subject, description })
|
| 329 |
+
})
|
| 330 |
+
.then(res => res.json())
|
| 331 |
+
.then(data => {
|
| 332 |
+
predictionArea.innerHTML = "";
|
| 333 |
+
const predictedContainer = document.createElement("div");
|
| 334 |
+
predictedContainer.className = "predicted-categories";
|
| 335 |
+
|
| 336 |
+
if(data.predicted_categories) {
|
| 337 |
+
data.predicted_categories.forEach(cat => {
|
| 338 |
+
const div = document.createElement("div");
|
| 339 |
+
div.textContent = cat;
|
| 340 |
+
div.className = "category-option";
|
| 341 |
+
div.onclick = () => selectCategory(div, cat);
|
| 342 |
+
predictedContainer.appendChild(div);
|
| 343 |
+
});
|
| 344 |
+
}
|
| 345 |
+
predictionArea.appendChild(predictedContainer);
|
| 346 |
+
})
|
| 347 |
+
.catch(err => {
|
| 348 |
+
predictionArea.innerHTML = "<p class='loading-message'>Failed to predict categories.</p>";
|
| 349 |
+
console.error(err);
|
| 350 |
+
});
|
| 351 |
+
return;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
const responseContainer = document.getElementById("response-container");
|
| 355 |
+
const responseText = document.getElementById("response-text");
|
| 356 |
+
responseContainer.style.display = 'block';
|
| 357 |
+
responseText.innerHTML = '<p class="loading-message">Thinking...</p>';
|
| 358 |
+
document.getElementById("metrics-container").innerHTML = "";
|
| 359 |
+
|
| 360 |
+
fetch('/generate_answer', {
|
| 361 |
+
method: 'POST',
|
| 362 |
+
headers: { 'Content-Type': 'application/json' },
|
| 363 |
+
body: JSON.stringify({ category, question: description })
|
| 364 |
+
})
|
| 365 |
+
.then(res => res.json())
|
| 366 |
+
.then(data => {
|
| 367 |
+
if (data.error) {
|
| 368 |
+
responseText.innerHTML = `<p style="color:red">Error: ${data.error}</p>`;
|
| 369 |
+
return;
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
// Render the response as Markdown
|
| 373 |
+
responseText.innerHTML = marked.parse(data.answer);
|
| 374 |
+
|
| 375 |
+
// Display metrics
|
| 376 |
+
const metricsContainer = document.getElementById("metrics-container");
|
| 377 |
+
if (data.metrics) {
|
| 378 |
+
metricsContainer.innerHTML = `
|
| 379 |
+
<h4>Performance Metrics:</h4>
|
| 380 |
+
<p><strong>Model:</strong> ${data.metrics.model}</p>
|
| 381 |
+
<p><strong>Temperature:</strong> ${data.metrics.temperature}</p>
|
| 382 |
+
<p><strong>Time to First Token (TTFT):</strong> ${data.metrics.ttft_seconds} seconds</p>
|
| 383 |
+
<p><strong>Total Response Time (TTLT):</strong> ${data.metrics.ttlt_seconds} seconds</p>
|
| 384 |
+
<p><strong>Speed:</strong> ${data.metrics.speed_tokens_per_second} tokens/second</p>
|
| 385 |
+
<p><strong>Input Tokens:</strong> ${data.metrics.input_tokens}</p>
|
| 386 |
+
<p><strong>Output Tokens:</strong> ${data.metrics.output_tokens}</p>
|
| 387 |
+
`;
|
| 388 |
+
} else {
|
| 389 |
+
metricsContainer.innerHTML = '<p>No metrics available.</p>';
|
| 390 |
+
}
|
| 391 |
+
})
|
| 392 |
+
.catch(err => {
|
| 393 |
+
console.error(err);
|
| 394 |
+
responseText.innerHTML = `<p style="color:red">Request failed.</p>`;
|
| 395 |
+
});
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
function selectCategory(element, category) {
|
| 399 |
+
document.querySelectorAll(".category-option").forEach(el => el.classList.remove("selected"));
|
| 400 |
+
element.classList.add("selected");
|
| 401 |
+
selectedCategory = category;
|
| 402 |
+
}
|
| 403 |
+
</script>
|
| 404 |
+
</body>
|
| 405 |
+
</html>
|
test_app.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
from app import app, categories
|
| 3 |
+
|
| 4 |
+
@pytest.fixture
|
| 5 |
+
def client():
|
| 6 |
+
with app.test_client() as client:
|
| 7 |
+
yield client
|
| 8 |
+
|
| 9 |
+
def test_homepage(client):
|
| 10 |
+
response = client.get('/')
|
| 11 |
+
assert response.status_code == 200
|
| 12 |
+
assert b"Saayam AI Assistant" in response.data
|
| 13 |
+
|
| 14 |
+
def test_predict_categories(client):
|
| 15 |
+
response = client.post('/predict_categories', json={
|
| 16 |
+
"subject": "How to save money?",
|
| 17 |
+
"description": "I want to start budgeting my expenses."
|
| 18 |
+
})
|
| 19 |
+
assert response.status_code == 200
|
| 20 |
+
data = response.get_json()
|
| 21 |
+
assert "predicted_categories" in data
|
| 22 |
+
assert len(data["predicted_categories"]) > 0
|
| 23 |
+
|
| 24 |
+
@pytest.mark.parametrize("category", categories[:5]) # Use a few categories for demo
|
| 25 |
+
def test_generate_answer(client, category):
|
| 26 |
+
response = client.post('/generate_answer', json={
|
| 27 |
+
"category": category,
|
| 28 |
+
"question": "Tell me something about this category."
|
| 29 |
+
})
|
| 30 |
+
assert response.status_code == 200
|
| 31 |
+
data = response.get_json()
|
| 32 |
+
assert "answer" in data or "error" in data
|