Spaces:
Running
Running
Upload 19 files
Browse files- .gitattributes +1 -0
- about.html +17 -18
- app.py +397 -0
- contact.html +371 -0
- examples.html +16 -18
- feedback.html +390 -0
- index.html +252 -536
- recognizer.html +560 -366
- samples/turotial.mp4 +3 -0
- technology.html +16 -18
.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 |
+
samples/turotial.mp4 filter=lfs diff=lfs merge=lfs -text
|
about.html
CHANGED
|
@@ -158,24 +158,23 @@ body {
|
|
| 158 |
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 159 |
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 160 |
<div class="flex items-center">
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
<nav class="hidden md:flex space-x-6">
|
| 164 |
-
<a href="
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
<a href="
|
| 177 |
-
|
| 178 |
-
data-lang-en="About" data-lang-hi="बारे में">About</a>
|
| 179 |
</nav>
|
| 180 |
</div>
|
| 181 |
<!-- Right side header items -->
|
|
|
|
| 158 |
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 159 |
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 160 |
<div class="flex items-center">
|
| 161 |
+
<a href="index.html" class="text-2xl font-['Pacifico'] text-primary dark:text-blue-400 mr-8" data-lang-en="AI Text Tools" data-lang-hi="एआई टेक्स्ट उपकरण">AI Text Tools</a>
|
| 162 |
+
<!-- *** START: Updated Navigation Bar *** -->
|
| 163 |
+
<nav id="mainNav" class="hidden md:flex space-x-6">
|
| 164 |
+
<a href="index.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Home" data-lang-hi="होम">Home</a>
|
| 165 |
+
<!-- Assuming recognizer.html is the main tool page, link from index.html might be better -->
|
| 166 |
+
<a href="recognizer.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a>
|
| 167 |
+
<!-- Assuming examples.html exists -->
|
| 168 |
+
<a href="examples.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</a>
|
| 169 |
+
<!-- Assuming technology.html exists -->
|
| 170 |
+
<a href="technology.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Technology" data-lang-hi="तकनीक">Technology</a>
|
| 171 |
+
<!-- Assuming about.html exists -->
|
| 172 |
+
<a href="about.html" class="nav-link text-primary dark:text-blue-400 font-medium" data-lang-en="About" data-lang-hi="बारे में">About</a>
|
| 173 |
+
<!-- Link to contact.html -->
|
| 174 |
+
<a href="contact.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</a>
|
| 175 |
+
<!-- Active link for this page -->
|
| 176 |
+
<a href="feedback.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Feedback" data-lang-hi="प्रतिक्रिया">Feedback</a>
|
| 177 |
+
</nav>
|
|
|
|
| 178 |
</nav>
|
| 179 |
</div>
|
| 180 |
<!-- Right side header items -->
|
app.py
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, status, Request
|
| 2 |
+
from fastapi.responses import FileResponse, JSONResponse, HTMLResponse
|
| 3 |
+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
| 4 |
+
from pydantic import BaseModel, EmailStr, Field
|
| 5 |
+
from typing import List, Optional
|
| 6 |
+
import cv2
|
| 7 |
+
import numpy as np
|
| 8 |
+
import tensorflow as tf
|
| 9 |
+
import pickle
|
| 10 |
+
import matplotlib.pyplot as plt
|
| 11 |
+
import matplotlib.font_manager as fm
|
| 12 |
+
import os
|
| 13 |
+
import io
|
| 14 |
+
import sys
|
| 15 |
+
import tempfile
|
| 16 |
+
import requests
|
| 17 |
+
from PIL import Image
|
| 18 |
+
import uvicorn
|
| 19 |
+
import shutil
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
import py_text_scan
|
| 22 |
+
from sqlalchemy import create_engine, Column, Integer, String, Boolean, Text, DateTime
|
| 23 |
+
from sqlalchemy.ext.declarative import declarative_base
|
| 24 |
+
from sqlalchemy.orm import sessionmaker, Session
|
| 25 |
+
from passlib.context import CryptContext
|
| 26 |
+
import datetime
|
| 27 |
+
|
| 28 |
+
# --- Database Setup (SQLite) ---
|
| 29 |
+
DATABASE_URL = "sqlite:///./test.db"
|
| 30 |
+
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
| 31 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 32 |
+
Base = declarative_base()
|
| 33 |
+
|
| 34 |
+
# --- Database Models ---
|
| 35 |
+
class UserModel(Base):
|
| 36 |
+
__tablename__ = "users"
|
| 37 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 38 |
+
username = Column(String, unique=True, index=True)
|
| 39 |
+
email = Column(String, unique=True, index=True)
|
| 40 |
+
hashed_password = Column(String)
|
| 41 |
+
is_active = Column(Boolean, default=True)
|
| 42 |
+
is_admin = Column(Boolean, default=False)
|
| 43 |
+
|
| 44 |
+
class FeedbackModel(Base):
|
| 45 |
+
__tablename__ = "feedback"
|
| 46 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 47 |
+
username = Column(String)
|
| 48 |
+
comment = Column(Text)
|
| 49 |
+
created_at = Column(DateTime, default=datetime.datetime.utcnow)
|
| 50 |
+
|
| 51 |
+
Base.metadata.create_all(bind=engine)
|
| 52 |
+
|
| 53 |
+
# --- Pydantic Schemas ---
|
| 54 |
+
class UserBase(BaseModel):
|
| 55 |
+
username: str = Field(..., min_length=3, max_length=50)
|
| 56 |
+
email: EmailStr
|
| 57 |
+
|
| 58 |
+
class UserCreate(UserBase):
|
| 59 |
+
password: str = Field(..., min_length=6)
|
| 60 |
+
|
| 61 |
+
class UserResponse(UserBase):
|
| 62 |
+
id: int
|
| 63 |
+
is_active: bool
|
| 64 |
+
is_admin: bool
|
| 65 |
+
class Config:
|
| 66 |
+
from_attributes = True
|
| 67 |
+
|
| 68 |
+
class UserUpdate(BaseModel):
|
| 69 |
+
username: Optional[str] = None
|
| 70 |
+
email: Optional[EmailStr] = None
|
| 71 |
+
is_active: Optional[bool] = None
|
| 72 |
+
is_admin: Optional[bool] = None
|
| 73 |
+
|
| 74 |
+
class FeedbackBase(BaseModel):
|
| 75 |
+
username: str
|
| 76 |
+
comment: str
|
| 77 |
+
|
| 78 |
+
class FeedbackCreate(FeedbackBase):
|
| 79 |
+
pass
|
| 80 |
+
|
| 81 |
+
class FeedbackResponse(FeedbackBase):
|
| 82 |
+
id: int
|
| 83 |
+
created_at: datetime.datetime
|
| 84 |
+
class Config:
|
| 85 |
+
from_attributes = True
|
| 86 |
+
|
| 87 |
+
class Token(BaseModel):
|
| 88 |
+
access_token: str
|
| 89 |
+
token_type: str
|
| 90 |
+
|
| 91 |
+
class TokenData(BaseModel):
|
| 92 |
+
username: Optional[str] = None
|
| 93 |
+
|
| 94 |
+
class OCRResponse(BaseModel):
|
| 95 |
+
sakshi_output: str
|
| 96 |
+
word_count: int
|
| 97 |
+
prediction_label: str
|
| 98 |
+
|
| 99 |
+
# --- Password Hashing ---
|
| 100 |
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 101 |
+
|
| 102 |
+
# --- Authentication ---
|
| 103 |
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
| 104 |
+
|
| 105 |
+
def get_db():
|
| 106 |
+
db = SessionLocal()
|
| 107 |
+
try:
|
| 108 |
+
yield db
|
| 109 |
+
finally:
|
| 110 |
+
db.close()
|
| 111 |
+
|
| 112 |
+
async def get_current_user(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
|
| 113 |
+
user = get_user_by_username(db, username=token)
|
| 114 |
+
if not user:
|
| 115 |
+
raise HTTPException(
|
| 116 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 117 |
+
detail="Invalid authentication credentials",
|
| 118 |
+
headers={"WWW-Authenticate": "Bearer"},
|
| 119 |
+
)
|
| 120 |
+
return user
|
| 121 |
+
|
| 122 |
+
async def get_current_active_user(current_user: UserModel = Depends(get_current_user)):
|
| 123 |
+
if not current_user.is_active:
|
| 124 |
+
raise HTTPException(status_code=400, detail="Inactive user")
|
| 125 |
+
return current_user
|
| 126 |
+
|
| 127 |
+
async def get_current_admin_user(current_user: UserModel = Depends(get_current_active_user)):
|
| 128 |
+
if not current_user.is_admin:
|
| 129 |
+
raise HTTPException(status_code=403, detail="Not an administrator")
|
| 130 |
+
return current_user
|
| 131 |
+
|
| 132 |
+
# --- CRUD Operations ---
|
| 133 |
+
def get_user(db: Session, user_id: int):
|
| 134 |
+
return db.query(UserModel).filter(UserModel.id == user_id).first()
|
| 135 |
+
|
| 136 |
+
def get_user_by_username(db: Session, username: str):
|
| 137 |
+
return db.query(UserModel).filter(UserModel.username == username).first()
|
| 138 |
+
|
| 139 |
+
def get_user_by_email(db: Session, email: str):
|
| 140 |
+
return db.query(UserModel).filter(UserModel.email == email).first()
|
| 141 |
+
|
| 142 |
+
def get_users(db: Session, skip: int = 0, limit: int = 100):
|
| 143 |
+
return db.query(UserModel).offset(skip).limit(limit).all()
|
| 144 |
+
|
| 145 |
+
def create_user(db: Session, user: UserCreate):
|
| 146 |
+
hashed_password = pwd_context.hash(user.password)
|
| 147 |
+
db_user = UserModel(username=user.username, email=user.email, hashed_password=hashed_password)
|
| 148 |
+
db.add(db_user)
|
| 149 |
+
db.commit()
|
| 150 |
+
db.refresh(db_user)
|
| 151 |
+
return db_user
|
| 152 |
+
|
| 153 |
+
def update_user(db: Session, user_id: int, user: UserUpdate):
|
| 154 |
+
db_user = get_user(db, user_id)
|
| 155 |
+
if db_user:
|
| 156 |
+
for key, value in user.dict(exclude_unset=True).items():
|
| 157 |
+
setattr(db_user, key, value)
|
| 158 |
+
db.commit()
|
| 159 |
+
db.refresh(db_user)
|
| 160 |
+
return db_user
|
| 161 |
+
|
| 162 |
+
def delete_user(db: Session, user_id: int):
|
| 163 |
+
db_user = get_user(db, user_id)
|
| 164 |
+
if db_user:
|
| 165 |
+
db.delete(db_user)
|
| 166 |
+
db.commit()
|
| 167 |
+
return True
|
| 168 |
+
return False
|
| 169 |
+
|
| 170 |
+
def verify_password(plain_password, hashed_password):
|
| 171 |
+
return pwd_context.verify(plain_password, hashed_password)
|
| 172 |
+
|
| 173 |
+
def create_feedback(db: Session, feedback: FeedbackCreate):
|
| 174 |
+
db_feedback = FeedbackModel(**feedback.dict())
|
| 175 |
+
db.add(db_feedback)
|
| 176 |
+
db.commit()
|
| 177 |
+
db.refresh(db_feedback)
|
| 178 |
+
return db_feedback
|
| 179 |
+
|
| 180 |
+
def get_feedback(db: Session, skip: int = 0, limit: int = 100):
|
| 181 |
+
return db.query(FeedbackModel).order_by(FeedbackModel.created_at.desc()).offset(skip).limit(limit).all()
|
| 182 |
+
|
| 183 |
+
# --- FastAPI App Setup ---
|
| 184 |
+
app = FastAPI(
|
| 185 |
+
title="Hindi OCR API",
|
| 186 |
+
description="API for Hindi OCR, word detection, authentication, and feedback",
|
| 187 |
+
version="1.0.0"
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
# --- Hugging Face Model and Resource URLs ---
|
| 191 |
+
MODEL_URL = "https://huggingface.co/sameernotes/hindi-ocr/resolve/main/hindi_ocr_model.keras"
|
| 192 |
+
ENCODER_URL = "https://huggingface.co/sameernotes/hindi-ocr/resolve/main/label_encoder.pkl"
|
| 193 |
+
FONT_URL = "https://huggingface.co/sameernotes/hindi-ocr/resolve/main/NotoSansDevanagari-Regular.ttf"
|
| 194 |
+
MODEL_PATH = "hindi_ocr_model.keras"
|
| 195 |
+
ENCODER_PATH = "label_encoder.pkl"
|
| 196 |
+
FONT_PATH = "NotoSansDevanagari-Regular.ttf"
|
| 197 |
+
|
| 198 |
+
def download_file(url, dest):
|
| 199 |
+
if not os.path.exists(dest):
|
| 200 |
+
print(f"Downloading {dest}...")
|
| 201 |
+
response = requests.get(url, stream=True)
|
| 202 |
+
response.raise_for_status()
|
| 203 |
+
with open(dest, 'wb') as f:
|
| 204 |
+
for chunk in response.iter_content(chunk_size=8192):
|
| 205 |
+
f.write(chunk)
|
| 206 |
+
print(f"Downloaded {dest}")
|
| 207 |
+
|
| 208 |
+
def load_model():
|
| 209 |
+
if not os.path.exists(MODEL_PATH):
|
| 210 |
+
return None
|
| 211 |
+
return tf.keras.models.load_model(MODEL_PATH)
|
| 212 |
+
|
| 213 |
+
def load_label_encoder():
|
| 214 |
+
if not os.path.exists(ENCODER_PATH):
|
| 215 |
+
return None
|
| 216 |
+
with open(ENCODER_PATH, 'rb') as f:
|
| 217 |
+
return pickle.load(f)
|
| 218 |
+
|
| 219 |
+
model = None
|
| 220 |
+
label_encoder = None
|
| 221 |
+
session_files = {}
|
| 222 |
+
|
| 223 |
+
@app.on_event("startup")
|
| 224 |
+
async def startup_event():
|
| 225 |
+
global model, label_encoder
|
| 226 |
+
download_file(MODEL_URL, MODEL_PATH)
|
| 227 |
+
download_file(ENCODER_URL, ENCODER_PATH)
|
| 228 |
+
download_file(FONT_URL, FONT_PATH)
|
| 229 |
+
|
| 230 |
+
if os.path.exists(FONT_PATH):
|
| 231 |
+
fm.fontManager.addfont(FONT_PATH)
|
| 232 |
+
plt.rcParams['font.family'] = 'Noto Sans Devanagari'
|
| 233 |
+
model = load_model()
|
| 234 |
+
label_encoder = load_label_encoder()
|
| 235 |
+
|
| 236 |
+
db = SessionLocal()
|
| 237 |
+
if not get_user_by_username(db, "admin"):
|
| 238 |
+
admin_user = UserCreate(username="admin", email="admin@example.com", password="adminpassword")
|
| 239 |
+
create_user(db, admin_user)
|
| 240 |
+
admin = get_user_by_username(db, "admin")
|
| 241 |
+
admin.is_admin = True
|
| 242 |
+
db.commit()
|
| 243 |
+
db.close()
|
| 244 |
+
|
| 245 |
+
def detect_words(image):
|
| 246 |
+
_, binary = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
|
| 247 |
+
kernel = np.ones((3,3), np.uint8)
|
| 248 |
+
dilated = cv2.dilate(binary, kernel, iterations=2)
|
| 249 |
+
contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 250 |
+
word_img = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
|
| 251 |
+
word_count = 0
|
| 252 |
+
for contour in contours:
|
| 253 |
+
x, y, w, h = cv2.boundingRect(contour)
|
| 254 |
+
if w > 10 and h > 10:
|
| 255 |
+
cv2.rectangle(word_img, (x, y), (x+w, y+h), (0, 255, 0), 2)
|
| 256 |
+
word_count += 1
|
| 257 |
+
return word_img, word_count
|
| 258 |
+
|
| 259 |
+
def run_py_text_scan(image_path):
|
| 260 |
+
buffer = io.StringIO()
|
| 261 |
+
old_stdout = sys.stdout
|
| 262 |
+
sys.stdout = buffer
|
| 263 |
+
try:
|
| 264 |
+
py_text_scan.generate(image_path)
|
| 265 |
+
finally:
|
| 266 |
+
sys.stdout = old_stdout
|
| 267 |
+
return buffer.getvalue()
|
| 268 |
+
|
| 269 |
+
def process_image(image_array):
|
| 270 |
+
img = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY)
|
| 271 |
+
word_detected_img, word_count = detect_words(img)
|
| 272 |
+
word_detection_path = tempfile.NamedTemporaryFile(delete=False, suffix=".png").name
|
| 273 |
+
cv2.imwrite(word_detection_path, word_detected_img)
|
| 274 |
+
session_files['word_detection'] = word_detection_path
|
| 275 |
+
|
| 276 |
+
pred_path = None
|
| 277 |
+
try:
|
| 278 |
+
img_resized = cv2.resize(img, (128, 32))
|
| 279 |
+
img_norm = img_resized / 255.0
|
| 280 |
+
img_input = img_norm[np.newaxis, ..., np.newaxis]
|
| 281 |
+
if model is not None and label_encoder is not None:
|
| 282 |
+
pred = model.predict(img_input)
|
| 283 |
+
pred_label_idx = np.argmax(pred)
|
| 284 |
+
pred_label = label_encoder.inverse_transform([pred_label_idx])[0]
|
| 285 |
+
fig, ax = plt.subplots()
|
| 286 |
+
ax.imshow(img, cmap='gray')
|
| 287 |
+
ax.set_title(f"Predicted: {pred_label}", fontsize=12)
|
| 288 |
+
ax.axis('off')
|
| 289 |
+
pred_path = tempfile.NamedTemporaryFile(delete=False, suffix=".png").name
|
| 290 |
+
plt.savefig(pred_path)
|
| 291 |
+
plt.close()
|
| 292 |
+
session_files['prediction'] = pred_path
|
| 293 |
+
else:
|
| 294 |
+
pred_label = "Model or encoder not loaded"
|
| 295 |
+
except Exception as e:
|
| 296 |
+
pred_label = f"Error: {str(e)}"
|
| 297 |
+
|
| 298 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp_file:
|
| 299 |
+
cv2.imwrite(tmp_file.name, img)
|
| 300 |
+
sakshi_output = run_py_text_scan(tmp_file.name)
|
| 301 |
+
os.unlink(tmp_file.name)
|
| 302 |
+
return {
|
| 303 |
+
"sakshi_output": sakshi_output,
|
| 304 |
+
"word_detection_path": word_detection_path if 'word_detection' in session_files else None,
|
| 305 |
+
"word_count": word_count,
|
| 306 |
+
"prediction_path": pred_path if 'prediction' in session_files else None,
|
| 307 |
+
"prediction_label": pred_label
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
@app.post("/token", response_model=Token)
|
| 311 |
+
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
|
| 312 |
+
user = get_user_by_username(db, form_data.username)
|
| 313 |
+
if not user or not verify_password(form_data.password, user.hashed_password):
|
| 314 |
+
raise HTTPException(
|
| 315 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 316 |
+
detail="Incorrect username or password",
|
| 317 |
+
headers={"WWW-Authenticate": "Bearer"},
|
| 318 |
+
)
|
| 319 |
+
access_token = user.username
|
| 320 |
+
return {"access_token": access_token, "token_type": "bearer"}
|
| 321 |
+
|
| 322 |
+
@app.post("/signup", response_model=UserResponse)
|
| 323 |
+
async def signup(user: UserCreate, db: Session = Depends(get_db)):
|
| 324 |
+
db_user_username = get_user_by_username(db, username=user.username)
|
| 325 |
+
if db_user_username:
|
| 326 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Username already registered")
|
| 327 |
+
db_user_email = get_user_by_email(db, email=user.email)
|
| 328 |
+
if db_user_email:
|
| 329 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered")
|
| 330 |
+
created = create_user(db=db, user=user)
|
| 331 |
+
return created
|
| 332 |
+
|
| 333 |
+
@app.post("/process/", response_model=OCRResponse)
|
| 334 |
+
async def process(file: UploadFile = File(...), current_user: UserModel = Depends(get_current_active_user)):
|
| 335 |
+
if not file.content_type.startswith("image/"):
|
| 336 |
+
raise HTTPException(status_code=400, detail="File must be an image")
|
| 337 |
+
|
| 338 |
+
for key, filepath in session_files.items():
|
| 339 |
+
if os.path.exists(filepath):
|
| 340 |
+
try:
|
| 341 |
+
os.unlink(filepath)
|
| 342 |
+
except:
|
| 343 |
+
pass
|
| 344 |
+
session_files.clear()
|
| 345 |
+
|
| 346 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
| 347 |
+
try:
|
| 348 |
+
with temp_file as f:
|
| 349 |
+
shutil.copyfileobj(file.file, f)
|
| 350 |
+
image = Image.open(temp_file.name)
|
| 351 |
+
image_array = np.array(image)
|
| 352 |
+
result = process_image(image_array)
|
| 353 |
+
return OCRResponse(
|
| 354 |
+
sakshi_output=result["sakshi_output"],
|
| 355 |
+
word_count=result["word_count"],
|
| 356 |
+
prediction_label=result["prediction_label"]
|
| 357 |
+
)
|
| 358 |
+
except Exception as e:
|
| 359 |
+
raise HTTPException(status_code=500, detail=f"Error processing image: {str(e)}")
|
| 360 |
+
finally:
|
| 361 |
+
os.unlink(temp_file.name)
|
| 362 |
+
|
| 363 |
+
@app.get("/word-detection/")
|
| 364 |
+
async def get_word_detection(current_user: UserModel = Depends(get_current_active_user)):
|
| 365 |
+
if 'word_detection' not in session_files or not os.path.exists(session_files['word_detection']):
|
| 366 |
+
raise HTTPException(status_code=404, detail="Word detection image not found")
|
| 367 |
+
return FileResponse(session_files['word_detection'])
|
| 368 |
+
|
| 369 |
+
@app.get("/prediction/")
|
| 370 |
+
async def get_prediction(current_user: UserModel = Depends(get_current_active_user)):
|
| 371 |
+
if 'prediction' not in session_files or not os.path.exists(session_files['prediction']):
|
| 372 |
+
raise HTTPException(status_code=404, detail="Prediction image not found")
|
| 373 |
+
return FileResponse(session_files['prediction'])
|
| 374 |
+
|
| 375 |
+
# --- Modified Feedback Endpoint ---
|
| 376 |
+
# No authentication dependency is used here so that anyone can submit feedback.
|
| 377 |
+
@app.post("/feedback/", response_model=FeedbackResponse)
|
| 378 |
+
async def create_feedback_route(feedback: FeedbackCreate, db: Session = Depends(get_db)):
|
| 379 |
+
return create_feedback(db=db, feedback=feedback)
|
| 380 |
+
|
| 381 |
+
# --- Admin Endpoints ---
|
| 382 |
+
@app.get("/admin/users/")
|
| 383 |
+
async def admin_get_users(skip: int = 0, limit: int = 100, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)):
|
| 384 |
+
return get_users(db, skip=skip, limit=limit)
|
| 385 |
+
|
| 386 |
+
@app.delete("/admin/users/{user_id}")
|
| 387 |
+
async def admin_delete_user(user_id: int, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)):
|
| 388 |
+
if delete_user(db, user_id):
|
| 389 |
+
return {"detail": "User deleted successfully"}
|
| 390 |
+
raise HTTPException(status_code=404, detail="User not found")
|
| 391 |
+
|
| 392 |
+
@app.get("/admin/feedback/")
|
| 393 |
+
async def admin_get_feedback(skip: int = 0, limit: int = 100, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)):
|
| 394 |
+
return get_feedback(db, skip=skip, limit=limit)
|
| 395 |
+
|
| 396 |
+
if __name__ == "__main__":
|
| 397 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
contact.html
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class=""> <!-- Start without 'dark' initially -->
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Contact Us - AI Text Tools</title> <!-- Changed Title -->
|
| 7 |
+
<!-- Tailwind CSS via CDN -->
|
| 8 |
+
<script src="https://cdn.tailwindcss.com/3.4.1"></script>
|
| 9 |
+
<script>
|
| 10 |
+
// Tailwind config (Keep as is)
|
| 11 |
+
tailwind.config = {
|
| 12 |
+
darkMode: 'class',
|
| 13 |
+
theme: {
|
| 14 |
+
extend: {
|
| 15 |
+
colors: { primary: '#1a73e8', secondary: '#e8f0fe' },
|
| 16 |
+
borderRadius: { 'none': '0px', 'sm': '4px', DEFAULT: '8px', 'md': '12px', 'lg': '16px', 'xl': '20px', '2xl': '24px', '3xl': '32px', 'full': '9999px', 'button': '8px' }
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
</script>
|
| 21 |
+
<!-- Fonts (Keep as is) -->
|
| 22 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 23 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 24 |
+
<link href="https://fonts.googleapis.com/css2?family=Pacifico&display=swap" rel="stylesheet">
|
| 25 |
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Devanagari:wght@400;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
| 26 |
+
<!-- Icons (Keep as is) -->
|
| 27 |
+
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.2.0/fonts/remixicon.css" rel="stylesheet">
|
| 28 |
+
<!-- Custom Styles (Keep as is, including focus, dark mode, etc.) -->
|
| 29 |
+
<style>
|
| 30 |
+
body { font-family: 'Inter', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
| 31 |
+
.hindi-font { font-family: 'Noto Sans Devanagari', sans-serif; }
|
| 32 |
+
.custom-switch { position: relative; display: inline-block; width: 50px; height: 24px; }
|
| 33 |
+
.custom-switch-input { opacity: 0; width: 0; height: 0; }
|
| 34 |
+
.custom-switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
|
| 35 |
+
.custom-switch-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
|
| 36 |
+
.custom-switch-input:checked + .custom-switch-slider { background-color: #1a73e8; }
|
| 37 |
+
.custom-switch-input:checked + .custom-switch-slider:before { transform: translateX(26px); }
|
| 38 |
+
*:focus-visible { outline: 2px solid #1a73e8; outline-offset: 2px; box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.3); }
|
| 39 |
+
/* Base Dark Mode */
|
| 40 |
+
html.dark body { background-color: #1a202c; color: #e2e8f0; }
|
| 41 |
+
html.dark header, html.dark footer { background-color: #2d3748; color: #e2e8f0; }
|
| 42 |
+
html.dark .card, html.dark .auth-card, html.dark .result-card { background-color: #2d3748; border-color: #4a5568; }
|
| 43 |
+
html.dark h1, html.dark h2, html.dark h3, html.dark p, html.dark span, html.dark li, html.dark label, html.dark small, html.dark strong, html.dark button:not(.bg-red-500):not(.bg-green-600), html.dark a { color: #e2e8f0; }
|
| 44 |
+
html.dark a.text-primary { color: #60a5fa; }
|
| 45 |
+
html.dark .text-gray-600, html.dark .text-gray-700, html.dark .text-gray-800, html.dark .text-gray-900 { color: #a0aec0; }
|
| 46 |
+
html.dark .text-gray-500, html.dark .text-gray-400 { color: #718096; }
|
| 47 |
+
html.dark .text-muted { color: #718096; }
|
| 48 |
+
html.dark .bg-white { background-color: #2d3748 !important; }
|
| 49 |
+
html.dark .bg-gray-50 { background-color: #1a202c !important; }
|
| 50 |
+
html.dark .bg-gray-100 { background-color: #374151 !important; }
|
| 51 |
+
html.dark .border-gray-100, html.dark .border-gray-200 { border-color: #4a5568 !important; }
|
| 52 |
+
html.dark .hover\:bg-gray-50:hover { background-color: #4a5568 !important; }
|
| 53 |
+
/* Dark mode Forms */
|
| 54 |
+
html.dark input, html.dark textarea { background-color: #1f2937; border-color: #4b5563; color: #e5e7eb; }
|
| 55 |
+
html.dark input::placeholder, html.dark textarea::placeholder { color: #6b7280; }
|
| 56 |
+
html.dark .error-message { background-color: #450a0a; color: #fecaca; border-color: #7f1d1d; }
|
| 57 |
+
html.dark .success-message { background-color: #064e3b; color: #a7f3d0; border-color: #047857;}
|
| 58 |
+
html.dark .spinner { border-color: rgba(255, 255, 255, 0.1); border-left-color: #60a5fa; }
|
| 59 |
+
/* General Loading Spinner */
|
| 60 |
+
.spinner { border: 3px solid rgba(255, 255, 255, 0.3); width: 16px; height: 16px; border-radius: 50%; border-left-color: #fff; animation: spin 1s ease infinite; }
|
| 61 |
+
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
| 62 |
+
/* Form Message Styling */
|
| 63 |
+
.form-message { padding: 0.75rem 1rem; border-radius: 8px; margin-top: 1rem; font-weight: 500; border: 1px solid; font-size: 0.9rem; display: none; }
|
| 64 |
+
.error-message { color: #dc2626; background-color: #fee2e2; border-color: #fecaca; }
|
| 65 |
+
.success-message { color: #059669; background-color: #d1fae5; border-color: #a7f3d0; }
|
| 66 |
+
/* Input Field Style (consistent) */
|
| 67 |
+
.input-field {
|
| 68 |
+
display: block;
|
| 69 |
+
width: 100%;
|
| 70 |
+
padding: 0.5rem 0.75rem; /* Adjusted padding */
|
| 71 |
+
border: 1px solid #d1d5db; /* Default border */
|
| 72 |
+
border-radius: 8px; /* Match button radius */
|
| 73 |
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
| 74 |
+
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
| 75 |
+
font-size: 0.875rem; /* sm text */
|
| 76 |
+
}
|
| 77 |
+
html.dark .input-field {
|
| 78 |
+
background-color: #374151; /* Dark background */
|
| 79 |
+
border-color: #4b5563; /* Dark border */
|
| 80 |
+
color: #e5e7eb; /* Dark text */
|
| 81 |
+
}
|
| 82 |
+
.input-field:focus {
|
| 83 |
+
outline: none;
|
| 84 |
+
border-color: #1a73e8; /* primary color focus */
|
| 85 |
+
box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.3); /* primary color focus ring */
|
| 86 |
+
}
|
| 87 |
+
html.dark .input-field:focus {
|
| 88 |
+
border-color: #60a5fa; /* primary dark focus */
|
| 89 |
+
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.3); /* primary dark focus ring */
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
</style>
|
| 93 |
+
</head>
|
| 94 |
+
<body class="bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-200">
|
| 95 |
+
|
| 96 |
+
<!-- Header (Same as index.html, but update active nav link if desired) -->
|
| 97 |
+
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 98 |
+
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 99 |
+
<div class="flex items-center">
|
| 100 |
+
<a href="index.html" class="text-2xl font-['Pacifico'] text-primary dark:text-blue-400 mr-8" data-lang-en="AI Text Tools" data-lang-hi="एआई टेक्स्ट उपकरण">AI Text Tools</a>
|
| 101 |
+
<!-- *** START: Updated Navigation Bar *** -->
|
| 102 |
+
<nav id="mainNav" class="hidden md:flex space-x-6">
|
| 103 |
+
<a href="index.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Home" data-lang-hi="होम">Home</a>
|
| 104 |
+
<!-- Assuming recognizer.html is the main tool page, link from index.html might be better -->
|
| 105 |
+
<a href="recognizer.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a>
|
| 106 |
+
<!-- Assuming examples.html exists -->
|
| 107 |
+
<a href="examples.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</a>
|
| 108 |
+
<!-- Assuming technology.html exists -->
|
| 109 |
+
<a href="technology.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Technology" data-lang-hi="तकनीक">Technology</a>
|
| 110 |
+
<!-- Assuming about.html exists -->
|
| 111 |
+
<a href="about.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="About" data-lang-hi="बारे में">About</a>
|
| 112 |
+
<!-- Link to contact.html -->
|
| 113 |
+
<a href="contact.html" class="nav-link text-primary dark:text-blue-400 font-medium" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</a>
|
| 114 |
+
<!-- Active link for this page -->
|
| 115 |
+
<a href="feedback.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Feedback" data-lang-hi="प्रतिक्रिया">Feedback</a>
|
| 116 |
+
</nav>
|
| 117 |
+
</div>
|
| 118 |
+
<!-- Right side header items (Theme, Lang Toggles) -->
|
| 119 |
+
<div class="flex items-center space-x-4">
|
| 120 |
+
<div class="items-center space-x-2 hidden md:flex"> <span class="text-sm text-gray-600 dark:text-gray-400">EN</span> <label class="custom-switch"><input type="checkbox" id="languageToggle" class="custom-switch-input"><span class="custom-switch-slider"></span></label> <span class="text-sm text-gray-600 dark:text-gray-400 hindi-font">हिंदी</span> </div>
|
| 121 |
+
<div class="items-center space-x-2 hidden md:flex"> <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-sun-line"></i></span> <label class="custom-switch"><input type="checkbox" id="themeToggle" class="custom-switch-input"><span class="custom-switch-slider"></span></label> <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-moon-line"></i></span> </div>
|
| 122 |
+
<!-- Removed Logout Button and User Icons for contact page -->
|
| 123 |
+
<button class="md:hidden w-10 h-10 flex items-center justify-center" aria-label="Toggle Menu"><i class="ri-menu-line text-gray-600 dark:text-gray-300 text-xl"></i></button>
|
| 124 |
+
</div>
|
| 125 |
+
</div>
|
| 126 |
+
</header>
|
| 127 |
+
|
| 128 |
+
<!-- ===== Main Content Area ===== -->
|
| 129 |
+
<main class="flex-grow container mx-auto px-4 py-12"> <!-- Increased padding -->
|
| 130 |
+
|
| 131 |
+
<h1 class="text-3xl font-bold text-center text-gray-800 dark:text-white mb-4" data-lang-en="Contact Us" data-lang-hi="हमसे संपर्क करें">Contact Us</h1>
|
| 132 |
+
<p class="text-center text-gray-600 dark:text-gray-400 mb-10" data-lang-en="We'd love to hear from you! Send us your feedback or questions." data-lang-hi="हमें आपसे सुनना प्रिय लगेगा! अपनी प्रतिक्रिया या प्रश्न हमें भेजें।">
|
| 133 |
+
We'd love to hear from you! Send us your feedback or questions.
|
| 134 |
+
</p>
|
| 135 |
+
|
| 136 |
+
<div class="max-w-2xl mx-auto bg-white dark:bg-gray-800 p-8 rounded-xl shadow-lg border border-gray-100 dark:border-gray-700 card">
|
| 137 |
+
<form id="contactForm" novalidate>
|
| 138 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
| 139 |
+
<div>
|
| 140 |
+
<label for="contactName" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" data-lang-en="Your Name" data-lang-hi="आपका नाम">Your Name</label>
|
| 141 |
+
<input type="text" id="contactName" name="name" required autocomplete="name" class="input-field" placeholder="John Doe">
|
| 142 |
+
</div>
|
| 143 |
+
<div>
|
| 144 |
+
<label for="contactEmail" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" data-lang-en="Your Email" data-lang-hi="आपका ईमेल">Your Email</label>
|
| 145 |
+
<input type="email" id="contactEmail" name="email" required autocomplete="email" class="input-field" placeholder="you@example.com">
|
| 146 |
+
</div>
|
| 147 |
+
</div>
|
| 148 |
+
|
| 149 |
+
<div class="mb-6">
|
| 150 |
+
<label for="contactSubject" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" data-lang-en="Subject" data-lang-hi="विषय">Subject</label>
|
| 151 |
+
<input type="text" id="contactSubject" name="subject" class="input-field" placeholder="Feedback about OCR">
|
| 152 |
+
</div>
|
| 153 |
+
|
| 154 |
+
<div class="mb-8">
|
| 155 |
+
<label for="contactMessage" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" data-lang-en="Message" data-lang-hi="संदेश">Message</label>
|
| 156 |
+
<textarea id="contactMessage" name="message" rows="5" required class="input-field" placeholder="Enter your message here..."></textarea>
|
| 157 |
+
</div>
|
| 158 |
+
|
| 159 |
+
<div id="formMessage" class="form-message"></div> <!-- For Success/Error Messages -->
|
| 160 |
+
|
| 161 |
+
<button type="submit" id="submitContactButton" class="w-full flex justify-center items-center gap-2 py-2.5 px-4 border border-transparent rounded-button shadow-sm text-sm font-medium text-white bg-primary hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary dark:focus:ring-offset-gray-800 disabled:opacity-50">
|
| 162 |
+
<span data-lang-en="Send Message" data-lang-hi="संदेश भेजें">Send Message</span>
|
| 163 |
+
<i class="ri-send-plane-line text-base"></i>
|
| 164 |
+
<div class="spinner hidden ml-2"></div> <!-- Loading Spinner -->
|
| 165 |
+
</button>
|
| 166 |
+
</form>
|
| 167 |
+
</div>
|
| 168 |
+
|
| 169 |
+
</main>
|
| 170 |
+
|
| 171 |
+
<!-- Footer (Same as index.html) -->
|
| 172 |
+
<footer class="bg-gray-900 text-gray-400 py-12 mt-16"> <!-- Increased margin top -->
|
| 173 |
+
<div class="container mx-auto px-4">
|
| 174 |
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
| 175 |
+
<div>
|
| 176 |
+
<a href="index.html" class="text-2xl font-['Pacifico'] text-white mb-4 inline-block" data-lang-en="AI Text Tools" data-lang-hi="एआई टेक्स्ट उपकरण">AI Text Tools</a>
|
| 177 |
+
<p class="mb-4 text-sm" data-lang-en="AI-powered tools for text analysis and processing." data-lang-hi="पाठ विश्लेषण और प्रसंस्करण के लिए एआई-संचालित उपकरण।">AI-powered tools for text analysis.</p>
|
| 178 |
+
<div class="flex space-x-4">
|
| 179 |
+
<a href="#" class="hover:text-white" title="GitHub (Placeholder)"><i class="ri-github-fill"></i></a>
|
| 180 |
+
<a href="#" class="hover:text-white" title="LinkedIn (Placeholder)"><i class="ri-linkedin-box-fill"></i></a>
|
| 181 |
+
</div>
|
| 182 |
+
</div>
|
| 183 |
+
<div>
|
| 184 |
+
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Quick Links" data-lang-hi="त्वरित लिंक्स">Quick Links</h3>
|
| 185 |
+
<ul class="space-y-2 text-sm">
|
| 186 |
+
<li><a href="index.html" class="hover:text-white" data-lang-en="Home" data-lang-hi="होम">Home</a></li>
|
| 187 |
+
<li><a href="index.html#ocrContent" class="hover:text-white" data-lang-en="Tools" data-lang-hi="उपकरण">Tools</a></li>
|
| 188 |
+
<li><a href="#" class="hover:text-white" data-lang-en="About" data-lang-hi="बारे में">About</a></li>
|
| 189 |
+
<li><a href="contact.html" class="hover:text-white" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</a></li>
|
| 190 |
+
</ul>
|
| 191 |
+
</div>
|
| 192 |
+
<div>
|
| 193 |
+
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Resources" data-lang-hi="संसाधन">Resources</h3>
|
| 194 |
+
<ul class="space-y-2 text-sm">
|
| 195 |
+
<li><a href="#" class="hover:text-white" data-lang-en="API Docs (TBD)" data-lang-hi="एपीआई डॉक्स (TBD)">API Docs (TBD)</a></li>
|
| 196 |
+
<li><a href="#" class="hover:text-white" data-lang-en="GitHub Repo" data-lang-hi="गिटहब रेपो">GitHub Repo</a></li>
|
| 197 |
+
</ul>
|
| 198 |
+
</div>
|
| 199 |
+
<div>
|
| 200 |
+
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</h3>
|
| 201 |
+
<ul class="space-y-2 text-sm">
|
| 202 |
+
<li class="flex items-start"><i class="ri-mail-line mt-1 mr-2"></i><span>your-email@example.com</span></li>
|
| 203 |
+
<!-- Maybe add phone number or address if needed -->
|
| 204 |
+
</ul>
|
| 205 |
+
</div>
|
| 206 |
+
</div>
|
| 207 |
+
<div class="border-t border-gray-700 mt-8 pt-8 text-center">
|
| 208 |
+
<p class="text-sm">© 2024 Your Name/Org | AI Text Tools Project</p>
|
| 209 |
+
</div>
|
| 210 |
+
</div>
|
| 211 |
+
</footer>
|
| 212 |
+
|
| 213 |
+
<!-- Base Theme/Language Script (Keep as is) -->
|
| 214 |
+
<script>
|
| 215 |
+
const themeToggle = document.getElementById('themeToggle');
|
| 216 |
+
const htmlElement = document.documentElement;
|
| 217 |
+
function applyTheme(isDark) { if (isDark) { htmlElement.classList.add('dark'); if(themeToggle) themeToggle.checked = true; } else { htmlElement.classList.remove('dark'); if(themeToggle) themeToggle.checked = false; } }
|
| 218 |
+
const prefersDark = localStorage.getItem('theme') === 'dark' || (localStorage.getItem('theme') === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
| 219 |
+
applyTheme(prefersDark);
|
| 220 |
+
if(themeToggle){ themeToggle.addEventListener('change', (event) => { const isDark = event.target.checked; applyTheme(isDark); localStorage.setItem('theme', isDark ? 'dark' : 'light'); }); } else { console.warn("Theme toggle button not found."); }
|
| 221 |
+
|
| 222 |
+
const languageToggle = document.getElementById('languageToggle');
|
| 223 |
+
const langElements = document.querySelectorAll('[data-lang-en]');
|
| 224 |
+
function applyLanguage(lang) { htmlElement.setAttribute('lang', lang); langElements.forEach(el => { const text = el.getAttribute(`data-lang-${lang}`); if (text) { const icon = el.querySelector('i'); if (icon && el.childNodes.length > 1) { let updated = false; el.childNodes.forEach(node => { if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '') { node.textContent = ` ${text} `; updated = true; } }); if (!updated) { const currentText = el.getAttribute(`data-lang-${lang === 'en' ? 'hi' : 'en'}`); if(currentText) el.innerHTML = el.innerHTML.replace(currentText, text);}} else { el.textContent = text; } } }); if (languageToggle) languageToggle.checked = (lang === 'hi'); localStorage.setItem('language', lang); }
|
| 225 |
+
const savedLang = localStorage.getItem('language') || 'en';
|
| 226 |
+
if (languageToggle) { languageToggle.addEventListener('change', (event) => { const newLang = event.target.checked ? 'hi' : 'en'; applyLanguage(newLang); }); } else { console.warn("Language toggle button not found."); }
|
| 227 |
+
applyLanguage(savedLang); // Apply language on initial load
|
| 228 |
+
</script>
|
| 229 |
+
|
| 230 |
+
<!-- Contact Form Submission Script -->
|
| 231 |
+
<script>
|
| 232 |
+
// Define the API endpoint for feedback submission
|
| 233 |
+
// MAKE SURE THIS URL IS CORRECT AND THE BACKEND SERVER IS RUNNING
|
| 234 |
+
const FEEDBACK_API_ENDPOINT = '/feedback/'; // Adjust if your backend runs elsewhere (e.g., http://localhost:8000/feedback/)
|
| 235 |
+
|
| 236 |
+
// Get DOM elements
|
| 237 |
+
const contactForm = document.getElementById('contactForm');
|
| 238 |
+
const submitButton = document.getElementById('submitContactButton');
|
| 239 |
+
const formMessageDiv = document.getElementById('formMessage');
|
| 240 |
+
const spinner = submitButton ? submitButton.querySelector('.spinner') : null;
|
| 241 |
+
|
| 242 |
+
// Function to display messages
|
| 243 |
+
function showFormMessage(message, isSuccess = true) {
|
| 244 |
+
if (!formMessageDiv) return;
|
| 245 |
+
formMessageDiv.textContent = message;
|
| 246 |
+
formMessageDiv.className = 'form-message'; // Reset classes
|
| 247 |
+
if (isSuccess) {
|
| 248 |
+
formMessageDiv.classList.add('success-message');
|
| 249 |
+
} else {
|
| 250 |
+
formMessageDiv.classList.add('error-message');
|
| 251 |
+
}
|
| 252 |
+
formMessageDiv.style.display = 'block';
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
function hideFormMessage() {
|
| 256 |
+
if (formMessageDiv) {
|
| 257 |
+
formMessageDiv.style.display = 'none';
|
| 258 |
+
formMessageDiv.textContent = '';
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
// Function to handle loading state
|
| 263 |
+
function setFormLoading(isLoading) {
|
| 264 |
+
if (!submitButton || !spinner) return;
|
| 265 |
+
submitButton.disabled = isLoading;
|
| 266 |
+
if (isLoading) {
|
| 267 |
+
spinner.classList.remove('hidden');
|
| 268 |
+
// Optional: Reduce opacity of button text
|
| 269 |
+
submitButton.querySelector('span').style.opacity = '0.5';
|
| 270 |
+
submitButton.querySelector('i').style.opacity = '0.5';
|
| 271 |
+
} else {
|
| 272 |
+
spinner.classList.add('hidden');
|
| 273 |
+
submitButton.querySelector('span').style.opacity = '1';
|
| 274 |
+
submitButton.querySelector('i').style.opacity = '1';
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
if (contactForm && submitButton) {
|
| 279 |
+
contactForm.addEventListener('submit', async (event) => {
|
| 280 |
+
event.preventDefault(); // Prevent default form submission
|
| 281 |
+
hideFormMessage(); // Clear previous messages
|
| 282 |
+
|
| 283 |
+
// Get form data
|
| 284 |
+
const nameInput = document.getElementById('contactName');
|
| 285 |
+
const emailInput = document.getElementById('contactEmail');
|
| 286 |
+
const subjectInput = document.getElementById('contactSubject');
|
| 287 |
+
const messageInput = document.getElementById('contactMessage');
|
| 288 |
+
|
| 289 |
+
const name = nameInput.value.trim();
|
| 290 |
+
const email = emailInput.value.trim();
|
| 291 |
+
const subject = subjectInput.value.trim(); // Subject is optional in form, but good to send
|
| 292 |
+
const message = messageInput.value.trim();
|
| 293 |
+
|
| 294 |
+
// Basic Frontend Validation
|
| 295 |
+
if (!name || !email || !message) {
|
| 296 |
+
const missing = [];
|
| 297 |
+
if (!name) missing.push(languageToggle.checked ? 'नाम' : 'Name');
|
| 298 |
+
if (!email) missing.push(languageToggle.checked ? 'ईमेल' : 'Email');
|
| 299 |
+
if (!message) missing.push(languageToggle.checked ? 'संदेश' : 'Message');
|
| 300 |
+
const errorMessage = (languageToggle.checked ? 'कृपया अनिवार्य फ़ील्ड भरें: ' : 'Please fill in the required fields: ') + missing.join(', ');
|
| 301 |
+
showFormMessage(errorMessage, false);
|
| 302 |
+
return;
|
| 303 |
+
}
|
| 304 |
+
// Basic email format check
|
| 305 |
+
if (!/\S+@\S+\.\S+/.test(email)) {
|
| 306 |
+
const errorMessage = languageToggle.checked ? 'कृपया एक मान्य ईमेल पता दर्ज करें।' : 'Please enter a valid email address.';
|
| 307 |
+
showFormMessage(errorMessage, false);
|
| 308 |
+
return;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
setFormLoading(true);
|
| 313 |
+
|
| 314 |
+
// Prepare payload for the /feedback/ endpoint
|
| 315 |
+
// It expects 'username' and 'comment'
|
| 316 |
+
const payload = {
|
| 317 |
+
username: name, // Use the name field as username
|
| 318 |
+
comment: `Subject: ${subject}\n\nEmail: ${email}\n\nMessage: ${message}` // Combine subject, email, and message
|
| 319 |
+
};
|
| 320 |
+
|
| 321 |
+
console.log("Sending feedback payload:", payload);
|
| 322 |
+
|
| 323 |
+
try {
|
| 324 |
+
// Send data to the backend (NO authentication needed)
|
| 325 |
+
const response = await fetch(FEEDBACK_API_ENDPOINT, {
|
| 326 |
+
method: 'POST',
|
| 327 |
+
headers: {
|
| 328 |
+
'Content-Type': 'application/json',
|
| 329 |
+
'Accept': 'application/json' // Expect JSON response (even for success/error)
|
| 330 |
+
},
|
| 331 |
+
body: JSON.stringify(payload)
|
| 332 |
+
});
|
| 333 |
+
|
| 334 |
+
const responseData = await response.json(); // Try to parse JSON always
|
| 335 |
+
|
| 336 |
+
if (!response.ok) {
|
| 337 |
+
// Handle FastAPI/HTTP errors (usually JSON with 'detail')
|
| 338 |
+
const errorDetail = responseData.detail || `Request failed with status ${response.status}`;
|
| 339 |
+
throw new Error(errorDetail);
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
// Success!
|
| 343 |
+
console.log("Feedback submitted successfully:", responseData);
|
| 344 |
+
const successMsg = languageToggle.checked ? 'आपका संदेश सफलतापूर्वक भेज दिया गया है!' : 'Your message has been sent successfully!';
|
| 345 |
+
showFormMessage(successMsg, true);
|
| 346 |
+
contactForm.reset(); // Clear the form
|
| 347 |
+
|
| 348 |
+
} catch (error) {
|
| 349 |
+
console.error('Error submitting contact form:', error);
|
| 350 |
+
const errorMsg = (languageToggle.checked ? 'संदेश भेजने में त्रुटि हुई: ' : 'Error sending message: ') + error.message;
|
| 351 |
+
showFormMessage(errorMsg, false);
|
| 352 |
+
} finally {
|
| 353 |
+
setFormLoading(false); // Ensure loading state is turned off
|
| 354 |
+
}
|
| 355 |
+
});
|
| 356 |
+
} else {
|
| 357 |
+
console.error("Contact form or submit button not found.");
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
// Initial setup on page load
|
| 361 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 362 |
+
applyLanguage(savedLang); // Apply saved language
|
| 363 |
+
// Ensure theme is applied correctly on load too
|
| 364 |
+
applyTheme(localStorage.getItem('theme') === 'dark');
|
| 365 |
+
console.log("Contact page DOM Loaded and Initialized.");
|
| 366 |
+
});
|
| 367 |
+
|
| 368 |
+
</script>
|
| 369 |
+
|
| 370 |
+
</body>
|
| 371 |
+
</html>
|
examples.html
CHANGED
|
@@ -175,24 +175,22 @@ body {
|
|
| 175 |
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 176 |
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 177 |
<div class="flex items-center">
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
<nav class="hidden md:flex space-x-6">
|
| 181 |
-
<a href="
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
<a href="
|
| 194 |
-
class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
|
| 195 |
-
data-lang-en="About" data-lang-hi="बारे में">About</a>
|
| 196 |
</nav>
|
| 197 |
</div>
|
| 198 |
<!-- Right side header items -->
|
|
|
|
| 175 |
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 176 |
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 177 |
<div class="flex items-center">
|
| 178 |
+
<a href="index.html" class="text-2xl font-['Pacifico'] text-primary dark:text-blue-400 mr-8" data-lang-en="AI Text Tools" data-lang-hi="एआई टेक्स्ट उपकरण">AI Text Tools</a>
|
| 179 |
+
<!-- *** START: Updated Navigation Bar *** -->
|
| 180 |
+
<nav id="mainNav" class="hidden md:flex space-x-6">
|
| 181 |
+
<a href="index.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Home" data-lang-hi="होम">Home</a>
|
| 182 |
+
<!-- Assuming recognizer.html is the main tool page, link from index.html might be better -->
|
| 183 |
+
<a href="recognizer.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a>
|
| 184 |
+
<!-- Assuming examples.html exists -->
|
| 185 |
+
<a href="examples.html" class="nav-link text-primary dark:text-blue-400 font-medium" data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</a>
|
| 186 |
+
<!-- Assuming technology.html exists -->
|
| 187 |
+
<a href="technology.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Technology" data-lang-hi="तकनीक">Technology</a>
|
| 188 |
+
<!-- Assuming about.html exists -->
|
| 189 |
+
<a href="about.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="About" data-lang-hi="बारे में">About</a>
|
| 190 |
+
<!-- Link to contact.html -->
|
| 191 |
+
<a href="contact.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</a>
|
| 192 |
+
<!-- Active link for this page -->
|
| 193 |
+
<a href="feedback.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Feedback" data-lang-hi="प्रतिक्रिया">Feedback</a>
|
|
|
|
|
|
|
| 194 |
</nav>
|
| 195 |
</div>
|
| 196 |
<!-- Right side header items -->
|
feedback.html
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class=""> <!-- Start without 'dark' initially -->
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Feedback - AI Text Tools</title>
|
| 7 |
+
<!-- Tailwind CSS via CDN -->
|
| 8 |
+
<script src="https://cdn.tailwindcss.com/3.4.1"></script>
|
| 9 |
+
<script>
|
| 10 |
+
// Tailwind config
|
| 11 |
+
tailwind.config = {
|
| 12 |
+
darkMode: 'class',
|
| 13 |
+
theme: {
|
| 14 |
+
extend: {
|
| 15 |
+
colors: { primary: '#1a73e8', secondary: '#e8f0fe' },
|
| 16 |
+
borderRadius: { 'none': '0px', 'sm': '4px', DEFAULT: '8px', 'md': '12px', 'lg': '16px', 'xl': '20px', '2xl': '24px', '3xl': '32px', 'full': '9999px', 'button': '8px' }
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
</script>
|
| 21 |
+
<!-- Fonts -->
|
| 22 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 23 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 24 |
+
<link href="https://fonts.googleapis.com/css2?family=Pacifico&display=swap" rel="stylesheet">
|
| 25 |
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Devanagari:wght@400;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
| 26 |
+
<!-- Icons -->
|
| 27 |
+
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.2.0/fonts/remixicon.css" rel="stylesheet">
|
| 28 |
+
<!-- Custom Styles -->
|
| 29 |
+
<style>
|
| 30 |
+
body { font-family: 'Inter', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
| 31 |
+
.hindi-font { font-family: 'Noto Sans Devanagari', sans-serif; }
|
| 32 |
+
.custom-switch { position: relative; display: inline-block; width: 50px; height: 24px; }
|
| 33 |
+
.custom-switch-input { opacity: 0; width: 0; height: 0; }
|
| 34 |
+
.custom-switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
|
| 35 |
+
.custom-switch-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
|
| 36 |
+
.custom-switch-input:checked + .custom-switch-slider { background-color: #1a73e8; }
|
| 37 |
+
.custom-switch-input:checked + .custom-switch-slider:before { transform: translateX(26px); }
|
| 38 |
+
*:focus-visible { outline: 2px solid #1a73e8; outline-offset: 2px; box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.3); }
|
| 39 |
+
/* Base Dark Mode */
|
| 40 |
+
html.dark body { background-color: #1a202c; color: #e2e8f0; }
|
| 41 |
+
html.dark header, html.dark footer { background-color: #2d3748; color: #e2e8f0; }
|
| 42 |
+
html.dark .card { background-color: #2d3748; border-color: #4a5568; } /* Simplified card selector */
|
| 43 |
+
html.dark h1, html.dark h2, html.dark h3, html.dark p, html.dark span, html.dark li, html.dark label, html.dark small, html.dark strong, html.dark button:not(.bg-red-500):not(.bg-green-600), html.dark a { color: #e2e8f0; }
|
| 44 |
+
html.dark a.text-primary { color: #60a5fa; }
|
| 45 |
+
html.dark .text-gray-600, html.dark .text-gray-700, html.dark .text-gray-800, html.dark .text-gray-900 { color: #a0aec0; }
|
| 46 |
+
html.dark .text-gray-500, html.dark .text-gray-400 { color: #718096; }
|
| 47 |
+
html.dark .bg-white { background-color: #2d3748 !important; }
|
| 48 |
+
html.dark .bg-gray-50 { background-color: #1a202c !important; }
|
| 49 |
+
html.dark .bg-gray-100 { background-color: #374151 !important; }
|
| 50 |
+
html.dark .border-gray-100, html.dark .border-gray-200 { border-color: #4a5568 !important; }
|
| 51 |
+
/* Dark mode Forms */
|
| 52 |
+
html.dark input, html.dark textarea { background-color: #1f2937; border-color: #4b5563; color: #e5e7eb; }
|
| 53 |
+
html.dark input::placeholder, html.dark textarea::placeholder { color: #6b7280; }
|
| 54 |
+
html.dark .error-message { background-color: #450a0a; color: #fecaca; border-color: #7f1d1d; }
|
| 55 |
+
html.dark .success-message { background-color: #064e3b; color: #a7f3d0; border-color: #047857;}
|
| 56 |
+
html.dark .spinner { border-color: rgba(255, 255, 255, 0.1); border-left-color: #60a5fa; }
|
| 57 |
+
/* General Loading Spinner */
|
| 58 |
+
.spinner { border: 3px solid rgba(255, 255, 255, 0.3); width: 16px; height: 16px; border-radius: 50%; border-left-color: #fff; animation: spin 1s ease infinite; }
|
| 59 |
+
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
| 60 |
+
/* Form Message Styling */
|
| 61 |
+
.form-message { padding: 0.75rem 1rem; border-radius: 8px; margin-top: 1rem; font-weight: 500; border: 1px solid; font-size: 0.9rem; display: none; }
|
| 62 |
+
.error-message { color: #dc2626; background-color: #fee2e2; border-color: #fecaca; }
|
| 63 |
+
.success-message { color: #059669; background-color: #d1fae5; border-color: #a7f3d0; }
|
| 64 |
+
/* Input Field Style */
|
| 65 |
+
.input-field {
|
| 66 |
+
display: block;
|
| 67 |
+
width: 100%;
|
| 68 |
+
padding: 0.5rem 0.75rem;
|
| 69 |
+
border: 1px solid #d1d5db;
|
| 70 |
+
border-radius: 8px;
|
| 71 |
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
| 72 |
+
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
| 73 |
+
font-size: 0.875rem;
|
| 74 |
+
}
|
| 75 |
+
html.dark .input-field {
|
| 76 |
+
background-color: #374151;
|
| 77 |
+
border-color: #4b5563;
|
| 78 |
+
color: #e5e7eb;
|
| 79 |
+
}
|
| 80 |
+
.input-field:focus {
|
| 81 |
+
outline: none;
|
| 82 |
+
border-color: #1a73e8;
|
| 83 |
+
box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.3);
|
| 84 |
+
}
|
| 85 |
+
html.dark .input-field:focus {
|
| 86 |
+
border-color: #60a5fa;
|
| 87 |
+
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.3);
|
| 88 |
+
}
|
| 89 |
+
/* Ensure nav link styling is consistent */
|
| 90 |
+
.nav-link { transition: color 0.2s ease-in-out; }
|
| 91 |
+
|
| 92 |
+
</style>
|
| 93 |
+
</head>
|
| 94 |
+
<body class="bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-200">
|
| 95 |
+
|
| 96 |
+
<!-- Header -->
|
| 97 |
+
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 98 |
+
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 99 |
+
<div class="flex items-center">
|
| 100 |
+
<a href="index.html" class="text-2xl font-['Pacifico'] text-primary dark:text-blue-400 mr-8" data-lang-en="AI Text Tools" data-lang-hi="एआई टेक्स्ट उपकरण">AI Text Tools</a>
|
| 101 |
+
<!-- *** START: Updated Navigation Bar *** -->
|
| 102 |
+
<nav id="mainNav" class="hidden md:flex space-x-6">
|
| 103 |
+
<a href="index.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Home" data-lang-hi="होम">Home</a>
|
| 104 |
+
<!-- Assuming recognizer.html is the main tool page, link from index.html might be better -->
|
| 105 |
+
<a href="recognizer.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a>
|
| 106 |
+
<!-- Assuming examples.html exists -->
|
| 107 |
+
<a href="examples.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</a>
|
| 108 |
+
<!-- Assuming technology.html exists -->
|
| 109 |
+
<a href="technology.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Technology" data-lang-hi="तकनीक">Technology</a>
|
| 110 |
+
<!-- Assuming about.html exists -->
|
| 111 |
+
<a href="about.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="About" data-lang-hi="बारे में">About</a>
|
| 112 |
+
<!-- Link to contact.html -->
|
| 113 |
+
<a href="contact.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</a>
|
| 114 |
+
<!-- Active link for this page -->
|
| 115 |
+
<a href="feedback.html" class="nav-link text-primary dark:text-blue-400 font-medium" data-lang-en="Feedback" data-lang-hi="प्रतिक्रिया">Feedback</a>
|
| 116 |
+
</nav>
|
| 117 |
+
<!-- *** END: Updated Navigation Bar *** -->
|
| 118 |
+
</div>
|
| 119 |
+
<!-- Right side header items (Theme, Lang Toggles) -->
|
| 120 |
+
<div class="flex items-center space-x-4">
|
| 121 |
+
<div class="items-center space-x-2 hidden md:flex"> <span class="text-sm text-gray-600 dark:text-gray-400">EN</span> <label class="custom-switch"><input type="checkbox" id="languageToggle" class="custom-switch-input"><span class="custom-switch-slider"></span></label> <span class="text-sm text-gray-600 dark:text-gray-400 hindi-font">हिंदी</span> </div>
|
| 122 |
+
<div class="items-center space-x-2 hidden md:flex"> <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-sun-line"></i></span> <label class="custom-switch"><input type="checkbox" id="themeToggle" class="custom-switch-input"><span class="custom-switch-slider"></span></label> <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-moon-line"></i></span> </div>
|
| 123 |
+
<!-- Removed Logout Button and User Icons for feedback page -->
|
| 124 |
+
<button class="md:hidden w-10 h-10 flex items-center justify-center" aria-label="Toggle Menu"><i class="ri-menu-line text-gray-600 dark:text-gray-300 text-xl"></i></button>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
</header>
|
| 128 |
+
|
| 129 |
+
<!-- ===== Main Content Area ===== -->
|
| 130 |
+
<main class="flex-grow container mx-auto px-4 py-12">
|
| 131 |
+
|
| 132 |
+
<h1 class="text-3xl font-bold text-center text-gray-800 dark:text-white mb-4" data-lang-en="Provide Feedback" data-lang-hi="प्रतिक्रिया दें">Provide Feedback</h1>
|
| 133 |
+
<p class="text-center text-gray-600 dark:text-gray-400 mb-10" data-lang-en="Help us improve the AI Text Tools by sharing your thoughts." data-lang-hi="अपने विचार साझा करके एआई टेक्स्ट उपकरण को बेहतर बनाने में हमारी सहायता करें।">
|
| 134 |
+
Help us improve the AI Text Tools by sharing your thoughts.
|
| 135 |
+
</p>
|
| 136 |
+
|
| 137 |
+
<div class="max-w-2xl mx-auto bg-white dark:bg-gray-800 p-8 rounded-xl shadow-lg border border-gray-100 dark:border-gray-700 card">
|
| 138 |
+
<form id="feedbackForm" novalidate>
|
| 139 |
+
<div class="mb-6">
|
| 140 |
+
<label for="feedbackText" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" data-lang-en="Your Feedback" data-lang-hi="आपकी प्रतिक्रिया">Your Feedback</label>
|
| 141 |
+
<textarea id="feedbackText" name="feedback" rows="6" required class="input-field" placeholder="Enter your feedback here..." data-lang-en-placeholder="Enter your feedback here..." data-lang-hi-placeholder="अपनी प्रतिक्रिया यहाँ दर्ज करें..."></textarea>
|
| 142 |
+
</div>
|
| 143 |
+
|
| 144 |
+
<div id="formMessage" class="form-message mb-6"></div> <!-- For Success/Error Messages -->
|
| 145 |
+
|
| 146 |
+
<button type="submit" id="submitFeedbackButton" class="w-full flex justify-center items-center gap-2 py-2.5 px-4 border border-transparent rounded-button shadow-sm text-sm font-medium text-white bg-primary hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary dark:focus:ring-offset-gray-800 disabled:opacity-50">
|
| 147 |
+
<span data-lang-en="Submit Feedback" data-lang-hi="प्रतिक्रिया जमा करें">Submit Feedback</span>
|
| 148 |
+
<i class="ri-send-plane-fill text-base"></i>
|
| 149 |
+
<div class="spinner hidden ml-2"></div> <!-- Loading Spinner -->
|
| 150 |
+
</button>
|
| 151 |
+
</form>
|
| 152 |
+
</div>
|
| 153 |
+
|
| 154 |
+
</main>
|
| 155 |
+
|
| 156 |
+
<!-- Footer -->
|
| 157 |
+
<footer class="bg-gray-900 text-gray-400 py-12 mt-16">
|
| 158 |
+
<div class="container mx-auto px-4">
|
| 159 |
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
| 160 |
+
<div>
|
| 161 |
+
<a href="index.html" class="text-2xl font-['Pacifico'] text-white mb-4 inline-block" data-lang-en="AI Text Tools" data-lang-hi="एआई टेक्स्ट उपकरण">AI Text Tools</a>
|
| 162 |
+
<p class="mb-4 text-sm" data-lang-en="AI-powered tools for text analysis and processing." data-lang-hi="पाठ विश्लेषण और प्रसंस्करण के लिए एआई-संचालित उपकरण।">AI-powered tools for text analysis.</p>
|
| 163 |
+
<div class="flex space-x-4">
|
| 164 |
+
<a href="#" class="hover:text-white" title="GitHub (Placeholder)"><i class="ri-github-fill"></i></a>
|
| 165 |
+
<a href="#" class="hover:text-white" title="LinkedIn (Placeholder)"><i class="ri-linkedin-box-fill"></i></a>
|
| 166 |
+
</div>
|
| 167 |
+
</div>
|
| 168 |
+
<div>
|
| 169 |
+
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Quick Links" data-lang-hi="त्वरित लिंक्स">Quick Links</h3>
|
| 170 |
+
<ul class="space-y-2 text-sm">
|
| 171 |
+
<li><a href="index.html" class="hover:text-white" data-lang-en="Home" data-lang-hi="होम">Home</a></li>
|
| 172 |
+
<li><a href="index.html#ocrContent" class="hover:text-white" data-lang-en="Tools" data-lang-hi="उपकरण">Tools</a></li> <!-- Link to Tools section on index -->
|
| 173 |
+
<li><a href="about.html" class="hover:text-white" data-lang-en="About" data-lang-hi="बारे में">About</a></li>
|
| 174 |
+
<li><a href="contact.html" class="hover:text-white" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</a></li>
|
| 175 |
+
<li><a href="feedback.html" class="hover:text-white" data-lang-en="Feedback" data-lang-hi="प्रतिक्रिया">Feedback</a></li>
|
| 176 |
+
</ul>
|
| 177 |
+
</div>
|
| 178 |
+
<div>
|
| 179 |
+
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Resources" data-lang-hi="संसाधन">Resources</h3>
|
| 180 |
+
<ul class="space-y-2 text-sm">
|
| 181 |
+
<li><a href="#" class="hover:text-white" data-lang-en="API Docs (TBD)" data-lang-hi="एपीआई डॉक्स (TBD)">API Docs (TBD)</a></li>
|
| 182 |
+
<li><a href="#" class="hover:text-white" data-lang-en="GitHub Repo" data-lang-hi="गिटहब रेपो">GitHub Repo</a></li>
|
| 183 |
+
</ul>
|
| 184 |
+
</div>
|
| 185 |
+
<div>
|
| 186 |
+
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</h3>
|
| 187 |
+
<ul class="space-y-2 text-sm">
|
| 188 |
+
<li class="flex items-start"><i class="ri-mail-line mt-1 mr-2"></i><span>your-email@example.com</span></li>
|
| 189 |
+
</ul>
|
| 190 |
+
</div>
|
| 191 |
+
</div>
|
| 192 |
+
<div class="border-t border-gray-700 mt-8 pt-8 text-center">
|
| 193 |
+
<p class="text-sm">© 2024 Your Name/Org | AI Text Tools Project</p>
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
</footer>
|
| 197 |
+
|
| 198 |
+
<!-- Base Theme/Language Script -->
|
| 199 |
+
<script>
|
| 200 |
+
const themeToggle = document.getElementById('themeToggle');
|
| 201 |
+
const htmlElement = document.documentElement;
|
| 202 |
+
function applyTheme(isDark) { if (isDark) { htmlElement.classList.add('dark'); if(themeToggle) themeToggle.checked = true; } else { htmlElement.classList.remove('dark'); if(themeToggle) themeToggle.checked = false; } }
|
| 203 |
+
const languageToggle = document.getElementById('languageToggle');
|
| 204 |
+
const langElements = document.querySelectorAll('[data-lang-en]');
|
| 205 |
+
const placeholderElements = document.querySelectorAll('[data-lang-en-placeholder]'); // Select elements with placeholder translations
|
| 206 |
+
|
| 207 |
+
function applyLanguage(lang) {
|
| 208 |
+
htmlElement.setAttribute('lang', lang);
|
| 209 |
+
// Handle regular text elements
|
| 210 |
+
langElements.forEach(el => {
|
| 211 |
+
const text = el.getAttribute(`data-lang-${lang}`);
|
| 212 |
+
if (text) {
|
| 213 |
+
const icon = el.querySelector('i');
|
| 214 |
+
if (icon && el.childNodes.length > 1) { // Element has icon and text node
|
| 215 |
+
let updated = false;
|
| 216 |
+
el.childNodes.forEach(node => {
|
| 217 |
+
if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '') {
|
| 218 |
+
node.textContent = ` ${text} `; // Add spaces around text if next to icon
|
| 219 |
+
updated = true;
|
| 220 |
+
}
|
| 221 |
+
});
|
| 222 |
+
// Fallback if text node wasn't found/updated directly
|
| 223 |
+
if (!updated) {
|
| 224 |
+
const currentText = el.getAttribute(`data-lang-${lang === 'en' ? 'hi' : 'en'}`);
|
| 225 |
+
if (currentText) el.innerHTML = el.innerHTML.replace(currentText.trim(), text);
|
| 226 |
+
}
|
| 227 |
+
} else { // Element is likely just text
|
| 228 |
+
el.textContent = text;
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
});
|
| 232 |
+
// Handle placeholder attributes
|
| 233 |
+
placeholderElements.forEach(el => {
|
| 234 |
+
const placeholderText = el.getAttribute(`data-lang-${lang}-placeholder`);
|
| 235 |
+
if (placeholderText !== null) { // Check if attribute exists
|
| 236 |
+
el.placeholder = placeholderText;
|
| 237 |
+
}
|
| 238 |
+
});
|
| 239 |
+
|
| 240 |
+
if (languageToggle) languageToggle.checked = (lang === 'hi');
|
| 241 |
+
localStorage.setItem('language', lang);
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
// Function to initialize theme/language from storage or system preference
|
| 245 |
+
function initializeSettings() {
|
| 246 |
+
const savedLang = localStorage.getItem('language') || 'en';
|
| 247 |
+
const prefersDark = localStorage.getItem('theme') === 'dark' || (localStorage.getItem('theme') === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
| 248 |
+
applyTheme(prefersDark);
|
| 249 |
+
applyLanguage(savedLang); // Apply language after DOM is ready potentially
|
| 250 |
+
|
| 251 |
+
if (languageToggle) {
|
| 252 |
+
languageToggle.addEventListener('change', (event) => {
|
| 253 |
+
const newLang = event.target.checked ? 'hi' : 'en';
|
| 254 |
+
applyLanguage(newLang);
|
| 255 |
+
});
|
| 256 |
+
} else {
|
| 257 |
+
console.warn("Language toggle button not found.");
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
if (themeToggle) {
|
| 261 |
+
themeToggle.addEventListener('change', (event) => {
|
| 262 |
+
const isDark = event.target.checked;
|
| 263 |
+
applyTheme(isDark);
|
| 264 |
+
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
| 265 |
+
});
|
| 266 |
+
} else {
|
| 267 |
+
console.warn("Theme toggle button not found.");
|
| 268 |
+
}
|
| 269 |
+
}
|
| 270 |
+
</script>
|
| 271 |
+
|
| 272 |
+
<!-- Feedback Form Submission Script (Corrected API Endpoint) -->
|
| 273 |
+
<script>
|
| 274 |
+
// *** CORRECTED: Define the FULL API endpoint URL from app.py context ***
|
| 275 |
+
// MAKE SURE this URL points to where your app.py (OCR API) is running
|
| 276 |
+
const OCR_API_BASE_URL = 'https://sameernotes-ocr.hf.space'; // Or 'http://localhost:8000' if running locally
|
| 277 |
+
const FEEDBACK_API_ENDPOINT = `${OCR_API_BASE_URL}/feedback/`;
|
| 278 |
+
|
| 279 |
+
// Get DOM elements (ensure these are correct IDs)
|
| 280 |
+
const feedbackForm = document.getElementById('feedbackForm');
|
| 281 |
+
const submitButton = document.getElementById('submitFeedbackButton');
|
| 282 |
+
const feedbackTextInput = document.getElementById('feedbackText');
|
| 283 |
+
const formMessageDiv = document.getElementById('formMessage');
|
| 284 |
+
const spinner = submitButton ? submitButton.querySelector('.spinner') : null;
|
| 285 |
+
// Language toggle reference is needed from the base script above, assumed available
|
| 286 |
+
|
| 287 |
+
// Function to display messages
|
| 288 |
+
function showFormMessage(message, isSuccess = true) {
|
| 289 |
+
if (!formMessageDiv) return;
|
| 290 |
+
formMessageDiv.textContent = message;
|
| 291 |
+
formMessageDiv.className = 'form-message mb-6'; // Reset classes, keep margin
|
| 292 |
+
if (isSuccess) {
|
| 293 |
+
formMessageDiv.classList.add('success-message');
|
| 294 |
+
} else {
|
| 295 |
+
formMessageDiv.classList.add('error-message');
|
| 296 |
+
}
|
| 297 |
+
formMessageDiv.style.display = 'block';
|
| 298 |
+
setTimeout(() => hideFormMessage(), 5000); // Hide after 5 seconds
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
function hideFormMessage() {
|
| 302 |
+
if (formMessageDiv) {
|
| 303 |
+
formMessageDiv.style.display = 'none';
|
| 304 |
+
formMessageDiv.textContent = '';
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
// Function to handle loading state
|
| 309 |
+
function setFormLoading(isLoading) {
|
| 310 |
+
if (!submitButton || !spinner) return;
|
| 311 |
+
submitButton.disabled = isLoading;
|
| 312 |
+
spinner.classList.toggle('hidden', !isLoading);
|
| 313 |
+
// Adjust opacity of button content for visual feedback
|
| 314 |
+
const contentElements = submitButton.querySelectorAll('span, i');
|
| 315 |
+
contentElements.forEach(el => el.style.opacity = isLoading ? '0.5' : '1');
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
// Add event listener only if the form exists
|
| 319 |
+
if (feedbackForm && submitButton && feedbackTextInput) {
|
| 320 |
+
feedbackForm.addEventListener('submit', async (event) => {
|
| 321 |
+
event.preventDefault();
|
| 322 |
+
hideFormMessage();
|
| 323 |
+
|
| 324 |
+
const comment = feedbackTextInput.value.trim();
|
| 325 |
+
const currentLangIsHi = languageToggle && languageToggle.checked;
|
| 326 |
+
|
| 327 |
+
if (!comment) {
|
| 328 |
+
const errorMessage = currentLangIsHi ? 'कृपया अपनी प्रतिक्रिया दर्ज करें।' : 'Please enter your feedback.';
|
| 329 |
+
showFormMessage(errorMessage, false);
|
| 330 |
+
return;
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
setFormLoading(true);
|
| 334 |
+
|
| 335 |
+
const payload = {
|
| 336 |
+
username: "Anonymous", // Explicitly anonymous for this page
|
| 337 |
+
comment: comment
|
| 338 |
+
};
|
| 339 |
+
|
| 340 |
+
console.log("Sending feedback payload to:", FEEDBACK_API_ENDPOINT, payload);
|
| 341 |
+
|
| 342 |
+
try {
|
| 343 |
+
const response = await fetch(FEEDBACK_API_ENDPOINT, {
|
| 344 |
+
method: 'POST',
|
| 345 |
+
headers: {
|
| 346 |
+
'Content-Type': 'application/json',
|
| 347 |
+
'Accept': 'application/json'
|
| 348 |
+
},
|
| 349 |
+
body: JSON.stringify(payload)
|
| 350 |
+
});
|
| 351 |
+
|
| 352 |
+
const responseData = await response.json();
|
| 353 |
+
|
| 354 |
+
if (!response.ok) {
|
| 355 |
+
// Attempt to get detail from common FastAPI error structure
|
| 356 |
+
const errorDetail = responseData.detail || `Request failed with status ${response.status}`;
|
| 357 |
+
throw new Error(errorDetail);
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
console.log("Feedback submitted successfully:", responseData);
|
| 361 |
+
const successMsg = currentLangIsHi ? 'आपकी प्रतिक्रिया सफलतापूर्वक जमा कर दी गई है! धन्यवाद।' : 'Feedback submitted successfully! Thank you.';
|
| 362 |
+
showFormMessage(successMsg, true);
|
| 363 |
+
feedbackTextInput.value = ''; // Clear the textarea
|
| 364 |
+
|
| 365 |
+
} catch (error) {
|
| 366 |
+
console.error('Error submitting feedback:', error);
|
| 367 |
+
let detailedErrorMsg = error.message;
|
| 368 |
+
if (error instanceof TypeError && error.message.toLowerCase().includes('failed to fetch')) {
|
| 369 |
+
detailedErrorMsg = currentLangIsHi ? 'नेटवर्क त्रुटि या सर्वर तक पहुंचने में असमर्थ। CORS समस्या या सर्वर डाउन हो सकता है।' : 'Network error or unable to reach server. Check CORS or if the server is running.';
|
| 370 |
+
}
|
| 371 |
+
const errorMsg = (currentLangIsHi ? 'प्रतिक्रिया जमा करने में त्रुटि हुई: ' : 'Error submitting feedback: ') + detailedErrorMsg;
|
| 372 |
+
showFormMessage(errorMsg, false);
|
| 373 |
+
} finally {
|
| 374 |
+
setFormLoading(false);
|
| 375 |
+
}
|
| 376 |
+
});
|
| 377 |
+
} else {
|
| 378 |
+
console.error("Required feedback form elements not found on the page.");
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
// Initialize theme and language settings when the DOM is fully loaded
|
| 382 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 383 |
+
initializeSettings(); // Call the function defined in the base script block
|
| 384 |
+
console.log("Feedback page DOM Loaded and Initialized.");
|
| 385 |
+
});
|
| 386 |
+
|
| 387 |
+
</script>
|
| 388 |
+
|
| 389 |
+
</body>
|
| 390 |
+
</html>
|
index.html
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
-
<!-- this is index.html (Tailwind UI with Auth, OCR, Translation, Gender - Fixed Nav & OCR Prediction - REVISED OCR OUTPUT LAYOUT) -->
|
| 2 |
<!DOCTYPE html>
|
| 3 |
<html lang="en" class=""> <!-- Start without 'dark' initially -->
|
| 4 |
<head>
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
-
<title>AI Text
|
| 8 |
<!-- Tailwind CSS via CDN -->
|
| 9 |
<script src="https://cdn.tailwindcss.com/3.4.1"></script>
|
| 10 |
<script>
|
|
@@ -40,61 +39,37 @@
|
|
| 40 |
/* Base Dark Mode */
|
| 41 |
html.dark body { background-color: #1a202c; color: #e2e8f0; }
|
| 42 |
html.dark header, html.dark footer { background-color: #2d3748; color: #e2e8f0; }
|
| 43 |
-
html.dark .card
|
| 44 |
-
html.dark h1, html.dark h2, html.dark h3, html.dark p, html.dark span, html.dark li, html.dark label, html.dark small, html.dark strong, html.dark button:not(.bg-red-500):not(.bg-green-600), html.dark a { color: #e2e8f0; }
|
| 45 |
-
html.dark a.text-primary { color: #60a5fa; }
|
| 46 |
html.dark .text-gray-600, html.dark .text-gray-700, html.dark .text-gray-800, html.dark .text-gray-900 { color: #a0aec0; }
|
| 47 |
html.dark .text-gray-500, html.dark .text-gray-400 { color: #718096; }
|
| 48 |
-
html.dark .text-muted { color: #718096; }
|
| 49 |
html.dark .bg-white { background-color: #2d3748 !important; }
|
| 50 |
html.dark .bg-gray-50 { background-color: #1a202c !important; }
|
| 51 |
html.dark .bg-gray-100 { background-color: #374151 !important; }
|
| 52 |
html.dark .border-gray-100, html.dark .border-gray-200 { border-color: #4a5568 !important; }
|
| 53 |
-
|
| 54 |
-
/* Dark mode Forms */
|
| 55 |
html.dark input, html.dark textarea { background-color: #1f2937; border-color: #4b5563; color: #e5e7eb; }
|
| 56 |
html.dark input::placeholder, html.dark textarea::placeholder { color: #6b7280; }
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
.
|
| 62 |
-
|
| 63 |
-
.
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
.
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
.tab-button { transition: all 0.3s ease; border-bottom: 3px solid transparent; }
|
| 77 |
-
.tab-button.active { border-bottom-color: #1a73e8; color: #1a73e8; font-weight: 600; }
|
| 78 |
-
html.dark .tab-button.active { border-bottom-color: #60a5fa; color: #60a5fa; }
|
| 79 |
-
.tab-content { display: none; animation: fadeIn 0.5s ease-in-out; }
|
| 80 |
-
.tab-content.active { display: block; }
|
| 81 |
-
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
| 82 |
-
/* Result Styling Refinements */
|
| 83 |
-
.result-card { background-color: #f9fafb; border: 1px solid #e5e7eb; margin-bottom: 1.5rem; padding: 1.5rem; border-radius: 8px; }
|
| 84 |
-
html.dark .result-card { background-color: #374151; border-color: #4b5563; }
|
| 85 |
-
.result-label { font-weight: 600; color: #4b5563; }
|
| 86 |
-
html.dark .result-label { color: #9ca3af; }
|
| 87 |
-
.result-value { font-weight: 500; word-break: break-word; }
|
| 88 |
-
html.dark .result-value { color: #e5e7eb; }
|
| 89 |
-
.result-item { display: grid; grid-template-columns: 1fr; gap: 0.25rem; margin-bottom: 1rem; padding-bottom: 1rem; border-bottom: 1px solid #e5e7eb; }
|
| 90 |
-
html.dark .result-item { border-color: #4b5563; }
|
| 91 |
-
.result-item:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }
|
| 92 |
-
@media (min-width: 768px) { .result-item { grid-template-columns: 150px 1fr; gap: 1rem; align-items: start; } .result-label { text-align: right; } } /* Grid for larger screens */
|
| 93 |
-
/* Loading Indicator general */
|
| 94 |
-
.loading-indicator { display: none; flex-direction: column; align-items: center; justify-content: center; gap: 0.5rem; margin: 1.5rem 0; color: #6b7280; }
|
| 95 |
-
html.dark .loading-indicator { color: #9ca3af; }
|
| 96 |
-
.loading-indicator .spinner { width: 24px; height: 24px; border-width: 3px; }
|
| 97 |
-
|
| 98 |
</style>
|
| 99 |
</head>
|
| 100 |
<body class="bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-200">
|
|
@@ -103,527 +78,268 @@
|
|
| 103 |
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 104 |
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 105 |
<div class="flex items-center">
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
<!-- *** RESTORED Navigation Links *** -->
|
| 109 |
<nav id="mainNav" class="hidden md:flex space-x-6">
|
| 110 |
-
<a href="index.html" class="nav-link
|
| 111 |
-
<a href="recognizer.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-
|
| 112 |
-
|
| 113 |
<a href="technology.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Technology" data-lang-hi="तकनीक">Technology</a>
|
| 114 |
-
|
|
|
|
|
|
|
| 115 |
</nav>
|
| 116 |
</div>
|
| 117 |
-
<!-- Right side header items
|
| 118 |
<div class="flex items-center space-x-4">
|
| 119 |
<div class="items-center space-x-2 hidden md:flex"> <span class="text-sm text-gray-600 dark:text-gray-400">EN</span> <label class="custom-switch"><input type="checkbox" id="languageToggle" class="custom-switch-input"><span class="custom-switch-slider"></span></label> <span class="text-sm text-gray-600 dark:text-gray-400 hindi-font">हिंदी</span> </div>
|
| 120 |
<div class="items-center space-x-2 hidden md:flex"> <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-sun-line"></i></span> <label class="custom-switch"><input type="checkbox" id="themeToggle" class="custom-switch-input"><span class="custom-switch-slider"></span></label> <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-moon-line"></i></span> </div>
|
| 121 |
-
<button id="logoutButton" class="hidden bg-red-500 hover:bg-red-600 text-white px-3 py-1.5 rounded-button text-sm inline-flex items-center gap-1"> <i class="ri-logout-box-r-line"></i> <span data-lang-en="Logout" data-lang-hi="लॉग आउट">Logout</span> </button>
|
| 122 |
-
<div id="userInfoIcons" class="hidden md:flex items-center space-x-4"> <div class="w-10 h-10 flex items-center justify-center bg-gray-100 dark:bg-gray-700 rounded-full cursor-pointer" title="User Profile (Placeholder)"><i class="ri-user-line text-gray-600 dark:text-gray-300"></i></div> </div>
|
| 123 |
<button class="md:hidden w-10 h-10 flex items-center justify-center" aria-label="Toggle Menu"><i class="ri-menu-line text-gray-600 dark:text-gray-300 text-xl"></i></button>
|
| 124 |
</div>
|
| 125 |
</div>
|
| 126 |
</header>
|
| 127 |
|
| 128 |
-
<!-- =====
|
| 129 |
-
<
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
<
|
| 140 |
-
<
|
| 141 |
-
<
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
<
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
<h3 class="text-md font-semibold text-gray-700 dark:text-gray-300 mb-2" data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</h3>
|
| 158 |
-
<div id="examplesContainer" class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-3 mb-4"><div class="col-span-full"><span id="examplesLoadingText" class="text-gray-500 dark:text-gray-400 text-sm" data-lang-en="Loading examples..." data-lang-hi="उदाहरण लोड हो रहे हैं...">Loading examples...</span></div></div>
|
| 159 |
-
<div id="imagePreviewContainer" class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-600 hidden"><h3 class="text-md font-semibold text-gray-700 dark:text-gray-300 mb-2 text-center" data-lang-en="Preview" data-lang-hi="पूर्वावलोकन">Preview</h3><div class="flex justify-center items-center bg-gray-50 dark:bg-gray-700 dark:border-gray-600 rounded border p-1 min-h-[100px]"><img id="imagePreview" src="#" alt="Image Preview" class="max-h-60 w-auto rounded"/></div></div>
|
| 160 |
-
<div class="mt-6 pt-4 border-t border-gray-200 dark:border-gray-600 flex gap-4">
|
| 161 |
-
<button id="predictButton" class="flex-1 bg-green-600 text-white px-6 py-3 rounded-button font-medium hover:bg-green-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-lg inline-flex items-center justify-center gap-2" disabled><i class="ri-search-line"></i><span data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</span></button>
|
| 162 |
-
<button id="clearButton" class="flex-none bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-500 text-gray-800 dark:text-gray-200 px-4 py-3 rounded-button text-lg inline-flex items-center justify-center gap-2 hidden"><i class="ri-delete-bin-line"></i><span data-lang-en="Clear" data-lang-hi="साफ़ करें">Clear</span></button>
|
| 163 |
</div>
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
| 188 |
</div>
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
</div>
|
| 191 |
-
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
</div>
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
|
| 199 |
-
<!-- Translation Input -->
|
| 200 |
-
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 card">
|
| 201 |
-
<h2 class="text-xl font-semibold mb-4 border-b pb-2 flex items-center gap-2"><i class="ri-translate-2 text-primary dark:text-blue-400"></i><span data-lang-en="Translate Text" data-lang-hi="पाठ का अनुवाद करें">Translate Text</span></h2>
|
| 202 |
-
<div class="mb-4">
|
| 203 |
-
<label for="textToTranslate" class="block text-sm font-medium mb-1" data-lang-en="Text to Translate:" data-lang-hi="अनुवाद के लिए पाठ:">Text to Translate:</label>
|
| 204 |
-
<textarea id="textToTranslate" rows="5" placeholder="Enter text here or use OCR output..." class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600"></textarea>
|
| 205 |
-
</div>
|
| 206 |
-
<div class="mb-4">
|
| 207 |
-
<label for="sourceLanguage" class="block text-sm font-medium mb-1" data-lang-en="Source Language (Optional):" data-lang-hi="स्रोत भाषा (वैकल्पिक):">Source Language (Optional):</label>
|
| 208 |
-
<input type="text" id="sourceLanguage" placeholder="Leave empty for auto-detection" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600">
|
| 209 |
-
</div>
|
| 210 |
-
<div class="mb-4">
|
| 211 |
-
<label for="targetLanguage" class="block text-sm font-medium mb-1" data-lang-en="Target Language:" data-lang-hi="लक्ष्य भाषा:">Target Language:</label>
|
| 212 |
-
<input type="text" id="targetLanguage" placeholder="e.g., English, Hindi, French" required class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600">
|
| 213 |
-
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1" data-lang-en="Enter target language name (e.g., English)." data-lang-hi="लक्ष्य भाषा का नाम दर्ज करें (उदा., English)।">Enter target language name (e.g., English).</p>
|
| 214 |
-
</div>
|
| 215 |
-
<button id="translateButton" class="w-full bg-primary text-white px-4 py-2 rounded-button hover:bg-blue-700 transition-colors inline-flex items-center justify-center gap-2">
|
| 216 |
-
<i class="ri-translate"></i><span data-lang-en="Translate" data-lang-hi="अनुवाद करें">Translate</span>
|
| 217 |
-
</button>
|
| 218 |
-
<div id="translationError" class="error-message mt-4"></div>
|
| 219 |
</div>
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
</div>
|
| 229 |
-
<p id="initialMessageTranslation" class="text-gray-500 dark:text-gray-400 mt-6 text-center" data-lang-en="Enter text and languages, then click Translate." data-lang-hi="पाठ और भाषाएँ दर्ज करें, फिर अनुवाद करें पर क्लिक करें।">Enter text and languages, then click Translate.</p>
|
| 230 |
-
</div>
|
| 231 |
-
</div>
|
| 232 |
</div>
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
<
|
| 253 |
-
<
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
</div>
|
| 258 |
-
</
|
| 259 |
|
| 260 |
</main>
|
| 261 |
|
| 262 |
<!-- Footer -->
|
| 263 |
-
<footer class="bg-gray-900 text-gray-400 py-12 mt-
|
| 264 |
-
|
| 265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
</footer>
|
| 267 |
|
| 268 |
<!-- Base Theme/Language Script -->
|
| 269 |
<script>
|
| 270 |
-
|
| 271 |
-
const
|
| 272 |
-
|
| 273 |
-
const languageToggle = document.getElementById('languageToggle');
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
let currentExampleSelection = null;
|
| 295 |
-
|
| 296 |
-
// --- DOM Elements ---
|
| 297 |
-
// Auth & Core App
|
| 298 |
-
const authContainer = document.getElementById('authContainer');
|
| 299 |
-
const appContainer = document.getElementById('appContainer');
|
| 300 |
-
const loginForm = document.getElementById('loginForm');
|
| 301 |
-
const signupForm = document.getElementById('signupForm');
|
| 302 |
-
const loginUsernameInput = document.getElementById('loginUsername');
|
| 303 |
-
const loginPasswordInput = document.getElementById('loginPassword');
|
| 304 |
-
const loginErrorDiv = document.getElementById('loginErrorDiv');
|
| 305 |
-
const loginSubmitButton = document.getElementById('loginSubmitButton');
|
| 306 |
-
const signupUsernameInput = document.getElementById('signupUsername');
|
| 307 |
-
const signupEmailInput = document.getElementById('signupEmail');
|
| 308 |
-
const signupPasswordInput = document.getElementById('signupPassword');
|
| 309 |
-
const signupErrorDiv = document.getElementById('signupErrorDiv');
|
| 310 |
-
const signupSubmitButton = document.getElementById('signupSubmitButton');
|
| 311 |
-
const showSignupLink = document.getElementById('showSignupLink');
|
| 312 |
-
const showLoginLink = document.getElementById('showLoginLink');
|
| 313 |
-
const logoutButton = document.getElementById('logoutButton');
|
| 314 |
-
const mainNav = document.getElementById('mainNav');
|
| 315 |
-
const userInfoIcons = document.getElementById('userInfoIcons');
|
| 316 |
-
// Tabs
|
| 317 |
-
const tabButtons = document.querySelectorAll('.tab-button');
|
| 318 |
-
const tabContents = document.querySelectorAll('.tab-content');
|
| 319 |
-
// OCR Input
|
| 320 |
-
const imageUpload = document.getElementById('imageUpload');
|
| 321 |
-
const fileNameSpan = document.getElementById('fileName');
|
| 322 |
-
const examplesContainer = document.getElementById('examplesContainer');
|
| 323 |
-
const examplesLoadingText = document.getElementById('examplesLoadingText');
|
| 324 |
-
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
|
| 325 |
-
const imagePreview = document.getElementById('imagePreview');
|
| 326 |
-
const predictButton = document.getElementById('predictButton');
|
| 327 |
-
const clearButton = document.getElementById('clearButton');
|
| 328 |
-
const errorMessage = document.getElementById('errorMessage');
|
| 329 |
-
// OCR Output
|
| 330 |
-
const loadingIndicator = document.getElementById('loadingIndicator');
|
| 331 |
-
const loadingText = document.getElementById('loadingText');
|
| 332 |
-
const resultDisplay = document.getElementById('resultDisplay');
|
| 333 |
-
// const ocrRecognizedText = document.getElementById('ocrRecognizedText'); // *** REMOVED ***
|
| 334 |
-
// const wordCountText = document.getElementById('wordCountText'); // *** REMOVED ***
|
| 335 |
-
const sakshiOutputText = document.getElementById('sakshiOutputText');
|
| 336 |
-
const wordDetectionImg = document.getElementById('wordDetectionImg');
|
| 337 |
-
// const predictionImg = document.getElementById('predictionImg'); // Removed
|
| 338 |
-
const initialMessageOutput = document.getElementById('initialMessageOutput');
|
| 339 |
-
// Translation
|
| 340 |
-
const textToTranslateInput = document.getElementById('textToTranslate');
|
| 341 |
-
const sourceLanguageInput = document.getElementById('sourceLanguage');
|
| 342 |
-
const targetLanguageInput = document.getElementById('targetLanguage');
|
| 343 |
-
const translateButton = document.getElementById('translateButton');
|
| 344 |
-
const translationLoading = document.getElementById('translationLoading');
|
| 345 |
-
const translationResultDisplay = document.getElementById('translationResultDisplay');
|
| 346 |
-
const detectedSourceLanguage = document.getElementById('detectedSourceLanguage');
|
| 347 |
-
const translationTargetLanguage = document.getElementById('translationTargetLanguage');
|
| 348 |
-
const translatedText = document.getElementById('translatedText');
|
| 349 |
-
const translationError = document.getElementById('translationError');
|
| 350 |
-
const initialMessageTranslation = document.getElementById('initialMessageTranslation');
|
| 351 |
-
// Gender Prediction
|
| 352 |
-
const namesInput = document.getElementById('namesInput');
|
| 353 |
-
const predictGenderButton = document.getElementById('predictGenderButton');
|
| 354 |
-
const genderLoading = document.getElementById('genderLoading');
|
| 355 |
-
const genderResultDisplay = document.getElementById('genderResultDisplay');
|
| 356 |
-
const genderError = document.getElementById('genderError');
|
| 357 |
-
const initialMessageGender = document.getElementById('initialMessageGender');
|
| 358 |
-
|
| 359 |
-
// --- Example Images ---
|
| 360 |
-
const exampleImages = [
|
| 361 |
-
{ name: "अखिल", url: "samples/अखिल.png" }, // Assuming a relative path based on the image structure
|
| 362 |
-
{ name: "अग्रसर", url: "samples/अग्रसर.png" },
|
| 363 |
-
{ name: "अंतर्राष्ट्रीय", url: "samples/अंतर्राष्ट्रीय.png" },
|
| 364 |
-
{ name: "अध्ययन", url: "samples/अध्ययन.png" },
|
| 365 |
-
{ name: "अनुसंधान", url: "samples/अनुसंधान.png" },
|
| 366 |
-
{ name: "अन्य", url: "samples/अन्य.png" },
|
| 367 |
-
{ name: "अलावा", url: "samples/अलावा.png" },
|
| 368 |
-
{ name: "अवसर", url: "samples/अवसर.png" },
|
| 369 |
-
{ name: "आती", url: "samples/आती.png" },
|
| 370 |
-
{ name: "आधुनिक", url: "samples/आधुनिक.png" }
|
| 371 |
-
];
|
| 372 |
-
// --- Helper Functions (Auth UI, Error, Loading) ---
|
| 373 |
-
// ... (Keep showAuthScreen, showAppScreen, displayError, hideError, showAuthLoading, showLoadingIndicator, showOcrLoading functions exactly as in the previous version) ...
|
| 374 |
-
function showAuthScreen() { console.log("Showing Auth Screen"); accessToken = null; localStorage.removeItem(TOKEN_STORAGE_KEY); if (authContainer) authContainer.classList.remove('hidden'); if (appContainer) appContainer.classList.add('hidden'); if (mainNav) mainNav.classList.add('hidden'); if (userInfoIcons) userInfoIcons.classList.add('hidden'); if (logoutButton) logoutButton.classList.add('hidden'); hideError(loginErrorDiv); hideError(signupErrorDiv); if(loginForm) loginForm.classList.remove('hidden'); if(signupForm) signupForm.classList.add('hidden'); if(loginUsernameInput) loginUsernameInput.value = ''; if(loginPasswordInput) loginPasswordInput.value = ''; }
|
| 375 |
-
function showAppScreen() { console.log("Showing App Screen"); if (!accessToken) { showAuthScreen(); return; } if (authContainer) authContainer.classList.add('hidden'); if (appContainer) appContainer.classList.remove('hidden'); if (mainNav) mainNav.classList.remove('hidden', 'md:flex'); if (mainNav) mainNav.classList.add('md:flex'); if (userInfoIcons) userInfoIcons.classList.remove('hidden', 'md:flex'); if (userInfoIcons) userInfoIcons.classList.add('md:flex'); if (logoutButton) logoutButton.classList.remove('hidden'); loadExamples(); activateTab('ocr'); }
|
| 376 |
-
function displayError(element, message) { if (element) { element.textContent = message; element.classList.remove('hidden'); console.warn("Error:", message); } else { console.error("Element missing for error:", message); } }
|
| 377 |
-
function hideError(element) { if (element) { element.classList.add('hidden'); element.textContent = ''; } }
|
| 378 |
-
function showAuthLoading(formType, isLoading) { const button = formType === 'login' ? loginSubmitButton : signupSubmitButton; if (!button) return; const spinner = button.querySelector('.spinner'); button.disabled = isLoading; if (spinner) spinner.classList.toggle('hidden', !isLoading); }
|
| 379 |
-
function showLoadingIndicator(element, show = true) { if (element) { element.style.display = show ? 'flex' : 'none'; } }
|
| 380 |
-
function showOcrLoading(isLoading, message = "Processing...") { if (loadingIndicator) { loadingIndicator.classList.toggle('hidden', !isLoading); if (loadingText) loadingText.textContent = message; } if (predictButton) predictButton.disabled = isLoading; if (clearButton) clearButton.disabled = isLoading; }
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
// --- Generic Authenticated Fetch ---
|
| 384 |
-
// ... (Keep authenticatedFetch function exactly as in the previous correct version) ...
|
| 385 |
-
async function authenticatedFetch(url, options = {}) { if (!accessToken) { showAuthScreen(); throw new Error("Not authenticated."); } const defaultOptions = { headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/json', ...options.headers }, }; if (options.body && !(options.body instanceof FormData) && !(options.body instanceof URLSearchParams) && !defaultOptions.headers['Content-Type']) { defaultOptions.headers['Content-Type'] = 'application/json'; } const finalOptions = { ...options, ...defaultOptions }; /* console.log("Auth Fetch:", url); */ try { const response = await fetch(url, finalOptions); /* console.log(`Status ${url}:`, response.status); */ if (!response.ok) { let errorData = { detail: `Request failed: ${response.status}` }; let bodyText = ''; try { bodyText = await response.text(); errorData = JSON.parse(bodyText); if (!errorData.detail) errorData.detail = bodyText; } catch (e) { errorData.detail = bodyText || errorData.detail; } console.error(`API Error ${url}:`, response.status, errorData.detail); if (response.status === 401) { showAuthScreen(); throw new Error("Auth failed/expired."); } throw new Error(typeof errorData.detail === 'string' ? errorData.detail : JSON.stringify(errorData.detail)); } const contentType = response.headers.get("content-type"); if (response.status === 204) return null; if (contentType?.includes("application/json")) return await response.json(); if (contentType?.includes("image/") || contentType?.includes("application/octet-stream")) return await response.blob(); return await response.text(); } catch (error) { console.error(`Fetch Catch ${url}:`, error); throw error; } }
|
| 386 |
-
|
| 387 |
-
// --- LOGIN / SIGNUP / LOGOUT ---
|
| 388 |
-
// ... (Keep handleLogin, handleSignup, handleLogout functions exactly as in the previous correct version) ...
|
| 389 |
-
if (loginForm) { loginForm.addEventListener('submit', async (event) => { event.preventDefault(); const username = loginUsernameInput.value.trim(); const password = loginPasswordInput.value.trim(); hideError(loginErrorDiv); if (!username || !password) { displayError(loginErrorDiv, "Username & Password required."); return; } const body = new URLSearchParams(); body.append('username', username); body.append('password', password); showAuthLoading('login', true); try { const response = await fetch(TOKEN_ENDPOINT, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'}, body: body }); const data = await response.json().catch(() => null); if (!response.ok) { throw new Error(data?.detail || `Login failed (${response.status})`); } if (data?.access_token) { accessToken = data.access_token; localStorage.setItem(TOKEN_STORAGE_KEY, accessToken); console.log("Login success."); showAppScreen(); } else { throw new Error('No access token received.'); } } catch (error) { let msg = error.message || "Unknown login error."; if (msg.includes("Incorrect")) msg = "Incorrect username or password."; if (error instanceof TypeError) msg = "Network error."; displayError(loginErrorDiv, msg); } finally { showAuthLoading('login', false); } }); }
|
| 390 |
-
if (signupForm) { signupForm.addEventListener('submit', async (event) => { event.preventDefault(); const username = signupUsernameInput.value.trim(); const email = signupEmailInput.value.trim(); const password = signupPasswordInput.value.trim(); hideError(signupErrorDiv); if (!username || !email || !password) { displayError(signupErrorDiv, "Please fill all fields."); return; } if (password.length < 6) { displayError(signupErrorDiv, "Password must be >= 6 characters."); return; } if (!/\S+@\S+\.\S+/.test(email)) { displayError(signupErrorDiv, "Invalid email format."); return; } showAuthLoading('signup', true); try { const response = await fetch(SIGNUP_ENDPOINT, { method: 'POST', headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}, body: JSON.stringify({ username, email, password }) }); const data = await response.json().catch(() => null); if (!response.ok) { throw new Error(data?.detail || `Signup failed (${response.status})`); } console.log("Signup successful:", data); alert('Signup successful! Please login.'); if(signupForm) signupForm.classList.add('hidden'); if(loginForm) loginForm.classList.remove('hidden'); signupUsernameInput.value = ''; signupEmailInput.value = ''; signupPasswordInput.value = ''; } catch (error) { let msg = error.message || "Unknown signup error."; if (error instanceof TypeError) msg = "Network error."; displayError(signupErrorDiv, msg); } finally { showAuthLoading('signup', false); } }); }
|
| 391 |
-
if(logoutButton) logoutButton.addEventListener('click', () => { showAuthScreen(); clearOCRState(); clearTranslationState(); clearGenderState(); });
|
| 392 |
-
if (showSignupLink) showSignupLink.addEventListener('click', (e) => { e.preventDefault(); if(loginForm) loginForm.classList.add('hidden'); if(signupForm) signupForm.classList.remove('hidden'); hideError(loginErrorDiv); hideError(signupErrorDiv); });
|
| 393 |
-
if (showLoginLink) showLoginLink.addEventListener('click', (e) => { e.preventDefault(); if(signupForm) signupForm.classList.add('hidden'); if(loginForm) loginForm.classList.remove('hidden'); hideError(loginErrorDiv); hideError(signupErrorDiv); });
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
// --- TAB MANAGEMENT ---
|
| 397 |
-
function activateTab(tabId) { /* ... (Keep implementation) ... */ tabButtons.forEach(b => { b.classList.toggle('active', b.dataset.tab === tabId); }); tabContents.forEach(c => { c.classList.toggle('active', c.id === `${tabId}Content`); }); console.log("Activated tab:", tabId); }
|
| 398 |
-
tabButtons.forEach(button => { button.addEventListener('click', () => activateTab(button.dataset.tab)); });
|
| 399 |
-
|
| 400 |
-
// --- OCR Functionality ---
|
| 401 |
-
function loadExamples() { /* ... (Keep implementation) ... */ if (!examplesContainer || !appContainer || appContainer.classList.contains('hidden')) return; if (examplesLoadingText) examplesContainer.innerHTML = ''; const examplesToShow = exampleImages.slice(0, 12); examplesToShow.forEach((imgData, index) => { const div = document.createElement('div'); div.className = 'example-image-container bg-gray-100 dark:bg-gray-700 rounded-md overflow-hidden border border-gray-200 dark:border-gray-600'; div.setAttribute('role', 'button'); div.setAttribute('tabindex', '0'); div.setAttribute('aria-label', `Example ${index + 1}: ${imgData.name}`); div.dataset.imageUrl = imgData.url; div.dataset.imageName = imgData.name; const img = document.createElement('img'); img.src = imgData.url; img.alt = `Example ${index + 1}: ${imgData.name}`; img.className = 'example-image'; img.loading = 'lazy'; img.onerror = () => { div.innerHTML = `<span class="text-xs text-red-500 p-1">Load Error</span>`; }; div.appendChild(img); div.addEventListener('click', () => handleExampleClick(div, imgData.url, imgData.name)); div.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleExampleClick(div, imgData.url, imgData.name); } }); examplesContainer.appendChild(div); }); }
|
| 402 |
-
async function handleExampleClick(element, imageUrl, imageName) { /* ... (Keep implementation) ... */ if (currentExampleSelection) { currentExampleSelection.classList.remove('selected'); } element.classList.add('selected'); currentExampleSelection = element; if (imageUpload) imageUpload.value = null; displayFileName(`Example: ${imageName}`); resetResultState(); hideError(errorMessage); try { showPreview(imageUrl); showOcrLoading(true, "Fetching..."); const response = await fetch(imageUrl); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const blob = await response.blob(); const fileName = imageUrl.substring(imageUrl.lastIndexOf('/') + 1)?.split('?')[0] || `${imageName.replace(/\s+/g, '_')}.png`; currentImageSource = new File([blob], fileName, { type: blob.type || 'image/png' }); enableProcessButtons(); } catch (error) { console.error("Error fetching example:", error); displayError(errorMessage, `Could not load example: ${error.message}`); disableProcessButtons(); currentImageSource = null; hidePreview(); } finally { showOcrLoading(false); } }
|
| 403 |
-
function handleFileSelect() { /* ... (Keep implementation) ... */ const file = imageUpload.files[0]; if (!file) return; const allowed = ['image/png', 'image/jpeg', 'image/jpg']; if (!allowed.includes(file.type)) { displayError(errorMessage, 'Invalid file type.'); clearOCRState(); return; } currentImageSource = file; displayFileName(file.name); resetResultState(); hideError(errorMessage); if (currentExampleSelection) { currentExampleSelection.classList.remove('selected'); currentExampleSelection = null; } showPreview(file); enableProcessButtons(); }
|
| 404 |
-
function displayFileName(name) { /* ... (Keep implementation) ... */ if (fileNameSpan) { fileNameSpan.textContent = name; fileNameSpan.classList.remove('italic', 'text-gray-500', 'dark:text-gray-400'); fileNameSpan.classList.add('font-medium'); } }
|
| 405 |
-
function resetFileName() { /* ... (Keep implementation) ... */ if (fileNameSpan) { const defaultEn = "No file chosen"; const defaultHi = "कोई फ़ाइल नहीं चुनी गई"; const currentLang = document.documentElement.lang || 'en'; fileNameSpan.textContent = currentLang === 'hi' ? defaultHi : defaultEn; fileNameSpan.classList.add('italic', 'text-gray-500', 'dark:text-gray-400'); fileNameSpan.classList.remove('font-medium'); } }
|
| 406 |
-
function showPreview(source) { /* ... (Keep implementation) ... */ if (!imagePreview || !imagePreviewContainer) return; imagePreview.src = ''; imagePreviewContainer.classList.remove('hidden'); if (typeof source === 'string') { imagePreview.src = source; } else if (source instanceof File) { const reader = new FileReader(); reader.onload = (e) => { imagePreview.src = e.target.result; }; reader.onerror = () => displayError(errorMessage, "Could not read file."); reader.readAsDataURL(source); } }
|
| 407 |
-
function hidePreview() { /* ... (Keep implementation) ... */ if (imagePreview && imagePreviewContainer) { imagePreview.src = '#'; imagePreviewContainer.classList.add('hidden'); } }
|
| 408 |
-
function enableProcessButtons() { /* ... (Keep implementation) ... */ if (predictButton) predictButton.disabled = false; if (clearButton) clearButton.classList.remove('hidden'); }
|
| 409 |
-
function disableProcessButtons() { /* ... (Keep implementation) ... */ if (predictButton) predictButton.disabled = true; if (clearButton) clearButton.classList.add('hidden'); }
|
| 410 |
-
|
| 411 |
-
function resetResultState() { /* *** MODIFIED *** */
|
| 412 |
-
if (resultDisplay) resultDisplay.classList.add('hidden');
|
| 413 |
-
if (initialMessageOutput) initialMessageOutput.classList.remove('hidden');
|
| 414 |
-
// *** REMOVED lines for ocrRecognizedText and wordCountText ***
|
| 415 |
-
if (sakshiOutputText) sakshiOutputText.textContent = '';
|
| 416 |
-
if (wordDetectionImg) { wordDetectionImg.src = '#'; wordDetectionImg.style.display = 'none'; }
|
| 417 |
-
hideError(errorMessage);
|
| 418 |
-
}
|
| 419 |
-
|
| 420 |
-
function clearOCRState() { /* *** MODIFIED (only removed reference to predictionImg reset which was already removed) *** */
|
| 421 |
-
if (imageUpload) imageUpload.value = ''; currentImageSource = null; if (currentExampleSelection) { currentExampleSelection.classList.remove('selected'); currentExampleSelection = null; } hidePreview(); resetFileName(); disableProcessButtons(); resetResultState(); hideError(errorMessage); showOcrLoading(false); console.log("OCR State Cleared");
|
| 422 |
-
}
|
| 423 |
-
|
| 424 |
-
if (imageUpload) imageUpload.addEventListener('change', handleFileSelect);
|
| 425 |
-
if (predictButton) predictButton.addEventListener('click', processImage);
|
| 426 |
-
if (clearButton) clearButton.addEventListener('click', clearOCRState);
|
| 427 |
-
|
| 428 |
-
async function processImage() { /* *** MODIFIED *** */
|
| 429 |
-
if (!currentImageSource) { displayError(errorMessage, 'Please choose/select an image.'); return; } hideError(errorMessage); resetResultState(); showOcrLoading(true, "Preparing...");
|
| 430 |
-
try {
|
| 431 |
-
const formData = new FormData(); if (currentImageSource instanceof File) { formData.append('file', currentImageSource); } else if (typeof currentImageSource === 'string') { showOcrLoading(true, "Fetching sample..."); const response = await fetch(currentImageSource); if (!response.ok) throw new Error(`Fetch failed: ${response.statusText}`); const blob = await response.blob(); const filename = currentImageSource.split('/').pop()?.split('?')[0] || 'sample.png'; formData.append('file', new File([blob], filename, { type: blob.type || 'image/png' })); } else { throw new Error("Invalid image source."); }
|
| 432 |
-
showOcrLoading(true, "Processing OCR...");
|
| 433 |
-
const data = await authenticatedFetch(PROCESS_ENDPOINT, { method: 'POST', body: formData });
|
| 434 |
-
console.log("OCR Response:", data);
|
| 435 |
-
|
| 436 |
-
// *** REMOVED lines for ocrRecognizedText and wordCountText ***
|
| 437 |
-
|
| 438 |
-
// *** KEPT these lines ***
|
| 439 |
-
if(sakshiOutputText) sakshiOutputText.textContent = data.sakshi_output || 'No raw output.';
|
| 440 |
-
if(textToTranslateInput) textToTranslateInput.value = data.prediction_label || ''; // Use prediction_label for translate input
|
| 441 |
-
|
| 442 |
-
// *** Only fetch Word Detection Image ***
|
| 443 |
-
const fetchWordDetectImage = async () => {
|
| 444 |
-
wordDetectionImg.style.display = 'none';
|
| 445 |
-
wordDetectionImg.src = '#';
|
| 446 |
-
try {
|
| 447 |
-
showOcrLoading(true, `Fetching Word Detection...`);
|
| 448 |
-
const blob = await authenticatedFetch(WORD_DETECT_ENDPOINT);
|
| 449 |
-
if (blob instanceof Blob) {
|
| 450 |
-
const imageUrl = URL.createObjectURL(blob);
|
| 451 |
-
wordDetectionImg.onload = () => URL.revokeObjectURL(imageUrl);
|
| 452 |
-
wordDetectionImg.onerror = () => console.error(`Failed to load Word Detection from blob URL`);
|
| 453 |
-
wordDetectionImg.src = imageUrl;
|
| 454 |
-
wordDetectionImg.alt = "Word Detection Preview";
|
| 455 |
-
wordDetectionImg.style.display = 'block';
|
| 456 |
-
} else {
|
| 457 |
-
console.warn(`Failed blob fetch for Word Detection`);
|
| 458 |
}
|
| 459 |
-
}
|
| 460 |
-
|
| 461 |
}
|
| 462 |
-
};
|
| 463 |
-
|
| 464 |
-
await fetchWordDetectImage(); // Await only the word detection image
|
| 465 |
-
|
| 466 |
-
if(resultDisplay) resultDisplay.classList.remove('hidden');
|
| 467 |
-
if(initialMessageOutput) initialMessageOutput.classList.add('hidden');
|
| 468 |
-
} catch (error) {
|
| 469 |
-
console.error('Error processing image:', error);
|
| 470 |
-
displayError(errorMessage, `Processing Error: ${error.message}`);
|
| 471 |
-
resetResultState(); // Ensure results are cleared on error
|
| 472 |
-
} finally {
|
| 473 |
-
showOcrLoading(false);
|
| 474 |
-
}
|
| 475 |
-
}
|
| 476 |
-
|
| 477 |
-
// --- TRANSLATION Functionality (Updated for Gemini Backend) ---
|
| 478 |
-
function clearTranslationState() {
|
| 479 |
-
if(textToTranslateInput) textToTranslateInput.value = '';
|
| 480 |
-
if(sourceLanguageInput) sourceLanguageInput.value = '';
|
| 481 |
-
if(targetLanguageInput) targetLanguageInput.value = '';
|
| 482 |
-
if(translationResultDisplay) translationResultDisplay.classList.add('hidden');
|
| 483 |
-
if(initialMessageTranslation) initialMessageTranslation.classList.remove('hidden');
|
| 484 |
-
// No 'detectedSourceLanguage' element to clear anymore
|
| 485 |
-
if(translationTargetLanguage) translationTargetLanguage.textContent = '';
|
| 486 |
-
if(translatedText) translatedText.innerHTML = ''; // Clear innerHTML
|
| 487 |
-
hideError(translationError);
|
| 488 |
-
showLoadingIndicator(translationLoading, false);
|
| 489 |
-
console.log("Translation Cleared");
|
| 490 |
-
// Ensure source language row is hidden on clear
|
| 491 |
-
const sourceLangItem = document.getElementById('detectedSourceLanguage')?.closest('.result-item');
|
| 492 |
-
if (sourceLangItem) {
|
| 493 |
-
sourceLangItem.style.display = 'none';
|
| 494 |
-
}
|
| 495 |
-
}
|
| 496 |
-
|
| 497 |
-
if(translateButton) {
|
| 498 |
-
translateButton.addEventListener('click', async () => {
|
| 499 |
-
const text = textToTranslateInput.value.trim();
|
| 500 |
-
const sourceLang = sourceLanguageInput.value.trim();
|
| 501 |
-
const targetLang = targetLanguageInput.value.trim();
|
| 502 |
-
|
| 503 |
-
hideError(translationError);
|
| 504 |
-
showLoadingIndicator(translationLoading, true);
|
| 505 |
-
if(translationResultDisplay) translationResultDisplay.classList.add('hidden');
|
| 506 |
-
if(initialMessageTranslation) initialMessageTranslation.classList.remove('hidden'); // Show initial message while loading/error
|
| 507 |
-
|
| 508 |
-
if (!text) {
|
| 509 |
-
displayError(translationError, 'Please enter text to translate.');
|
| 510 |
-
showLoadingIndicator(translationLoading, false);
|
| 511 |
-
return;
|
| 512 |
-
}
|
| 513 |
-
if (!targetLang) {
|
| 514 |
-
displayError(translationError, 'Please enter the target language (e.g., English, Hindi).');
|
| 515 |
-
showLoadingIndicator(translationLoading, false);
|
| 516 |
-
return;
|
| 517 |
-
}
|
| 518 |
-
|
| 519 |
-
console.log(`Attempting translation from '${sourceLang || 'auto-detect'}' to '${targetLang}'. Text:`, text.substring(0, 50) + "...");
|
| 520 |
-
|
| 521 |
-
const payload = {
|
| 522 |
-
text: text,
|
| 523 |
-
target_language: targetLang
|
| 524 |
-
};
|
| 525 |
-
// Only include source_language if the user provided it
|
| 526 |
-
if (sourceLang) {
|
| 527 |
-
payload.source_language = sourceLang;
|
| 528 |
}
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
method: 'POST',
|
| 535 |
-
headers: {
|
| 536 |
-
'Content-Type': 'application/json',
|
| 537 |
-
'Accept': 'text/html' // Indicate preference for HTML response
|
| 538 |
-
},
|
| 539 |
-
body: JSON.stringify(payload)
|
| 540 |
-
});
|
| 541 |
-
|
| 542 |
-
// Check if the request was successful (status code 2xx)
|
| 543 |
-
if (!response.ok) {
|
| 544 |
-
let errorDetail = `Request failed with status: ${response.status}`;
|
| 545 |
-
try {
|
| 546 |
-
// Try to parse FastAPI's JSON error response
|
| 547 |
-
const errorJson = await response.json();
|
| 548 |
-
errorDetail = errorJson.detail || JSON.stringify(errorJson);
|
| 549 |
-
} catch (e) {
|
| 550 |
-
// If parsing JSON fails, try to get plain text error
|
| 551 |
-
try {
|
| 552 |
-
errorDetail = await response.text();
|
| 553 |
-
} catch (textErr) {
|
| 554 |
-
// Keep the original status code message if text fails too
|
| 555 |
-
}
|
| 556 |
-
}
|
| 557 |
-
// Throw an error with the extracted detail
|
| 558 |
-
throw new Error(errorDetail);
|
| 559 |
-
}
|
| 560 |
-
|
| 561 |
-
// Get the response body as HTML text
|
| 562 |
-
const translatedHtml = await response.text();
|
| 563 |
-
console.log("Translation API Raw HTML Response:", translatedHtml);
|
| 564 |
-
|
| 565 |
-
// --- Update UI ---
|
| 566 |
-
// Display the target language requested by the user
|
| 567 |
-
if(translationTargetLanguage) translationTargetLanguage.textContent = targetLang;
|
| 568 |
-
// Set the innerHTML of the result element to render the HTML tags
|
| 569 |
-
if(translatedText) translatedText.innerHTML = translatedHtml;
|
| 570 |
-
|
| 571 |
-
// Remove the section displaying detected source language as it's not returned
|
| 572 |
-
const sourceLangItem = document.getElementById('detectedSourceLanguage')?.closest('.result-item');
|
| 573 |
-
if (sourceLangItem) {
|
| 574 |
-
sourceLangItem.style.display = 'none'; // Hide the source language display row
|
| 575 |
-
}
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
if(translationResultDisplay) translationResultDisplay.classList.remove('hidden');
|
| 579 |
-
if(initialMessageTranslation) initialMessageTranslation.classList.add('hidden'); // Hide initial message on success
|
| 580 |
-
|
| 581 |
-
} catch (error) {
|
| 582 |
-
console.error('Error during translation fetch/processing:', error);
|
| 583 |
-
let userMessage = `Translation Error: ${error.message}`; // Default message
|
| 584 |
-
|
| 585 |
-
// Provide more specific feedback if possible
|
| 586 |
-
if (error.message.includes("supported")) {
|
| 587 |
-
userMessage = `Translation Error: The target language '${targetLang}' might not be supported by the backend.`;
|
| 588 |
-
} else if (error.message.includes("Failed to fetch")) {
|
| 589 |
-
userMessage = "Translation Error: Network error or CORS issue. Could not reach the translation server. Check console logs.";
|
| 590 |
-
} else if (error.message.startsWith("Request failed with status:")) {
|
| 591 |
-
userMessage = `Translation Error: Server responded with an error (${error.message})`; // Show status code error
|
| 592 |
-
}
|
| 593 |
-
|
| 594 |
-
displayError(translationError, userMessage);
|
| 595 |
-
if(initialMessageTranslation) initialMessageTranslation.classList.remove('hidden'); // Ensure initial message placeholder is visible on error
|
| 596 |
-
} finally {
|
| 597 |
-
showLoadingIndicator(translationLoading, false);
|
| 598 |
}
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
// --- GENDER PREDICTION Functionality ---
|
| 603 |
-
function clearGenderState() { /* ... (Keep implementation) ... */ if(namesInput) namesInput.value = ''; if(genderResultDisplay) genderResultDisplay.innerHTML = ''; if(genderResultDisplay) genderResultDisplay.classList.add('hidden'); if(initialMessageGender) initialMessageGender.classList.remove('hidden'); hideError(genderError); showLoadingIndicator(genderLoading, false); console.log("Gender Cleared"); }
|
| 604 |
-
function displayGenderResults(data) { /* ... (Keep implementation) ... */ if (!genderResultDisplay) return; genderResultDisplay.innerHTML = ''; if (!data || !data.predictions || data.predictions.length === 0) { genderResultDisplay.innerHTML = "<p class='text-center text-gray-500 dark:text-gray-400'>No prediction results.</p>"; return; } let resultsHTML = ''; data.predictions.forEach(p => { const maleProb = (p.male_probability !== null && p.male_probability !== undefined) ? p.male_probability.toFixed(2) : 'N/A'; const confidence = (p.confidence !== null && p.confidence !== undefined) ? p.confidence.toFixed(2) : 'N/A'; resultsHTML += `<div class="result-item"><div class="result-label">Name:</div><div class="result-value">${p.name || 'N/A'}</div><div class="result-label">Predicted:</div><div class="result-value">${p.predicted_gender || 'N/A'}</div><div class="result-label">Male Prob:</div><div class="result-value">${maleProb}</div><div class="result-label">Confidence:</div><div class="result-value">${confidence}</div></div>`; }); genderResultDisplay.innerHTML = resultsHTML; genderResultDisplay.classList.remove('hidden'); if(initialMessageGender) initialMessageGender.classList.add('hidden'); }
|
| 605 |
-
if(predictGenderButton) {
|
| 606 |
-
predictGenderButton.addEventListener('click', async () => { /* ... (Keep implementation) ... */
|
| 607 |
-
const namesString = namesInput.value.trim(); const names = namesString.split(',').map(name => name.trim()).filter(Boolean); hideError(genderError); showLoadingIndicator(genderLoading, true); if(genderResultDisplay) genderResultDisplay.classList.add('hidden'); if(initialMessageGender) initialMessageGender.classList.remove('hidden'); if (names.length === 0) { displayError(genderError, "Please enter names."); showLoadingIndicator(genderLoading, false); return; }
|
| 608 |
-
try { const data = await authenticatedFetch(GENDER_PREDICT_ENDPOINT, { method: 'POST', body: JSON.stringify({ names: names, threshold: 0.5 }) }); console.log("Gender Response:", data); displayGenderResults(data); } catch (error) { console.error("Gender Prediction error:", error); displayError(genderError, `Prediction Error: ${error.message}`); } finally { showLoadingIndicator(genderLoading, false); }
|
| 609 |
-
});
|
| 610 |
}
|
| 611 |
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 626 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 627 |
</script>
|
| 628 |
|
| 629 |
</body>
|
|
|
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en" class=""> <!-- Start without 'dark' initially -->
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Home - AI Text Tools</title>
|
| 7 |
<!-- Tailwind CSS via CDN -->
|
| 8 |
<script src="https://cdn.tailwindcss.com/3.4.1"></script>
|
| 9 |
<script>
|
|
|
|
| 39 |
/* Base Dark Mode */
|
| 40 |
html.dark body { background-color: #1a202c; color: #e2e8f0; }
|
| 41 |
html.dark header, html.dark footer { background-color: #2d3748; color: #e2e8f0; }
|
| 42 |
+
html.dark .card { background-color: #2d3748; border-color: #4a5568; }
|
| 43 |
+
html.dark h1, html.dark h2, html.dark h3, html.dark p, html.dark span, html.dark li, html.dark label, html.dark small, html.dark strong, html.dark button:not(.bg-red-500):not(.bg-green-600), html.dark a { color: #e2e8f0; }
|
| 44 |
+
html.dark a.text-primary { color: #60a5fa; }
|
| 45 |
html.dark .text-gray-600, html.dark .text-gray-700, html.dark .text-gray-800, html.dark .text-gray-900 { color: #a0aec0; }
|
| 46 |
html.dark .text-gray-500, html.dark .text-gray-400 { color: #718096; }
|
|
|
|
| 47 |
html.dark .bg-white { background-color: #2d3748 !important; }
|
| 48 |
html.dark .bg-gray-50 { background-color: #1a202c !important; }
|
| 49 |
html.dark .bg-gray-100 { background-color: #374151 !important; }
|
| 50 |
html.dark .border-gray-100, html.dark .border-gray-200 { border-color: #4a5568 !important; }
|
| 51 |
+
/* Dark mode Forms/Inputs */
|
|
|
|
| 52 |
html.dark input, html.dark textarea { background-color: #1f2937; border-color: #4b5563; color: #e5e7eb; }
|
| 53 |
html.dark input::placeholder, html.dark textarea::placeholder { color: #6b7280; }
|
| 54 |
+
/* Style for step list items */
|
| 55 |
+
.step-item { display: flex; align-items: flex-start; gap: 0.75rem; margin-bottom: 1rem; }
|
| 56 |
+
.step-icon { flex-shrink: 0; width: 1.75rem; height: 1.75rem; display: inline-flex; align-items: center; justify-content: center; background-color: #e8f0fe; color: #1a73e8; border-radius: 50%; font-weight: 600; font-size: 0.875rem; }
|
| 57 |
+
html.dark .step-icon { background-color: #374151; color: #60a5fa;}
|
| 58 |
+
.step-content { margin-top: 0.1rem; }
|
| 59 |
+
/* Video Styling */
|
| 60 |
+
.video-container video {
|
| 61 |
+
display: block; /* Remove extra space below video */
|
| 62 |
+
max-width: 100%; /* Ensure video scales down */
|
| 63 |
+
height: auto; /* Maintain aspect ratio */
|
| 64 |
+
border-radius: 8px; /* Match card rounding */
|
| 65 |
+
border: 1px solid #e5e7eb; /* Subtle border */
|
| 66 |
+
background-color: #000; /* Black background for letterboxing or loading */
|
| 67 |
+
}
|
| 68 |
+
html.dark .video-container video {
|
| 69 |
+
border-color: #4b5563; /* Dark mode border */
|
| 70 |
+
}
|
| 71 |
+
/* Ensure nav link styling is consistent */
|
| 72 |
+
.nav-link { transition: color 0.2s ease-in-out; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
</style>
|
| 74 |
</head>
|
| 75 |
<body class="bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-200">
|
|
|
|
| 78 |
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 79 |
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 80 |
<div class="flex items-center">
|
| 81 |
+
<a href="index.html" class="text-2xl font-['Pacifico'] text-primary dark:text-blue-400 mr-8" data-lang-en="AI Text Tools" data-lang-hi="एआई टेक्स्ट उपकरण">AI Text Tools</a>
|
| 82 |
+
<!-- Navigation Bar -->
|
|
|
|
| 83 |
<nav id="mainNav" class="hidden md:flex space-x-6">
|
| 84 |
+
<a href="index.html" class="nav-link text-primary dark:text-blue-400 font-medium" data-lang-en="Home" data-lang-hi="होम">Home</a>
|
| 85 |
+
<a href="recognizer.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a>
|
| 86 |
+
<a href="examples.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</a>
|
| 87 |
<a href="technology.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Technology" data-lang-hi="तकनीक">Technology</a>
|
| 88 |
+
<a href="about.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="About" data-lang-hi="बारे में">About</a>
|
| 89 |
+
<a href="contact.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</a>
|
| 90 |
+
<a href="feedback.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Feedback" data-lang-hi="प्रतिक्रिया">Feedback</a>
|
| 91 |
</nav>
|
| 92 |
</div>
|
| 93 |
+
<!-- Right side header items -->
|
| 94 |
<div class="flex items-center space-x-4">
|
| 95 |
<div class="items-center space-x-2 hidden md:flex"> <span class="text-sm text-gray-600 dark:text-gray-400">EN</span> <label class="custom-switch"><input type="checkbox" id="languageToggle" class="custom-switch-input"><span class="custom-switch-slider"></span></label> <span class="text-sm text-gray-600 dark:text-gray-400 hindi-font">हिंदी</span> </div>
|
| 96 |
<div class="items-center space-x-2 hidden md:flex"> <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-sun-line"></i></span> <label class="custom-switch"><input type="checkbox" id="themeToggle" class="custom-switch-input"><span class="custom-switch-slider"></span></label> <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-moon-line"></i></span> </div>
|
|
|
|
|
|
|
| 97 |
<button class="md:hidden w-10 h-10 flex items-center justify-center" aria-label="Toggle Menu"><i class="ri-menu-line text-gray-600 dark:text-gray-300 text-xl"></i></button>
|
| 98 |
</div>
|
| 99 |
</div>
|
| 100 |
</header>
|
| 101 |
|
| 102 |
+
<!-- ===== Main Content Area ===== -->
|
| 103 |
+
<main class="flex-grow container mx-auto px-4 py-12">
|
| 104 |
+
|
| 105 |
+
<!-- Hero Section -->
|
| 106 |
+
<section class="text-center mb-16">
|
| 107 |
+
<h1 class="text-4xl md:text-5xl font-bold text-gray-800 dark:text-white mb-4 leading-tight" data-lang-en="Unlock Text Insights with AI" data-lang-hi="एआई के साथ टेक्स्ट अंतर्दृष्टि अनलॉक करें">
|
| 108 |
+
Unlock Text Insights with AI
|
| 109 |
+
</h1>
|
| 110 |
+
<p class="text-lg text-gray-600 dark:text-gray-400 max-w-3xl mx-auto mb-8" data-lang-en="Easily recognize Hindi text from images, translate languages, and predict gender from names using our powerful AI tools." data-lang-hi="हमारे शक्तिशाली एआई उपकरणों का उपयोग करके आसानी से छवियों से हिंदी पाठ पहचानें, भाषाओं का अनुवाद करें, और नामों से लिंग की भविष्यवाणी करें।">
|
| 111 |
+
Easily recognize Hindi text from images, translate languages, and predict gender from names using our powerful AI tools.
|
| 112 |
+
</p>
|
| 113 |
+
<a href="index.html" class="inline-flex items-center justify-center gap-2 px-8 py-3 border border-transparent text-base font-medium rounded-button text-white bg-primary hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary dark:focus:ring-offset-gray-800 transition">
|
| 114 |
+
<i class="ri-camera-lens-line"></i>
|
| 115 |
+
<span data-lang-en="Get Started with OCR" data-lang-hi="ओसीआर के साथ आरंभ करें">Get Started with OCR</span>
|
| 116 |
+
</a>
|
| 117 |
+
</section>
|
| 118 |
+
|
| 119 |
+
<!-- How to Use Section -->
|
| 120 |
+
<section class="mb-16">
|
| 121 |
+
<div class="max-w-3xl mx-auto bg-white dark:bg-gray-800 p-8 rounded-xl shadow-lg border border-gray-100 dark:border-gray-700 card">
|
| 122 |
+
<h2 class="text-2xl font-semibold text-center text-gray-800 dark:text-white mb-6" data-lang-en="How to Recognize Hindi Text" data-lang-hi="हिंदी पाठ कैसे पहचानें">
|
| 123 |
+
How to Recognize Hindi Text
|
| 124 |
+
</h2>
|
| 125 |
+
<ol class="space-y-4">
|
| 126 |
+
<li class="step-item">
|
| 127 |
+
<span class="step-icon">1</span>
|
| 128 |
+
<div class="step-content">
|
| 129 |
+
<strong data-lang-en="Login/Sign Up:" data-lang-hi="लॉग इन / साइन अप करें:">Login/Sign Up:</strong>
|
| 130 |
+
<span data-lang-en=" Access the application by logging in or creating a new account." data-lang-hi=" लॉग इन करके या नया खाता बनाकर एप्लिकेशन तक पहुंचें।"> Access the application by logging in or creating a new account.</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
</div>
|
| 132 |
+
</li>
|
| 133 |
+
<li class="step-item">
|
| 134 |
+
<span class="step-icon">2</span>
|
| 135 |
+
<div class="step-content">
|
| 136 |
+
<strong data-lang-en="Go to Recognize:" data-lang-hi="पहचानें पर जाएं:">Go to Recognize:</strong>
|
| 137 |
+
<span data-lang-en=" Navigate to the 'Recognize' or 'Hindi OCR' tab/page." data-lang-hi=" 'पहचानें' या 'हिंदी ओसीआर' टैब/पेज पर नेविगेट करें।"> Navigate to the 'Recognize' or 'Hindi OCR' tab/page.</span>
|
| 138 |
+
</div>
|
| 139 |
+
</li>
|
| 140 |
+
<li class="step-item">
|
| 141 |
+
<span class="step-icon">3</span>
|
| 142 |
+
<div class="step-content">
|
| 143 |
+
<strong data-lang-en="Upload or Select:" data-lang-hi="अपलोड करें या चुनें:">Upload or Select:</strong>
|
| 144 |
+
<span data-lang-en=" Choose 'Choose File' to upload your Hindi word image (PNG, JPG) or click on one of the provided examples." data-lang-hi=" अपनी हिंदी शब्द छवि (PNG, JPG) अपलोड करने के लिए 'फ़ाइल चुनें' चुनें या दिए गए उदाहरणों में से किसी एक पर क्लिक करें।"> Choose 'Choose File' to upload your Hindi word image (PNG, JPG) or click on one of the provided examples.</span>
|
| 145 |
+
</div>
|
| 146 |
+
</li>
|
| 147 |
+
<li class="step-item">
|
| 148 |
+
<span class="step-icon">4</span>
|
| 149 |
+
<div class="step-content">
|
| 150 |
+
<strong data-lang-en="Recognize Image:" data-lang-hi="छवि पहचानें:">Recognize Image:</strong>
|
| 151 |
+
<span data-lang-en=" Click the 'Recognize' button to start the analysis." data-lang-hi=" विश्लेषण शुरू करने के लिए 'पहचानें' बटन पर क्लिक करें।"> Click the 'Recognize' button to start the analysis.</span>
|
| 152 |
+
</div>
|
| 153 |
+
</li>
|
| 154 |
+
<li class="step-item">
|
| 155 |
+
<span class="step-icon">5</span>
|
| 156 |
+
<div class="step-content">
|
| 157 |
+
<strong data-lang-en="View Results:" data-lang-hi="परिणाम देखें:">View Results:</strong>
|
| 158 |
+
<span data-lang-en=" The recognized Hindi text and word detection preview will appear in the results area." data-lang-hi=" पहचाना गया हिंदी पाठ और शब्द पहचान पूर्वावलोकन परिणाम क्षेत्र में दिखाई देगा।"> The recognized Hindi text and word detection preview will appear in the results area.</span>
|
| 159 |
</div>
|
| 160 |
+
</li>
|
| 161 |
+
</ol>
|
| 162 |
+
</div>
|
| 163 |
+
</section>
|
| 164 |
+
|
| 165 |
+
<!-- Other Features Section -->
|
| 166 |
+
<section class="mb-16">
|
| 167 |
+
<h2 class="text-3xl font-bold text-center text-gray-800 dark:text-white mb-10" data-lang-en="Explore More Tools" data-lang-hi="और उपकरण एक्सप्लोर करें">
|
| 168 |
+
Explore More Tools
|
| 169 |
+
</h2>
|
| 170 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto">
|
| 171 |
+
<!-- Translation Feature Card -->
|
| 172 |
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-lg border border-gray-100 dark:border-gray-700 card text-center md:text-left">
|
| 173 |
+
<div class="flex justify-center md:justify-start mb-4">
|
| 174 |
+
<i class="ri-translate-2 text-4xl text-primary dark:text-blue-400"></i>
|
| 175 |
</div>
|
| 176 |
+
<h3 class="text-xl font-semibold mb-2" data-lang-en="Text Translation" data-lang-hi="पाठ अनुवाद">Text Translation</h3>
|
| 177 |
+
<p class="text-gray-600 dark:text-gray-400 mb-4" data-lang-en="Translate text between various languages. Supports auto-detection of source language." data-lang-hi="विभिन्न भाषाओं के बीच पाठ का अनुवाद करें। स्रोत भाषा का स्वतः पता लगाने का समर्थन करता है।">
|
| 178 |
+
Translate text between various languages. Supports auto-detection of source language.
|
| 179 |
+
</p>
|
| 180 |
+
<a href="index.html#translationContent" class="text-primary dark:text-blue-400 hover:underline font-medium inline-flex items-center gap-1" data-lang-en="Go to Translator" data-lang-hi="अनुवादक पर जाएँ">
|
| 181 |
+
Go to Translator <i class="ri-arrow-right-line"></i>
|
| 182 |
+
</a>
|
| 183 |
</div>
|
| 184 |
+
<!-- Gender Prediction Feature Card -->
|
| 185 |
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-lg border border-gray-100 dark:border-gray-700 card text-center md:text-left">
|
| 186 |
+
<div class="flex justify-center md:justify-start mb-4">
|
| 187 |
+
<i class="ri-men-line text-4xl text-primary dark:text-blue-400"></i><i class="ri-women-line text-4xl text-primary dark:text-blue-400 -ml-2"></i>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
</div>
|
| 189 |
+
<h3 class="text-xl font-semibold mb-2" data-lang-en="Gender Prediction" data-lang-hi="लिंग भविष्यवाणी">Gender Prediction</h3>
|
| 190 |
+
<p class="text-gray-600 dark:text-gray-400 mb-4" data-lang-en="Predict the likely gender associated with one or more names." data-lang-hi="एक या अधिक नामों से जुड़े संभावित लिंग की भविष्यवाणी करें।">
|
| 191 |
+
Predict the likely gender associated with one or more names.
|
| 192 |
+
</p>
|
| 193 |
+
<a href="index.html#genderContent" class="text-primary dark:text-blue-400 hover:underline font-medium inline-flex items-center gap-1" data-lang-en="Go to Predictor" data-lang-hi="भविष्यवक्ता पर जाएँ">
|
| 194 |
+
Go to Predictor <i class="ri-arrow-right-line"></i>
|
| 195 |
+
</a>
|
| 196 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
</div>
|
| 198 |
+
</section>
|
| 199 |
+
|
| 200 |
+
<!-- Tutorial Video Section -->
|
| 201 |
+
<section class="mb-12">
|
| 202 |
+
<div class="max-w-3xl mx-auto bg-white dark:bg-gray-800 p-8 rounded-xl shadow-lg border border-gray-100 dark:border-gray-700 card">
|
| 203 |
+
<h2 class="text-2xl font-semibold text-center text-gray-800 dark:text-white mb-4" data-lang-en="Watch the Tutorial" data-lang-hi="ट्यूटोरियल देखें">
|
| 204 |
+
Watch the Tutorial
|
| 205 |
+
</h2>
|
| 206 |
+
<p class="text-center text-gray-600 dark:text-gray-400 mb-6" data-lang-en="See the AI Text Tools in action with this step-by-step video guide." data-lang-hi="इस चरण-दर-चरण वीडियो गाइड के साथ एआई टेक्स्ट टूल को क्रियान्वित देखें।">
|
| 207 |
+
See the AI Text Tools in action with this step-by-step video guide.
|
| 208 |
+
</p>
|
| 209 |
+
<!-- Video Player Container -->
|
| 210 |
+
<div class="video-container">
|
| 211 |
+
<video class="w-full" controls>
|
| 212 |
+
<!-- Ensure the path '/sample/tutorial.mp4' is correct relative to your web server's root -->
|
| 213 |
+
<!-- Or use a relative path like 'sample/tutorial.mp4' if the folder is next to home.html -->
|
| 214 |
+
<source src="/samples/turotial.mp4" type="video/mp4">
|
| 215 |
+
|
| 216 |
+
<!-- Optional: Add sources for other formats like WebM or Ogg for wider compatibility -->
|
| 217 |
+
<!-- <source src="/sample/tutorial.webm" type="video/webm"> -->
|
| 218 |
+
<!-- <source src="/sample/tutorial.ogg" type="video/ogg"> -->
|
| 219 |
+
|
| 220 |
+
<!-- Fallback text for browsers that don't support the video tag -->
|
| 221 |
+
<span data-lang-en="Your browser does not support the video tag. Please update your browser or use a different one to watch the tutorial." data-lang-hi="आपका ब्राउज़र वीडियो टैग का समर्थन नहीं करता है। कृपया ट्यूटोरियल देखने के लिए अपना ब्राउज़र अपडेट करें या किसी भिन्न ब्राउज़र का उपयोग करें।">
|
| 222 |
+
Your browser does not support the video tag. Please update your browser or use a different one to watch the tutorial.
|
| 223 |
+
</span>
|
| 224 |
+
</video>
|
| 225 |
+
</div>
|
| 226 |
+
<!-- End Video Player Container -->
|
| 227 |
</div>
|
| 228 |
+
</section>
|
| 229 |
|
| 230 |
</main>
|
| 231 |
|
| 232 |
<!-- Footer -->
|
| 233 |
+
<footer class="bg-gray-900 text-gray-400 py-12 mt-16">
|
| 234 |
+
<div class="container mx-auto px-4">
|
| 235 |
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
| 236 |
+
<div>
|
| 237 |
+
<a href="index.html" class="text-2xl font-['Pacifico'] text-white mb-4 inline-block" data-lang-en="AI Text Tools" data-lang-hi="एआई टेक्स्ट उपकरण">AI Text Tools</a>
|
| 238 |
+
<p class="mb-4 text-sm" data-lang-en="AI-powered tools for text analysis and processing." data-lang-hi="पाठ विश्लेषण और प्रसंस्करण के लिए एआई-संचालित उपकरण।">AI-powered tools for text analysis.</p>
|
| 239 |
+
<div class="flex space-x-4">
|
| 240 |
+
<a href="#" class="hover:text-white" title="GitHub (Placeholder)"><i class="ri-github-fill"></i></a>
|
| 241 |
+
<a href="#" class="hover:text-white" title="LinkedIn (Placeholder)"><i class="ri-linkedin-box-fill"></i></a>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
<div>
|
| 245 |
+
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Quick Links" data-lang-hi="त्वरित लिंक्स">Quick Links</h3>
|
| 246 |
+
<ul class="space-y-2 text-sm">
|
| 247 |
+
<li><a href="home.html" class="hover:text-white" data-lang-en="Home" data-lang-hi="होम">Home</a></li>
|
| 248 |
+
<li><a href="index.html" class="hover:text-white" data-lang-en="Tools" data-lang-hi="उपकरण">Tools</a></li>
|
| 249 |
+
<li><a href="about.html" class="hover:text-white" data-lang-en="About" data-lang-hi="ब��रे में">About</a></li>
|
| 250 |
+
<li><a href="contact.html" class="hover:text-white" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</a></li>
|
| 251 |
+
<li><a href="feedback.html" class="hover:text-white" data-lang-en="Feedback" data-lang-hi="प्रतिक्रिया">Feedback</a></li>
|
| 252 |
+
</ul>
|
| 253 |
+
</div>
|
| 254 |
+
<div>
|
| 255 |
+
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Resources" data-lang-hi="संसाधन">Resources</h3>
|
| 256 |
+
<ul class="space-y-2 text-sm">
|
| 257 |
+
<li><a href="#" class="hover:text-white" data-lang-en="API Docs (TBD)" data-lang-hi="एपीआई डॉक्स (TBD)">API Docs (TBD)</a></li>
|
| 258 |
+
<li><a href="#" class="hover:text-white" data-lang-en="GitHub Repo" data-lang-hi="गिटहब रेपो">GitHub Repo</a></li>
|
| 259 |
+
</ul>
|
| 260 |
+
</div>
|
| 261 |
+
<div>
|
| 262 |
+
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</h3>
|
| 263 |
+
<ul class="space-y-2 text-sm">
|
| 264 |
+
<li class="flex items-start"><i class="ri-mail-line mt-1 mr-2"></i><span>your-email@example.com</span></li>
|
| 265 |
+
</ul>
|
| 266 |
+
</div>
|
| 267 |
+
</div>
|
| 268 |
+
<div class="border-t border-gray-700 mt-8 pt-8 text-center">
|
| 269 |
+
<p class="text-sm">© 2024 Your Name/Org | AI Text Tools Project</p>
|
| 270 |
+
</div>
|
| 271 |
+
</div>
|
| 272 |
</footer>
|
| 273 |
|
| 274 |
<!-- Base Theme/Language Script -->
|
| 275 |
<script>
|
| 276 |
+
const themeToggle = document.getElementById('themeToggle');
|
| 277 |
+
const htmlElement = document.documentElement;
|
| 278 |
+
function applyTheme(isDark) { if (isDark) { htmlElement.classList.add('dark'); if(themeToggle) themeToggle.checked = true; } else { htmlElement.classList.remove('dark'); if(themeToggle) themeToggle.checked = false; } }
|
| 279 |
+
const languageToggle = document.getElementById('languageToggle');
|
| 280 |
+
const langElements = document.querySelectorAll('[data-lang-en]');
|
| 281 |
+
const placeholderElements = document.querySelectorAll('[data-lang-en-placeholder]');
|
| 282 |
+
|
| 283 |
+
function applyLanguage(lang) {
|
| 284 |
+
htmlElement.setAttribute('lang', lang);
|
| 285 |
+
langElements.forEach(el => {
|
| 286 |
+
const text = el.getAttribute(`data-lang-${lang}`);
|
| 287 |
+
if (text !== null) { // Check if attribute exists
|
| 288 |
+
const icon = el.querySelector('i');
|
| 289 |
+
if (icon && el.childNodes.length > 1) {
|
| 290 |
+
let updated = false;
|
| 291 |
+
el.childNodes.forEach(node => {
|
| 292 |
+
if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '') {
|
| 293 |
+
node.textContent = ` ${text} `;
|
| 294 |
+
updated = true;
|
| 295 |
+
}
|
| 296 |
+
});
|
| 297 |
+
if (!updated) {
|
| 298 |
+
const currentText = el.getAttribute(`data-lang-${lang === 'en' ? 'hi' : 'en'}`);
|
| 299 |
+
if (currentText) el.innerHTML = el.innerHTML.replace(currentText.trim(), text);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
}
|
| 301 |
+
} else {
|
| 302 |
+
el.textContent = text;
|
| 303 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
}
|
| 305 |
+
});
|
| 306 |
+
placeholderElements.forEach(el => {
|
| 307 |
+
const placeholderText = el.getAttribute(`data-lang-${lang}-placeholder`);
|
| 308 |
+
if (placeholderText !== null) {
|
| 309 |
+
el.placeholder = placeholderText;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
}
|
| 311 |
+
});
|
| 312 |
+
if (languageToggle) languageToggle.checked = (lang === 'hi');
|
| 313 |
+
localStorage.setItem('language', lang);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
}
|
| 315 |
|
| 316 |
+
function initializeSettings() {
|
| 317 |
+
const savedLang = localStorage.getItem('language') || 'en';
|
| 318 |
+
const prefersDark = localStorage.getItem('theme') === 'dark' || (localStorage.getItem('theme') === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
| 319 |
+
applyTheme(prefersDark);
|
| 320 |
+
applyLanguage(savedLang);
|
| 321 |
+
|
| 322 |
+
if (languageToggle) {
|
| 323 |
+
languageToggle.addEventListener('change', (event) => {
|
| 324 |
+
const newLang = event.target.checked ? 'hi' : 'en';
|
| 325 |
+
applyLanguage(newLang);
|
| 326 |
+
});
|
| 327 |
+
} else { console.warn("Language toggle button not found."); }
|
| 328 |
+
|
| 329 |
+
if (themeToggle) {
|
| 330 |
+
themeToggle.addEventListener('change', (event) => {
|
| 331 |
+
const isDark = event.target.checked;
|
| 332 |
+
applyTheme(isDark);
|
| 333 |
+
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
| 334 |
+
});
|
| 335 |
+
} else { console.warn("Theme toggle button not found."); }
|
| 336 |
+
}
|
| 337 |
|
| 338 |
+
// Initialize theme and language settings when the DOM is fully loaded
|
| 339 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 340 |
+
initializeSettings();
|
| 341 |
+
console.log("Home page DOM Loaded and Initialized.");
|
| 342 |
+
});
|
| 343 |
</script>
|
| 344 |
|
| 345 |
</body>
|
recognizer.html
CHANGED
|
@@ -1,32 +1,20 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
<html lang="en" class=""> <!-- Start without 'dark' initially -->
|
| 4 |
<head>
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
-
<title>
|
| 8 |
<!-- Tailwind CSS via CDN -->
|
| 9 |
<script src="https://cdn.tailwindcss.com/3.4.1"></script>
|
| 10 |
<script>
|
| 11 |
-
//
|
| 12 |
tailwind.config = {
|
| 13 |
-
darkMode: 'class',
|
| 14 |
theme: {
|
| 15 |
extend: {
|
| 16 |
-
colors: {
|
| 17 |
-
|
| 18 |
-
secondary: '#e8f0fe',
|
| 19 |
-
// Define dark mode colors if needed, or rely on Tailwind's defaults
|
| 20 |
-
// dark: {
|
| 21 |
-
// background: '#1a202c',
|
| 22 |
-
// text: '#e2e8f0',
|
| 23 |
-
// }
|
| 24 |
-
},
|
| 25 |
-
borderRadius: {
|
| 26 |
-
'none': '0px', 'sm': '4px', DEFAULT: '8px', 'md': '12px',
|
| 27 |
-
'lg': '16px', 'xl': '20px', '2xl': '24px', '3xl': '32px',
|
| 28 |
-
'full': '9999px', 'button': '8px'
|
| 29 |
-
}
|
| 30 |
}
|
| 31 |
}
|
| 32 |
}
|
|
@@ -40,399 +28,605 @@
|
|
| 40 |
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.2.0/fonts/remixicon.css" rel="stylesheet">
|
| 41 |
<!-- Custom Styles -->
|
| 42 |
<style>
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
}
|
| 49 |
-
|
| 50 |
-
.
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
.
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
.
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
.
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
.
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
.
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
.
|
| 97 |
-
|
| 98 |
-
}
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
/* Example: Add a specific style for the example image containers if needed beyond grid/aspect ratio */
|
| 111 |
-
.example-image-container {
|
| 112 |
-
/* Custom styles here */
|
| 113 |
-
/* aspect-ratio: 3 / 2; */ /* Tailwind handles aspect ratio now */
|
| 114 |
-
}
|
| 115 |
-
|
| 116 |
-
.example-image {
|
| 117 |
-
/* Custom image styles here */
|
| 118 |
-
}
|
| 119 |
-
/* Add base dark mode styles */
|
| 120 |
-
html.dark body {
|
| 121 |
-
background-color: #1a202c; /* Example dark background */
|
| 122 |
-
color: #e2e8f0; /* Example dark text */
|
| 123 |
-
}
|
| 124 |
-
html.dark header, html.dark footer { /* Adjust header/footer */
|
| 125 |
-
background-color: #2d3748;
|
| 126 |
-
color: #e2e8f0;
|
| 127 |
-
}
|
| 128 |
-
html.dark .card { /* Adjust card styles */
|
| 129 |
-
background-color: #2d3748;
|
| 130 |
-
border-color: #4a5568;
|
| 131 |
-
}
|
| 132 |
-
html.dark h1, html.dark h2, html.dark h3, html.dark h4, html.dark h5, html.dark h6, html.dark p, html.dark span, html.dark li, html.dark label, html.dark small {
|
| 133 |
-
/* Adjust general text elements */
|
| 134 |
-
color: #e2e8f0; /* Light text */
|
| 135 |
-
}
|
| 136 |
-
html.dark .text-gray-600, html.dark .text-gray-700, html.dark .text-gray-800, html.dark .text-gray-900 {
|
| 137 |
-
color: #a0aec0; /* Lighter gray for dark mode */
|
| 138 |
-
}
|
| 139 |
-
html.dark .text-muted {
|
| 140 |
-
color: #718096;
|
| 141 |
-
}
|
| 142 |
-
html.dark .bg-white { background-color: #2d3748 !important; }
|
| 143 |
-
html.dark .bg-gray-50 { background-color: #1a202c !important; }
|
| 144 |
-
html.dark .bg-gray-100 { background-color: #2d3748 !important; }
|
| 145 |
-
html.dark .border-gray-100, html.dark .border-gray-200 { border-color: #4a5568 !important; }
|
| 146 |
-
html.dark .hover\:bg-gray-50:hover { background-color: #4a5568 !important; }
|
| 147 |
-
html.dark .navbar-dark .navbar-brand, html.dark .navbar-dark .nav-link { color: #fff; }
|
| 148 |
-
html.dark .navbar-dark .nav-link.active { color: #fff; font-weight: bold; }
|
| 149 |
-
|
| 150 |
|
| 151 |
</style>
|
| 152 |
-
|
| 153 |
</head>
|
| 154 |
-
<!-- Add dark mode background/text for body base -->
|
| 155 |
<body class="bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-200">
|
| 156 |
|
| 157 |
<!-- Header -->
|
| 158 |
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 159 |
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 160 |
<div class="flex items-center">
|
| 161 |
-
|
| 162 |
-
<a href="/" class="text-2xl font-['Pacifico'] text-primary mr-8" data-lang-en="
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
<a href="
|
| 171 |
-
|
| 172 |
-
data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</a>
|
| 173 |
-
<a href="/technology.html"
|
| 174 |
-
class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
|
| 175 |
-
data-lang-en="Technology" data-lang-hi="तकनीक">Technology</a>
|
| 176 |
-
<a href="/about.html"
|
| 177 |
-
class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
|
| 178 |
-
data-lang-en="About" data-lang-hi="बारे में">About</a>
|
| 179 |
</nav>
|
| 180 |
</div>
|
| 181 |
-
<!-- Right side header items -->
|
| 182 |
<div class="flex items-center space-x-4">
|
| 183 |
-
|
| 184 |
-
<div class="items-center space-x-2 hidden md:flex">
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
<input type="checkbox" id="languageToggle" class="custom-switch-input">
|
| 189 |
-
<span class="custom-switch-slider"></span>
|
| 190 |
-
</label>
|
| 191 |
-
<span class="text-sm text-gray-600 dark:text-gray-400 hindi-font">हिंदी</span>
|
| 192 |
-
</div>
|
| 193 |
-
<!-- Theme Switch -->
|
| 194 |
-
<div class="items-center space-x-2 hidden md:flex">
|
| 195 |
-
<span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-sun-line"></i></span>
|
| 196 |
-
<label class="custom-switch">
|
| 197 |
-
<!-- *** Add ID: themeToggle *** -->
|
| 198 |
-
<input type="checkbox" id="themeToggle" class="custom-switch-input">
|
| 199 |
-
<span class="custom-switch-slider"></span>
|
| 200 |
-
</label>
|
| 201 |
-
<span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-moon-line"></i></span>
|
| 202 |
-
</div>
|
| 203 |
-
<!-- User/Notification Icons (Keep as placeholders) -->
|
| 204 |
-
<div class="w-10 h-10 flex items-center justify-center bg-gray-100 dark:bg-gray-700 rounded-full cursor-pointer" title="User Profile (Placeholder)">
|
| 205 |
-
<i class="ri-user-line text-gray-600 dark:text-gray-300"></i>
|
| 206 |
-
</div>
|
| 207 |
-
<div class="w-10 h-10 flex items-center justify-center relative cursor-pointer" title="Notifications (Placeholder)">
|
| 208 |
-
<i class="ri-notification-3-line text-gray-600 dark:text-gray-300"></i>
|
| 209 |
-
<span class="absolute top-0 right-0 bg-red-500 text-white text-xs w-5 h-5 flex items-center justify-center rounded-full">3</span>
|
| 210 |
-
</div>
|
| 211 |
-
<!-- Mobile Menu Button (Placeholder) -->
|
| 212 |
-
<button class="md:hidden w-10 h-10 flex items-center justify-center" aria-label="Toggle Menu">
|
| 213 |
-
<i class="ri-menu-line text-gray-600 dark:text-gray-300 text-xl"></i>
|
| 214 |
-
</button>
|
| 215 |
</div>
|
| 216 |
</div>
|
| 217 |
</header>
|
| 218 |
|
| 219 |
-
<!--
|
| 220 |
-
<
|
| 221 |
-
|
| 222 |
-
<div class="
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
<!-- Input Column -->
|
| 226 |
-
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700">
|
| 227 |
-
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4 border-b border-gray-200 dark:border-gray-600 pb-2">
|
| 228 |
-
<i class="ri-image-add-line mr-2 text-primary align-bottom"></i>
|
| 229 |
-
<span data-lang-en="Input Image" data-lang-hi="इनपुट छवि">Input Image</span>
|
| 230 |
-
</h2>
|
| 231 |
-
<p class="text-gray-600 dark:text-gray-400 text-sm mb-4" data-lang-en="Upload an image (PNG, JPG, JPEG) or select an example below." data-lang-hi="एक छवि (PNG, JPG, JPEG) अपलोड करें या नीचे दिए गए उदाहरण का चयन करें।">
|
| 232 |
-
Upload an image (PNG, JPG, JPEG) or select an example below.
|
| 233 |
-
</p>
|
| 234 |
-
|
| 235 |
-
<!-- File Input -->
|
| 236 |
-
<input type="file" id="imageUpload" accept="image/png, image/jpeg, image/jpg" class="hidden"/>
|
| 237 |
-
<div class="flex items-center mb-4">
|
| 238 |
-
<label for="imageUpload" class="bg-primary text-white px-4 py-2 rounded-button cursor-pointer hover:bg-blue-700 transition-colors text-sm inline-flex items-center mr-4 whitespace-nowrap">
|
| 239 |
-
<i class="ri-upload-2-line mr-2"></i>
|
| 240 |
-
<span data-lang-en="Choose File" data-lang-hi="फ़ाइल चुनें">Choose File</span>
|
| 241 |
-
</label>
|
| 242 |
-
<span id="fileName" class="text-gray-500 dark:text-gray-400 text-sm italic truncate" data-lang-en="No file chosen" data-lang-hi="कोई फ़ाइल नहीं चुनी गई">No file chosen</span>
|
| 243 |
-
</div>
|
| 244 |
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
<div id="examplesContainer" class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-2 mb-4">
|
| 248 |
-
<div class="col-span-full">
|
| 249 |
-
<span class="text-gray-500 dark:text-gray-400 text-sm" data-lang-en="Loading examples..." data-lang-hi="उदाहरण लोड हो रहे हैं...">Loading examples...</span>
|
| 250 |
-
</div>
|
| 251 |
-
</div>
|
| 252 |
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
<
|
| 257 |
-
|
| 258 |
-
</
|
| 259 |
-
</
|
| 260 |
</div>
|
| 261 |
|
| 262 |
-
<!--
|
| 263 |
-
<div
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
<
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
-
|
| 278 |
-
<div id="loadingIndicator" class="flex items-center justify-center my-4 text-blue-600 dark:text-blue-400 hidden" style="min-height: 40px;">
|
| 279 |
-
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-blue-600 dark:text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
| 280 |
-
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
| 281 |
-
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
| 282 |
-
</svg>
|
| 283 |
-
<span data-lang-en="Processing... Please wait." data-lang-hi="संसाधित हो रहा है... कृपया प्रतीक्षा करें।">Processing... Please wait.</span>
|
| 284 |
</div>
|
| 285 |
|
| 286 |
-
<!--
|
| 287 |
-
<div id="
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
</div>
|
| 292 |
|
| 293 |
-
<!--
|
| 294 |
-
<div id="
|
| 295 |
-
<
|
| 296 |
-
|
| 297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
</div>
|
| 299 |
</div>
|
| 300 |
-
</div>
|
| 301 |
-
</div>
|
| 302 |
|
| 303 |
</main>
|
| 304 |
|
| 305 |
<!-- Footer -->
|
| 306 |
-
<footer class="bg-gray-900 text-gray-400 py-12">
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
<div>
|
| 310 |
-
<!-- Add data-lang attributes to footer elements if needed -->
|
| 311 |
-
<a href="/" class="text-2xl font-['Pacifico'] text-white mb-4 inline-block" data-lang-en="HindiOCR" data-lang-hi="हिन्दी ओसीआर">HindiOCR</a>
|
| 312 |
-
<p class="mb-4 text-sm" data-lang-en="Transforming handwritten Hindi documents into digital text." data-lang-hi="हस्तलिखित हिंदी दस्तावेज़ों को डिजिटल टेक्स्ट में बदलना।">Transforming handwritten Hindi documents into digital text.</p>
|
| 313 |
-
<div class="flex space-x-4">
|
| 314 |
-
<a href="#" class="hover:text-white" title="GitHub (Placeholder)"><i class="ri-github-fill"></i></a>
|
| 315 |
-
<a href="#" class="hover:text-white" title="LinkedIn (Placeholder)"><i class="ri-linkedin-box-fill"></i></a>
|
| 316 |
-
</div>
|
| 317 |
-
</div>
|
| 318 |
-
<div>
|
| 319 |
-
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Quick Links" data-lang-hi="त्वरित लिंक्स">Quick Links</h3>
|
| 320 |
-
<ul class="space-y-2 text-sm">
|
| 321 |
-
<li><a href="/" class="hover:text-white" data-lang-en="Home" data-lang-hi="होम">Home</a></li>
|
| 322 |
-
<li><a href="/recognize" class="hover:text-white" data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a></li>
|
| 323 |
-
<li><a href="/examples" class="hover:text-white" data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</a></li>
|
| 324 |
-
<li><a href="/technology" class="hover:text-white" data-lang-en="Technology" data-lang-hi="तकनीक">Technology</a></li>
|
| 325 |
-
<li><a href="/about" class="hover:text-white" data-lang-en="About" data-lang-hi="बारे में">About</a></li>
|
| 326 |
-
</ul>
|
| 327 |
-
</div>
|
| 328 |
-
<div>
|
| 329 |
-
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Resources" data-lang-hi="संसाधन">Resources</h3>
|
| 330 |
-
<ul class="space-y-2 text-sm">
|
| 331 |
-
<li><a href="#" class="hover:text-white" data-lang-en="Documentation (TBD)" data-lang-hi="प्रलेखन (TBD)">Documentation (TBD)</a></li>
|
| 332 |
-
<li><a href="#" class="hover:text-white" data-lang-en="GitHub Repo (Link)" data-lang-hi="गिटहब रेपो (लिंक)">GitHub Repo (Link)</a></li>
|
| 333 |
-
<li><a href="#" class="hover:text-white" data-lang-en="Support (TBD)" data-lang-hi="समर्थन (TBD)">Support (TBD)</a></li>
|
| 334 |
-
</ul>
|
| 335 |
-
</div>
|
| 336 |
-
<div>
|
| 337 |
-
<h3 class="text-lg font-medium text-white mb-4" data-lang-en="Contact (Project)" data-lang-hi="संपर्क (परियोजना)">Contact (Project)</h3>
|
| 338 |
-
<ul class="space-y-2 text-sm">
|
| 339 |
-
<li class="flex items-start">
|
| 340 |
-
<i class="ri-mail-line mt-1 mr-2"></i>
|
| 341 |
-
<span>khansamayashaswini@gmail.com</span>
|
| 342 |
-
</li>
|
| 343 |
-
<li class="flex items-center">
|
| 344 |
-
<i class="ri-building-line mr-2"></i>
|
| 345 |
-
<span>MATS University,Raipur / NIC Durg</span>
|
| 346 |
-
</li>
|
| 347 |
-
</ul>
|
| 348 |
-
</div>
|
| 349 |
-
</div>
|
| 350 |
-
<!-- Copyright -->
|
| 351 |
-
<div class="border-t border-gray-700 mt-8 pt-8 text-center">
|
| 352 |
-
<p class="text-sm">© 2025 Yashaswini khansama | Final Year Project | NIC</p>
|
| 353 |
-
</div>
|
| 354 |
-
</div>
|
| 355 |
</footer>
|
| 356 |
|
| 357 |
-
<!-- Bootstrap JS Bundle (Not needed for Tailwind, can remove if not used elsewhere) -->
|
| 358 |
-
<!-- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script> -->
|
| 359 |
-
|
| 360 |
<!-- Base Theme/Language Script -->
|
| 361 |
<script>
|
| 362 |
-
//
|
| 363 |
-
const themeToggle = document.getElementById('themeToggle');
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
function applyTheme(isDark) {
|
| 368 |
-
if (isDark) {
|
| 369 |
-
htmlElement.classList.add('dark');
|
| 370 |
-
if(themeToggle) themeToggle.checked = true;
|
| 371 |
-
console.log("Theme applied: dark");
|
| 372 |
-
} else {
|
| 373 |
-
htmlElement.classList.remove('dark');
|
| 374 |
-
if(themeToggle) themeToggle.checked = false;
|
| 375 |
-
console.log("Theme applied: light");
|
| 376 |
-
}
|
| 377 |
-
}
|
| 378 |
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
}
|
| 395 |
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
// Function to apply language
|
| 401 |
-
function applyLanguage(lang) { // lang should be 'en' or 'hi'
|
| 402 |
-
console.log("Applying language:", lang);
|
| 403 |
-
htmlElement.setAttribute('lang', lang); // Set overall page lang
|
| 404 |
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
|
| 412 |
-
|
| 413 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
|
| 415 |
-
|
| 416 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
}
|
| 418 |
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
applyLanguage(newLang);
|
| 428 |
-
});
|
| 429 |
-
} else {
|
| 430 |
-
console.warn("Language toggle button not found.");
|
| 431 |
}
|
| 432 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 433 |
</script>
|
| 434 |
|
| 435 |
-
<!-- Block for page-specific scripts -->
|
| 436 |
-
|
| 437 |
</body>
|
| 438 |
</html>
|
|
|
|
| 1 |
+
<!-- this is index.html (Tailwind UI with Auth, OCR, Translation, Gender - Fixed Nav & OCR Prediction - REVISED OCR OUTPUT LAYOUT) -->
|
| 2 |
+
<!DOCTYPE html>
|
| 3 |
<html lang="en" class=""> <!-- Start without 'dark' initially -->
|
| 4 |
<head>
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>AI Text Analysis - NIC Project</title>
|
| 8 |
<!-- Tailwind CSS via CDN -->
|
| 9 |
<script src="https://cdn.tailwindcss.com/3.4.1"></script>
|
| 10 |
<script>
|
| 11 |
+
// Tailwind config
|
| 12 |
tailwind.config = {
|
| 13 |
+
darkMode: 'class',
|
| 14 |
theme: {
|
| 15 |
extend: {
|
| 16 |
+
colors: { primary: '#1a73e8', secondary: '#e8f0fe' },
|
| 17 |
+
borderRadius: { 'none': '0px', 'sm': '4px', DEFAULT: '8px', 'md': '12px', 'lg': '16px', 'xl': '20px', '2xl': '24px', '3xl': '32px', 'full': '9999px', 'button': '8px' }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
}
|
| 19 |
}
|
| 20 |
}
|
|
|
|
| 28 |
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.2.0/fonts/remixicon.css" rel="stylesheet">
|
| 29 |
<!-- Custom Styles -->
|
| 30 |
<style>
|
| 31 |
+
body { font-family: 'Inter', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
| 32 |
+
.hindi-font { font-family: 'Noto Sans Devanagari', sans-serif; }
|
| 33 |
+
.custom-switch { position: relative; display: inline-block; width: 50px; height: 24px; }
|
| 34 |
+
.custom-switch-input { opacity: 0; width: 0; height: 0; }
|
| 35 |
+
.custom-switch-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
|
| 36 |
+
.custom-switch-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
|
| 37 |
+
.custom-switch-input:checked + .custom-switch-slider { background-color: #1a73e8; }
|
| 38 |
+
.custom-switch-input:checked + .custom-switch-slider:before { transform: translateX(26px); }
|
| 39 |
+
*:focus-visible { outline: 2px solid #1a73e8; outline-offset: 2px; box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.3); }
|
| 40 |
+
/* Base Dark Mode */
|
| 41 |
+
html.dark body { background-color: #1a202c; color: #e2e8f0; }
|
| 42 |
+
html.dark header, html.dark footer { background-color: #2d3748; color: #e2e8f0; }
|
| 43 |
+
html.dark .card, html.dark .auth-card, html.dark .result-card { background-color: #2d3748; border-color: #4a5568; }
|
| 44 |
+
html.dark h1, html.dark h2, html.dark h3, html.dark p, html.dark span, html.dark li, html.dark label, html.dark small, html.dark strong, html.dark button:not(.bg-red-500):not(.bg-green-600), html.dark a { color: #e2e8f0; } /* Adjusted a selector */
|
| 45 |
+
html.dark a.text-primary { color: #60a5fa; } /* Make sure primary links are visible */
|
| 46 |
+
html.dark .text-gray-600, html.dark .text-gray-700, html.dark .text-gray-800, html.dark .text-gray-900 { color: #a0aec0; }
|
| 47 |
+
html.dark .text-gray-500, html.dark .text-gray-400 { color: #718096; }
|
| 48 |
+
html.dark .text-muted { color: #718096; }
|
| 49 |
+
html.dark .bg-white { background-color: #2d3748 !important; }
|
| 50 |
+
html.dark .bg-gray-50 { background-color: #1a202c !important; }
|
| 51 |
+
html.dark .bg-gray-100 { background-color: #374151 !important; }
|
| 52 |
+
html.dark .border-gray-100, html.dark .border-gray-200 { border-color: #4a5568 !important; }
|
| 53 |
+
html.dark .hover\:bg-gray-50:hover { background-color: #4a5568 !important; }
|
| 54 |
+
/* Dark mode Forms */
|
| 55 |
+
html.dark input, html.dark textarea { background-color: #1f2937; border-color: #4b5563; color: #e5e7eb; }
|
| 56 |
+
html.dark input::placeholder, html.dark textarea::placeholder { color: #6b7280; }
|
| 57 |
+
html.dark .auth-link { color: #60a5fa; } html.dark .auth-link:hover { color: #93c5fd; }
|
| 58 |
+
html.dark .error-message { background-color: #450a0a; color: #fecaca; border-color: #7f1d1d; }
|
| 59 |
+
html.dark .spinner { border-color: rgba(255, 255, 255, 0.1); border-left-color: #60a5fa; }
|
| 60 |
+
/* Example Images */
|
| 61 |
+
.example-image-container { cursor: pointer; transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; border: 1px solid transparent; aspect-ratio: 1 / 1; }
|
| 62 |
+
.example-image-container:hover { transform: scale(1.05); box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
|
| 63 |
+
.example-image-container.selected { border: 2px solid #1a73e8; box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.3); }
|
| 64 |
+
.example-image { width: 100%; height: 100%; object-fit: contain; }
|
| 65 |
+
html.dark .example-image-container.selected { border-color: #60a5fa; box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.3); }
|
| 66 |
+
html.dark .example-image-container { background-color: #4a5568; }
|
| 67 |
+
/* General Loading Spinner */
|
| 68 |
+
.spinner { border: 3px solid rgba(255, 255, 255, 0.3); width: 16px; height: 16px; border-radius: 50%; border-left-color: #fff; animation: spin 1s ease infinite; }
|
| 69 |
+
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
| 70 |
+
/* Error Message Styling */
|
| 71 |
+
.error-message { color: #dc2626; background-color: #fee2e2; padding: 0.75rem 1rem; border-radius: 8px; margin-top: 1rem; font-weight: 500; border: 1px solid #fecaca; font-size: 0.9rem; display: none; }
|
| 72 |
+
/* Result Images */
|
| 73 |
+
.result-image { max-width: 100%; height: auto; display: block; margin: 1rem auto 0 auto; border: 1px solid; border-radius: 8px; background-color: #eee; min-height: 100px; } /* Centered result image */
|
| 74 |
+
html.dark .result-image { background-color: #4a5568; border-color: #4a5568;}
|
| 75 |
+
/* Tab Styling */
|
| 76 |
+
.tab-button { transition: all 0.3s ease; border-bottom: 3px solid transparent; }
|
| 77 |
+
.tab-button.active { border-bottom-color: #1a73e8; color: #1a73e8; font-weight: 600; }
|
| 78 |
+
html.dark .tab-button.active { border-bottom-color: #60a5fa; color: #60a5fa; }
|
| 79 |
+
.tab-content { display: none; animation: fadeIn 0.5s ease-in-out; }
|
| 80 |
+
.tab-content.active { display: block; }
|
| 81 |
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
| 82 |
+
/* Result Styling Refinements */
|
| 83 |
+
.result-card { background-color: #f9fafb; border: 1px solid #e5e7eb; margin-bottom: 1.5rem; padding: 1.5rem; border-radius: 8px; }
|
| 84 |
+
html.dark .result-card { background-color: #374151; border-color: #4b5563; }
|
| 85 |
+
.result-label { font-weight: 600; color: #4b5563; }
|
| 86 |
+
html.dark .result-label { color: #9ca3af; }
|
| 87 |
+
.result-value { font-weight: 500; word-break: break-word; }
|
| 88 |
+
html.dark .result-value { color: #e5e7eb; }
|
| 89 |
+
.result-item { display: grid; grid-template-columns: 1fr; gap: 0.25rem; margin-bottom: 1rem; padding-bottom: 1rem; border-bottom: 1px solid #e5e7eb; }
|
| 90 |
+
html.dark .result-item { border-color: #4b5563; }
|
| 91 |
+
.result-item:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }
|
| 92 |
+
@media (min-width: 768px) { .result-item { grid-template-columns: 150px 1fr; gap: 1rem; align-items: start; } .result-label { text-align: right; } } /* Grid for larger screens */
|
| 93 |
+
/* Loading Indicator general */
|
| 94 |
+
.loading-indicator { display: none; flex-direction: column; align-items: center; justify-content: center; gap: 0.5rem; margin: 1.5rem 0; color: #6b7280; }
|
| 95 |
+
html.dark .loading-indicator { color: #9ca3af; }
|
| 96 |
+
.loading-indicator .spinner { width: 24px; height: 24px; border-width: 3px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
</style>
|
|
|
|
| 99 |
</head>
|
|
|
|
| 100 |
<body class="bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col text-gray-900 dark:text-gray-200">
|
| 101 |
|
| 102 |
<!-- Header -->
|
| 103 |
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 104 |
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 105 |
<div class="flex items-center">
|
| 106 |
+
<!-- *** Updated Brand Name *** -->
|
| 107 |
+
<a href="/" class="text-2xl font-['Pacifico'] text-primary dark:text-blue-400 mr-8" data-lang-en="AI Text Tools" data-lang-hi="एआई टेक्स्ट उपकरण">AI Text Tools</a>
|
| 108 |
+
<!-- *** RESTORED Navigation Links *** -->
|
| 109 |
+
<nav id="mainNav" class="hidden md:flex space-x-6">
|
| 110 |
+
<a href="index.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Home" data-lang-hi="होम">Home</a>
|
| 111 |
+
<a href="recognizer.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-primary dark:text-blue-400 font-medium" data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a> <!-- Current page? -->
|
| 112 |
+
<a href="examples.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</a> <!-- Link to examples section? -->
|
| 113 |
+
<a href="technology.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Technology" data-lang-hi="तकनीक">Technology</a>
|
| 114 |
+
<a href="about.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="About" data-lang-hi="बारे में">About</a>
|
| 115 |
+
<a href="contact.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="contact" data-lang-hi="बारे में">contact</a>
|
| 116 |
+
<a href="feedback.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="feedback" data-lang-hi="बारे में">feedback</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
</nav>
|
| 118 |
</div>
|
| 119 |
+
<!-- Right side header items (Theme, Lang, Logout, etc.) -->
|
| 120 |
<div class="flex items-center space-x-4">
|
| 121 |
+
<div class="items-center space-x-2 hidden md:flex"> <span class="text-sm text-gray-600 dark:text-gray-400">EN</span> <label class="custom-switch"><input type="checkbox" id="languageToggle" class="custom-switch-input"><span class="custom-switch-slider"></span></label> <span class="text-sm text-gray-600 dark:text-gray-400 hindi-font">हिंदी</span> </div>
|
| 122 |
+
<div class="items-center space-x-2 hidden md:flex"> <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-sun-line"></i></span> <label class="custom-switch"><input type="checkbox" id="themeToggle" class="custom-switch-input"><span class="custom-switch-slider"></span></label> <span class="text-sm text-gray-600 dark:text-gray-400"><i class="ri-moon-line"></i></span> </div>
|
| 123 |
+
<button id="logoutButton" class="hidden bg-red-500 hover:bg-red-600 text-white px-3 py-1.5 rounded-button text-sm inline-flex items-center gap-1"> <i class="ri-logout-box-r-line"></i> <span data-lang-en="Logout" data-lang-hi="लॉग आउट">Logout</span> </button>
|
| 124 |
+
<div id="userInfoIcons" class="hidden md:flex items-center space-x-4"> <div class="w-10 h-10 flex items-center justify-center bg-gray-100 dark:bg-gray-700 rounded-full cursor-pointer" title="User Profile (Placeholder)"><i class="ri-user-line text-gray-600 dark:text-gray-300"></i></div> </div>
|
| 125 |
+
<button class="md:hidden w-10 h-10 flex items-center justify-center" aria-label="Toggle Menu"><i class="ri-menu-line text-gray-600 dark:text-gray-300 text-xl"></i></button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
</div>
|
| 127 |
</div>
|
| 128 |
</header>
|
| 129 |
|
| 130 |
+
<!-- ===== Authentication Container ===== -->
|
| 131 |
+
<div id="authContainer" class="flex-grow flex items-center justify-center p-4">
|
| 132 |
+
<!-- Login/Signup Forms (Keep as is) -->
|
| 133 |
+
<div class="w-full max-w-md"> <form id="loginForm" class="bg-white dark:bg-gray-800 p-8 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 auth-card"> <h2 class="text-2xl font-semibold text-center text-gray-800 dark:text-white mb-6" data-lang-en="Login" data-lang-hi="लॉग इन करें">Login</h2> <div id="loginErrorDiv" class="error-message"></div> <div class="mb-4"> <label for="loginUsername" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" data-lang-en="Username" data-lang-hi="उपयोगकर्ता नाम">Username</label> <input type="text" id="loginUsername" required autocomplete="username" class="input-field w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-white"> </div> <div class="mb-6"> <label for="loginPassword" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" data-lang-en="Password" data-lang-hi="पासवर्ड">Password</label> <input type="password" id="loginPassword" required autocomplete="current-password" class="input-field w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-white"> </div> <button type="submit" id="loginSubmitButton" class="w-full flex justify-center items-center gap-2 py-2 px-4 border border-transparent rounded-button shadow-sm text-sm font-medium text-white bg-primary hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary dark:focus:ring-offset-gray-800 disabled:opacity-50"> <span data-lang-en="Login" data-lang-hi="लॉग इन करें">Login</span><div class="spinner hidden"></div> </button> <p class="mt-4 text-center text-sm text-gray-600 dark:text-gray-400"> <span data-lang-en="Don't have an account?" data-lang-hi="खाता नहीं है?">Don't have an account?</span> <a href="#" id="showSignupLink" class="font-medium text-primary hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 auth-link" data-lang-en="Sign up" data-lang-hi="साइन अप करें">Sign up</a> </p> </form> <form id="signupForm" class="hidden bg-white dark:bg-gray-800 p-8 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 auth-card"> <h2 class="text-2xl font-semibold text-center text-gray-800 dark:text-white mb-6" data-lang-en="Sign Up" data-lang-hi="साइन अप करें">Sign Up</h2> <div id="signupErrorDiv" class="error-message"></div> <div class="mb-4"><label for="signupUsername" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" data-lang-en="Username" data-lang-hi="उपयोगकर्ता नाम">Username</label><input type="text" id="signupUsername" required minlength="3" maxlength="50" autocomplete="username" class="input-field w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-white"></div> <div class="mb-4"><label for="signupEmail" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" data-lang-en="Email" data-lang-hi="ईमेल">Email</label><input type="email" id="signupEmail" required autocomplete="email" class="input-field w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-white"></div> <div class="mb-6"><label for="signupPassword" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" data-lang-en="Password (min. 6 chars)" data-lang-hi="पासवर्ड (न्यून. 6 अक्षर)">Password (min. 6 chars)</label><input type="password" id="signupPassword" required minlength="6" autocomplete="new-password" class="input-field w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-white"></div> <button type="submit" id="signupSubmitButton" class="w-full flex justify-center items-center gap-2 py-2 px-4 border border-transparent rounded-button shadow-sm text-sm font-medium text-white bg-primary hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary dark:focus:ring-offset-gray-800 disabled:opacity-50"> <span data-lang-en="Sign Up" data-lang-hi="साइन अप करें">Sign Up</span><div class="spinner hidden"></div> </button> <p class="mt-4 text-center text-sm text-gray-600 dark:text-gray-400"> <span data-lang-en="Already have an account?" data-lang-hi="पहले से ही खाता है?">Already have an account?</span> <a href="#" id="showLoginLink" class="font-medium text-primary hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 auth-link" data-lang-en="Login" data-lang-hi="लॉग इन करें">Login</a> </p> </form> </div>
|
| 134 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
+
<!-- ===== Main Application Container ===== -->
|
| 137 |
+
<main id="appContainer" class="hidden flex-grow container mx-auto px-4 py-8">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
+
<!-- Tab Navigation -->
|
| 140 |
+
<div class="mb-6 border-b border-gray-200 dark:border-gray-700">
|
| 141 |
+
<nav class="-mb-px flex space-x-6 overflow-x-auto" aria-label="Tabs"> <!-- Added overflow-x-auto -->
|
| 142 |
+
<button class="tab-button active whitespace-nowrap py-4 px-1 text-sm font-medium flex items-center gap-2" data-tab="ocr"> <i class="ri-image-line text-lg"></i> <span data-lang-en="Hindi OCR" data-lang-hi="हिन्दी ओसीआर">Hindi OCR</span> </button>
|
| 143 |
+
<button class="tab-button whitespace-nowrap py-4 px-1 text-sm font-medium flex items-center gap-2" data-tab="translation"> <i class="ri-translate-2 text-lg"></i> <span data-lang-en="Translation" data-lang-hi="अनुवाद">Translation</span> </button>
|
| 144 |
+
<button class="tab-button whitespace-nowrap py-4 px-1 text-sm font-medium flex items-center gap-2" data-tab="gender"> <i class="ri-men-line text-lg"></i><i class="ri-women-line text-lg -ml-1"></i> <span data-lang-en="Gender Prediction" data-lang-hi="लिंग भविष्यवाणी">Gender Prediction</span> </button>
|
| 145 |
+
</nav>
|
| 146 |
</div>
|
| 147 |
|
| 148 |
+
<!-- Tab Content Area -->
|
| 149 |
+
<div>
|
| 150 |
+
<!-- OCR Tab Content -->
|
| 151 |
+
<div id="ocrContent" class="tab-content active">
|
| 152 |
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8">
|
| 153 |
+
<!-- Input Column -->
|
| 154 |
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 card">
|
| 155 |
+
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4 border-b border-gray-200 dark:border-gray-600 pb-2 flex items-center gap-2"> <i class="ri-image-add-line text-primary dark:text-blue-400"></i><span data-lang-en="Input Image" data-lang-hi="इनपुट छवि">Input Image</span> </h2>
|
| 156 |
+
<p class="text-gray-600 dark:text-gray-400 text-sm mb-4" data-lang-en="Upload a Hindi word image (PNG, JPG, JPEG) or select an example." data-lang-hi="एक हिन्दी शब्द छवि (PNG, JPG, JPEG) अपलोड करें या उदाहरण चुनें।">Upload a Hindi word image (PNG, JPG, JPEG) or select an example.</p>
|
| 157 |
+
<input type="file" id="imageUpload" accept="image/png, image/jpeg, image/jpg" class="hidden"/>
|
| 158 |
+
<div class="flex items-center mb-4"><label for="imageUpload" class="bg-primary text-white px-4 py-2 rounded-button cursor-pointer hover:bg-blue-700 transition-colors text-sm inline-flex items-center mr-4 whitespace-nowrap gap-2"><i class="ri-upload-2-line"></i><span data-lang-en="Choose File" data-lang-hi="फ़ाइल चुनें">Choose File</span></label><span id="fileName" class="text-gray-500 dark:text-gray-400 text-sm italic truncate" data-lang-en="No file chosen" data-lang-hi="कोई फ़ाइल नहीं चुनी गई">No file chosen</span></div>
|
| 159 |
+
<h3 class="text-md font-semibold text-gray-700 dark:text-gray-300 mb-2" data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</h3>
|
| 160 |
+
<div id="examplesContainer" class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-3 mb-4"><div class="col-span-full"><span id="examplesLoadingText" class="text-gray-500 dark:text-gray-400 text-sm" data-lang-en="Loading examples..." data-lang-hi="उदाहरण लोड हो रहे हैं...">Loading examples...</span></div></div>
|
| 161 |
+
<div id="imagePreviewContainer" class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-600 hidden"><h3 class="text-md font-semibold text-gray-700 dark:text-gray-300 mb-2 text-center" data-lang-en="Preview" data-lang-hi="पूर्वावलोकन">Preview</h3><div class="flex justify-center items-center bg-gray-50 dark:bg-gray-700 dark:border-gray-600 rounded border p-1 min-h-[100px]"><img id="imagePreview" src="#" alt="Image Preview" class="max-h-60 w-auto rounded"/></div></div>
|
| 162 |
+
<div class="mt-6 pt-4 border-t border-gray-200 dark:border-gray-600 flex gap-4">
|
| 163 |
+
<button id="predictButton" class="flex-1 bg-green-600 text-white px-6 py-3 rounded-button font-medium hover:bg-green-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-lg inline-flex items-center justify-center gap-2" disabled><i class="ri-search-line"></i><span data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</span></button>
|
| 164 |
+
<button id="clearButton" class="flex-none bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-500 text-gray-800 dark:text-gray-200 px-4 py-3 rounded-button text-lg inline-flex items-center justify-center gap-2 hidden"><i class="ri-delete-bin-line"></i><span data-lang-en="Clear" data-lang-hi="साफ़ करें">Clear</span></button>
|
| 165 |
+
</div>
|
| 166 |
+
<div id="errorMessage" class="error-message mt-4"></div>
|
| 167 |
+
</div>
|
| 168 |
+
|
| 169 |
+
<!-- ***** MODIFIED Output Column ***** -->
|
| 170 |
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 flex flex-col items-center card">
|
| 171 |
+
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4 border-b border-gray-200 dark:border-gray-600 pb-2 w-full text-center flex items-center justify-center gap-2"><i class="ri-text text-primary dark:text-blue-400"></i><span data-lang-en="Recognition Result" data-lang-hi="पहचान परिणाम">Recognition Result</span></h2>
|
| 172 |
+
<div id="loadingIndicator" class="loading-indicator w-full"><div class="spinner"></div><span id="loadingText" data-lang-en="Processing... Please wait." data-lang-hi="संसाधित हो रहा है... कृपया प्रतीक्षा करें।">Processing...</span></div>
|
| 173 |
+
<div id="resultDisplay" class="mt-4 w-full text-center hidden space-y-6">
|
| 174 |
+
|
| 175 |
+
<!-- *** MOVED Word Detection Section First *** -->
|
| 176 |
+
<div class="result-card !p-4"> <!-- Adjusted padding -->
|
| 177 |
+
<h3 class="text-md font-semibold text-center mb-2" data-lang-en="Word Detection" data-lang-hi="शब्द पहचान">Word Detection</h3>
|
| 178 |
+
<img id="wordDetectionImg" src="#" alt="Word Detection Preview" class="result-image" style="display: none;">
|
| 179 |
+
</div>
|
| 180 |
+
|
| 181 |
+
<!-- *** REMOVED Recognized Text Section *** -->
|
| 182 |
+
<!-- The div containing ocrRecognizedText and wordCountText has been deleted -->
|
| 183 |
+
|
| 184 |
+
<!-- Raw Detector Output Section (Now Second) -->
|
| 185 |
+
<div class="result-card">
|
| 186 |
+
<h3 class="text-md font-semibold mb-2" data-lang-en="Raw Detector Output:" data-lang-hi="रॉ डिटेक्टर आउटपुट:">Raw Detector Output:</h3>
|
| 187 |
+
<pre id="sakshiOutputText" class="text-xs text-left whitespace-pre-wrap break-words max-h-40 overflow-y-auto bg-gray-50 dark:bg-gray-800 p-2 rounded border border-gray-200 dark:border-gray-600"></pre>
|
| 188 |
+
</div>
|
| 189 |
+
|
| 190 |
+
</div>
|
| 191 |
+
<p id="initialMessageOutput" class="text-gray-500 dark:text-gray-400 mt-6 text-center" data-lang-en="Results will appear here." data-lang-hi="परिणाम यहां दिखाई देंगे।">Results will appear here.</p>
|
| 192 |
+
</div>
|
| 193 |
+
<!-- ***** End of MODIFIED Output Column ***** -->
|
| 194 |
|
| 195 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
</div>
|
| 197 |
|
| 198 |
+
<!-- Translation Tab Content -->
|
| 199 |
+
<div id="translationContent" class="tab-content">
|
| 200 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
|
| 201 |
+
<!-- Translation Input -->
|
| 202 |
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 card">
|
| 203 |
+
<h2 class="text-xl font-semibold mb-4 border-b pb-2 flex items-center gap-2"><i class="ri-translate-2 text-primary dark:text-blue-400"></i><span data-lang-en="Translate Text" data-lang-hi="पाठ का अनुवाद करें">Translate Text</span></h2>
|
| 204 |
+
<div class="mb-4">
|
| 205 |
+
<label for="textToTranslate" class="block text-sm font-medium mb-1" data-lang-en="Text to Translate:" data-lang-hi="अनुवाद के लिए पाठ:">Text to Translate:</label>
|
| 206 |
+
<textarea id="textToTranslate" rows="5" placeholder="Enter text here or use OCR output..." class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600"></textarea>
|
| 207 |
+
</div>
|
| 208 |
+
<div class="mb-4">
|
| 209 |
+
<label for="sourceLanguage" class="block text-sm font-medium mb-1" data-lang-en="Source Language (Optional):" data-lang-hi="स्रोत भाषा (वैकल्पिक):">Source Language (Optional):</label>
|
| 210 |
+
<input type="text" id="sourceLanguage" placeholder="Leave empty for auto-detection" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600">
|
| 211 |
+
</div>
|
| 212 |
+
<div class="mb-4">
|
| 213 |
+
<label for="targetLanguage" class="block text-sm font-medium mb-1" data-lang-en="Target Language:" data-lang-hi="लक्ष्य भाषा:">Target Language:</label>
|
| 214 |
+
<input type="text" id="targetLanguage" placeholder="e.g., English, Hindi, French" required class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600">
|
| 215 |
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1" data-lang-en="Enter target language name (e.g., English)." data-lang-hi="लक्ष्य भाषा का नाम दर्ज करें (उदा., English)।">Enter target language name (e.g., English).</p>
|
| 216 |
+
</div>
|
| 217 |
+
<button id="translateButton" class="w-full bg-primary text-white px-4 py-2 rounded-button hover:bg-blue-700 transition-colors inline-flex items-center justify-center gap-2">
|
| 218 |
+
<i class="ri-translate"></i><span data-lang-en="Translate" data-lang-hi="अनुवाद करें">Translate</span>
|
| 219 |
+
</button>
|
| 220 |
+
<div id="translationError" class="error-message mt-4"></div>
|
| 221 |
+
</div>
|
| 222 |
+
<!-- Translation Output -->
|
| 223 |
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 card">
|
| 224 |
+
<h2 class="text-xl font-semibold mb-4 border-b pb-2 flex items-center gap-2"><i class="ri-chat-check-line text-primary dark:text-blue-400"></i><span data-lang-en="Translation Result" data-lang-hi="अनुवाद परिणाम">Translation Result</span></h2>
|
| 225 |
+
<div id="translationLoading" class="loading-indicator"><div class="spinner"></div><span data-lang-en="Translating..." data-lang-hi="अनुवाद हो रहा है...">Translating...</span></div>
|
| 226 |
+
<div id="translationResultDisplay" class="hidden space-y-4">
|
| 227 |
+
<div class="result-item !border-none !pb-0"> <div class="result-label" data-lang-en="Source Language:" data-lang-hi="स्रोत भाषा:">Source Language:</div> <div id="detectedSourceLanguage" class="result-value font-medium"></div> </div>
|
| 228 |
+
<div class="result-item !border-none !pb-0"> <div class="result-label" data-lang-en="Target Language:" data-lang-hi="लक्ष्य भाषा:">Target Language:</div> <div id="translationTargetLanguage" class="result-value font-medium"></div> </div>
|
| 229 |
+
<div class="mt-2"> <div class="result-label mb-1" data-lang-en="Translated Text:" data-lang-hi="अनुवादित पाठ:">Translated Text:</div> <div id="translatedText" class="result-value p-3 bg-gray-100 dark:bg-gray-700 rounded border border-gray-200 dark:border-gray-600 min-h-[100px] whitespace-pre-wrap"></div> </div>
|
| 230 |
+
</div>
|
| 231 |
+
<p id="initialMessageTranslation" class="text-gray-500 dark:text-gray-400 mt-6 text-center" data-lang-en="Enter text and languages, then click Translate." data-lang-hi="पाठ और भाषाएँ दर्ज करें, फिर अनुवाद करें पर क्लिक करें।">Enter text and languages, then click Translate.</p>
|
| 232 |
+
</div>
|
| 233 |
+
</div>
|
| 234 |
</div>
|
| 235 |
|
| 236 |
+
<!-- Gender Prediction Tab Content -->
|
| 237 |
+
<div id="genderContent" class="tab-content">
|
| 238 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
|
| 239 |
+
<!-- Gender Input -->
|
| 240 |
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 card">
|
| 241 |
+
<h2 class="text-xl font-semibold mb-4 border-b pb-2 flex items-center gap-2"><i class="ri-user-search-line text-primary dark:text-blue-400"></i><span data-lang-en="Predict Gender from Names" data-lang-hi="नामों से लिंग की भविष्यवाणी करें">Predict Gender from Names</span></h2>
|
| 242 |
+
<div class="mb-4">
|
| 243 |
+
<label for="namesInput" class="block text-sm font-medium mb-1" data-lang-en="Enter Names (comma-separated):" data-lang-hi="नाम दर्ज करें (अल्पविराम से अलग):">Enter Names (comma-separated):</label>
|
| 244 |
+
<textarea id="namesInput" rows="5" placeholder="e.g., Priya, Rahul, Alex, Sam" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm dark:bg-gray-700 dark:border-gray-600"></textarea>
|
| 245 |
+
</div>
|
| 246 |
+
<button id="predictGenderButton" class="w-full bg-primary text-white px-4 py-2 rounded-button hover:bg-blue-700 transition-colors inline-flex items-center justify-center gap-2">
|
| 247 |
+
<i class="ri-men-line"></i><i class="ri-women-line -ml-1"></i><span data-lang-en="Predict Gender" data-lang-hi="लिंग की भविष्यवाणी करें">Predict Gender</span>
|
| 248 |
+
</button>
|
| 249 |
+
<div id="genderError" class="error-message mt-4"></div>
|
| 250 |
+
</div>
|
| 251 |
+
<!-- Gender Output -->
|
| 252 |
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 card">
|
| 253 |
+
<h2 class="text-xl font-semibold mb-4 border-b pb-2 flex items-center gap-2"><i class="ri-bar-chart-2-line text-primary dark:text-blue-400"></i><span data-lang-en="Prediction Results" data-lang-hi="भविष्यवाणी परिणाम">Prediction Results</span></h2>
|
| 254 |
+
<div id="genderLoading" class="loading-indicator"><div class="spinner"></div><span data-lang-en="Analyzing..." data-lang-hi="विश्लेषण हो रहा है...">Analyzing...</span></div>
|
| 255 |
+
<div id="genderResultDisplay" class="hidden space-y-4"> <!-- Results populated here --> </div>
|
| 256 |
+
<p id="initialMessageGender" class="text-gray-500 dark:text-gray-400 mt-6 text-center" data-lang-en="Enter names and click Predict." data-lang-hi="नाम दर्ज करें और भविष्यवाणी पर क्लिक करें।">Enter names and click Predict.</p>
|
| 257 |
+
</div>
|
| 258 |
+
</div>
|
| 259 |
</div>
|
| 260 |
</div>
|
|
|
|
|
|
|
| 261 |
|
| 262 |
</main>
|
| 263 |
|
| 264 |
<!-- Footer -->
|
| 265 |
+
<footer class="bg-gray-900 text-gray-400 py-12 mt-12">
|
| 266 |
+
<!-- Footer content (Keep as is) -->
|
| 267 |
+
<div class="container mx-auto px-4"> <div class="grid grid-cols-1 md:grid-cols-4 gap-8"> <div><a href="/" class="text-2xl font-['Pacifico'] text-white mb-4 inline-block" data-lang-en="AI Text Tools" data-lang-hi="एआई टेक्स्ट उपकरण">AI Text Tools</a><p class="mb-4 text-sm" data-lang-en="AI-powered tools for text analysis and processing." data-lang-hi="पाठ विश्लेषण और प्रसंस्करण के लिए एआई-संचालित उपकरण।">AI-powered tools for text analysis.</p><div class="flex space-x-4"><a href="#" class="hover:text-white" title="GitHub (Placeholder)"><i class="ri-github-fill"></i></a><a href="#" class="hover:text-white" title="LinkedIn (Placeholder)"><i class="ri-linkedin-box-fill"></i></a></div></div> <div><h3 class="text-lg font-medium text-white mb-4" data-lang-en="Quick Links" data-lang-hi="त्वरित लिंक्स">Quick Links</h3><ul class="space-y-2 text-sm"><li><a href="#" class="hover:text-white" data-lang-en="Home" data-lang-hi="होम">Home</a></li><li><a href="#" class="hover:text-white" data-lang-en="Tools" data-lang-hi="उपकरण">Tools</a></li><li><a href="#" class="hover:text-white" data-lang-en="About" data-lang-hi="बारे में">About</a></li></ul></div> <div><h3 class="text-lg font-medium text-white mb-4" data-lang-en="Resources" data-lang-hi="संसाधन">Resources</h3><ul class="space-y-2 text-sm"><li><a href="#" class="hover:text-white" data-lang-en="API Docs (TBD)" data-lang-hi="एपीआई डॉक्स (TBD)">API Docs (TBD)</a></li><li><a href="#" class="hover:text-white" data-lang-en="GitHub Repo" data-lang-hi="गिटहब रेपो">GitHub Repo</a></li></ul></div> <div><h3 class="text-lg font-medium text-white mb-4" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</h3><ul class="space-y-2 text-sm"><li class="flex items-start"><i class="ri-mail-line mt-1 mr-2"></i><span>your-email@example.com</span></li></ul></div> </div> <div class="border-t border-gray-700 mt-8 pt-8 text-center"><p class="text-sm">© 2025 Slimshadow.org | AI Text Tools Project</p></div> </div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
</footer>
|
| 269 |
|
|
|
|
|
|
|
|
|
|
| 270 |
<!-- Base Theme/Language Script -->
|
| 271 |
<script>
|
| 272 |
+
// Theme Toggle (Keep as is)
|
| 273 |
+
const themeToggle = document.getElementById('themeToggle'); const htmlElement = document.documentElement; function applyTheme(isDark) { if (isDark) { htmlElement.classList.add('dark'); if(themeToggle) themeToggle.checked = true; } else { htmlElement.classList.remove('dark'); if(themeToggle) themeToggle.checked = false; } } const prefersDark = localStorage.getItem('theme') === 'dark' || (localStorage.getItem('theme') === null && window.matchMedia('(prefers-color-scheme: dark)').matches); applyTheme(prefersDark); if(themeToggle){ themeToggle.addEventListener('change', (event) => { const isDark = event.target.checked; applyTheme(isDark); localStorage.setItem('theme', isDark ? 'dark' : 'light'); }); } else { console.warn("Theme toggle button not found."); }
|
| 274 |
+
// Language Toggle (Keep as is)
|
| 275 |
+
const languageToggle = document.getElementById('languageToggle'); const langElements = document.querySelectorAll('[data-lang-en]'); function applyLanguage(lang) { htmlElement.setAttribute('lang', lang); langElements.forEach(el => { const text = el.getAttribute(`data-lang-${lang}`); if (text) { const icon = el.querySelector('i'); if (icon && el.childNodes.length > 1) { let updated = false; el.childNodes.forEach(node => { if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '') { node.textContent = ` ${text} `; updated = true; } }); if (!updated) { const currentText = el.getAttribute(`data-lang-${lang === 'en' ? 'hi' : 'en'}`); if(currentText) el.innerHTML = el.innerHTML.replace(currentText, text);}} else { el.textContent = text; } } }); if (languageToggle) languageToggle.checked = (lang === 'hi'); localStorage.setItem('language', lang); } const savedLang = localStorage.getItem('language') || 'en'; if (languageToggle) { languageToggle.addEventListener('change', (event) => { const newLang = event.target.checked ? 'hi' : 'en'; applyLanguage(newLang); }); } else { console.warn("Language toggle button not found."); }
|
| 276 |
+
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
+
<!-- ***** Page Interaction & Full App Script (MODIFIED) ***** -->
|
| 279 |
+
<script>
|
| 280 |
+
// --- API Configuration ---
|
| 281 |
+
const OCR_API_BASE_URL = 'https://sameernotes-ocr.hf.space';
|
| 282 |
+
const TRANSLATION_API_BASE_URL = 'https://sameernotes-translation-prediction-space.hf.space';
|
| 283 |
+
const GENDER_API_BASE_URL = "https://sidvilas-gender-prediction-space.hf.space";
|
| 284 |
+
const TOKEN_ENDPOINT = `${OCR_API_BASE_URL}/token`;
|
| 285 |
+
const SIGNUP_ENDPOINT = `${OCR_API_BASE_URL}/signup`;
|
| 286 |
+
const PROCESS_ENDPOINT = `${OCR_API_BASE_URL}/process/`;
|
| 287 |
+
const WORD_DETECT_ENDPOINT = `${OCR_API_BASE_URL}/word-detection/`;
|
| 288 |
+
// const PREDICTION_IMG_ENDPOINT = `${OCR_API_BASE_URL}/prediction/`; // Removed
|
| 289 |
+
const TRANSLATE_ENDPOINT = `${TRANSLATION_API_BASE_URL}/translate`;
|
| 290 |
+
const GENDER_PREDICT_ENDPOINT = `${GENDER_API_BASE_URL}/predict`;
|
| 291 |
+
const TOKEN_STORAGE_KEY = 'aiTextToolsAuthToken';
|
| 292 |
+
|
| 293 |
+
// --- State Variables ---
|
| 294 |
+
let accessToken = localStorage.getItem(TOKEN_STORAGE_KEY);
|
| 295 |
+
let currentImageSource = null;
|
| 296 |
+
let currentExampleSelection = null;
|
| 297 |
+
|
| 298 |
+
// --- DOM Elements ---
|
| 299 |
+
// Auth & Core App
|
| 300 |
+
const authContainer = document.getElementById('authContainer');
|
| 301 |
+
const appContainer = document.getElementById('appContainer');
|
| 302 |
+
const loginForm = document.getElementById('loginForm');
|
| 303 |
+
const signupForm = document.getElementById('signupForm');
|
| 304 |
+
const loginUsernameInput = document.getElementById('loginUsername');
|
| 305 |
+
const loginPasswordInput = document.getElementById('loginPassword');
|
| 306 |
+
const loginErrorDiv = document.getElementById('loginErrorDiv');
|
| 307 |
+
const loginSubmitButton = document.getElementById('loginSubmitButton');
|
| 308 |
+
const signupUsernameInput = document.getElementById('signupUsername');
|
| 309 |
+
const signupEmailInput = document.getElementById('signupEmail');
|
| 310 |
+
const signupPasswordInput = document.getElementById('signupPassword');
|
| 311 |
+
const signupErrorDiv = document.getElementById('signupErrorDiv');
|
| 312 |
+
const signupSubmitButton = document.getElementById('signupSubmitButton');
|
| 313 |
+
const showSignupLink = document.getElementById('showSignupLink');
|
| 314 |
+
const showLoginLink = document.getElementById('showLoginLink');
|
| 315 |
+
const logoutButton = document.getElementById('logoutButton');
|
| 316 |
+
const mainNav = document.getElementById('mainNav');
|
| 317 |
+
const userInfoIcons = document.getElementById('userInfoIcons');
|
| 318 |
+
// Tabs
|
| 319 |
+
const tabButtons = document.querySelectorAll('.tab-button');
|
| 320 |
+
const tabContents = document.querySelectorAll('.tab-content');
|
| 321 |
+
// OCR Input
|
| 322 |
+
const imageUpload = document.getElementById('imageUpload');
|
| 323 |
+
const fileNameSpan = document.getElementById('fileName');
|
| 324 |
+
const examplesContainer = document.getElementById('examplesContainer');
|
| 325 |
+
const examplesLoadingText = document.getElementById('examplesLoadingText');
|
| 326 |
+
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
|
| 327 |
+
const imagePreview = document.getElementById('imagePreview');
|
| 328 |
+
const predictButton = document.getElementById('predictButton');
|
| 329 |
+
const clearButton = document.getElementById('clearButton');
|
| 330 |
+
const errorMessage = document.getElementById('errorMessage');
|
| 331 |
+
// OCR Output
|
| 332 |
+
const loadingIndicator = document.getElementById('loadingIndicator');
|
| 333 |
+
const loadingText = document.getElementById('loadingText');
|
| 334 |
+
const resultDisplay = document.getElementById('resultDisplay');
|
| 335 |
+
// const ocrRecognizedText = document.getElementById('ocrRecognizedText'); // *** REMOVED ***
|
| 336 |
+
// const wordCountText = document.getElementById('wordCountText'); // *** REMOVED ***
|
| 337 |
+
const sakshiOutputText = document.getElementById('sakshiOutputText');
|
| 338 |
+
const wordDetectionImg = document.getElementById('wordDetectionImg');
|
| 339 |
+
// const predictionImg = document.getElementById('predictionImg'); // Removed
|
| 340 |
+
const initialMessageOutput = document.getElementById('initialMessageOutput');
|
| 341 |
+
// Translation
|
| 342 |
+
const textToTranslateInput = document.getElementById('textToTranslate');
|
| 343 |
+
const sourceLanguageInput = document.getElementById('sourceLanguage');
|
| 344 |
+
const targetLanguageInput = document.getElementById('targetLanguage');
|
| 345 |
+
const translateButton = document.getElementById('translateButton');
|
| 346 |
+
const translationLoading = document.getElementById('translationLoading');
|
| 347 |
+
const translationResultDisplay = document.getElementById('translationResultDisplay');
|
| 348 |
+
const detectedSourceLanguage = document.getElementById('detectedSourceLanguage');
|
| 349 |
+
const translationTargetLanguage = document.getElementById('translationTargetLanguage');
|
| 350 |
+
const translatedText = document.getElementById('translatedText');
|
| 351 |
+
const translationError = document.getElementById('translationError');
|
| 352 |
+
const initialMessageTranslation = document.getElementById('initialMessageTranslation');
|
| 353 |
+
// Gender Prediction
|
| 354 |
+
const namesInput = document.getElementById('namesInput');
|
| 355 |
+
const predictGenderButton = document.getElementById('predictGenderButton');
|
| 356 |
+
const genderLoading = document.getElementById('genderLoading');
|
| 357 |
+
const genderResultDisplay = document.getElementById('genderResultDisplay');
|
| 358 |
+
const genderError = document.getElementById('genderError');
|
| 359 |
+
const initialMessageGender = document.getElementById('initialMessageGender');
|
| 360 |
+
|
| 361 |
+
// --- Example Images ---
|
| 362 |
+
const exampleImages = [
|
| 363 |
+
{ name: "अखिल", url: "samples/अखिल.png" }, // Assuming a relative path based on the image structure
|
| 364 |
+
{ name: "अग्रसर", url: "samples/अग्रसर.png" },
|
| 365 |
+
{ name: "अंतर्राष्ट्रीय", url: "samples/अंतर्राष्ट्रीय.png" },
|
| 366 |
+
{ name: "अध्ययन", url: "samples/अध्ययन.png" },
|
| 367 |
+
{ name: "अनुसंधान", url: "samples/अनुसंधान.png" },
|
| 368 |
+
{ name: "अन्य", url: "samples/अन्य.png" },
|
| 369 |
+
{ name: "अलावा", url: "samples/अलावा.png" },
|
| 370 |
+
{ name: "अवसर", url: "samples/अवसर.png" },
|
| 371 |
+
{ name: "आती", url: "samples/आती.png" },
|
| 372 |
+
{ name: "आधुनिक", url: "samples/आधुनिक.png" }
|
| 373 |
+
];
|
| 374 |
+
// --- Helper Functions (Auth UI, Error, Loading) ---
|
| 375 |
+
// ... (Keep showAuthScreen, showAppScreen, displayError, hideError, showAuthLoading, showLoadingIndicator, showOcrLoading functions exactly as in the previous version) ...
|
| 376 |
+
function showAuthScreen() { console.log("Showing Auth Screen"); accessToken = null; localStorage.removeItem(TOKEN_STORAGE_KEY); if (authContainer) authContainer.classList.remove('hidden'); if (appContainer) appContainer.classList.add('hidden'); if (mainNav) mainNav.classList.add('hidden'); if (userInfoIcons) userInfoIcons.classList.add('hidden'); if (logoutButton) logoutButton.classList.add('hidden'); hideError(loginErrorDiv); hideError(signupErrorDiv); if(loginForm) loginForm.classList.remove('hidden'); if(signupForm) signupForm.classList.add('hidden'); if(loginUsernameInput) loginUsernameInput.value = ''; if(loginPasswordInput) loginPasswordInput.value = ''; }
|
| 377 |
+
function showAppScreen() { console.log("Showing App Screen"); if (!accessToken) { showAuthScreen(); return; } if (authContainer) authContainer.classList.add('hidden'); if (appContainer) appContainer.classList.remove('hidden'); if (mainNav) mainNav.classList.remove('hidden', 'md:flex'); if (mainNav) mainNav.classList.add('md:flex'); if (userInfoIcons) userInfoIcons.classList.remove('hidden', 'md:flex'); if (userInfoIcons) userInfoIcons.classList.add('md:flex'); if (logoutButton) logoutButton.classList.remove('hidden'); loadExamples(); activateTab('ocr'); }
|
| 378 |
+
function displayError(element, message) { if (element) { element.textContent = message; element.classList.remove('hidden'); console.warn("Error:", message); } else { console.error("Element missing for error:", message); } }
|
| 379 |
+
function hideError(element) { if (element) { element.classList.add('hidden'); element.textContent = ''; } }
|
| 380 |
+
function showAuthLoading(formType, isLoading) { const button = formType === 'login' ? loginSubmitButton : signupSubmitButton; if (!button) return; const spinner = button.querySelector('.spinner'); button.disabled = isLoading; if (spinner) spinner.classList.toggle('hidden', !isLoading); }
|
| 381 |
+
function showLoadingIndicator(element, show = true) { if (element) { element.style.display = show ? 'flex' : 'none'; } }
|
| 382 |
+
function showOcrLoading(isLoading, message = "Processing...") { if (loadingIndicator) { loadingIndicator.classList.toggle('hidden', !isLoading); if (loadingText) loadingText.textContent = message; } if (predictButton) predictButton.disabled = isLoading; if (clearButton) clearButton.disabled = isLoading; }
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
// --- Generic Authenticated Fetch ---
|
| 386 |
+
// ... (Keep authenticatedFetch function exactly as in the previous correct version) ...
|
| 387 |
+
async function authenticatedFetch(url, options = {}) { if (!accessToken) { showAuthScreen(); throw new Error("Not authenticated."); } const defaultOptions = { headers: { 'Authorization': `Bearer ${accessToken}`, 'Accept': 'application/json', ...options.headers }, }; if (options.body && !(options.body instanceof FormData) && !(options.body instanceof URLSearchParams) && !defaultOptions.headers['Content-Type']) { defaultOptions.headers['Content-Type'] = 'application/json'; } const finalOptions = { ...options, ...defaultOptions }; /* console.log("Auth Fetch:", url); */ try { const response = await fetch(url, finalOptions); /* console.log(`Status ${url}:`, response.status); */ if (!response.ok) { let errorData = { detail: `Request failed: ${response.status}` }; let bodyText = ''; try { bodyText = await response.text(); errorData = JSON.parse(bodyText); if (!errorData.detail) errorData.detail = bodyText; } catch (e) { errorData.detail = bodyText || errorData.detail; } console.error(`API Error ${url}:`, response.status, errorData.detail); if (response.status === 401) { showAuthScreen(); throw new Error("Auth failed/expired."); } throw new Error(typeof errorData.detail === 'string' ? errorData.detail : JSON.stringify(errorData.detail)); } const contentType = response.headers.get("content-type"); if (response.status === 204) return null; if (contentType?.includes("application/json")) return await response.json(); if (contentType?.includes("image/") || contentType?.includes("application/octet-stream")) return await response.blob(); return await response.text(); } catch (error) { console.error(`Fetch Catch ${url}:`, error); throw error; } }
|
| 388 |
+
|
| 389 |
+
// --- LOGIN / SIGNUP / LOGOUT ---
|
| 390 |
+
// ... (Keep handleLogin, handleSignup, handleLogout functions exactly as in the previous correct version) ...
|
| 391 |
+
if (loginForm) { loginForm.addEventListener('submit', async (event) => { event.preventDefault(); const username = loginUsernameInput.value.trim(); const password = loginPasswordInput.value.trim(); hideError(loginErrorDiv); if (!username || !password) { displayError(loginErrorDiv, "Username & Password required."); return; } const body = new URLSearchParams(); body.append('username', username); body.append('password', password); showAuthLoading('login', true); try { const response = await fetch(TOKEN_ENDPOINT, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'}, body: body }); const data = await response.json().catch(() => null); if (!response.ok) { throw new Error(data?.detail || `Login failed (${response.status})`); } if (data?.access_token) { accessToken = data.access_token; localStorage.setItem(TOKEN_STORAGE_KEY, accessToken); console.log("Login success."); showAppScreen(); } else { throw new Error('No access token received.'); } } catch (error) { let msg = error.message || "Unknown login error."; if (msg.includes("Incorrect")) msg = "Incorrect username or password."; if (error instanceof TypeError) msg = "Network error."; displayError(loginErrorDiv, msg); } finally { showAuthLoading('login', false); } }); }
|
| 392 |
+
if (signupForm) { signupForm.addEventListener('submit', async (event) => { event.preventDefault(); const username = signupUsernameInput.value.trim(); const email = signupEmailInput.value.trim(); const password = signupPasswordInput.value.trim(); hideError(signupErrorDiv); if (!username || !email || !password) { displayError(signupErrorDiv, "Please fill all fields."); return; } if (password.length < 6) { displayError(signupErrorDiv, "Password must be >= 6 characters."); return; } if (!/\S+@\S+\.\S+/.test(email)) { displayError(signupErrorDiv, "Invalid email format."); return; } showAuthLoading('signup', true); try { const response = await fetch(SIGNUP_ENDPOINT, { method: 'POST', headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}, body: JSON.stringify({ username, email, password }) }); const data = await response.json().catch(() => null); if (!response.ok) { throw new Error(data?.detail || `Signup failed (${response.status})`); } console.log("Signup successful:", data); alert('Signup successful! Please login.'); if(signupForm) signupForm.classList.add('hidden'); if(loginForm) loginForm.classList.remove('hidden'); signupUsernameInput.value = ''; signupEmailInput.value = ''; signupPasswordInput.value = ''; } catch (error) { let msg = error.message || "Unknown signup error."; if (error instanceof TypeError) msg = "Network error."; displayError(signupErrorDiv, msg); } finally { showAuthLoading('signup', false); } }); }
|
| 393 |
+
if(logoutButton) logoutButton.addEventListener('click', () => { showAuthScreen(); clearOCRState(); clearTranslationState(); clearGenderState(); });
|
| 394 |
+
if (showSignupLink) showSignupLink.addEventListener('click', (e) => { e.preventDefault(); if(loginForm) loginForm.classList.add('hidden'); if(signupForm) signupForm.classList.remove('hidden'); hideError(loginErrorDiv); hideError(signupErrorDiv); });
|
| 395 |
+
if (showLoginLink) showLoginLink.addEventListener('click', (e) => { e.preventDefault(); if(signupForm) signupForm.classList.add('hidden'); if(loginForm) loginForm.classList.remove('hidden'); hideError(loginErrorDiv); hideError(signupErrorDiv); });
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
// --- TAB MANAGEMENT ---
|
| 399 |
+
function activateTab(tabId) { /* ... (Keep implementation) ... */ tabButtons.forEach(b => { b.classList.toggle('active', b.dataset.tab === tabId); }); tabContents.forEach(c => { c.classList.toggle('active', c.id === `${tabId}Content`); }); console.log("Activated tab:", tabId); }
|
| 400 |
+
tabButtons.forEach(button => { button.addEventListener('click', () => activateTab(button.dataset.tab)); });
|
| 401 |
+
|
| 402 |
+
// --- OCR Functionality ---
|
| 403 |
+
function loadExamples() { /* ... (Keep implementation) ... */ if (!examplesContainer || !appContainer || appContainer.classList.contains('hidden')) return; if (examplesLoadingText) examplesContainer.innerHTML = ''; const examplesToShow = exampleImages.slice(0, 12); examplesToShow.forEach((imgData, index) => { const div = document.createElement('div'); div.className = 'example-image-container bg-gray-100 dark:bg-gray-700 rounded-md overflow-hidden border border-gray-200 dark:border-gray-600'; div.setAttribute('role', 'button'); div.setAttribute('tabindex', '0'); div.setAttribute('aria-label', `Example ${index + 1}: ${imgData.name}`); div.dataset.imageUrl = imgData.url; div.dataset.imageName = imgData.name; const img = document.createElement('img'); img.src = imgData.url; img.alt = `Example ${index + 1}: ${imgData.name}`; img.className = 'example-image'; img.loading = 'lazy'; img.onerror = () => { div.innerHTML = `<span class="text-xs text-red-500 p-1">Load Error</span>`; }; div.appendChild(img); div.addEventListener('click', () => handleExampleClick(div, imgData.url, imgData.name)); div.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleExampleClick(div, imgData.url, imgData.name); } }); examplesContainer.appendChild(div); }); }
|
| 404 |
+
async function handleExampleClick(element, imageUrl, imageName) { /* ... (Keep implementation) ... */ if (currentExampleSelection) { currentExampleSelection.classList.remove('selected'); } element.classList.add('selected'); currentExampleSelection = element; if (imageUpload) imageUpload.value = null; displayFileName(`Example: ${imageName}`); resetResultState(); hideError(errorMessage); try { showPreview(imageUrl); showOcrLoading(true, "Fetching..."); const response = await fetch(imageUrl); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const blob = await response.blob(); const fileName = imageUrl.substring(imageUrl.lastIndexOf('/') + 1)?.split('?')[0] || `${imageName.replace(/\s+/g, '_')}.png`; currentImageSource = new File([blob], fileName, { type: blob.type || 'image/png' }); enableProcessButtons(); } catch (error) { console.error("Error fetching example:", error); displayError(errorMessage, `Could not load example: ${error.message}`); disableProcessButtons(); currentImageSource = null; hidePreview(); } finally { showOcrLoading(false); } }
|
| 405 |
+
function handleFileSelect() { /* ... (Keep implementation) ... */ const file = imageUpload.files[0]; if (!file) return; const allowed = ['image/png', 'image/jpeg', 'image/jpg']; if (!allowed.includes(file.type)) { displayError(errorMessage, 'Invalid file type.'); clearOCRState(); return; } currentImageSource = file; displayFileName(file.name); resetResultState(); hideError(errorMessage); if (currentExampleSelection) { currentExampleSelection.classList.remove('selected'); currentExampleSelection = null; } showPreview(file); enableProcessButtons(); }
|
| 406 |
+
function displayFileName(name) { /* ... (Keep implementation) ... */ if (fileNameSpan) { fileNameSpan.textContent = name; fileNameSpan.classList.remove('italic', 'text-gray-500', 'dark:text-gray-400'); fileNameSpan.classList.add('font-medium'); } }
|
| 407 |
+
function resetFileName() { /* ... (Keep implementation) ... */ if (fileNameSpan) { const defaultEn = "No file chosen"; const defaultHi = "कोई फ़ाइल नहीं चुनी गई"; const currentLang = document.documentElement.lang || 'en'; fileNameSpan.textContent = currentLang === 'hi' ? defaultHi : defaultEn; fileNameSpan.classList.add('italic', 'text-gray-500', 'dark:text-gray-400'); fileNameSpan.classList.remove('font-medium'); } }
|
| 408 |
+
function showPreview(source) { /* ... (Keep implementation) ... */ if (!imagePreview || !imagePreviewContainer) return; imagePreview.src = ''; imagePreviewContainer.classList.remove('hidden'); if (typeof source === 'string') { imagePreview.src = source; } else if (source instanceof File) { const reader = new FileReader(); reader.onload = (e) => { imagePreview.src = e.target.result; }; reader.onerror = () => displayError(errorMessage, "Could not read file."); reader.readAsDataURL(source); } }
|
| 409 |
+
function hidePreview() { /* ... (Keep implementation) ... */ if (imagePreview && imagePreviewContainer) { imagePreview.src = '#'; imagePreviewContainer.classList.add('hidden'); } }
|
| 410 |
+
function enableProcessButtons() { /* ... (Keep implementation) ... */ if (predictButton) predictButton.disabled = false; if (clearButton) clearButton.classList.remove('hidden'); }
|
| 411 |
+
function disableProcessButtons() { /* ... (Keep implementation) ... */ if (predictButton) predictButton.disabled = true; if (clearButton) clearButton.classList.add('hidden'); }
|
| 412 |
+
|
| 413 |
+
function resetResultState() { /* *** MODIFIED *** */
|
| 414 |
+
if (resultDisplay) resultDisplay.classList.add('hidden');
|
| 415 |
+
if (initialMessageOutput) initialMessageOutput.classList.remove('hidden');
|
| 416 |
+
// *** REMOVED lines for ocrRecognizedText and wordCountText ***
|
| 417 |
+
if (sakshiOutputText) sakshiOutputText.textContent = '';
|
| 418 |
+
if (wordDetectionImg) { wordDetectionImg.src = '#'; wordDetectionImg.style.display = 'none'; }
|
| 419 |
+
hideError(errorMessage);
|
| 420 |
}
|
| 421 |
|
| 422 |
+
function clearOCRState() { /* *** MODIFIED (only removed reference to predictionImg reset which was already removed) *** */
|
| 423 |
+
if (imageUpload) imageUpload.value = ''; currentImageSource = null; if (currentExampleSelection) { currentExampleSelection.classList.remove('selected'); currentExampleSelection = null; } hidePreview(); resetFileName(); disableProcessButtons(); resetResultState(); hideError(errorMessage); showOcrLoading(false); console.log("OCR State Cleared");
|
| 424 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
|
| 426 |
+
if (imageUpload) imageUpload.addEventListener('change', handleFileSelect);
|
| 427 |
+
if (predictButton) predictButton.addEventListener('click', processImage);
|
| 428 |
+
if (clearButton) clearButton.addEventListener('click', clearOCRState);
|
| 429 |
+
|
| 430 |
+
async function processImage() { /* *** MODIFIED *** */
|
| 431 |
+
if (!currentImageSource) { displayError(errorMessage, 'Please choose/select an image.'); return; } hideError(errorMessage); resetResultState(); showOcrLoading(true, "Preparing...");
|
| 432 |
+
try {
|
| 433 |
+
const formData = new FormData(); if (currentImageSource instanceof File) { formData.append('file', currentImageSource); } else if (typeof currentImageSource === 'string') { showOcrLoading(true, "Fetching sample..."); const response = await fetch(currentImageSource); if (!response.ok) throw new Error(`Fetch failed: ${response.statusText}`); const blob = await response.blob(); const filename = currentImageSource.split('/').pop()?.split('?')[0] || 'sample.png'; formData.append('file', new File([blob], filename, { type: blob.type || 'image/png' })); } else { throw new Error("Invalid image source."); }
|
| 434 |
+
showOcrLoading(true, "Processing OCR...");
|
| 435 |
+
const data = await authenticatedFetch(PROCESS_ENDPOINT, { method: 'POST', body: formData });
|
| 436 |
+
console.log("OCR Response:", data);
|
| 437 |
+
|
| 438 |
+
// *** REMOVED lines for ocrRecognizedText and wordCountText ***
|
| 439 |
+
|
| 440 |
+
// *** KEPT these lines ***
|
| 441 |
+
if(sakshiOutputText) sakshiOutputText.textContent = data.sakshi_output || 'No raw output.';
|
| 442 |
+
if(textToTranslateInput) textToTranslateInput.value = data.prediction_label || ''; // Use prediction_label for translate input
|
| 443 |
+
|
| 444 |
+
// *** Only fetch Word Detection Image ***
|
| 445 |
+
const fetchWordDetectImage = async () => {
|
| 446 |
+
wordDetectionImg.style.display = 'none';
|
| 447 |
+
wordDetectionImg.src = '#';
|
| 448 |
+
try {
|
| 449 |
+
showOcrLoading(true, `Fetching Word Detection...`);
|
| 450 |
+
const blob = await authenticatedFetch(WORD_DETECT_ENDPOINT);
|
| 451 |
+
if (blob instanceof Blob) {
|
| 452 |
+
const imageUrl = URL.createObjectURL(blob);
|
| 453 |
+
wordDetectionImg.onload = () => URL.revokeObjectURL(imageUrl);
|
| 454 |
+
wordDetectionImg.onerror = () => console.error(`Failed to load Word Detection from blob URL`);
|
| 455 |
+
wordDetectionImg.src = imageUrl;
|
| 456 |
+
wordDetectionImg.alt = "Word Detection Preview";
|
| 457 |
+
wordDetectionImg.style.display = 'block';
|
| 458 |
+
} else {
|
| 459 |
+
console.warn(`Failed blob fetch for Word Detection`);
|
| 460 |
+
}
|
| 461 |
+
} catch (imgError) {
|
| 462 |
+
console.error(`Error fetching Word Detection image:`, imgError);
|
| 463 |
+
}
|
| 464 |
+
};
|
| 465 |
+
|
| 466 |
+
await fetchWordDetectImage(); // Await only the word detection image
|
| 467 |
+
|
| 468 |
+
if(resultDisplay) resultDisplay.classList.remove('hidden');
|
| 469 |
+
if(initialMessageOutput) initialMessageOutput.classList.add('hidden');
|
| 470 |
+
} catch (error) {
|
| 471 |
+
console.error('Error processing image:', error);
|
| 472 |
+
displayError(errorMessage, `Processing Error: ${error.message}`);
|
| 473 |
+
resetResultState(); // Ensure results are cleared on error
|
| 474 |
+
} finally {
|
| 475 |
+
showOcrLoading(false);
|
| 476 |
+
}
|
| 477 |
+
}
|
| 478 |
|
| 479 |
+
// --- TRANSLATION Functionality (Updated for Gemini Backend) ---
|
| 480 |
+
function clearTranslationState() {
|
| 481 |
+
if(textToTranslateInput) textToTranslateInput.value = '';
|
| 482 |
+
if(sourceLanguageInput) sourceLanguageInput.value = '';
|
| 483 |
+
if(targetLanguageInput) targetLanguageInput.value = '';
|
| 484 |
+
if(translationResultDisplay) translationResultDisplay.classList.add('hidden');
|
| 485 |
+
if(initialMessageTranslation) initialMessageTranslation.classList.remove('hidden');
|
| 486 |
+
// No 'detectedSourceLanguage' element to clear anymore
|
| 487 |
+
if(translationTargetLanguage) translationTargetLanguage.textContent = '';
|
| 488 |
+
if(translatedText) translatedText.innerHTML = ''; // Clear innerHTML
|
| 489 |
+
hideError(translationError);
|
| 490 |
+
showLoadingIndicator(translationLoading, false);
|
| 491 |
+
console.log("Translation Cleared");
|
| 492 |
+
// Ensure source language row is hidden on clear
|
| 493 |
+
const sourceLangItem = document.getElementById('detectedSourceLanguage')?.closest('.result-item');
|
| 494 |
+
if (sourceLangItem) {
|
| 495 |
+
sourceLangItem.style.display = 'none';
|
| 496 |
+
}
|
| 497 |
+
}
|
| 498 |
|
| 499 |
+
if(translateButton) {
|
| 500 |
+
translateButton.addEventListener('click', async () => {
|
| 501 |
+
const text = textToTranslateInput.value.trim();
|
| 502 |
+
const sourceLang = sourceLanguageInput.value.trim();
|
| 503 |
+
const targetLang = targetLanguageInput.value.trim();
|
| 504 |
+
|
| 505 |
+
hideError(translationError);
|
| 506 |
+
showLoadingIndicator(translationLoading, true);
|
| 507 |
+
if(translationResultDisplay) translationResultDisplay.classList.add('hidden');
|
| 508 |
+
if(initialMessageTranslation) initialMessageTranslation.classList.remove('hidden'); // Show initial message while loading/error
|
| 509 |
+
|
| 510 |
+
if (!text) {
|
| 511 |
+
displayError(translationError, 'Please enter text to translate.');
|
| 512 |
+
showLoadingIndicator(translationLoading, false);
|
| 513 |
+
return;
|
| 514 |
+
}
|
| 515 |
+
if (!targetLang) {
|
| 516 |
+
displayError(translationError, 'Please enter the target language (e.g., English, Hindi).');
|
| 517 |
+
showLoadingIndicator(translationLoading, false);
|
| 518 |
+
return;
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
console.log(`Attempting translation from '${sourceLang || 'auto-detect'}' to '${targetLang}'. Text:`, text.substring(0, 50) + "...");
|
| 522 |
+
|
| 523 |
+
const payload = {
|
| 524 |
+
text: text,
|
| 525 |
+
target_language: targetLang
|
| 526 |
+
};
|
| 527 |
+
// Only include source_language if the user provided it
|
| 528 |
+
if (sourceLang) {
|
| 529 |
+
payload.source_language = sourceLang;
|
| 530 |
+
}
|
| 531 |
+
console.log("Translation API Request Payload:", payload);
|
| 532 |
+
|
| 533 |
+
try {
|
| 534 |
+
// Use standard fetch, NOT authenticatedFetch, as this endpoint doesn't require the OCR token
|
| 535 |
+
const response = await fetch(TRANSLATE_ENDPOINT, {
|
| 536 |
+
method: 'POST',
|
| 537 |
+
headers: {
|
| 538 |
+
'Content-Type': 'application/json',
|
| 539 |
+
'Accept': 'text/html' // Indicate preference for HTML response
|
| 540 |
+
},
|
| 541 |
+
body: JSON.stringify(payload)
|
| 542 |
+
});
|
| 543 |
+
|
| 544 |
+
// Check if the request was successful (status code 2xx)
|
| 545 |
+
if (!response.ok) {
|
| 546 |
+
let errorDetail = `Request failed with status: ${response.status}`;
|
| 547 |
+
try {
|
| 548 |
+
// Try to parse FastAPI's JSON error response
|
| 549 |
+
const errorJson = await response.json();
|
| 550 |
+
errorDetail = errorJson.detail || JSON.stringify(errorJson);
|
| 551 |
+
} catch (e) {
|
| 552 |
+
// If parsing JSON fails, try to get plain text error
|
| 553 |
+
try {
|
| 554 |
+
errorDetail = await response.text();
|
| 555 |
+
} catch (textErr) {
|
| 556 |
+
// Keep the original status code message if text fails too
|
| 557 |
+
}
|
| 558 |
+
}
|
| 559 |
+
// Throw an error with the extracted detail
|
| 560 |
+
throw new Error(errorDetail);
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
// Get the response body as HTML text
|
| 564 |
+
const translatedHtml = await response.text();
|
| 565 |
+
console.log("Translation API Raw HTML Response:", translatedHtml);
|
| 566 |
+
|
| 567 |
+
// --- Update UI ---
|
| 568 |
+
// Display the target language requested by the user
|
| 569 |
+
if(translationTargetLanguage) translationTargetLanguage.textContent = targetLang;
|
| 570 |
+
// Set the innerHTML of the result element to render the HTML tags
|
| 571 |
+
if(translatedText) translatedText.innerHTML = translatedHtml;
|
| 572 |
+
|
| 573 |
+
// Remove the section displaying detected source language as it's not returned
|
| 574 |
+
const sourceLangItem = document.getElementById('detectedSourceLanguage')?.closest('.result-item');
|
| 575 |
+
if (sourceLangItem) {
|
| 576 |
+
sourceLangItem.style.display = 'none'; // Hide the source language display row
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
|
| 580 |
+
if(translationResultDisplay) translationResultDisplay.classList.remove('hidden');
|
| 581 |
+
if(initialMessageTranslation) initialMessageTranslation.classList.add('hidden'); // Hide initial message on success
|
| 582 |
+
|
| 583 |
+
} catch (error) {
|
| 584 |
+
console.error('Error during translation fetch/processing:', error);
|
| 585 |
+
let userMessage = `Translation Error: ${error.message}`; // Default message
|
| 586 |
+
|
| 587 |
+
// Provide more specific feedback if possible
|
| 588 |
+
if (error.message.includes("supported")) {
|
| 589 |
+
userMessage = `Translation Error: The target language '${targetLang}' might not be supported by the backend.`;
|
| 590 |
+
} else if (error.message.includes("Failed to fetch")) {
|
| 591 |
+
userMessage = "Translation Error: Network error or CORS issue. Could not reach the translation server. Check console logs.";
|
| 592 |
+
} else if (error.message.startsWith("Request failed with status:")) {
|
| 593 |
+
userMessage = `Translation Error: Server responded with an error (${error.message})`; // Show status code error
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
displayError(translationError, userMessage);
|
| 597 |
+
if(initialMessageTranslation) initialMessageTranslation.classList.remove('hidden'); // Ensure initial message placeholder is visible on error
|
| 598 |
+
} finally {
|
| 599 |
+
showLoadingIndicator(translationLoading, false);
|
| 600 |
+
}
|
| 601 |
+
});
|
| 602 |
}
|
| 603 |
|
| 604 |
+
// --- GENDER PREDICTION Functionality ---
|
| 605 |
+
function clearGenderState() { /* ... (Keep implementation) ... */ if(namesInput) namesInput.value = ''; if(genderResultDisplay) genderResultDisplay.innerHTML = ''; if(genderResultDisplay) genderResultDisplay.classList.add('hidden'); if(initialMessageGender) initialMessageGender.classList.remove('hidden'); hideError(genderError); showLoadingIndicator(genderLoading, false); console.log("Gender Cleared"); }
|
| 606 |
+
function displayGenderResults(data) { /* ... (Keep implementation) ... */ if (!genderResultDisplay) return; genderResultDisplay.innerHTML = ''; if (!data || !data.predictions || data.predictions.length === 0) { genderResultDisplay.innerHTML = "<p class='text-center text-gray-500 dark:text-gray-400'>No prediction results.</p>"; return; } let resultsHTML = ''; data.predictions.forEach(p => { const maleProb = (p.male_probability !== null && p.male_probability !== undefined) ? p.male_probability.toFixed(2) : 'N/A'; const confidence = (p.confidence !== null && p.confidence !== undefined) ? p.confidence.toFixed(2) : 'N/A'; resultsHTML += `<div class="result-item"><div class="result-label">Name:</div><div class="result-value">${p.name || 'N/A'}</div><div class="result-label">Predicted:</div><div class="result-value">${p.predicted_gender || 'N/A'}</div><div class="result-label">Male Prob:</div><div class="result-value">${maleProb}</div><div class="result-label">Confidence:</div><div class="result-value">${confidence}</div></div>`; }); genderResultDisplay.innerHTML = resultsHTML; genderResultDisplay.classList.remove('hidden'); if(initialMessageGender) initialMessageGender.classList.add('hidden'); }
|
| 607 |
+
if(predictGenderButton) {
|
| 608 |
+
predictGenderButton.addEventListener('click', async () => { /* ... (Keep implementation) ... */
|
| 609 |
+
const namesString = namesInput.value.trim(); const names = namesString.split(',').map(name => name.trim()).filter(Boolean); hideError(genderError); showLoadingIndicator(genderLoading, true); if(genderResultDisplay) genderResultDisplay.classList.add('hidden'); if(initialMessageGender) initialMessageGender.classList.remove('hidden'); if (names.length === 0) { displayError(genderError, "Please enter names."); showLoadingIndicator(genderLoading, false); return; }
|
| 610 |
+
try { const data = await authenticatedFetch(GENDER_PREDICT_ENDPOINT, { method: 'POST', body: JSON.stringify({ names: names, threshold: 0.5 }) }); console.log("Gender Response:", data); displayGenderResults(data); } catch (error) { console.error("Gender Prediction error:", error); displayError(genderError, `Prediction Error: ${error.message}`); } finally { showLoadingIndicator(genderLoading, false); }
|
| 611 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
}
|
| 613 |
|
| 614 |
+
// --- Initialization ---
|
| 615 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 616 |
+
console.log("DOM Loaded. Initializing...");
|
| 617 |
+
accessToken = localStorage.getItem(TOKEN_STORAGE_KEY);
|
| 618 |
+
if (accessToken) { showAppScreen(); } else { showAuthScreen(); }
|
| 619 |
+
resetFileName(); applyLanguage(savedLang);
|
| 620 |
+
clearOCRState(); clearTranslationState(); clearGenderState();
|
| 621 |
+
activateTab('ocr'); // Ensure OCR tab is active first
|
| 622 |
+
// Ensure source language row is hidden initially
|
| 623 |
+
const sourceLangItem = document.getElementById('detectedSourceLanguage')?.closest('.result-item');
|
| 624 |
+
if (sourceLangItem) {
|
| 625 |
+
sourceLangItem.style.display = 'none';
|
| 626 |
+
}
|
| 627 |
+
});
|
| 628 |
+
|
| 629 |
</script>
|
| 630 |
|
|
|
|
|
|
|
| 631 |
</body>
|
| 632 |
</html>
|
samples/turotial.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:02b421b9c9d0ade03b6f5a24144a343d5cd4db02b17a14af169f58b5d1f5b26b
|
| 3 |
+
size 9065701
|
technology.html
CHANGED
|
@@ -158,24 +158,22 @@ body {
|
|
| 158 |
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 159 |
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 160 |
<div class="flex items-center">
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
<nav class="hidden md:flex space-x-6">
|
| 164 |
-
<a href="
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
<a href="
|
| 177 |
-
class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
|
| 178 |
-
data-lang-en="About" data-lang-hi="बारे में">About</a>
|
| 179 |
</nav>
|
| 180 |
</div>
|
| 181 |
<!-- Right side header items -->
|
|
|
|
| 158 |
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
| 159 |
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 160 |
<div class="flex items-center">
|
| 161 |
+
<a href="index.html" class="text-2xl font-['Pacifico'] text-primary dark:text-blue-400 mr-8" data-lang-en="AI Text Tools" data-lang-hi="एआई टेक्स्ट उपकरण">AI Text Tools</a>
|
| 162 |
+
<!-- *** START: Updated Navigation Bar *** -->
|
| 163 |
+
<nav id="mainNav" class="hidden md:flex space-x-6">
|
| 164 |
+
<a href="index.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Home" data-lang-hi="होम">Home</a>
|
| 165 |
+
<!-- Assuming recognizer.html is the main tool page, link from index.html might be better -->
|
| 166 |
+
<a href="recognizer.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a>
|
| 167 |
+
<!-- Assuming examples.html exists -->
|
| 168 |
+
<a href="examples.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</a>
|
| 169 |
+
<!-- Assuming technology.html exists -->
|
| 170 |
+
<a href="technology.html" class="nav-link text-primary dark:text-blue-400 font-medium" data-lang-en="Technology" data-lang-hi="तकनीक">Technology</a>
|
| 171 |
+
<!-- Assuming about.html exists -->
|
| 172 |
+
<a href="about.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="About" data-lang-hi="बारे में">About</a>
|
| 173 |
+
<!-- Link to contact.html -->
|
| 174 |
+
<a href="contact.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Contact" data-lang-hi="संपर्क">Contact</a>
|
| 175 |
+
<!-- Active link for this page -->
|
| 176 |
+
<a href="feedback.html" class="nav-link hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300" data-lang-en="Feedback" data-lang-hi="प्रतिक्रिया">Feedback</a>
|
|
|
|
|
|
|
| 177 |
</nav>
|
| 178 |
</div>
|
| 179 |
<!-- Right side header items -->
|