sameernotes commited on
Commit
3d6fdbe
·
verified ·
1 Parent(s): 8b13bfc

Upload 19 files

Browse files
Files changed (10) hide show
  1. .gitattributes +1 -0
  2. about.html +17 -18
  3. app.py +397 -0
  4. contact.html +371 -0
  5. examples.html +16 -18
  6. feedback.html +390 -0
  7. index.html +252 -536
  8. recognizer.html +560 -366
  9. samples/turotial.mp4 +3 -0
  10. 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
- <!-- Add data-lang attribute -->
162
- <a href="/" class="text-2xl font-['Pacifico'] text-primary mr-8" data-lang-en="HindiOCR" data-lang-hi="हिन्दी ओसीआर">HindiOCR</a>
163
- <nav class="hidden md:flex space-x-6">
164
- <a href="/index.html"
165
- class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
166
- data-lang-en="Home" data-lang-hi="होम">Home</a>
167
- <a href="/recognizer.html"
168
- class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
169
- data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a>
170
- <a href="/examples.html"
171
- class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
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-primary dark:text-blue-400 font-medium"
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
- <!-- Add data-lang attribute -->
179
- <a href="/" class="text-2xl font-['Pacifico'] text-primary mr-8" data-lang-en="HindiOCR" data-lang-hi="हिन्दी ओसीआर">HindiOCR</a>
180
- <nav class="hidden md:flex space-x-6">
181
- <a href="/index.html"
182
- class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
183
- data-lang-en="Home" data-lang-hi="होम">Home</a>
184
- <a href="/recognizer.html"
185
- class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
186
- data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a>
187
- <a href="/examples.html"
188
- class="hover:text-primary dark:hover:text-blue-400 text-primary dark:text-blue-400 font-medium"
189
- data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</a>
190
- <a href="/technology.html"
191
- class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
192
- data-lang-en="Technology" data-lang-hi="तकनीक">Technology</a>
193
- <a href="/about.html"
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 Analysis - NIC Project</title>
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, 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">
@@ -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
- <!-- *** 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
  </nav>
116
  </div>
117
- <!-- Right side header items (Theme, Lang, Logout, etc.) -->
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
- <!-- ===== Authentication Container ===== -->
129
- <div id="authContainer" class="flex-grow flex items-center justify-center p-4">
130
- <!-- Login/Signup Forms (Keep as is) -->
131
- <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>
132
- </div>
133
-
134
- <!-- ===== Main Application Container ===== -->
135
- <main id="appContainer" class="hidden flex-grow container mx-auto px-4 py-8">
136
-
137
- <!-- Tab Navigation -->
138
- <div class="mb-6 border-b border-gray-200 dark:border-gray-700">
139
- <nav class="-mb-px flex space-x-6 overflow-x-auto" aria-label="Tabs"> <!-- Added overflow-x-auto -->
140
- <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>
141
- <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>
142
- <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>
143
- </nav>
144
- </div>
145
-
146
- <!-- Tab Content Area -->
147
- <div>
148
- <!-- OCR Tab Content -->
149
- <div id="ocrContent" class="tab-content active">
150
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8">
151
- <!-- Input Column -->
152
- <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 card">
153
- <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>
154
- <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>
155
- <input type="file" id="imageUpload" accept="image/png, image/jpeg, image/jpg" class="hidden"/>
156
- <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>
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
- <div id="errorMessage" class="error-message mt-4"></div>
165
- </div>
166
-
167
- <!-- ***** MODIFIED Output Column ***** -->
168
- <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">
169
- <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>
170
- <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>
171
- <div id="resultDisplay" class="mt-4 w-full text-center hidden space-y-6">
172
-
173
- <!-- *** MOVED Word Detection Section First *** -->
174
- <div class="result-card !p-4"> <!-- Adjusted padding -->
175
- <h3 class="text-md font-semibold text-center mb-2" data-lang-en="Word Detection" data-lang-hi="शब्द पहचान">Word Detection</h3>
176
- <img id="wordDetectionImg" src="#" alt="Word Detection Preview" class="result-image" style="display: none;">
177
- </div>
178
-
179
- <!-- *** REMOVED Recognized Text Section *** -->
180
- <!-- The div containing ocrRecognizedText and wordCountText has been deleted -->
181
-
182
- <!-- Raw Detector Output Section (Now Second) -->
183
- <div class="result-card">
184
- <h3 class="text-md font-semibold mb-2" data-lang-en="Raw Detector Output:" data-lang-hi="रॉ डिटेक्टर आउटपुट:">Raw Detector Output:</h3>
185
- <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>
186
- </div>
187
-
 
 
 
188
  </div>
189
- <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>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  </div>
191
- <!-- ***** End of MODIFIED Output Column ***** -->
192
-
 
 
 
 
 
193
  </div>
194
- </div>
195
-
196
- <!-- Translation Tab Content -->
197
- <div id="translationContent" class="tab-content">
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
- <!-- Translation Output -->
221
- <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 card">
222
- <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>
223
- <div id="translationLoading" class="loading-indicator"><div class="spinner"></div><span data-lang-en="Translating..." data-lang-hi="अनुवाद हो रहा है...">Translating...</span></div>
224
- <div id="translationResultDisplay" class="hidden space-y-4">
225
- <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>
226
- <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>
227
- <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>
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
- <!-- Gender Prediction Tab Content -->
235
- <div id="genderContent" class="tab-content">
236
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
237
- <!-- Gender Input -->
238
- <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 card">
239
- <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>
240
- <div class="mb-4">
241
- <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>
242
- <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>
243
- </div>
244
- <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">
245
- <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>
246
- </button>
247
- <div id="genderError" class="error-message mt-4"></div>
248
- </div>
249
- <!-- Gender Output -->
250
- <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 card">
251
- <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>
252
- <div id="genderLoading" class="loading-indicator"><div class="spinner"></div><span data-lang-en="Analyzing..." data-lang-hi="विश्लेषण हो रहा है...">Analyzing...</span></div>
253
- <div id="genderResultDisplay" class="hidden space-y-4"> <!-- Results populated here --> </div>
254
- <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>
255
- </div>
256
- </div>
 
 
 
 
 
257
  </div>
258
- </div>
259
 
260
  </main>
261
 
262
  <!-- Footer -->
263
- <footer class="bg-gray-900 text-gray-400 py-12 mt-12">
264
- <!-- Footer content (Keep as is) -->
265
- <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>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  </footer>
267
 
268
  <!-- Base Theme/Language Script -->
269
  <script>
270
- // Theme Toggle (Keep as is)
271
- 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."); }
272
- // Language Toggle (Keep as is)
273
- 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."); }
274
- </script>
275
-
276
- <!-- ***** Page Interaction & Full App Script (MODIFIED) ***** -->
277
- <script>
278
- // --- API Configuration ---
279
- const OCR_API_BASE_URL = 'https://sameernotes-ocr.hf.space';
280
- const TRANSLATION_API_BASE_URL = 'https://sameernotes-translation-prediction-space.hf.space';
281
- const GENDER_API_BASE_URL = "https://sidvilas-gender-prediction-space.hf.space";
282
- const TOKEN_ENDPOINT = `${OCR_API_BASE_URL}/token`;
283
- const SIGNUP_ENDPOINT = `${OCR_API_BASE_URL}/signup`;
284
- const PROCESS_ENDPOINT = `${OCR_API_BASE_URL}/process/`;
285
- const WORD_DETECT_ENDPOINT = `${OCR_API_BASE_URL}/word-detection/`;
286
- // const PREDICTION_IMG_ENDPOINT = `${OCR_API_BASE_URL}/prediction/`; // Removed
287
- const TRANSLATE_ENDPOINT = `${TRANSLATION_API_BASE_URL}/translate`;
288
- const GENDER_PREDICT_ENDPOINT = `${GENDER_API_BASE_URL}/predict`;
289
- const TOKEN_STORAGE_KEY = 'aiTextToolsAuthToken';
290
-
291
- // --- State Variables ---
292
- let accessToken = localStorage.getItem(TOKEN_STORAGE_KEY);
293
- let currentImageSource = null;
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
- } catch (imgError) {
460
- console.error(`Error fetching Word Detection image:`, imgError);
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
- console.log("Translation API Request Payload:", payload);
530
-
531
- try {
532
- // Use standard fetch, NOT authenticatedFetch, as this endpoint doesn't require the OCR token
533
- const response = await fetch(TRANSLATE_ENDPOINT, {
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
- // --- Initialization ---
613
- document.addEventListener('DOMContentLoaded', () => {
614
- console.log("DOM Loaded. Initializing...");
615
- accessToken = localStorage.getItem(TOKEN_STORAGE_KEY);
616
- if (accessToken) { showAppScreen(); } else { showAuthScreen(); }
617
- resetFileName(); applyLanguage(savedLang);
618
- clearOCRState(); clearTranslationState(); clearGenderState();
619
- activateTab('ocr'); // Ensure OCR tab is active first
620
- // Ensure source language row is hidden initially
621
- const sourceLangItem = document.getElementById('detectedSourceLanguage')?.closest('.result-item');
622
- if (sourceLangItem) {
623
- sourceLangItem.style.display = 'none';
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
- !DOCTYPE html>
2
- <!-- Add lang attribute and potentially 'dark' class -->
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>Recognize Word - NIC Project</title>
8
  <!-- Tailwind CSS via CDN -->
9
  <script src="https://cdn.tailwindcss.com/3.4.1"></script>
10
  <script>
11
- // *** Add dark mode strategy to Tailwind config ***
12
  tailwind.config = {
13
- darkMode: 'class', // Enable class-based dark mode
14
  theme: {
15
  extend: {
16
- colors: {
17
- primary: '#1a73e8',
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
- /* --- Font Definitions (Ensure loaded via <link> tags in base.html) --- */
44
- body {
45
- font-family: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
46
- -webkit-font-smoothing: antialiased; /* Smoother fonts */
47
- -moz-osx-font-smoothing: grayscale;
48
- }
49
-
50
- .hindi-font {
51
- font-family: 'Noto Sans Devanagari', sans-serif;
52
- }
53
-
54
- /* --- Custom Switch Styles (from provided HTML) --- */
55
- .custom-switch {
56
- position: relative;
57
- display: inline-block;
58
- width: 50px;
59
- height: 24px;
60
- }
61
-
62
- .custom-switch-input {
63
- opacity: 0;
64
- width: 0;
65
- height: 0;
66
- }
67
-
68
- .custom-switch-slider {
69
- position: absolute;
70
- cursor: pointer;
71
- top: 0;
72
- left: 0;
73
- right: 0;
74
- bottom: 0;
75
- background-color: #ccc; /* Default off color */
76
- transition: .4s;
77
- border-radius: 34px; /* Rounded */
78
- }
79
-
80
- .custom-switch-slider:before {
81
- position: absolute;
82
- content: "";
83
- height: 18px;
84
- width: 18px;
85
- left: 3px;
86
- bottom: 3px;
87
- background-color: white; /* Changed from original rgb(136, 145, 212) to white for consistency with dark mode logic in base.html */
88
- transition: .4s;
89
- border-radius: 50%; /* Circle */
90
- }
91
-
92
- .custom-switch-input:checked + .custom-switch-slider {
93
- background-color: #1a73e8; /* Primary color when on */
94
- }
95
-
96
- .custom-switch-input:checked + .custom-switch-slider:before {
97
- transform: translateX(26px); /* Move the circle */
98
- }
99
-
100
- /* --- Accessibility: Ensure focus is visible --- */
101
- /* Tailwind's default focus rings are usually good, but this is a fallback */
102
- *:focus-visible {
103
- outline: 2px solid #1a73e8; /* Use primary color for focus */
104
- outline-offset: 2px;
105
- box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.3); /* Optional softer focus ring */
106
- }
107
-
108
- /* --- Other Minimal Custom Styles (if any) --- */
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
- <!-- Add data-lang attribute -->
162
- <a href="/" class="text-2xl font-['Pacifico'] text-primary mr-8" data-lang-en="HindiOCR" data-lang-hi="हिन्दी ओसीआर">HindiOCR</a>
163
- <nav class="hidden md:flex space-x-6">
164
- <a href="/index.html"
165
- class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
166
- data-lang-en="Home" data-lang-hi="होम">Home</a>
167
- <a href="/recognizer.html"
168
- class="hover:text-primary dark:hover:text-blue-400 text-primary dark:text-blue-400 font-medium"
169
- data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a>
170
- <a href="/examples.html"
171
- class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
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
- <!-- Language Switch -->
184
- <div class="items-center space-x-2 hidden md:flex">
185
- <span class="text-sm text-gray-600 dark:text-gray-400">EN</span>
186
- <label class="custom-switch">
187
- <!-- *** Add ID: languageToggle *** -->
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
- <!-- Main Content Area -->
220
- <main class="flex-grow">
221
-
222
- <div class="container mx-auto px-4">
223
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8">
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
- <!-- Examples -->
246
- <h3 class="text-md font-semibold text-gray-700 dark:text-gray-300 mb-2" data-lang-en="Examples" data-lang-hi="उदाहरण">Examples</h3>
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
- <!-- Preview -->
254
- <div id="imagePreviewContainer" class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-600 hidden">
255
- <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>
256
- <div class="flex justify-center">
257
- <img id="imagePreview" src="#" alt="Image Preview" class="max-h-60 w-auto rounded border bg-gray-50 dark:bg-gray-700 dark:border-gray-600 p-1"/>
258
- </div>
259
- </div>
260
  </div>
261
 
262
- <!-- Output Column -->
263
- <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 justify-center">
264
- <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">
265
- <i class="ri-text mr-2 text-primary align-bottom"></i>
266
- <span data-lang-en="Recognition Result" data-lang-hi="पहचान परिणाम">Recognition Result</span>
267
- </h2>
268
-
269
- <!-- Recognize Button -->
270
- <div class="my-4">
271
- <button id="predictButton" class="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" disabled>
272
- <i class="ri-search-line mr-2"></i>
273
- <span data-lang-en="Recognize Word" data-lang-hi="शब्द पहचानें">Recognize Word</span>
274
- </button>
275
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
- <!-- Loading Indicator -->
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
- <!-- Result Display Area -->
287
- <div id="resultDisplay" class="mt-4 p-4 bg-gray-100 dark:bg-gray-700 rounded-lg w-full text-center hidden" style="min-height: 100px;">
288
- <h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-3" data-lang-en="Predicted Text:" data-lang-hi="अनुमानित पाठ:">Predicted Text:</h3>
289
- <p id="predictionText" class="text-4xl hindi-font font-bold text-gray-900 dark:text-white"></p>
290
- <p id="probabilityText" class="text-gray-600 dark:text-gray-400 text-sm mt-2 mb-0"></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
  </div>
292
 
293
- <!-- Error Display Area -->
294
- <div id="errorDisplay" class="mt-4 p-3 bg-red-100 dark:bg-red-900 dark:bg-opacity-50 border border-red-300 dark:border-red-600 text-red-800 dark:text-red-200 rounded-lg w-full text-sm flex items-center hidden" role="alert">
295
- <i class="ri-error-warning-fill mr-2 text-lg"></i>
296
- <strong class="mr-1" data-lang-en="Error:" data-lang-hi="त्रुटि:">Error:</strong>
297
- <span id="errorText"></span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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"> <!-- Adjusted footer text color -->
307
- <div class="container mx-auto px-4">
308
- <div class="grid grid-cols-1 md:grid-cols-4 gap-8">
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
- // --- Theme Toggle ---
363
- const themeToggle = document.getElementById('themeToggle');
364
- const htmlElement = document.documentElement; // Target <html> tag
365
-
366
- // Function to apply theme based on preference
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
- // Check localStorage on load
380
- const prefersDark = localStorage.getItem('theme') === 'dark' ||
381
- (localStorage.getItem('theme') === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
382
- applyTheme(prefersDark);
383
-
384
- // Add listener to toggle button
385
- if(themeToggle){
386
- themeToggle.addEventListener('change', (event) => {
387
- const isDark = event.target.checked;
388
- applyTheme(isDark);
389
- // Save preference to localStorage
390
- localStorage.setItem('theme', isDark ? 'dark' : 'light');
391
- });
392
- } else {
393
- console.warn("Theme toggle button not found.");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  }
395
 
396
- // --- Language Toggle (Simple Version) ---
397
- const languageToggle = document.getElementById('languageToggle');
398
- const langElements = document.querySelectorAll('[data-lang-en]'); // Select elements with language data
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
- langElements.forEach(el => {
406
- const text = el.getAttribute(`data-lang-${lang}`);
407
- if (text) {
408
- el.textContent = text; // Replace text content
409
- }
410
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
 
412
- // Update toggle state
413
- if (languageToggle) languageToggle.checked = (lang === 'hi');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
 
415
- // Save preference
416
- localStorage.setItem('language', lang);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  }
418
 
419
- // Check localStorage on load for language
420
- const savedLang = localStorage.getItem('language') || 'en'; // Default to English
421
- applyLanguage(savedLang);
422
-
423
- // Add listener to language toggle button
424
- if (languageToggle) {
425
- languageToggle.addEventListener('change', (event) => {
426
- const newLang = event.target.checked ? 'hi' : 'en';
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
- <!-- Add data-lang attribute -->
162
- <a href="/" class="text-2xl font-['Pacifico'] text-primary mr-8" data-lang-en="HindiOCR" data-lang-hi="हिन्दी ओसीआर">HindiOCR</a>
163
- <nav class="hidden md:flex space-x-6">
164
- <a href="/index.html"
165
- class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
166
- data-lang-en="Home" data-lang-hi="होम">Home</a>
167
- <a href="/recognizer.html"
168
- class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
169
- data-lang-en="Recognize" data-lang-hi="पहचानें">Recognize</a>
170
- <a href="/examples.html"
171
- class="hover:text-primary dark:hover:text-blue-400 text-gray-600 dark:text-gray-300"
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-primary dark:text-blue-400 font-medium"
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 -->
 
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 -->