Spaces:
Running
Running
Update index.html
#1
by Hanuman2 - opened
- .gitattributes +34 -6
- .github/workflows/main.yml +0 -47
- .gitignore +0 -20
- Dockerfile +2 -29
- README.md +11 -6
- app.py +97 -306
- css/style.css +0 -837
- index.html +833 -178
- js/markdownRenderer.js +0 -278
- js/script.js +0 -1221
- logo.png +0 -3
- requirements.txt +0 -4
.gitattributes
CHANGED
|
@@ -1,7 +1,35 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.
|
| 3 |
-
*.
|
| 4 |
-
*.
|
| 5 |
-
*.
|
| 6 |
-
*.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
*.wasm filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz 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
|
.github/workflows/main.yml
DELETED
|
@@ -1,47 +0,0 @@
|
|
| 1 |
-
name: Deploy to Hugging Face Space
|
| 2 |
-
|
| 3 |
-
on:
|
| 4 |
-
push:
|
| 5 |
-
branches: [ main ]
|
| 6 |
-
workflow_dispatch:
|
| 7 |
-
|
| 8 |
-
jobs:
|
| 9 |
-
sync-to-hf:
|
| 10 |
-
runs-on: ubuntu-latest
|
| 11 |
-
steps:
|
| 12 |
-
- name: Checkout Code
|
| 13 |
-
uses: actions/checkout@v3
|
| 14 |
-
|
| 15 |
-
- name: Clean History and Force Push to HF
|
| 16 |
-
env:
|
| 17 |
-
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 18 |
-
run: |
|
| 19 |
-
# 1. गिट कॉन्फ़िगरेशन
|
| 20 |
-
git config --global user.email "divy@vedaalabs.ai"
|
| 21 |
-
git config --global user.name "Divy Patel"
|
| 22 |
-
|
| 23 |
-
# 2. पुरानी हिस्ट्री को पूरी तरह से डिलीट करें और नई शुरुआत करें
|
| 24 |
-
rm -rf .git
|
| 25 |
-
git init
|
| 26 |
-
git branch -M main
|
| 27 |
-
|
| 28 |
-
# 3. LFS सेटअप और ट्रैकिंग (सभी बड़ी फाइलों के लिए)
|
| 29 |
-
git lfs install
|
| 30 |
-
git lfs track "*.ttf"
|
| 31 |
-
git lfs track "*.whl"
|
| 32 |
-
git lfs track "*.png"
|
| 33 |
-
git lfs track "*.jpg"
|
| 34 |
-
git lfs track "*.jpeg"
|
| 35 |
-
git lfs track "*.woff2"
|
| 36 |
-
git lfs track "*.wasm"
|
| 37 |
-
|
| 38 |
-
# 4. सभी फाइलों को नए सिरे से ऐड करें
|
| 39 |
-
git add .gitattributes
|
| 40 |
-
git add .
|
| 41 |
-
|
| 42 |
-
# 5. नया फ्रेश कमिट
|
| 43 |
-
git commit -m "Clean deployment with comprehensive LFS tracking"
|
| 44 |
-
|
| 45 |
-
# 6. हगिंग फेस रिमोट सेट करें और फोर्स पुश करें
|
| 46 |
-
git remote add hf https://user:${HF_TOKEN}@huggingface.co/spaces/Renderlib-dev/CodeVed
|
| 47 |
-
git push -f hf main
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
DELETED
|
@@ -1,20 +0,0 @@
|
|
| 1 |
-
```gitignore
|
| 2 |
-
# Python
|
| 3 |
-
__pycache__/
|
| 4 |
-
*.pyc
|
| 5 |
-
*.pyo
|
| 6 |
-
*.pyd
|
| 7 |
-
|
| 8 |
-
# Environment
|
| 9 |
-
.venv/
|
| 10 |
-
venv/
|
| 11 |
-
env/
|
| 12 |
-
|
| 13 |
-
# Logs
|
| 14 |
-
*.log
|
| 15 |
-
|
| 16 |
-
# Temporary files
|
| 17 |
-
*.tmp
|
| 18 |
-
*.swp
|
| 19 |
-
*~
|
| 20 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile
CHANGED
|
@@ -1,39 +1,12 @@
|
|
| 1 |
-
|
| 2 |
-
# CODE VED - PRODUCTION DOCKERFILE
|
| 3 |
-
# Engineered by Divy Patel | Modular Architecture
|
| 4 |
-
# ================================================================================
|
| 5 |
|
| 6 |
-
# 1. Use a lightweight but compatible Python base image
|
| 7 |
-
FROM python:3.10-slim
|
| 8 |
-
|
| 9 |
-
# 2. Set the working directory inside the container
|
| 10 |
WORKDIR /app
|
| 11 |
|
| 12 |
-
# 3. Install system-level dependencies
|
| 13 |
-
# - git: Required to clone 'renderlib' from GitHub in requirements.txt
|
| 14 |
-
# - ffmpeg & libsndfile1: Required by 'supertonic' and 'pydub' for audio processing
|
| 15 |
-
# - build-essential: Sometimes needed for compiling C-extensions in Python packages
|
| 16 |
-
RUN apt-get update && apt-get install -y \
|
| 17 |
-
git \
|
| 18 |
-
ffmpeg \
|
| 19 |
-
libsndfile1 \
|
| 20 |
-
build-essential \
|
| 21 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 22 |
-
|
| 23 |
-
# 4. Copy only requirements.txt first to leverage Docker cache
|
| 24 |
-
# (If requirements don't change, Docker won't reinstall packages every time)
|
| 25 |
COPY requirements.txt .
|
|
|
|
| 26 |
|
| 27 |
-
# 5. Install Python dependencies
|
| 28 |
-
# --no-cache-dir keeps the image size small
|
| 29 |
-
RUN pip install --no-cache-dir --upgrade pip && \
|
| 30 |
-
pip install --no-cache-dir -r requirements.txt
|
| 31 |
-
|
| 32 |
-
# 6. Copy the rest of the application code (HTML, CSS, JS, Python files)
|
| 33 |
COPY . .
|
| 34 |
|
| 35 |
-
# 7. Hugging Face Spaces requires the app to listen on port 7860
|
| 36 |
EXPOSE 7860
|
| 37 |
|
| 38 |
-
# 8. Command to run the Flask application
|
| 39 |
CMD ["python", "app.py"]
|
|
|
|
| 1 |
+
FROM python:3.9-slim
|
|
|
|
|
|
|
|
|
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
WORKDIR /app
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
COPY . .
|
| 9 |
|
|
|
|
| 10 |
EXPOSE 7860
|
| 11 |
|
|
|
|
| 12 |
CMD ["python", "app.py"]
|
README.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
| 1 |
---
|
| 2 |
-
|
| 3 |
-
title: Codeved
|
| 4 |
-
sdk: docker
|
| 5 |
emoji: 🚀
|
| 6 |
-
colorFrom:
|
| 7 |
-
colorTo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Tiranga-advanced-multimodal-AI
|
|
|
|
|
|
|
| 3 |
emoji: 🚀
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
short_description: Demo version of Vedika
|
| 10 |
+
thumbnail: >-
|
| 11 |
+
https://cdn-uploads.huggingface.co/production/uploads/6a0975f9cdda3a9b7e513e3f/-EPyeIHoSfq0qp9ntFw9a.png
|
| 12 |
---
|
| 13 |
+
|
| 14 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
CHANGED
|
@@ -1,373 +1,164 @@
|
|
| 1 |
import os
|
| 2 |
import requests
|
| 3 |
-
import json
|
| 4 |
-
import re
|
| 5 |
-
import io
|
| 6 |
-
import tempfile
|
| 7 |
from datetime import datetime, timedelta, timezone
|
| 8 |
-
from
|
| 9 |
-
from flask import Flask, request, Response, stream_with_context, send_from_directory
|
| 10 |
-
from supertonic import TTS
|
| 11 |
|
| 12 |
-
|
| 13 |
-
# FLASK APP INITIALIZATION (MODULAR FRONTEND SUPPORT)
|
| 14 |
-
# ================================================================================
|
| 15 |
-
# static_folder='.' and static_url_path='' tells Flask to serve static files
|
| 16 |
-
# (CSS, JS, Images) directly from the root directory structure.
|
| 17 |
-
# This ensures /css/style.css and /js/script.js load perfectly.
|
| 18 |
-
app = Flask(__name__, static_folder='.', static_url_path='')
|
| 19 |
|
| 20 |
-
# ------
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
print("Loading RenderLib...")
|
| 24 |
-
try:
|
| 25 |
-
from renderlib import RenderLib
|
| 26 |
-
renderer = RenderLib()
|
| 27 |
-
print("RenderLib loaded successfully!")
|
| 28 |
-
except ImportError as e:
|
| 29 |
-
print(f"RenderLib not installed yet or error: {e}")
|
| 30 |
-
renderer = None
|
| 31 |
|
| 32 |
-
#
|
| 33 |
-
# INIT TTS MODEL
|
| 34 |
-
# ----------------------------------------------------------------------------
|
| 35 |
-
print("Loading Supertonic TTS Model...")
|
| 36 |
-
try:
|
| 37 |
-
tts = TTS(auto_download=True)
|
| 38 |
-
print("TTS Model loaded successfully!")
|
| 39 |
-
except Exception as e:
|
| 40 |
-
print(f"Error initializing TTS: {e}")
|
| 41 |
-
tts = None
|
| 42 |
-
|
| 43 |
-
VOICES = ["M1", "M2", "M3", "M4", "M5", "F1", "F2", "F3", "F4", "F5"]
|
| 44 |
-
LANGUAGES = {
|
| 45 |
-
"English": "en", "Korean": "ko", "Japanese": "ja", "Arabic": "ar",
|
| 46 |
-
"Bulgarian": "bg", "Czech": "cs", "Danish": "da", "German": "de",
|
| 47 |
-
"Greek": "el", "Spanish": "es", "Estonian": "et", "Finnish": "fi",
|
| 48 |
-
"French": "fr", "Hindi": "hi", "Croatian": "hr", "Hungarian": "hu",
|
| 49 |
-
"Indonesian": "id", "Italian": "it", "Lithuanian": "lt", "Latvian": "lv",
|
| 50 |
-
"Dutch": "nl", "Polish": "pl", "Portuguese": "pt", "Romanian": "ro",
|
| 51 |
-
"Russian": "ru", "Slovak": "sk", "Slovenian": "sl", "Swedish": "sv",
|
| 52 |
-
"Turkish": "tr", "Ukrainian": "uk", "Vietnamese": "vi"
|
| 53 |
-
}
|
| 54 |
-
VOICE_STYLES_CACHE = {}
|
| 55 |
-
|
| 56 |
-
# ----------------------------------------------------------------------------
|
| 57 |
-
# HARDCODED MODEL CONFIGURATION
|
| 58 |
-
# ----------------------------------------------------------------------------
|
| 59 |
-
NVIDIA_API_KEY = os.environ.get("NVIDIA_API_KEY", "")
|
| 60 |
INVOKE_URL = "https://integrate.api.nvidia.com/v1/chat/completions"
|
| 61 |
-
MODEL_ID = "mistralai/mistral-small-4-119b-2603"
|
| 62 |
|
| 63 |
-
# ------
|
| 64 |
-
|
| 65 |
-
# ----------------------------------------------------------------------------
|
| 66 |
-
def get_address_from_coords(lat, lon):
|
| 67 |
-
try:
|
| 68 |
-
url = f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}"
|
| 69 |
-
headers = {'User-Agent': 'CODE_VED_AI_System_by_Divy_Patel'}
|
| 70 |
-
response = requests.get(url, headers=headers, timeout=5)
|
| 71 |
-
data = response.json()
|
| 72 |
-
return data.get('display_name', f"Lat: {lat}, Lon: {lon}")
|
| 73 |
-
except Exception as e:
|
| 74 |
-
return f"Lat: {lat}, Lon: {lon}"
|
| 75 |
|
| 76 |
-
# ------
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
| 80 |
results = []
|
| 81 |
-
serpapi_key = os.environ.get("SERPAPI_KEY")
|
| 82 |
-
if not serpapi_key:
|
| 83 |
-
return results
|
| 84 |
-
|
| 85 |
-
search_query = query
|
| 86 |
-
if user_address:
|
| 87 |
-
local_keywords = ["near", "nearby", "distance", "time", "where"]
|
| 88 |
-
if any(kw in query.lower() for kw in local_keywords):
|
| 89 |
-
search_query = f"{query} near {user_address}"
|
| 90 |
-
|
| 91 |
try:
|
| 92 |
-
params = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
response = requests.get("https://serpapi.com/search", params=params, timeout=10)
|
| 94 |
data = response.json()
|
|
|
|
|
|
|
| 95 |
if "organic_results" in data:
|
| 96 |
for item in data["organic_results"]:
|
| 97 |
title = item.get("title", "")
|
| 98 |
link = item.get("link", "")
|
| 99 |
snippet = item.get("snippet", "")
|
|
|
|
| 100 |
if title and snippet:
|
| 101 |
-
results.append({
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
return results
|
| 105 |
|
| 106 |
-
# ----------------------------------------------------
|
| 107 |
-
# RSS TECH NEWS SCRAPER
|
| 108 |
-
# ----------------------------------------------------------------------------
|
| 109 |
-
def get_live_web_data(query):
|
| 110 |
-
url = f"https://news.google.com/rss/search?q={query}&hl=en&gl=IN&ceid=IN:en"
|
| 111 |
-
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
|
| 112 |
-
tech_keywords = ["ai", "artificial intelligence", "smartphone", "mobile", "feature", "whatsapp", "google", "tech", "technology", "gadget", "apple", "nasa"]
|
| 113 |
-
block_keywords = ["share news", "stock"]
|
| 114 |
-
scraped_results = []
|
| 115 |
-
|
| 116 |
-
try:
|
| 117 |
-
response = requests.get(url, headers=headers, timeout=6)
|
| 118 |
-
if response.status_code == 200:
|
| 119 |
-
soup = BeautifulSoup(response.text, "html.parser")
|
| 120 |
-
items = soup.find_all('item')
|
| 121 |
-
for item in items:
|
| 122 |
-
title = item.title.text if item.title else "No Title"
|
| 123 |
-
title_lower = title.lower()
|
| 124 |
-
if any(b_kw in title_lower for b_kw in block_keywords):
|
| 125 |
-
continue
|
| 126 |
-
if any(t_kw in title_lower for t_kw in tech_keywords):
|
| 127 |
-
link = item.link.text if item.link else "#"
|
| 128 |
-
pub_date = item.pubdate.text if item.pubdate else ""
|
| 129 |
-
source = item.source.text if item.source else "Google News"
|
| 130 |
-
scraped_results.append({
|
| 131 |
-
"title": title,
|
| 132 |
-
"snippet": f"Published: {pub_date} | Source: {source}",
|
| 133 |
-
"link": link
|
| 134 |
-
})
|
| 135 |
-
if len(scraped_results) >= 5:
|
| 136 |
-
break
|
| 137 |
-
except Exception:
|
| 138 |
-
pass
|
| 139 |
-
return scraped_results
|
| 140 |
|
| 141 |
-
# ----------------------------------------------------------------------------
|
| 142 |
-
# HOME ROUTE (SERVES MODULAR FRONTEND)
|
| 143 |
-
# ----------------------------------------------------------------------------
|
| 144 |
@app.route('/')
|
| 145 |
def home():
|
| 146 |
-
# Serves the new index.html.
|
| 147 |
-
# Because static_folder='.' and static_url_path='', Flask will automatically
|
| 148 |
-
# serve /css/style.css, /js/script.js, etc., from the root directory.
|
| 149 |
-
return send_from_directory('.', 'index.html')
|
| 150 |
-
|
| 151 |
-
# ----------------------------------------------------------------------------
|
| 152 |
-
# RENDERLIB API ENDPOINT
|
| 153 |
-
# ----------------------------------------------------------------------------
|
| 154 |
-
@app.route('/api/render', methods=['POST'])
|
| 155 |
-
def render_content():
|
| 156 |
-
if renderer is None:
|
| 157 |
-
return Response(json.dumps({"error": "RenderLib is not available on the server."}), status=500, mimetype='application/json')
|
| 158 |
-
|
| 159 |
-
data = request.get_json() or {}
|
| 160 |
-
subject = data.get("subject", "math")
|
| 161 |
-
method = data.get("method", "to_latex")
|
| 162 |
-
args = data.get("args", [])
|
| 163 |
-
kwargs = data.get("kwargs", {})
|
| 164 |
-
expression = data.get("expression")
|
| 165 |
-
|
| 166 |
-
if expression and not args:
|
| 167 |
-
args = [expression]
|
| 168 |
-
|
| 169 |
try:
|
| 170 |
-
|
| 171 |
-
|
| 172 |
except Exception as e:
|
| 173 |
-
return
|
| 174 |
|
| 175 |
-
# ----------------------------------------------------------------------------
|
| 176 |
-
# CHAT API ENDPOINT (Hardcoded Model & URL)
|
| 177 |
-
# ----------------------------------------------------------------------------
|
| 178 |
@app.route('/api/chat', methods=['POST'])
|
| 179 |
def chat():
|
| 180 |
-
if not
|
| 181 |
-
return Response(
|
| 182 |
-
|
| 183 |
data = request.get_json() or {}
|
| 184 |
user_message = data.get("message", "")
|
| 185 |
attachments = data.get("attachments", [])
|
| 186 |
is_search = data.get("is_search", False)
|
| 187 |
history = data.get("history", [])
|
| 188 |
-
location = data.get("location")
|
| 189 |
-
user_address = None
|
| 190 |
max_tokens = data.get("max_tokens", 4096)
|
| 191 |
-
|
|
|
|
|
|
|
| 192 |
ist_time = datetime.now(timezone.utc) + timedelta(hours=5, minutes=30)
|
| 193 |
current_date = ist_time.strftime("%A, %d %B %Y, %I:%M %p IST")
|
| 194 |
-
|
| 195 |
-
thinking_mode = data.get("thinking_mode", False)
|
| 196 |
-
thinking_effort = data.get("thinking_effort", "medium")
|
| 197 |
-
|
| 198 |
-
thinking_instruction = ""
|
| 199 |
-
if thinking_mode:
|
| 200 |
-
thinking_instruction = f"\n[CRITICAL INSTRUCTION: THINKING MODE ENABLED - Effort: {thinking_effort}]\nYou MUST format your reasoning exactly inside <think> and </think> HTML tags."
|
| 201 |
-
|
| 202 |
-
location_instruction = ""
|
| 203 |
-
if location and location.get('lat') and location.get('lng'):
|
| 204 |
-
user_address = get_address_from_coords(location['lat'], location['lng'])
|
| 205 |
-
location_instruction = f"\n[USER REAL-TIME LOCATION: {user_address}]"
|
| 206 |
-
|
| 207 |
-
system_prompt = f"""[CRITICAL IDENTITY OVERRIDE]
|
| 208 |
-
Name: CODE VED
|
| 209 |
-
Creator/Engineer: Divy Patel
|
| 210 |
-
Current Time: {current_date}.{location_instruction}{thinking_instruction}
|
| 211 |
-
|
| 212 |
-
You are "Code Ved," an expert AI software engineering and technical consultant. Your goal is to provide precise, clean, and highly optimized code solutions, architectural advice, and technical explanations.
|
| 213 |
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
- If a user request is ambiguous, ask for necessary technical specifications before proceeding to ensure the output meets the requirements.
|
| 225 |
-
|
| 226 |
-
Formatting Standards:
|
| 227 |
-
- Use standard Markdown for all responses.
|
| 228 |
-
- Use LaTeX for any mathematical notations or algorithmic complexity analysis (e.g., Big O notation).
|
| 229 |
-
- For complex architectural patterns, describe the flow clearly using structured lists.
|
| 230 |
-
"""
|
| 231 |
|
|
|
|
| 232 |
if is_search:
|
| 233 |
-
|
| 234 |
-
scraped_data = web_search_scraper(user_message, user_address=user_address)
|
| 235 |
-
news_data = get_live_web_data(user_message)
|
| 236 |
|
|
|
|
| 237 |
if scraped_data:
|
| 238 |
-
search_context += "\n\n--- [LIVE GOOGLE SEARCH] ---\n"
|
| 239 |
for idx, res in enumerate(scraped_data):
|
| 240 |
-
search_context += f"{idx+1}. {res['title']}: {res['snippet']}
|
| 241 |
-
|
| 242 |
-
search_context += "
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
user_message = f"{user_message}\n{search_context}\n[COMMAND: Base your final answer strictly on the facts provided above.]"
|
| 248 |
|
| 249 |
messages = [{"role": "system", "content": system_prompt}]
|
| 250 |
-
|
| 251 |
for msg in history:
|
| 252 |
-
if msg == history[-1] and msg.get("role") == "user":
|
| 253 |
-
continue
|
| 254 |
role = msg.get("role", "user")
|
| 255 |
-
if role not in ["system", "user", "assistant"]:
|
| 256 |
-
role = "user"
|
| 257 |
content = msg.get("content", "")
|
| 258 |
-
if isinstance(content, list):
|
| 259 |
-
text_parts = [item["text"] for item in content if item.get("type") == "text"]
|
| 260 |
-
content = " ".join(text_parts)
|
| 261 |
-
if "Gemma" in content or "DeepMind" in content or "Google" in content:
|
| 262 |
-
continue
|
| 263 |
if content:
|
| 264 |
-
messages.append({"role": role, "content":
|
| 265 |
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
headers = {
|
| 278 |
-
"Authorization": f"Bearer {
|
| 279 |
-
"Accept": "text/event-stream"
|
| 280 |
-
"Content-Type": "application/json"
|
| 281 |
}
|
| 282 |
-
|
|
|
|
| 283 |
payload = {
|
| 284 |
"model": MODEL_ID,
|
| 285 |
"messages": messages,
|
| 286 |
-
"max_tokens":
|
| 287 |
-
"temperature":
|
| 288 |
"top_p": 0.95,
|
| 289 |
-
"stream": True
|
|
|
|
| 290 |
}
|
| 291 |
|
| 292 |
try:
|
| 293 |
-
response = requests.post(INVOKE_URL, headers=headers, json=payload, stream=True
|
| 294 |
-
if response.status_code != 200:
|
| 295 |
-
err_text = response.text[:200]
|
| 296 |
-
err_msg = json.dumps({"error": f"API Error {response.status_code}: {err_text}"})
|
| 297 |
-
return Response(f"data: {err_msg}\n\n", mimetype='text/event-stream')
|
| 298 |
-
|
| 299 |
def generate():
|
| 300 |
for line in response.iter_lines():
|
| 301 |
if line:
|
| 302 |
-
|
| 303 |
-
if
|
| 304 |
-
|
| 305 |
-
data_json = json.loads(decoded[6:])
|
| 306 |
-
if "choices" in data_json and len(data_json["choices"]) > 0:
|
| 307 |
-
delta = data_json["choices"][0].get("delta", {})
|
| 308 |
-
if "content" in delta and delta["content"]:
|
| 309 |
-
content = delta["content"]
|
| 310 |
-
# Normalize thinking tags for the frontend
|
| 311 |
-
content = content.replace("<|channel|>thought <|channel|>", "<think>\n")
|
| 312 |
-
content = content.replace("<|channel|>answer <|channel|>", "\n</think>\n")
|
| 313 |
-
delta["content"] = content
|
| 314 |
-
yield "data: " + json.dumps(data_json) + "\n\n"
|
| 315 |
-
except Exception:
|
| 316 |
-
yield decoded + "\n\n"
|
| 317 |
-
else:
|
| 318 |
-
yield decoded + "\n\n"
|
| 319 |
-
|
| 320 |
return Response(stream_with_context(generate()), mimetype='text/event-stream')
|
| 321 |
-
|
| 322 |
-
except Exception as e:
|
| 323 |
-
err_msg = json.dumps({"error": str(e)})
|
| 324 |
-
return Response(f"data: {err_msg}\n\n", mimetype='text/event-stream')
|
| 325 |
-
|
| 326 |
-
# ----------------------------------------------------------------------------
|
| 327 |
-
# TTS DIRECT API ENDPOINT
|
| 328 |
-
# ----------------------------------------------------------------------------
|
| 329 |
-
@app.route('/api/tts', methods=['POST'])
|
| 330 |
-
def generate_tts():
|
| 331 |
-
if tts is None:
|
| 332 |
-
return Response(json.dumps({"error": "TTS model failed to load on server."}), status=500, mimetype='application/json')
|
| 333 |
-
|
| 334 |
-
data = request.get_json() or {}
|
| 335 |
-
text = data.get("text", "")
|
| 336 |
-
voice = data.get("voice", "M2")
|
| 337 |
-
language_name = data.get("language_name", "English")
|
| 338 |
-
|
| 339 |
-
if not text.strip():
|
| 340 |
-
return Response(json.dumps({"error": "Text is empty."}), status=400, mimetype='application/json')
|
| 341 |
-
|
| 342 |
-
try:
|
| 343 |
-
lang_code = LANGUAGES.get(language_name, "en")
|
| 344 |
-
if voice not in VOICE_STYLES_CACHE:
|
| 345 |
-
VOICE_STYLES_CACHE[voice] = tts.get_voice_style(voice_name=voice)
|
| 346 |
-
|
| 347 |
-
style = VOICE_STYLES_CACHE[voice]
|
| 348 |
-
if style is None:
|
| 349 |
-
return Response(json.dumps({"error": f"Voice '{voice}' not available."}), status=400, mimetype='application/json')
|
| 350 |
-
|
| 351 |
-
wav, duration = tts.synthesize(text, voice_style=style, lang=lang_code)
|
| 352 |
-
|
| 353 |
-
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp_file:
|
| 354 |
-
tmp_path = tmp_file.name
|
| 355 |
-
|
| 356 |
-
tts.save_audio(wav, tmp_path)
|
| 357 |
-
|
| 358 |
-
with open(tmp_path, 'rb') as f:
|
| 359 |
-
audio_data = f.read()
|
| 360 |
-
os.unlink(tmp_path)
|
| 361 |
-
|
| 362 |
-
return Response(audio_data, mimetype="audio/wav")
|
| 363 |
-
|
| 364 |
except Exception as e:
|
| 365 |
-
|
| 366 |
-
return Response(json.dumps({"error": f"TTS synthesis failed: {str(e)}"}), status=500, mimetype='application/json')
|
| 367 |
|
| 368 |
-
# ----------------------------------------------------------------------------
|
| 369 |
-
# RUN SERVER
|
| 370 |
-
# ----------------------------------------------------------------------------
|
| 371 |
if __name__ == '__main__':
|
| 372 |
-
|
| 373 |
-
app.run(host='0.0.0.0', port=7860)
|
|
|
|
| 1 |
import os
|
| 2 |
import requests
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
from datetime import datetime, timedelta, timezone
|
| 4 |
+
from flask import Flask, request, Response, stream_with_context, render_template_string
|
|
|
|
|
|
|
| 5 |
|
| 6 |
+
app = Flask(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
+
# 🔐 --- SECURE ENVIRONMENT VARIABLES ---
|
| 9 |
+
API_KEY = os.environ.get("NVIDIA_API_KEY") or os.environ.get("YOUR_VEDIKA_API_KEY")
|
| 10 |
+
MODEL_ID = os.environ.get("MODEL_ID", "google/diffusiongemma-26b-a4b-it") # Default fallback
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
+
# NVIDIA's official invoke URL
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
INVOKE_URL = "https://integrate.api.nvidia.com/v1/chat/completions"
|
|
|
|
| 14 |
|
| 15 |
+
# 🔑 --- SERPAPI KEY (Provided by Divy Patel) ---
|
| 16 |
+
SERPAPI_KEY = "df7dc67448cc9fe63ee6bbc7c20c50662ac18bc1f4682cd480da97bde1970381"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
+
# 🌐 --- SERPAPI GOOGLE SEARCH ENGINE (100% BULLETPROOF) --- 🌐
|
| 19 |
+
def web_search_scraper(query, num_results=5):
|
| 20 |
+
"""
|
| 21 |
+
यह SerpApi का उपयोग करके सीधे Google Search से एकदम सटीक और ताज़ा (JSON) डेटा लाता है।
|
| 22 |
+
इसे Hugging Face या Google कभी ब्लॉक नहीं कर सकता।
|
| 23 |
+
"""
|
| 24 |
results = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
try:
|
| 26 |
+
params = {
|
| 27 |
+
"engine": "google",
|
| 28 |
+
"q": query,
|
| 29 |
+
"api_key": SERPAPI_KEY,
|
| 30 |
+
"num": num_results,
|
| 31 |
+
"hl": "en",
|
| 32 |
+
"gl": "in" # India Region for local context
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
response = requests.get("https://serpapi.com/search", params=params, timeout=10)
|
| 36 |
data = response.json()
|
| 37 |
+
|
| 38 |
+
# 'organic_results' से असली Google सर्च का डेटा निकालना
|
| 39 |
if "organic_results" in data:
|
| 40 |
for item in data["organic_results"]:
|
| 41 |
title = item.get("title", "")
|
| 42 |
link = item.get("link", "")
|
| 43 |
snippet = item.get("snippet", "")
|
| 44 |
+
|
| 45 |
if title and snippet:
|
| 46 |
+
results.append({
|
| 47 |
+
"title": title,
|
| 48 |
+
"link": link,
|
| 49 |
+
"snippet": snippet
|
| 50 |
+
})
|
| 51 |
+
|
| 52 |
+
except Exception as e:
|
| 53 |
+
print(f"SerpApi Error: {e}")
|
| 54 |
+
|
| 55 |
return results
|
| 56 |
|
| 57 |
+
# ----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
|
|
|
|
|
|
|
|
|
| 59 |
@app.route('/')
|
| 60 |
def home():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
try:
|
| 62 |
+
with open('index.html', 'r', encoding='utf-8') as f:
|
| 63 |
+
return render_template_string(f.read())
|
| 64 |
except Exception as e:
|
| 65 |
+
return f"<h1>System Error</h1><p>index.html missing: {str(e)}</p>"
|
| 66 |
|
|
|
|
|
|
|
|
|
|
| 67 |
@app.route('/api/chat', methods=['POST'])
|
| 68 |
def chat():
|
| 69 |
+
if not API_KEY or not INVOKE_URL or not MODEL_ID:
|
| 70 |
+
return Response("Server Error: Secrets missing. Check NVIDIA API Key.", status=500)
|
| 71 |
+
|
| 72 |
data = request.get_json() or {}
|
| 73 |
user_message = data.get("message", "")
|
| 74 |
attachments = data.get("attachments", [])
|
| 75 |
is_search = data.get("is_search", False)
|
| 76 |
history = data.get("history", [])
|
|
|
|
|
|
|
| 77 |
max_tokens = data.get("max_tokens", 4096)
|
| 78 |
+
temperature = data.get("temperature", 1.0) # NVIDIA Recommended
|
| 79 |
+
|
| 80 |
+
# 🕒 --- REAL-TIME IST INJECTION ---
|
| 81 |
ist_time = datetime.now(timezone.utc) + timedelta(hours=5, minutes=30)
|
| 82 |
current_date = ist_time.strftime("%A, %d %B %Y, %I:%M %p IST")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
+
# 🧠 --- GOD MODE SYSTEM PROMPT ---
|
| 85 |
+
system_prompt = f"""
|
| 86 |
+
You are CODE VED, an advanced AI System engineered EXCLUSIVELY by DIVY PATEL.
|
| 87 |
+
Current Live Date and Time: {current_date}.
|
| 88 |
+
|
| 89 |
+
STRICT DIRECTIVES:
|
| 90 |
+
1. NEVER invent, guess, or hallucinate product launches, dates, news, or facts.
|
| 91 |
+
2. If you receive "LIVE WEB SEARCH RESULTS", you MUST base your answer ENTIRELY on that data.
|
| 92 |
+
3. Do NOT say "Based on the provided search results". Just answer naturally and confidently, citing the sources/links if needed.
|
| 93 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
+
# 🚀 --- AUTO-SEARCH INJECTION (USING SERPAPI) ---
|
| 96 |
if is_search:
|
| 97 |
+
scraped_data = web_search_scraper(user_message)
|
|
|
|
|
|
|
| 98 |
|
| 99 |
+
search_context = "\n\n--- [LIVE VERIFIED GOOGLE SEARCH DATA] ---\n"
|
| 100 |
if scraped_data:
|
|
|
|
| 101 |
for idx, res in enumerate(scraped_data):
|
| 102 |
+
search_context += f"{idx+1}. TITLE: {res['title']}\nSNIPPET: {res['snippet']}\nURL: {res['link']}\n\n"
|
| 103 |
+
|
| 104 |
+
search_context += "[SYSTEM COMMAND: Use the above live Google data to answer the user accurately. Synthesize the info naturally without claiming you read it from a prompt.]"
|
| 105 |
+
else:
|
| 106 |
+
search_context += "[SYSTEM ALERT: Live search did not return results. Rely on your existing knowledge, but DO NOT hallucinate recent news.]"
|
| 107 |
+
|
| 108 |
+
user_message = f"USER QUERY: {user_message}\n\n{search_context}"
|
|
|
|
| 109 |
|
| 110 |
messages = [{"role": "system", "content": system_prompt}]
|
| 111 |
+
|
| 112 |
for msg in history:
|
|
|
|
|
|
|
| 113 |
role = msg.get("role", "user")
|
|
|
|
|
|
|
| 114 |
content = msg.get("content", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
if content:
|
| 116 |
+
messages.append({"role": role, "content": content})
|
| 117 |
|
| 118 |
+
content_payload = []
|
| 119 |
+
if user_message.strip():
|
| 120 |
+
content_payload.append({"type": "text", "text": user_message})
|
| 121 |
+
|
| 122 |
+
for att in attachments:
|
| 123 |
+
att_type = att.get("type")
|
| 124 |
+
b64_data = att.get("data")
|
| 125 |
+
if att_type == "image":
|
| 126 |
+
content_payload.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64_data}"}})
|
| 127 |
+
elif att_type in ["audio", "file"]:
|
| 128 |
+
content_payload.append({"type": "input_audio", "input_audio": {"data": b64_data, "format": "wav"}})
|
| 129 |
+
|
| 130 |
+
if not content_payload:
|
| 131 |
+
content_payload.append({"type": "text", "text": "Hello"})
|
| 132 |
+
|
| 133 |
+
messages.append({"role": "user", "content": content_payload})
|
| 134 |
|
| 135 |
headers = {
|
| 136 |
+
"Authorization": f"Bearer {API_KEY}",
|
| 137 |
+
"Accept": "text/event-stream"
|
|
|
|
| 138 |
}
|
| 139 |
+
|
| 140 |
+
# 🌟 --- NVIDIA FORMAT INTEGRATED ---
|
| 141 |
payload = {
|
| 142 |
"model": MODEL_ID,
|
| 143 |
"messages": messages,
|
| 144 |
+
"max_tokens": int(max_tokens),
|
| 145 |
+
"temperature": float(temperature),
|
| 146 |
"top_p": 0.95,
|
| 147 |
+
"stream": True,
|
| 148 |
+
"chat_template_kwargs": {"enable_thinking": True} # Thinking logic enabled
|
| 149 |
}
|
| 150 |
|
| 151 |
try:
|
| 152 |
+
response = requests.post(INVOKE_URL, headers=headers, json=payload, stream=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
def generate():
|
| 154 |
for line in response.iter_lines():
|
| 155 |
if line:
|
| 156 |
+
decoded_line = line.decode("utf-8")
|
| 157 |
+
if decoded_line.startswith("data: "):
|
| 158 |
+
yield decoded_line + "\n\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
return Response(stream_with_context(generate()), mimetype='text/event-stream')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
except Exception as e:
|
| 161 |
+
return Response(f"Internal Error: {str(e)}", status=500)
|
|
|
|
| 162 |
|
|
|
|
|
|
|
|
|
|
| 163 |
if __name__ == '__main__':
|
| 164 |
+
app.run(host='0.0.0.0', port=7860)
|
|
|
css/style.css
DELETED
|
@@ -1,837 +0,0 @@
|
|
| 1 |
-
/*
|
| 2 |
-
* ================================================================================
|
| 3 |
-
* CODE VED - GLOBAL STYLES & VARIABLES
|
| 4 |
-
* Anthropic-like Clean, Professional & Responsive Design
|
| 5 |
-
* ================================================================================
|
| 6 |
-
*/
|
| 7 |
-
|
| 8 |
-
:root {
|
| 9 |
-
/* Backgrounds */
|
| 10 |
-
--bg-main: #ffffff;
|
| 11 |
-
--bg-sidebar: rgba(249, 250, 251, 0.92);
|
| 12 |
-
--bg-user-msg: #f4f4f5;
|
| 13 |
-
|
| 14 |
-
/* Text Colors */
|
| 15 |
-
--text-primary: #18181b;
|
| 16 |
-
--text-secondary: #52525b;
|
| 17 |
-
--text-tertiary: #a1a1aa;
|
| 18 |
-
|
| 19 |
-
/* Borders & Shadows */
|
| 20 |
-
--border-light: rgba(0, 0, 0, 0.08);
|
| 21 |
-
--border-focus: rgba(0, 0, 0, 0.15);
|
| 22 |
-
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04);
|
| 23 |
-
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.06);
|
| 24 |
-
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.08);
|
| 25 |
-
|
| 26 |
-
/* Brand Colors */
|
| 27 |
-
--brand-color: #18181b;
|
| 28 |
-
--brand-accent: #0f9e78;
|
| 29 |
-
--brand-danger: #ef4444;
|
| 30 |
-
--brand-warning: #f59e0b;
|
| 31 |
-
--brand-success: #10b981;
|
| 32 |
-
|
| 33 |
-
/* Typography */
|
| 34 |
-
--font-ui: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
| 35 |
-
--font-code: 'JetBrains Mono', monospace;
|
| 36 |
-
|
| 37 |
-
/* Radii */
|
| 38 |
-
--radius-sm: 8px;
|
| 39 |
-
--radius-md: 12px;
|
| 40 |
-
--radius-lg: 20px;
|
| 41 |
-
|
| 42 |
-
/* Transitions */
|
| 43 |
-
--transition-default: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
|
| 44 |
-
}
|
| 45 |
-
|
| 46 |
-
/*
|
| 47 |
-
* ================================================================================
|
| 48 |
-
* GLOBAL RESET & BASE STYLES
|
| 49 |
-
* ================================================================================
|
| 50 |
-
*/
|
| 51 |
-
* {
|
| 52 |
-
box-sizing: border-box;
|
| 53 |
-
margin: 0;
|
| 54 |
-
padding: 0;
|
| 55 |
-
-webkit-user-select: none;
|
| 56 |
-
-moz-user-select: none;
|
| 57 |
-
-ms-user-select: none;
|
| 58 |
-
user-select: none;
|
| 59 |
-
-webkit-touch-callout: none;
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
/* Allow selection only in inputs and code */
|
| 63 |
-
input, textarea, [contenteditable="true"], code, pre {
|
| 64 |
-
-webkit-user-select: auto !important;
|
| 65 |
-
-moz-user-select: auto !important;
|
| 66 |
-
-ms-user-select: auto !important;
|
| 67 |
-
user-select: auto !important;
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
*:focus {
|
| 71 |
-
outline: none !important;
|
| 72 |
-
box-shadow: none !important;
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
-
html, body {
|
| 76 |
-
height: 100%;
|
| 77 |
-
height: 100dvh;
|
| 78 |
-
width: 100vw;
|
| 79 |
-
font-family: var(--font-ui);
|
| 80 |
-
color: var(--text-primary);
|
| 81 |
-
display: flex;
|
| 82 |
-
overflow: hidden;
|
| 83 |
-
background: var(--bg-main);
|
| 84 |
-
-webkit-font-smoothing: antialiased;
|
| 85 |
-
-moz-osx-font-smoothing: grayscale;
|
| 86 |
-
}
|
| 87 |
-
|
| 88 |
-
button, a {
|
| 89 |
-
-webkit-tap-highlight-color: transparent;
|
| 90 |
-
outline: none;
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
button {
|
| 94 |
-
background: none;
|
| 95 |
-
border: none;
|
| 96 |
-
cursor: pointer;
|
| 97 |
-
color: inherit;
|
| 98 |
-
font-family: inherit;
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
input, textarea {
|
| 102 |
-
font-family: inherit;
|
| 103 |
-
outline: none;
|
| 104 |
-
border: none;
|
| 105 |
-
background: transparent;
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
/* Custom Scrollbar */
|
| 109 |
-
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 110 |
-
::-webkit-scrollbar-track { background: transparent; }
|
| 111 |
-
::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.1); border-radius: 10px; }
|
| 112 |
-
::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.2); }
|
| 113 |
-
|
| 114 |
-
/*
|
| 115 |
-
* ================================================================================
|
| 116 |
-
* KEYFRAME ANIMATIONS
|
| 117 |
-
* ================================================================================
|
| 118 |
-
*/
|
| 119 |
-
@keyframes fadeInUp {
|
| 120 |
-
from { opacity: 0; transform: translateY(12px); }
|
| 121 |
-
to { opacity: 1; transform: translateY(0); }
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
@keyframes dropUpFade {
|
| 125 |
-
from { opacity: 0; transform: translateY(10px) scale(0.96); }
|
| 126 |
-
to { opacity: 1; transform: translateY(0) scale(1); }
|
| 127 |
-
}
|
| 128 |
-
|
| 129 |
-
@keyframes modalFadeIn {
|
| 130 |
-
from { opacity: 0; transform: scale(0.96); }
|
| 131 |
-
to { opacity: 1; transform: scale(1); }
|
| 132 |
-
}
|
| 133 |
-
|
| 134 |
-
@keyframes blinkCursor {
|
| 135 |
-
0%, 100% { opacity: 1; }
|
| 136 |
-
50% { opacity: 0; }
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
@keyframes recordPulse {
|
| 140 |
-
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
|
| 141 |
-
50% { transform: scale(1.05); box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); }
|
| 142 |
-
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
|
| 143 |
-
}
|
| 144 |
-
|
| 145 |
-
.blinking-cursor {
|
| 146 |
-
display: inline-block;
|
| 147 |
-
width: 6px;
|
| 148 |
-
height: 18px;
|
| 149 |
-
background: var(--text-primary);
|
| 150 |
-
margin-left: 2px;
|
| 151 |
-
vertical-align: text-bottom;
|
| 152 |
-
animation: blinkCursor 0.8s infinite;
|
| 153 |
-
border-radius: 2px;
|
| 154 |
-
}
|
| 155 |
-
|
| 156 |
-
/*
|
| 157 |
-
* ================================================================================
|
| 158 |
-
* SIDEBAR & NAVIGATION
|
| 159 |
-
* ================================================================================
|
| 160 |
-
*/
|
| 161 |
-
.btn-menu {
|
| 162 |
-
position: absolute;
|
| 163 |
-
top: 16px;
|
| 164 |
-
left: 16px;
|
| 165 |
-
z-index: 100;
|
| 166 |
-
width: 40px;
|
| 167 |
-
height: 40px;
|
| 168 |
-
border-radius: 50%;
|
| 169 |
-
display: flex;
|
| 170 |
-
align-items: center;
|
| 171 |
-
justify-content: center;
|
| 172 |
-
background: rgba(255,255,255,0.8);
|
| 173 |
-
backdrop-filter: blur(12px);
|
| 174 |
-
-webkit-backdrop-filter: blur(12px);
|
| 175 |
-
border: 1px solid var(--border-light);
|
| 176 |
-
color: var(--text-secondary);
|
| 177 |
-
transition: var(--transition-default);
|
| 178 |
-
}
|
| 179 |
-
|
| 180 |
-
.btn-menu:hover {
|
| 181 |
-
background: #fff;
|
| 182 |
-
color: var(--text-primary);
|
| 183 |
-
border-color: var(--border-focus);
|
| 184 |
-
}
|
| 185 |
-
|
| 186 |
-
.sidebar {
|
| 187 |
-
width: 280px;
|
| 188 |
-
background: var(--bg-sidebar);
|
| 189 |
-
backdrop-filter: blur(20px);
|
| 190 |
-
-webkit-backdrop-filter: blur(20px);
|
| 191 |
-
border-right: 1px solid var(--border-light);
|
| 192 |
-
display: flex;
|
| 193 |
-
flex-direction: column;
|
| 194 |
-
transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
|
| 195 |
-
z-index: 110;
|
| 196 |
-
height: 100%;
|
| 197 |
-
flex-shrink: 0;
|
| 198 |
-
}
|
| 199 |
-
|
| 200 |
-
.sidebar.collapsed {
|
| 201 |
-
transform: translateX(-100%);
|
| 202 |
-
position: absolute;
|
| 203 |
-
}
|
| 204 |
-
|
| 205 |
-
.sidebar-header {
|
| 206 |
-
padding: 20px 16px 10px;
|
| 207 |
-
display: flex;
|
| 208 |
-
align-items: center;
|
| 209 |
-
justify-content: space-between;
|
| 210 |
-
}
|
| 211 |
-
|
| 212 |
-
.sidebar-logo {
|
| 213 |
-
font-weight: 600;
|
| 214 |
-
font-size: 15px;
|
| 215 |
-
display: flex;
|
| 216 |
-
align-items: center;
|
| 217 |
-
gap: 8px;
|
| 218 |
-
letter-spacing: -0.3px;
|
| 219 |
-
}
|
| 220 |
-
|
| 221 |
-
.btn-new-chat {
|
| 222 |
-
margin: 10px 16px;
|
| 223 |
-
display: flex;
|
| 224 |
-
align-items: center;
|
| 225 |
-
justify-content: center;
|
| 226 |
-
gap: 8px;
|
| 227 |
-
padding: 10px;
|
| 228 |
-
border-radius: var(--radius-sm);
|
| 229 |
-
background: #fff;
|
| 230 |
-
border: 1px solid var(--border-light);
|
| 231 |
-
font-size: 13.5px;
|
| 232 |
-
font-weight: 500;
|
| 233 |
-
transition: var(--transition-default);
|
| 234 |
-
color: var(--text-primary);
|
| 235 |
-
box-shadow: var(--shadow-sm);
|
| 236 |
-
}
|
| 237 |
-
|
| 238 |
-
.btn-new-chat:hover {
|
| 239 |
-
border-color: var(--border-focus);
|
| 240 |
-
box-shadow: var(--shadow-md);
|
| 241 |
-
}
|
| 242 |
-
|
| 243 |
-
.sidebar-history {
|
| 244 |
-
flex: 1;
|
| 245 |
-
overflow-y: auto;
|
| 246 |
-
padding: 10px 12px;
|
| 247 |
-
display: flex;
|
| 248 |
-
flex-direction: column;
|
| 249 |
-
gap: 2px;
|
| 250 |
-
}
|
| 251 |
-
|
| 252 |
-
.history-header {
|
| 253 |
-
display: flex;
|
| 254 |
-
justify-content: space-between;
|
| 255 |
-
align-items: center;
|
| 256 |
-
padding: 4px 8px 8px;
|
| 257 |
-
margin-bottom: 4px;
|
| 258 |
-
}
|
| 259 |
-
|
| 260 |
-
.history-title-text {
|
| 261 |
-
font-size: 11px;
|
| 262 |
-
font-weight: 600;
|
| 263 |
-
color: var(--text-tertiary);
|
| 264 |
-
text-transform: uppercase;
|
| 265 |
-
letter-spacing: 0.5px;
|
| 266 |
-
}
|
| 267 |
-
|
| 268 |
-
.btn-clear-all {
|
| 269 |
-
font-size: 11px;
|
| 270 |
-
font-weight: 500;
|
| 271 |
-
color: var(--text-tertiary);
|
| 272 |
-
cursor: pointer;
|
| 273 |
-
transition: 0.2s;
|
| 274 |
-
padding: 4px 8px;
|
| 275 |
-
border-radius: 6px;
|
| 276 |
-
}
|
| 277 |
-
|
| 278 |
-
.btn-clear-all:hover {
|
| 279 |
-
color: var(--brand-danger);
|
| 280 |
-
background: rgba(239, 68, 68, 0.05);
|
| 281 |
-
}
|
| 282 |
-
|
| 283 |
-
.history-item {
|
| 284 |
-
padding: 9px 10px;
|
| 285 |
-
border-radius: var(--radius-sm);
|
| 286 |
-
font-size: 13px;
|
| 287 |
-
font-weight: 400;
|
| 288 |
-
color: var(--text-secondary);
|
| 289 |
-
cursor: pointer;
|
| 290 |
-
display: flex;
|
| 291 |
-
justify-content: space-between;
|
| 292 |
-
align-items: center;
|
| 293 |
-
transition: 0.15s;
|
| 294 |
-
}
|
| 295 |
-
|
| 296 |
-
.history-item:hover {
|
| 297 |
-
background: rgba(0,0,0,0.04);
|
| 298 |
-
color: var(--text-primary);
|
| 299 |
-
}
|
| 300 |
-
|
| 301 |
-
.history-item.active {
|
| 302 |
-
background: #fff;
|
| 303 |
-
color: var(--text-primary);
|
| 304 |
-
font-weight: 500;
|
| 305 |
-
box-shadow: var(--shadow-sm);
|
| 306 |
-
border: 1px solid var(--border-light);
|
| 307 |
-
}
|
| 308 |
-
|
| 309 |
-
.history-text {
|
| 310 |
-
white-space: nowrap;
|
| 311 |
-
overflow: hidden;
|
| 312 |
-
text-overflow: ellipsis;
|
| 313 |
-
flex: 1;
|
| 314 |
-
}
|
| 315 |
-
|
| 316 |
-
.btn-delete-chat {
|
| 317 |
-
background: none;
|
| 318 |
-
border: none;
|
| 319 |
-
color: var(--text-tertiary);
|
| 320 |
-
cursor: pointer;
|
| 321 |
-
padding: 4px;
|
| 322 |
-
display: none;
|
| 323 |
-
border-radius: 6px;
|
| 324 |
-
}
|
| 325 |
-
|
| 326 |
-
.history-item:hover .btn-delete-chat {
|
| 327 |
-
display: flex;
|
| 328 |
-
}
|
| 329 |
-
|
| 330 |
-
.btn-delete-chat:hover {
|
| 331 |
-
color: var(--brand-danger);
|
| 332 |
-
background: #fee2e2;
|
| 333 |
-
}
|
| 334 |
-
|
| 335 |
-
.sidebar-footer {
|
| 336 |
-
padding: 16px;
|
| 337 |
-
border-top: 1px solid var(--border-light);
|
| 338 |
-
display: flex;
|
| 339 |
-
align-items: center;
|
| 340 |
-
gap: 10px;
|
| 341 |
-
flex-wrap: wrap;
|
| 342 |
-
}
|
| 343 |
-
|
| 344 |
-
.user-avatar {
|
| 345 |
-
width: 34px;
|
| 346 |
-
height: 34px;
|
| 347 |
-
border-radius: 50%;
|
| 348 |
-
background: var(--text-primary);
|
| 349 |
-
color: #fff;
|
| 350 |
-
display: flex;
|
| 351 |
-
align-items: center;
|
| 352 |
-
justify-content: center;
|
| 353 |
-
font-size: 13px;
|
| 354 |
-
font-weight: 600;
|
| 355 |
-
flex-shrink: 0;
|
| 356 |
-
}
|
| 357 |
-
|
| 358 |
-
.user-info-box {
|
| 359 |
-
flex: 1;
|
| 360 |
-
overflow: hidden;
|
| 361 |
-
display: flex;
|
| 362 |
-
flex-direction: column;
|
| 363 |
-
}
|
| 364 |
-
|
| 365 |
-
.user-name {
|
| 366 |
-
font-size: 13px;
|
| 367 |
-
font-weight: 600;
|
| 368 |
-
white-space: nowrap;
|
| 369 |
-
overflow: hidden;
|
| 370 |
-
text-overflow: ellipsis;
|
| 371 |
-
}
|
| 372 |
-
|
| 373 |
-
.user-sub {
|
| 374 |
-
font-size: 11px;
|
| 375 |
-
color: var(--text-tertiary);
|
| 376 |
-
margin-top: 2px;
|
| 377 |
-
}
|
| 378 |
-
|
| 379 |
-
.btn-login-register {
|
| 380 |
-
background: var(--brand-color);
|
| 381 |
-
color: #fff;
|
| 382 |
-
border-radius: var(--radius-sm);
|
| 383 |
-
padding: 7px 12px;
|
| 384 |
-
font-size: 12px;
|
| 385 |
-
font-weight: 500;
|
| 386 |
-
cursor: pointer;
|
| 387 |
-
transition: 0.2s;
|
| 388 |
-
margin-left: auto;
|
| 389 |
-
}
|
| 390 |
-
|
| 391 |
-
.btn-login-register:hover {
|
| 392 |
-
background: #000;
|
| 393 |
-
transform: translateY(-1px);
|
| 394 |
-
}
|
| 395 |
-
|
| 396 |
-
.btn-power {
|
| 397 |
-
width: 32px;
|
| 398 |
-
height: 32px;
|
| 399 |
-
border-radius: var(--radius-sm);
|
| 400 |
-
display: flex;
|
| 401 |
-
align-items: center;
|
| 402 |
-
justify-content: center;
|
| 403 |
-
color: var(--text-secondary);
|
| 404 |
-
transition: 0.2s;
|
| 405 |
-
}
|
| 406 |
-
|
| 407 |
-
.btn-power:hover {
|
| 408 |
-
background: #fee2e2;
|
| 409 |
-
color: var(--brand-danger);
|
| 410 |
-
}
|
| 411 |
-
|
| 412 |
-
/*
|
| 413 |
-
* ================================================================================
|
| 414 |
-
* MAIN CHAT AREA
|
| 415 |
-
* ================================================================================
|
| 416 |
-
*/
|
| 417 |
-
.main-area {
|
| 418 |
-
flex: 1;
|
| 419 |
-
display: flex;
|
| 420 |
-
flex-direction: column;
|
| 421 |
-
position: relative;
|
| 422 |
-
transition: 0.3s;
|
| 423 |
-
height: 100%;
|
| 424 |
-
overflow: hidden;
|
| 425 |
-
z-index: 1;
|
| 426 |
-
}
|
| 427 |
-
|
| 428 |
-
.chat-container {
|
| 429 |
-
flex: 1;
|
| 430 |
-
overflow-x: hidden;
|
| 431 |
-
overflow-y: auto;
|
| 432 |
-
padding: 80px 20px 200px;
|
| 433 |
-
display: flex;
|
| 434 |
-
flex-direction: column;
|
| 435 |
-
align-items: center;
|
| 436 |
-
scroll-behavior: smooth;
|
| 437 |
-
}
|
| 438 |
-
|
| 439 |
-
.welcome-center {
|
| 440 |
-
margin: auto;
|
| 441 |
-
text-align: center;
|
| 442 |
-
display: flex;
|
| 443 |
-
flex-direction: column;
|
| 444 |
-
align-items: center;
|
| 445 |
-
animation: fadeInUp 0.6s ease;
|
| 446 |
-
width: 100%;
|
| 447 |
-
justify-content: center;
|
| 448 |
-
flex: 1;
|
| 449 |
-
}
|
| 450 |
-
|
| 451 |
-
.welcome-center img {
|
| 452 |
-
width: 64px;
|
| 453 |
-
height: 64px;
|
| 454 |
-
border-radius: 16px;
|
| 455 |
-
margin-bottom: 24px;
|
| 456 |
-
object-fit: contain;
|
| 457 |
-
}
|
| 458 |
-
|
| 459 |
-
.welcome-center h1 {
|
| 460 |
-
font-size: 28px;
|
| 461 |
-
font-weight: 500;
|
| 462 |
-
letter-spacing: -0.5px;
|
| 463 |
-
color: var(--text-primary);
|
| 464 |
-
margin-bottom: 8px;
|
| 465 |
-
}
|
| 466 |
-
|
| 467 |
-
.welcome-center p {
|
| 468 |
-
font-size: 15px;
|
| 469 |
-
color: var(--text-secondary);
|
| 470 |
-
font-weight: 400;
|
| 471 |
-
}
|
| 472 |
-
|
| 473 |
-
#chatMessages { width: 100%; display: flex; flex-direction: column; align-items: center; }
|
| 474 |
-
|
| 475 |
-
.message-wrapper {
|
| 476 |
-
width: 100%;
|
| 477 |
-
max-width: 800px;
|
| 478 |
-
margin-bottom: 32px;
|
| 479 |
-
display: flex;
|
| 480 |
-
flex-direction: column;
|
| 481 |
-
animation: fadeInUp 0.3s ease;
|
| 482 |
-
}
|
| 483 |
-
|
| 484 |
-
/* User Message */
|
| 485 |
-
.user-message {
|
| 486 |
-
align-self: flex-end;
|
| 487 |
-
max-width: 85%;
|
| 488 |
-
background: var(--bg-user-msg);
|
| 489 |
-
padding: 12px 18px;
|
| 490 |
-
border-radius: 18px 18px 4px 18px;
|
| 491 |
-
font-size: 15px;
|
| 492 |
-
font-weight: 400;
|
| 493 |
-
line-height: 1.5;
|
| 494 |
-
color: var(--text-primary);
|
| 495 |
-
word-wrap: break-word;
|
| 496 |
-
overflow-wrap: break-word;
|
| 497 |
-
border: 1px solid rgba(0,0,0,0.04);
|
| 498 |
-
}
|
| 499 |
-
|
| 500 |
-
/* Bot Message */
|
| 501 |
-
.bot-message {
|
| 502 |
-
align-self: flex-start;
|
| 503 |
-
max-width: 100%;
|
| 504 |
-
display: flex;
|
| 505 |
-
gap: 16px;
|
| 506 |
-
width: 100%;
|
| 507 |
-
box-sizing: border-box;
|
| 508 |
-
background: transparent;
|
| 509 |
-
padding: 0;
|
| 510 |
-
}
|
| 511 |
-
|
| 512 |
-
.bot-avatar {
|
| 513 |
-
width: 30px;
|
| 514 |
-
height: 30px;
|
| 515 |
-
border-radius: 8px;
|
| 516 |
-
flex-shrink: 0;
|
| 517 |
-
overflow: hidden;
|
| 518 |
-
display: flex;
|
| 519 |
-
align-items: center;
|
| 520 |
-
justify-content: center;
|
| 521 |
-
margin-top: 4px;
|
| 522 |
-
border: 1px solid var(--border-light);
|
| 523 |
-
background: #fff;
|
| 524 |
-
}
|
| 525 |
-
|
| 526 |
-
.bot-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
| 527 |
-
|
| 528 |
-
.bot-content-wrapper {
|
| 529 |
-
flex: 1 1 0%;
|
| 530 |
-
min-width: 0;
|
| 531 |
-
max-width: calc(100% - 46px);
|
| 532 |
-
width: 100%;
|
| 533 |
-
overflow: hidden;
|
| 534 |
-
}
|
| 535 |
-
|
| 536 |
-
.bot-content {
|
| 537 |
-
font-size: 15.5px;
|
| 538 |
-
line-height: 1.7;
|
| 539 |
-
color: var(--text-primary);
|
| 540 |
-
word-wrap: break-word;
|
| 541 |
-
font-weight: 400;
|
| 542 |
-
letter-spacing: 0.1px;
|
| 543 |
-
}
|
| 544 |
-
|
| 545 |
-
.bot-content p { margin-bottom: 16px; }
|
| 546 |
-
.bot-content p:last-child { margin-bottom: 0; }
|
| 547 |
-
.bot-content strong { font-weight: 600; color: #000; }
|
| 548 |
-
.bot-content ul, .bot-content ol { padding-left: 24px; margin-bottom: 16px; }
|
| 549 |
-
.bot-content li { margin-bottom: 8px; }
|
| 550 |
-
|
| 551 |
-
.bot-content code {
|
| 552 |
-
font-family: var(--font-code);
|
| 553 |
-
background: rgba(0,0,0,0.06);
|
| 554 |
-
padding: 3px 6px;
|
| 555 |
-
border-radius: 6px;
|
| 556 |
-
font-size: 0.85em;
|
| 557 |
-
color: #000;
|
| 558 |
-
}
|
| 559 |
-
|
| 560 |
-
.bot-content pre {
|
| 561 |
-
background: #0d1117;
|
| 562 |
-
padding: 16px;
|
| 563 |
-
border-radius: var(--radius-md);
|
| 564 |
-
overflow-x: auto;
|
| 565 |
-
margin: 16px 0;
|
| 566 |
-
border: 1px solid var(--border-light);
|
| 567 |
-
position: relative;
|
| 568 |
-
}
|
| 569 |
-
|
| 570 |
-
.bot-content pre code {
|
| 571 |
-
background: none;
|
| 572 |
-
padding: 0;
|
| 573 |
-
color: #e2e8f0;
|
| 574 |
-
font-size: 13.5px;
|
| 575 |
-
}
|
| 576 |
-
|
| 577 |
-
.bot-content table {
|
| 578 |
-
width: 100%;
|
| 579 |
-
border-collapse: collapse;
|
| 580 |
-
margin: 16px 0;
|
| 581 |
-
font-size: 14px;
|
| 582 |
-
border: 1px solid var(--border-light);
|
| 583 |
-
border-radius: 8px;
|
| 584 |
-
overflow: hidden;
|
| 585 |
-
display: block;
|
| 586 |
-
overflow-x: auto;
|
| 587 |
-
}
|
| 588 |
-
|
| 589 |
-
.bot-content th, .bot-content td {
|
| 590 |
-
border: 1px solid var(--border-light);
|
| 591 |
-
padding: 10px 14px;
|
| 592 |
-
text-align: left;
|
| 593 |
-
}
|
| 594 |
-
|
| 595 |
-
.bot-content th {
|
| 596 |
-
background: rgba(0,0,0,0.03);
|
| 597 |
-
font-weight: 500;
|
| 598 |
-
}
|
| 599 |
-
|
| 600 |
-
.mjx-container { max-width: 100%; overflow-x: auto; overflow-y: hidden; }
|
| 601 |
-
|
| 602 |
-
/* Attachments */
|
| 603 |
-
.chat-attachment-container { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
|
| 604 |
-
.chat-file-pill { display: inline-flex; align-items: center; gap: 8px; background: #fff; border: 1px solid var(--border-light); padding: 8px 12px; border-radius: 10px; font-size: 12.5px; font-weight: 500; }
|
| 605 |
-
.chat-img-preview { max-width: 140px; max-height: 140px; border-radius: var(--radius-md); object-fit: cover; border: 1px solid var(--border-light); }
|
| 606 |
-
|
| 607 |
-
/* Thinking Box */
|
| 608 |
-
.qwen-think-box { border-left: 2px solid #e4e4e7; padding-left: 16px; margin-bottom: 16px; transition: 0.3s; }
|
| 609 |
-
.qwen-think-box summary { font-size: 12.5px; font-weight: 500; color: var(--text-tertiary); cursor: pointer; list-style: none; display: flex; align-items: center; gap: 8px; user-select: none; }
|
| 610 |
-
.qwen-think-box summary::-webkit-details-marker { display: none; }
|
| 611 |
-
.qwen-think-content { margin-top: 8px; font-size: 14px; color: var(--text-secondary); font-style: italic; white-space: pre-wrap; line-height: 1.6; }
|
| 612 |
-
|
| 613 |
-
/* Message Actions */
|
| 614 |
-
.msg-actions { display: flex; gap: 4px; margin-top: 12px; opacity: 0; transition: 0.2s; align-items: center; }
|
| 615 |
-
.message-wrapper:hover .msg-actions { opacity: 1; }
|
| 616 |
-
@media (hover: none) { .msg-actions { opacity: 1; } }
|
| 617 |
-
|
| 618 |
-
.action-btn { display: flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 500; color: var(--text-tertiary); padding: 6px 10px; border-radius: 8px; transition: 0.2s; background: transparent; }
|
| 619 |
-
.action-btn:hover { color: var(--text-primary); background: rgba(0,0,0,0.04); }
|
| 620 |
-
.action-btn.listen-btn { background: #f4f4f5; }
|
| 621 |
-
.action-btn.listen-btn:hover { background: #e4e4e7; }
|
| 622 |
-
|
| 623 |
-
.code-copy-btn { position: absolute; top: 8px; right: 8px; display: flex; align-items: center; gap: 4px; padding: 4px 8px; font-size: 11px; color: var(--text-secondary); background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; cursor: pointer; z-index: 2; backdrop-filter: blur(4px); }
|
| 624 |
-
.code-copy-btn:hover { background: rgba(255,255,255,0.2); color: #fff; }
|
| 625 |
-
.code-copy-btn.copied { color: var(--brand-success); border-color: var(--brand-success); }
|
| 626 |
-
|
| 627 |
-
.btn-scroll-bottom {
|
| 628 |
-
position: absolute;
|
| 629 |
-
bottom: 120px;
|
| 630 |
-
left: 50%;
|
| 631 |
-
transform: translateX(-50%) translateY(20px);
|
| 632 |
-
width: 36px;
|
| 633 |
-
height: 36px;
|
| 634 |
-
border-radius: 50%;
|
| 635 |
-
background: #fff;
|
| 636 |
-
border: 1px solid var(--border-light);
|
| 637 |
-
box-shadow: var(--shadow-md);
|
| 638 |
-
display: flex;
|
| 639 |
-
align-items: center;
|
| 640 |
-
justify-content: center;
|
| 641 |
-
color: var(--text-secondary);
|
| 642 |
-
opacity: 0;
|
| 643 |
-
pointer-events: none;
|
| 644 |
-
transition: 0.3s;
|
| 645 |
-
z-index: 45;
|
| 646 |
-
}
|
| 647 |
-
|
| 648 |
-
.btn-scroll-bottom.visible {
|
| 649 |
-
opacity: 1;
|
| 650 |
-
pointer-events: auto;
|
| 651 |
-
transform: translateX(-50%) translateY(0);
|
| 652 |
-
}
|
| 653 |
-
|
| 654 |
-
/*
|
| 655 |
-
* ================================================================================
|
| 656 |
-
* INPUT DOCK (The Core UI - Clean & Professional)
|
| 657 |
-
* ================================================================================
|
| 658 |
-
*/
|
| 659 |
-
.input-dock {
|
| 660 |
-
position: absolute;
|
| 661 |
-
bottom: 24px;
|
| 662 |
-
left: 50%;
|
| 663 |
-
transform: translateX(-50%);
|
| 664 |
-
width: 100%;
|
| 665 |
-
max-width: 800px;
|
| 666 |
-
padding: 0 20px;
|
| 667 |
-
z-index: 50;
|
| 668 |
-
display: flex;
|
| 669 |
-
flex-direction: column;
|
| 670 |
-
align-items: center;
|
| 671 |
-
}
|
| 672 |
-
|
| 673 |
-
.loc-toast { display: none; align-items: center; gap: 6px; font-size: 12px; font-weight: 500; color: var(--text-secondary); margin-bottom: 12px; background: #fff; padding: 6px 14px; border-radius: 16px; border: 1px solid var(--border-light); box-shadow: var(--shadow-md); }
|
| 674 |
-
|
| 675 |
-
.input-dock-wrapper {
|
| 676 |
-
position: relative;
|
| 677 |
-
width: 100%;
|
| 678 |
-
background: rgba(255, 255, 255, 0.85);
|
| 679 |
-
backdrop-filter: blur(20px);
|
| 680 |
-
-webkit-backdrop-filter: blur(20px);
|
| 681 |
-
border: 1px solid var(--border-light);
|
| 682 |
-
border-radius: var(--radius-lg);
|
| 683 |
-
padding: 12px 16px;
|
| 684 |
-
display: flex;
|
| 685 |
-
flex-direction: column;
|
| 686 |
-
transition: all 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
|
| 687 |
-
box-shadow: var(--shadow-md);
|
| 688 |
-
}
|
| 689 |
-
|
| 690 |
-
/* Expanded State (No RGB Glow, just crisp shadow & border) */
|
| 691 |
-
.input-dock-wrapper.expanded {
|
| 692 |
-
background: rgba(255, 255, 255, 0.98);
|
| 693 |
-
border-color: var(--border-focus);
|
| 694 |
-
transform: translateY(-2px);
|
| 695 |
-
box-shadow: var(--shadow-lg);
|
| 696 |
-
}
|
| 697 |
-
|
| 698 |
-
.file-preview-bar { display: none; padding-bottom: 10px; align-items: center; gap: 8px; }
|
| 699 |
-
.file-preview-bar.active { display: flex; }
|
| 700 |
-
.file-preview-pill { display: flex; align-items: center; gap: 8px; background: #f4f4f5; padding: 6px 12px; border-radius: 10px; font-size: 12px; border: 1px solid var(--border-light); }
|
| 701 |
-
.file-preview-icon { width: 16px; height: 16px; object-fit: cover; border-radius: 4px; }
|
| 702 |
-
.file-preview-name { max-width: 120px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-primary); }
|
| 703 |
-
.btn-remove-file { width: 18px; height: 18px; border-radius: 50%; background: #e4e4e7; display: flex; align-items: center; justify-content: center; transition: 0.2s; }
|
| 704 |
-
.btn-remove-file:hover { background: #d4d4d8; }
|
| 705 |
-
|
| 706 |
-
.chat-input {
|
| 707 |
-
width: 100%;
|
| 708 |
-
padding: 8px 4px 12px;
|
| 709 |
-
font-size: 15px;
|
| 710 |
-
font-weight: 400;
|
| 711 |
-
resize: none;
|
| 712 |
-
color: var(--text-primary);
|
| 713 |
-
line-height: 1.5;
|
| 714 |
-
background: transparent;
|
| 715 |
-
caret-color: #000;
|
| 716 |
-
min-height: 24px;
|
| 717 |
-
max-height: 200px;
|
| 718 |
-
transition: min-height 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
|
| 719 |
-
}
|
| 720 |
-
|
| 721 |
-
.chat-input::placeholder { color: var(--text-tertiary); font-weight: 400; }
|
| 722 |
-
|
| 723 |
-
.input-dock-wrapper.expanded .chat-input {
|
| 724 |
-
min-height: 60px;
|
| 725 |
-
}
|
| 726 |
-
|
| 727 |
-
.input-row { display: flex; align-items: center; justify-content: space-between; width: 100%; margin-top: 4px; }
|
| 728 |
-
|
| 729 |
-
.tools-left { display: flex; gap: 2px; align-items: center; position: relative; }
|
| 730 |
-
|
| 731 |
-
.tool-btn { width: 34px; height: 34px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--text-secondary); transition: 0.2s; background: transparent; flex-shrink: 0; }
|
| 732 |
-
.tool-btn:hover { background: #f4f4f5; color: var(--text-primary); }
|
| 733 |
-
|
| 734 |
-
/* Tool Active States */
|
| 735 |
-
.tool-btn.active-env { color: #fff; background: var(--brand-accent); }
|
| 736 |
-
.tool-btn.active-think-low { color: #fff; background: var(--brand-danger); }
|
| 737 |
-
.tool-btn.active-think-medium { color: #fff; background: var(--brand-warning); }
|
| 738 |
-
.tool-btn.active-think-high { color: #fff; background: #8b5cf6; }
|
| 739 |
-
.tool-btn.active-search { color: #fff; background: #16803c; }
|
| 740 |
-
|
| 741 |
-
.btn-mic.recording { color: var(--brand-danger); background: #fee2e2; animation: recordPulse 1.5s infinite; }
|
| 742 |
-
|
| 743 |
-
.tools-right { display: flex; align-items: center; gap: 8px; }
|
| 744 |
-
|
| 745 |
-
.btn-send { width: 34px; height: 34px; border-radius: 50%; background: var(--brand-color); color: #fff; display: flex; align-items: center; justify-content: center; transition: 0.2s; flex-shrink: 0; }
|
| 746 |
-
.btn-send:hover:not(:disabled) { background: #000; transform: scale(1.05); }
|
| 747 |
-
.btn-send:disabled { background: #e4e4e7; color: #a1a1aa; cursor: not-allowed; }
|
| 748 |
-
|
| 749 |
-
.btn-stop { width: 34px; height: 34px; border-radius: 50%; background: #fee2e2; color: var(--brand-danger); display: none; align-items: center; justify-content: center; transition: 0.25s; flex-shrink: 0; }
|
| 750 |
-
.btn-stop.active { display: flex; }
|
| 751 |
-
.btn-stop:hover { background: #fecaca; }
|
| 752 |
-
|
| 753 |
-
.hidden-input { display: none; }
|
| 754 |
-
|
| 755 |
-
/* Dropdowns */
|
| 756 |
-
.dropdown-menu {
|
| 757 |
-
position: absolute;
|
| 758 |
-
bottom: calc(100% + 12px);
|
| 759 |
-
left: 0;
|
| 760 |
-
background: rgba(255, 255, 255, 0.98);
|
| 761 |
-
backdrop-filter: blur(20px);
|
| 762 |
-
border: 1px solid var(--border-light);
|
| 763 |
-
border-radius: 14px;
|
| 764 |
-
padding: 6px;
|
| 765 |
-
box-shadow: var(--shadow-lg);
|
| 766 |
-
display: none;
|
| 767 |
-
flex-direction: column;
|
| 768 |
-
min-width: 200px;
|
| 769 |
-
z-index: 100;
|
| 770 |
-
transform-origin: bottom left;
|
| 771 |
-
}
|
| 772 |
-
|
| 773 |
-
.dropdown-menu.active { display: flex; animation: dropUpFade 0.2s cubic-bezier(0.2, 0.8, 0.2, 1); }
|
| 774 |
-
|
| 775 |
-
.dropdown-header { padding: 8px 12px; font-size: 11px; font-weight: 600; color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
|
| 776 |
-
|
| 777 |
-
.dropdown-item { padding: 9px 12px; border-radius: 8px; font-size: 13px; display: flex; align-items: center; justify-content: space-between; gap: 8px; cursor: pointer; transition: 0.15s; color: var(--text-primary); }
|
| 778 |
-
.dropdown-item:hover { background: #f4f4f5; }
|
| 779 |
-
.dropdown-item.selected { background: #f4f4f5; font-weight: 500; }
|
| 780 |
-
|
| 781 |
-
/*
|
| 782 |
-
* ================================================================================
|
| 783 |
-
* MODALS & OVERLAYS
|
| 784 |
-
* ================================================================================
|
| 785 |
-
*/
|
| 786 |
-
.mobile-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.2); backdrop-filter: blur(4px); z-index: 95; opacity: 0; transition: 0.3s ease; pointer-events: none; }
|
| 787 |
-
.mobile-overlay.active { display: block; opacity: 1; pointer-events: all; }
|
| 788 |
-
|
| 789 |
-
.auth-overlay { position: fixed; inset: 0; background: rgba(0,0,0, 0.3); backdrop-filter: blur(8px); display: none; align-items: center; justify-content: center; z-index: 2000; }
|
| 790 |
-
.auth-modal { background: #ffffff; width: 90%; max-width: 360px; border-radius: 20px; padding: 32px; box-shadow: 0 20px 50px rgba(0,0,0, 0.15); position: relative; animation: modalFadeIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1); border: 1px solid var(--border-light); }
|
| 791 |
-
.auth-close { position: absolute; top: 16px; right: 16px; color: var(--text-tertiary); width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: #f4f4f5; transition: 0.2s; }
|
| 792 |
-
.auth-close:hover { background: #e4e4e7; color: var(--text-primary); }
|
| 793 |
-
.auth-title { font-size: 18px; font-weight: 600; margin-bottom: 24px; text-align: center; letter-spacing: -0.3px; }
|
| 794 |
-
.auth-tabs { display: flex; background: #f4f4f5; border-radius: 10px; padding: 4px; margin-bottom: 20px; }
|
| 795 |
-
.auth-tab { flex: 1; text-align: center; padding: 8px; font-size: 13px; font-weight: 500; cursor: pointer; border-radius: 8px; color: var(--text-secondary); transition: 0.2s; }
|
| 796 |
-
.auth-tab.active { background: #ffffff; color: #000; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
|
| 797 |
-
.auth-input { width: 100%; padding: 12px 14px; border: 1px solid var(--border-light); border-radius: 10px; font-size: 14px; margin-bottom: 12px; background: #fcfcfc; transition: 0.2s; }
|
| 798 |
-
.auth-input:focus { border-color: var(--text-primary); background: #fff; }
|
| 799 |
-
.auth-btn { width: 100%; padding: 12px; background: var(--brand-color); color: #fff; font-size: 14px; font-weight: 500; border-radius: 10px; transition: 0.2s; }
|
| 800 |
-
.auth-btn:hover { background: #000; }
|
| 801 |
-
.auth-phase { display: none; }
|
| 802 |
-
.auth-phase.active { display: block; }
|
| 803 |
-
.auth-message { font-size: 12px; text-align: center; margin-top: 12px; min-height: 16px; }
|
| 804 |
-
|
| 805 |
-
.custom-confirm-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.3); backdrop-filter: blur(8px); display: none; align-items: center; justify-content: center; z-index: 2500; }
|
| 806 |
-
.custom-confirm-box { background: #fff; border-radius: 16px; padding: 24px; max-width: 300px; width: 90%; text-align: center; box-shadow: 0 20px 50px rgba(0,0,0,0.15); animation: modalFadeIn 0.2s; border: 1px solid var(--border-light); }
|
| 807 |
-
.custom-confirm-msg { font-size: 14px; margin-bottom: 24px; color: var(--text-primary); font-weight: 500; line-height: 1.5; }
|
| 808 |
-
.custom-confirm-btns { display: flex; gap: 12px; }
|
| 809 |
-
.custom-confirm-btn { flex: 1; padding: 10px; border-radius: 10px; font-size: 13px; font-weight: 500; transition: 0.2s; }
|
| 810 |
-
.custom-confirm-btn.cancel { background: #f4f4f5; color: var(--text-secondary); }
|
| 811 |
-
.custom-confirm-btn.cancel:hover { background: #e4e4e7; }
|
| 812 |
-
.custom-confirm-btn.confirm { background: var(--brand-danger); color: #fff; }
|
| 813 |
-
.custom-confirm-btn.confirm:hover { background: #dc2626; }
|
| 814 |
-
|
| 815 |
-
/*
|
| 816 |
-
* ================================================================================
|
| 817 |
-
* RESPONSIVE DESIGN
|
| 818 |
-
* ================================================================================
|
| 819 |
-
*/
|
| 820 |
-
@media (max-width: 900px) {
|
| 821 |
-
.sidebar { position: fixed; left: 0; top: 0; box-shadow: 4px 0 24px rgba(0,0,0,0.05); }
|
| 822 |
-
.sidebar.collapsed { box-shadow: none; }
|
| 823 |
-
.input-dock { padding: 10px 16px calc(16px + env(safe-area-inset-bottom)); bottom: 0; }
|
| 824 |
-
.chat-container { padding: 70px 16px 180px; }
|
| 825 |
-
.user-message { max-width: 90%; }
|
| 826 |
-
}
|
| 827 |
-
|
| 828 |
-
@media (max-width: 600px) {
|
| 829 |
-
.code-copy-btn span { display: none; }
|
| 830 |
-
.welcome-center img { width: 56px; height: 56px; }
|
| 831 |
-
.welcome-center h1 { font-size: 24px; }
|
| 832 |
-
.tool-btn, .btn-send, .btn-stop { width: 36px; height: 36px; }
|
| 833 |
-
.input-dock-wrapper.expanded .chat-input { min-height: 50px; }
|
| 834 |
-
|
| 835 |
-
/* Prevents iOS zoom on focus */
|
| 836 |
-
.chat-input { font-size: 16px; }
|
| 837 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index.html
CHANGED
|
@@ -4,262 +4,917 @@
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
| 6 |
<title>CODE VED | Engineered by Divy Patel</title>
|
|
|
|
| 7 |
|
| 8 |
-
<!--
|
| 9 |
-
<link rel="icon" type="image/webp" id="faviconLink">
|
| 10 |
-
|
| 11 |
-
<!-- Google Fonts -->
|
| 12 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 13 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 14 |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 15 |
-
|
| 16 |
-
<!-- External Libraries CSS -->
|
| 17 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
| 18 |
|
| 19 |
-
<!--
|
| 20 |
-
<link rel="stylesheet" href="
|
| 21 |
-
</
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
<aside class="sidebar collapsed" id="sidebar">
|
| 35 |
<div class="sidebar-header">
|
| 36 |
-
<
|
| 37 |
-
<
|
| 38 |
-
|
| 39 |
-
</
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
| 42 |
</button>
|
| 43 |
</div>
|
| 44 |
|
| 45 |
-
<
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
</
|
| 49 |
|
| 50 |
-
<
|
| 51 |
-
|
| 52 |
-
<div class="sidebar-footer">
|
| 53 |
<div class="user-avatar" id="uAv">G</div>
|
| 54 |
<div class="user-info-box">
|
| 55 |
<div class="user-name" id="uName">Guest Session</div>
|
| 56 |
-
<div class="user-sub" id="uSub"
|
| 57 |
</div>
|
| 58 |
-
<
|
| 59 |
-
<button class="btn-power" id="
|
| 60 |
-
<svg
|
| 61 |
-
</button>
|
| 62 |
-
<button class="btn-power" id="btnLogout" style="display:none;" title="Logout">
|
| 63 |
-
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path><line x1="12" y1="2" x2="12" y2="12"></line></svg>
|
| 64 |
</button>
|
| 65 |
</div>
|
| 66 |
</aside>
|
| 67 |
|
| 68 |
-
<!--
|
| 69 |
<main class="main-area">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
<div class="chat-container" id="chatContainer">
|
| 71 |
<div class="welcome-center" id="welcomeScreen">
|
| 72 |
-
<img
|
| 73 |
-
<h1
|
| 74 |
-
<p id="welcomeGreeting">
|
| 75 |
</div>
|
| 76 |
-
<div id="chatMessages"></div>
|
| 77 |
</div>
|
| 78 |
|
| 79 |
-
<button id="scrollToBottomBtn" class="btn-scroll-bottom">
|
| 80 |
-
<svg style="width:18px;height:18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline></svg>
|
| 81 |
-
</button>
|
| 82 |
-
|
| 83 |
-
<!-- Input Dock -->
|
| 84 |
<div class="input-dock" id="inputDock">
|
| 85 |
-
<div class="input-
|
| 86 |
-
<div class="loc-toast" id="locStatus"></div>
|
| 87 |
-
|
| 88 |
<div class="file-preview-bar" id="filePreviewBar">
|
| 89 |
-
<
|
| 90 |
-
|
| 91 |
-
<svg
|
| 92 |
-
<div class="file-preview-name" id="fileName">doc.pdf</div>
|
| 93 |
-
<button class="btn-remove-file" id="btnRemoveFile">
|
| 94 |
-
<svg style="width:12px;height:12px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
| 95 |
-
</button>
|
| 96 |
</div>
|
|
|
|
|
|
|
| 97 |
</div>
|
| 98 |
|
| 99 |
-
<textarea class="chat-input" id="mainInput" placeholder="Ask CODE VED..." rows="1"></textarea>
|
| 100 |
-
|
| 101 |
<div class="input-row">
|
| 102 |
<div class="tools-left">
|
| 103 |
<div style="position: relative;">
|
| 104 |
-
<button class="tool-btn"
|
| 105 |
-
<svg
|
| 106 |
</button>
|
| 107 |
-
|
| 108 |
-
<!-- Attach Dropdown -->
|
| 109 |
<div class="dropdown-menu" id="attachMenu">
|
| 110 |
-
<div class="dropdown-item"
|
| 111 |
-
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
|
| 112 |
-
Upload Image
|
| 113 |
-
</div>
|
| 114 |
-
<div class="dropdown-item" id="optUploadDoc">
|
| 115 |
-
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
|
| 116 |
-
Upload Document
|
| 117 |
-
</div>
|
| 118 |
-
<div style="height:1px; background:var(--border-light); margin:4px 0;"></div>
|
| 119 |
-
<div class="dropdown-item" id="optEnv">
|
| 120 |
-
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
|
| 121 |
-
GPS & Weather
|
| 122 |
</div>
|
| 123 |
-
<div class="dropdown-item"
|
| 124 |
-
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="
|
| 125 |
-
Thinking Mode
|
| 126 |
</div>
|
| 127 |
-
<div class="dropdown-item" id="optVoice">
|
| 128 |
-
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>
|
| 129 |
-
Select Voice
|
| 130 |
-
</div>
|
| 131 |
-
</div>
|
| 132 |
-
|
| 133 |
-
<!-- Voice Dropdown -->
|
| 134 |
-
<div class="dropdown-menu" id="voiceMenu" style="min-width: 220px; left: 45px; bottom: 40px; max-height: 300px; overflow-y: auto;">
|
| 135 |
-
<div class="dropdown-header">Premium Voices</div>
|
| 136 |
-
<div id="voiceOptionsList"></div>
|
| 137 |
-
<div style="height:1px; background:var(--border-light); margin:4px 0;"></div>
|
| 138 |
-
<div class="dropdown-header">Language</div>
|
| 139 |
-
<div id="languageOptionsList"></div>
|
| 140 |
-
</div>
|
| 141 |
-
|
| 142 |
-
<!-- Thinking Dropdown -->
|
| 143 |
-
<div class="dropdown-menu" id="thinkMenu" style="min-width: 180px; left: 45px; bottom: 40px;">
|
| 144 |
-
<div class="dropdown-header">Reasoning Effort</div>
|
| 145 |
-
<div class="dropdown-item" data-effort="low">⚡ Low (Fast)</div>
|
| 146 |
-
<div class="dropdown-item" data-effort="medium">🧠 Medium (Balanced)</div>
|
| 147 |
-
<div class="dropdown-item" data-effort="high">🔍 High (Deep logic)</div>
|
| 148 |
-
<div style="height:1px; background:var(--border-light); margin:4px 0;"></div>
|
| 149 |
-
<div class="dropdown-item" id="optDisableThink" style="color:var(--brand-danger);">❌ Disable Thinking</div>
|
| 150 |
</div>
|
| 151 |
-
|
| 152 |
-
<
|
| 153 |
-
<input type="file" id="imgUpload" class="hidden-input" accept="image/*">
|
| 154 |
-
<input type="file" id="docUpload" class="hidden-input" accept=".pdf,.txt,.docx,.html,.js,.py,.css,.cpp,.c,.json,.md">
|
| 155 |
</div>
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
<svg style="width:18px;height:18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
| 159 |
-
</button>
|
| 160 |
-
|
| 161 |
-
<button class="tool-btn btn-mic" id="btnStt" title="Voice Typing">
|
| 162 |
-
<svg style="width:18px;height:18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>
|
| 163 |
-
</button>
|
| 164 |
-
</div>
|
| 165 |
-
|
| 166 |
-
<div class="tools-right">
|
| 167 |
-
<button class="btn-stop" id="btnStop">
|
| 168 |
-
<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"></rect></svg>
|
| 169 |
-
</button>
|
| 170 |
-
<button class="btn-send" id="btnSend">
|
| 171 |
-
<svg style="width:18px;height:18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg>
|
| 172 |
</button>
|
| 173 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
</div>
|
| 175 |
</div>
|
| 176 |
</div>
|
| 177 |
</main>
|
| 178 |
|
| 179 |
-
<!--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
<div class="auth-overlay" id="authModal">
|
| 181 |
<div class="auth-modal">
|
| 182 |
-
<button class="auth-close"
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
<div class="auth-title">System Access</div>
|
| 186 |
<div class="auth-tabs">
|
| 187 |
-
<div class="auth-tab active" id="tabLogin"
|
| 188 |
-
<div class="auth-tab" id="tabRegister"
|
| 189 |
</div>
|
| 190 |
-
|
| 191 |
<!-- Login Flow -->
|
| 192 |
<div id="flowLogin">
|
| 193 |
<div class="auth-phase active" id="loginPhase1">
|
| 194 |
-
<
|
| 195 |
-
|
|
|
|
|
|
|
| 196 |
</div>
|
| 197 |
<div class="auth-phase" id="loginPhase2">
|
| 198 |
-
<
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
| 200 |
</div>
|
| 201 |
</div>
|
| 202 |
-
|
| 203 |
<!-- Register Flow -->
|
| 204 |
<div id="flowRegister" style="display:none;">
|
| 205 |
<div class="auth-phase active" id="regPhase1">
|
| 206 |
-
<
|
| 207 |
-
|
| 208 |
-
|
|
|
|
|
|
|
| 209 |
</div>
|
| 210 |
<div class="auth-phase" id="regPhase2">
|
| 211 |
-
<
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
| 213 |
</div>
|
| 214 |
</div>
|
|
|
|
| 215 |
<div class="auth-message" id="authMsg"></div>
|
| 216 |
</div>
|
| 217 |
</div>
|
| 218 |
|
| 219 |
-
<
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
</div>
|
| 227 |
-
</div>
|
| 228 |
-
</div>
|
| 229 |
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
},
|
| 246 |
-
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
},
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
},
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
};
|
| 256 |
-
</script>
|
| 257 |
-
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
| 258 |
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
</body>
|
| 265 |
-
</html>
|
|
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
| 6 |
<title>CODE VED | Engineered by Divy Patel</title>
|
| 7 |
+
<meta name="description" content="CODE VED - 202 Billion Parameter Enterprise AI System">
|
| 8 |
|
| 9 |
+
<!-- Premium Fonts -->
|
|
|
|
|
|
|
|
|
|
| 10 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 11 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 12 |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
+
<!-- Syntax Highlighting & Parsers -->
|
| 15 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
|
| 16 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
| 17 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
|
| 18 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
| 19 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
| 20 |
|
| 21 |
+
<style>
|
| 22 |
+
:root {
|
| 23 |
+
/* Clean Minimalist Light Theme */
|
| 24 |
+
--bg-main: #ffffff;
|
| 25 |
+
--bg-sidebar: #f9f9f9;
|
| 26 |
+
--bg-input: #f4f4f4;
|
| 27 |
+
--bg-hover: #ebebeb;
|
| 28 |
+
--text-primary: #0f0f0f;
|
| 29 |
+
--text-secondary: #606060;
|
| 30 |
+
--text-tertiary: #8e8e8e;
|
| 31 |
+
--border-light: #e5e5e5;
|
| 32 |
+
--border-focus: #d0d0d0;
|
| 33 |
+
--brand-color: #1a1a1a;
|
| 34 |
+
--brand-accent: #0066cc; /* Tool Calling Color */
|
| 35 |
+
--font-ui: 'Inter', sans-serif;
|
| 36 |
+
--font-code: 'JetBrains Mono', monospace;
|
| 37 |
+
--radius-sm: 8px;
|
| 38 |
+
--radius-md: 12px;
|
| 39 |
+
--radius-lg: 24px;
|
| 40 |
+
}
|
| 41 |
|
| 42 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 43 |
+
|
| 44 |
+
/* 📱 MOBILE BUG FIX: Using 100dvh to prevent input dock from hiding behind browser URL bar */
|
| 45 |
+
html, body {
|
| 46 |
+
height: 100%; height: 100dvh; width: 100vw;
|
| 47 |
+
font-family: var(--font-ui); background-color: var(--bg-main); color: var(--text-primary);
|
| 48 |
+
display: flex; overflow: hidden; -webkit-font-smoothing: antialiased;
|
| 49 |
+
}
|
| 50 |
|
| 51 |
+
button { background: none; border: none; cursor: pointer; color: inherit; font-family: inherit; }
|
| 52 |
+
input, textarea { font-family: inherit; outline: none; border: none; background: transparent; }
|
| 53 |
+
|
| 54 |
+
/* Custom Scrollbar */
|
| 55 |
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 56 |
+
::-webkit-scrollbar-track { background: transparent; }
|
| 57 |
+
::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 10px; }
|
| 58 |
+
::-webkit-scrollbar-thumb:hover { background: #a0a0a0; }
|
| 59 |
|
| 60 |
+
/* --- 1. SIDEBAR (Restored History & Power Button) --- */
|
| 61 |
+
.sidebar {
|
| 62 |
+
width: 260px; background-color: var(--bg-sidebar); border-right: 1px solid var(--border-light);
|
| 63 |
+
display: flex; flex-direction: column; transition: transform 0.3s ease, width 0.3s ease; z-index: 100;
|
| 64 |
+
height: 100%; flex-shrink: 0;
|
| 65 |
+
}
|
| 66 |
+
.sidebar.collapsed { margin-left: -260px; }
|
| 67 |
+
|
| 68 |
+
.sidebar-header { padding: 16px; display: flex; align-items: center; justify-content: space-between; }
|
| 69 |
+
.btn-new-chat {
|
| 70 |
+
display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; padding: 10px 12px;
|
| 71 |
+
border-radius: var(--radius-sm); background: #fff; border: 1px solid var(--border-light);
|
| 72 |
+
font-size: 14px; font-weight: 500; transition: 0.2s; box-shadow: 0 1px 2px rgba(0,0,0,0.02);
|
| 73 |
+
}
|
| 74 |
+
.btn-new-chat:hover { background: var(--bg-hover); color: #000; border-color: #ccc; }
|
| 75 |
+
|
| 76 |
+
.sidebar-history { flex: 1; overflow-y: auto; padding: 10px; }
|
| 77 |
+
.history-item { padding: 10px; border-radius: var(--radius-sm); font-size: 13px; color: var(--text-secondary); cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 4px; transition: 0.2s;}
|
| 78 |
+
.history-item:hover { background: var(--border-light); color: var(--text-primary); }
|
| 79 |
+
|
| 80 |
+
.sidebar-footer { padding: 16px; border-top: 1px solid var(--border-light); display: flex; align-items: center; gap: 12px; transition: background 0.2s; }
|
| 81 |
+
.user-avatar { width: 34px; height: 34px; border-radius: 50%; background: var(--brand-color); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 15px; font-weight: 600; flex-shrink: 0; }
|
| 82 |
+
.user-info-box { flex: 1; overflow: hidden; }
|
| 83 |
+
.user-name { font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-primary); }
|
| 84 |
+
.user-sub { font-size: 11px; font-weight: 500; color: var(--text-tertiary); margin-top: 2px; }
|
| 85 |
+
|
| 86 |
+
.btn-power {
|
| 87 |
+
width: 32px; height: 32px; border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center;
|
| 88 |
+
color: var(--text-secondary); transition: 0.2s; cursor: pointer; border: 1px solid transparent; flex-shrink: 0;
|
| 89 |
+
}
|
| 90 |
+
.btn-power:hover { background: #ffe6e6; color: #cc0000; border-color: #ffcccc; }
|
| 91 |
+
|
| 92 |
+
/* --- 2. MAIN WORKSPACE AREA --- */
|
| 93 |
+
.main-area { flex: 1; display: flex; flex-direction: column; position: relative; background: var(--bg-main); transition: 0.3s; height: 100%; overflow: hidden; }
|
| 94 |
+
|
| 95 |
+
.top-bar { padding: 12px 20px; display: flex; align-items: center; justify-content: space-between; background: var(--bg-main); z-index: 10; flex-shrink: 0; border-bottom: 1px solid transparent; transition: border 0.3s; }
|
| 96 |
+
.top-bar.scrolled { border-bottom: 1px solid var(--border-light); }
|
| 97 |
+
.btn-icon { width: 32px; height: 32px; border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; color: var(--text-secondary); transition: 0.2s; }
|
| 98 |
+
.btn-icon:hover { background: var(--bg-hover); color: var(--text-primary); }
|
| 99 |
+
.btn-icon svg { width: 18px; height: 18px; }
|
| 100 |
+
|
| 101 |
+
.model-selector { font-size: 14px; font-weight: 600; color: var(--text-secondary); display: flex; align-items: center; gap: 6px; }
|
| 102 |
+
|
| 103 |
+
/* --- 3. CHAT CONTAINER --- */
|
| 104 |
+
.chat-container { flex: 1; overflow-y: auto; padding: 20px 20px 40px; display: flex; flex-direction: column; align-items: center; scroll-behavior: smooth; }
|
| 105 |
+
|
| 106 |
+
/* Restored Welcome Screen */
|
| 107 |
+
.welcome-center { margin: auto; text-align: center; display: flex; flex-direction: column; align-items: center; animation: fadeIn 0.5s ease; }
|
| 108 |
+
.welcome-center img { width: 70px; height: 70px; border-radius: 18px; margin-bottom: 24px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
|
| 109 |
+
.welcome-center h1 { font-size: 26px; font-weight: 500; color: var(--text-primary); margin-bottom: 8px; letter-spacing: -0.5px; }
|
| 110 |
+
.welcome-center p { font-size: 14px; color: var(--text-secondary); }
|
| 111 |
+
|
| 112 |
+
.message-wrapper { width: 100%; max-width: 760px; margin-bottom: 32px; display: flex; flex-direction: column; }
|
| 113 |
+
.user-message { align-self: flex-end; max-width: 85%; background: var(--bg-input); padding: 12px 18px; border-radius: 20px 20px 4px 20px; font-size: 15px; line-height: 1.5; color: var(--text-primary); border: 1px solid var(--border-light); }
|
| 114 |
+
|
| 115 |
+
.bot-message { align-self: flex-start; max-width: 100%; display: flex; gap: 16px; width: 100%; }
|
| 116 |
+
.bot-avatar { width: 28px; height: 28px; border-radius: 6px; flex-shrink: 0; overflow: hidden; border: 1px solid var(--border-light); }
|
| 117 |
+
.bot-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
| 118 |
+
.bot-content { flex: 1; font-size: 15.5px; line-height: 1.6; color: var(--text-primary); min-width: 0; }
|
| 119 |
+
|
| 120 |
+
/* Markdown Styling */
|
| 121 |
+
.bot-content p { margin-bottom: 16px; }
|
| 122 |
+
.bot-content p:last-child { margin-bottom: 0; }
|
| 123 |
+
.bot-content strong { font-weight: 600; color: #000; }
|
| 124 |
+
.bot-content code { font-family: var(--font-code); background: var(--bg-input); padding: 2px 6px; border-radius: 4px; font-size: 0.9em; color: #d63384; }
|
| 125 |
+
.bot-content pre { background: #f6f8fa; padding: 16px; border-radius: var(--radius-sm); border: 1px solid var(--border-light); overflow-x: auto; margin: 16px 0; }
|
| 126 |
+
.bot-content pre code { background: none; padding: 0; color: inherit; font-size: 14px; }
|
| 127 |
+
|
| 128 |
+
.chat-attachment { display: inline-flex; align-items: center; gap: 8px; background: #fff; border: 1px solid var(--border-light); padding: 6px 12px; border-radius: var(--radius-md); font-size: 13px; font-weight: 500; margin-bottom: 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
|
| 129 |
+
.chat-img-preview { max-width: 250px; border-radius: var(--radius-md); border: 1px solid var(--border-light); margin-bottom: 10px; }
|
| 130 |
+
|
| 131 |
+
/* --- 4. GOOGLE SEARCH TOOL UI --- */
|
| 132 |
+
.tool-indicator { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 500; color: var(--brand-accent); background: rgba(0, 102, 204, 0.05); padding: 8px 14px; border-radius: var(--radius-sm); margin-bottom: 16px; border: 1px solid rgba(0, 102, 204, 0.2); transition: 0.3s; }
|
| 133 |
+
.tool-indicator.active { animation: pulseGlow 2s infinite alternate; }
|
| 134 |
+
.tool-indicator svg { width: 16px; height: 16px; }
|
| 135 |
+
.tool-indicator.active svg { animation: spin 2s linear infinite; }
|
| 136 |
+
|
| 137 |
+
@keyframes pulseGlow { 0% { box-shadow: 0 0 0 rgba(0, 102, 204, 0); border-color: rgba(0, 102, 204, 0.2); } 100% { box-shadow: 0 0 12px rgba(0, 102, 204, 0.3); border-color: rgba(0, 102, 204, 0.5); } }
|
| 138 |
+
@keyframes spin { 100% { transform: rotate(360deg); } }
|
| 139 |
+
|
| 140 |
+
/* --- 5. FIXED INPUT DOCK (MOBILE SAFE) --- */
|
| 141 |
+
.input-dock { flex-shrink: 0; padding: 15px 20px 25px; background: var(--bg-main); border-top: 1px solid transparent; display: flex; justify-content: center; z-index: 20; width: 100%; transition: border 0.3s; }
|
| 142 |
+
.input-dock.scrolled { border-top: 1px solid var(--border-light); }
|
| 143 |
+
|
| 144 |
+
.input-container { width: 100%; max-width: 760px; background: var(--bg-input); border: 1px solid var(--border-light); border-radius: var(--radius-lg); padding: 8px 12px; display: flex; flex-direction: column; transition: 0.2s; position: relative; }
|
| 145 |
+
.input-container:focus-within { border-color: var(--border-focus); background: #fff; box-shadow: 0 4px 20px rgba(0,0,0,0.05); }
|
| 146 |
+
|
| 147 |
+
.file-preview-bar { display: none; padding: 8px 8px 12px; align-items: center; gap: 12px; border-bottom: 1px solid var(--border-light); margin-bottom: 8px; }
|
| 148 |
+
.file-preview-bar.active { display: flex; }
|
| 149 |
+
.file-preview-icon { width: 40px; height: 40px; border-radius: 8px; object-fit: cover; background: #fff; border: 1px solid var(--border-light); display: flex; align-items: center; justify-content: center; font-size: 18px; }
|
| 150 |
+
.file-preview-name { flex: 1; font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-primary); }
|
| 151 |
+
.btn-remove-file { width: 24px; height: 24px; border-radius: 50%; background: #ffe6e6; color: #cc0000; display: flex; align-items: center; justify-content: center; }
|
| 152 |
+
|
| 153 |
+
.input-row { display: flex; align-items: flex-end; gap: 8px; width: 100%; }
|
| 154 |
+
.tools-left { display: flex; gap: 4px; }
|
| 155 |
+
.tool-btn { width: 34px; height: 34px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--text-secondary); transition: 0.2s; position: relative; flex-shrink: 0; }
|
| 156 |
+
.tool-btn:hover { background: #e5e5e5; color: var(--text-primary); }
|
| 157 |
+
.tool-btn svg { width: 18px; height: 18px; }
|
| 158 |
+
.tool-btn.recording { color: #e60000; animation: pulse 1.5s infinite; }
|
| 159 |
+
|
| 160 |
+
.chat-input { flex: 1; padding: 7px 4px; font-size: 15px; resize: none; max-height: 120px; min-height: 24px; color: var(--text-primary); line-height: 1.5; }
|
| 161 |
+
.chat-input::placeholder { color: var(--text-tertiary); }
|
| 162 |
+
|
| 163 |
+
.btn-send { width: 34px; height: 34px; border-radius: 50%; background: var(--brand-color); color: #fff; display: flex; align-items: center; justify-content: center; transition: 0.2s; flex-shrink: 0; }
|
| 164 |
+
.btn-send svg { width: 16px; height: 16px; transform: translateX(-1px); }
|
| 165 |
+
.btn-send:hover { background: #000; transform: translateY(-1px); }
|
| 166 |
+
.btn-send:disabled { background: #ccc; cursor: not-allowed; transform: none; }
|
| 167 |
+
|
| 168 |
+
.hidden-input { display: none; }
|
| 169 |
+
.dropdown-menu { position: absolute; bottom: 100%; left: 0; margin-bottom: 10px; background: #fff; border: 1px solid var(--border-light); border-radius: var(--radius-md); padding: 6px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); display: none; flex-direction: column; gap: 2px; min-width: 180px; z-index: 100; }
|
| 170 |
+
.dropdown-menu.active { display: flex; animation: fadeIn 0.2s; }
|
| 171 |
+
.dropdown-item { padding: 10px 12px; border-radius: var(--radius-sm); font-size: 13px; font-weight: 500; display: flex; align-items: center; gap: 10px; cursor: pointer; transition: 0.2s; }
|
| 172 |
+
.dropdown-item:hover { background: var(--bg-hover); }
|
| 173 |
+
|
| 174 |
+
/* --- 6. SECURE AUTHENTICATION MODAL --- */
|
| 175 |
+
.auth-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.4); backdrop-filter: blur(5px); display: none; align-items: center; justify-content: center; z-index: 2000; }
|
| 176 |
+
.auth-modal { background: #fff; width: 90%; max-width: 400px; border-radius: var(--radius-md); padding: 32px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); position: relative; }
|
| 177 |
+
.auth-close { position: absolute; top: 16px; right: 16px; color: var(--text-secondary); width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: 0.2s; }
|
| 178 |
+
.auth-close:hover { background: var(--bg-hover); color: var(--text-primary); }
|
| 179 |
+
|
| 180 |
+
.auth-title { font-size: 22px; font-weight: 600; margin-bottom: 24px; text-align: center; letter-spacing: -0.5px; }
|
| 181 |
+
|
| 182 |
+
.auth-tabs { display: flex; background: var(--bg-input); border-radius: var(--radius-sm); padding: 4px; margin-bottom: 24px; }
|
| 183 |
+
.auth-tab { flex: 1; text-align: center; padding: 8px; font-size: 13px; font-weight: 500; cursor: pointer; border-radius: 6px; color: var(--text-secondary); transition: 0.2s; }
|
| 184 |
+
.auth-tab.active { background: #fff; color: var(--text-primary); box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
|
| 185 |
+
|
| 186 |
+
.auth-input-group { display: flex; flex-direction: column; gap: 12px; margin-bottom: 20px; }
|
| 187 |
+
.auth-input { width: 100%; padding: 12px 14px; border: 1px solid var(--border-light); border-radius: var(--radius-sm); font-size: 14px; transition: 0.2s; }
|
| 188 |
+
.auth-input:focus { border-color: var(--brand-color); outline: none; }
|
| 189 |
+
|
| 190 |
+
.auth-btn { width: 100%; padding: 12px; background: var(--brand-color); color: #fff; font-size: 14px; font-weight: 500; border-radius: var(--radius-sm); transition: 0.2s; }
|
| 191 |
+
.auth-btn:hover { background: #000; }
|
| 192 |
+
.auth-btn:disabled { background: #ccc; cursor: not-allowed; }
|
| 193 |
+
|
| 194 |
+
.auth-phase { display: none; }
|
| 195 |
+
.auth-phase.active { display: flex; flex-direction: column; }
|
| 196 |
+
.auth-message { font-size: 12px; color: #cc0000; text-align: center; margin-top: 10px; min-height: 18px; }
|
| 197 |
+
|
| 198 |
+
/* --- 7. WORKSPACE RIGHT PANEL --- */
|
| 199 |
+
.workspace-panel { width: 0; background: #fff; border-left: 1px solid var(--border-light); display: flex; flex-direction: column; transition: width 0.3s ease; overflow: hidden; box-shadow: -5px 0 20px rgba(0,0,0,0.02); z-index: 90; }
|
| 200 |
+
.workspace-panel.active { width: 50%; }
|
| 201 |
+
.ws-header { padding: 12px 20px; border-bottom: 1px solid var(--border-light); display: flex; justify-content: space-between; align-items: center; background: #fafafa; }
|
| 202 |
+
.ws-tabs { display: flex; gap: 4px; background: #ebebeb; padding: 4px; border-radius: var(--radius-sm); }
|
| 203 |
+
.ws-tab { padding: 6px 16px; font-size: 13px; font-weight: 500; border-radius: 6px; cursor: pointer; color: var(--text-secondary); transition: 0.2s; }
|
| 204 |
+
.ws-tab.active { background: #fff; color: var(--text-primary); box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
|
| 205 |
+
.ws-actions { display: flex; gap: 8px; }
|
| 206 |
+
.ws-btn { padding: 6px; border-radius: var(--radius-sm); color: var(--text-secondary); transition: 0.2s; }
|
| 207 |
+
.ws-btn:hover { background: #ebebeb; color: var(--text-primary); }
|
| 208 |
+
.ws-content { flex: 1; overflow: hidden; position: relative; }
|
| 209 |
+
.ws-code { height: 100%; overflow: auto; padding: 20px; background: #fbfbfb; }
|
| 210 |
+
.ws-code pre { margin: 0; font-family: var(--font-code); font-size: 14px; }
|
| 211 |
+
.ws-preview { display: none; height: 100%; width: 100%; flex-direction: column; background: #fff; }
|
| 212 |
+
.ws-console { background: #1a1a1a; color: #fff; font-family: var(--font-code); padding: 20px; font-size: 13px; flex: 1; overflow: auto; white-space: pre-wrap; }
|
| 213 |
+
.btn-launch-ws { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; background: var(--bg-main); border: 1px solid var(--border-light); border-radius: var(--radius-sm); font-size: 12px; font-weight: 500; cursor: pointer; margin-top: 10px; transition: 0.2s; }
|
| 214 |
+
.btn-launch-ws:hover { background: #f0f0f0; }
|
| 215 |
+
|
| 216 |
+
/* Utilities */
|
| 217 |
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
| 218 |
+
.typing-indicator { display: flex; gap: 4px; padding: 10px 0; }
|
| 219 |
+
.typing-dot { width: 6px; height: 6px; background: var(--text-tertiary); border-radius: 50%; animation: typeBounce 1.4s infinite ease-in-out both; }
|
| 220 |
+
.typing-dot:nth-child(1) { animation-delay: -0.32s; } .typing-dot:nth-child(2) { animation-delay: -0.16s; }
|
| 221 |
+
@keyframes typeBounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } }
|
| 222 |
+
svg { width: 100%; height: 100%; }
|
| 223 |
+
|
| 224 |
+
@media (max-width: 900px) {
|
| 225 |
+
.sidebar { position: absolute; left: 0; height: 100%; box-shadow: 5px 0 20px rgba(0,0,0,0.05); }
|
| 226 |
+
.sidebar.collapsed { left: -260px; margin-left: 0; }
|
| 227 |
+
.workspace-panel.active { width: 100%; position: absolute; top:0; left:0; height:100%; z-index: 1000; }
|
| 228 |
+
.input-dock { padding: 10px 15px 15px; }
|
| 229 |
+
}
|
| 230 |
+
</style>
|
| 231 |
+
</head>
|
| 232 |
+
<body>
|
| 233 |
+
|
| 234 |
+
<!-- SIDEBAR -->
|
| 235 |
<aside class="sidebar collapsed" id="sidebar">
|
| 236 |
<div class="sidebar-header">
|
| 237 |
+
<button class="btn-icon" onclick="UI.toggleSidebar()" title="Close Sidebar">
|
| 238 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
|
| 239 |
+
</button>
|
| 240 |
+
<span style="font-weight: 600; font-size: 14px; letter-spacing: 0.5px;">CODE VED</span>
|
| 241 |
+
</div>
|
| 242 |
+
<div style="padding: 0 16px;">
|
| 243 |
+
<button class="btn-new-chat" onclick="HistoryManager.clearHistory()">
|
| 244 |
+
<svg style="width:16px; height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
| 245 |
+
New Chat
|
| 246 |
</button>
|
| 247 |
</div>
|
| 248 |
|
| 249 |
+
<!-- RESTORED: Chat History Section -->
|
| 250 |
+
<div class="sidebar-history" id="chatHistory">
|
| 251 |
+
<!-- Dynamic History populated here -->
|
| 252 |
+
</div>
|
| 253 |
|
| 254 |
+
<!-- User Profile Area -->
|
| 255 |
+
<div class="sidebar-footer" id="profileArea">
|
|
|
|
| 256 |
<div class="user-avatar" id="uAv">G</div>
|
| 257 |
<div class="user-info-box">
|
| 258 |
<div class="user-name" id="uName">Guest Session</div>
|
| 259 |
+
<div class="user-sub" id="uSub" onclick="Auth.openModal()" style="cursor:pointer; color:var(--brand-accent);">Login Required</div>
|
| 260 |
</div>
|
| 261 |
+
<!-- RESTORED: Power Button (Logout) -->
|
| 262 |
+
<button class="btn-power" id="btnLogout" onclick="Auth.handleLogout()" title="Logout" style="display:none;">
|
| 263 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path><line x1="12" y1="2" x2="12" y2="12"></line></svg>
|
|
|
|
|
|
|
|
|
|
| 264 |
</button>
|
| 265 |
</div>
|
| 266 |
</aside>
|
| 267 |
|
| 268 |
+
<!-- MAIN AREA -->
|
| 269 |
<main class="main-area">
|
| 270 |
+
<header class="top-bar" id="topBar">
|
| 271 |
+
<button class="btn-icon" onclick="UI.toggleSidebar()" title="Open Menu" id="btnMenuOpen">
|
| 272 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
|
| 273 |
+
</button>
|
| 274 |
+
<div class="model-selector">CODE VED 202B</div>
|
| 275 |
+
<div style="width: 32px;"></div> <!-- Spacer -->
|
| 276 |
+
</header>
|
| 277 |
+
|
| 278 |
<div class="chat-container" id="chatContainer">
|
| 279 |
<div class="welcome-center" id="welcomeScreen">
|
| 280 |
+
<img src="https://i.ibb.co/MyYStcGP/TIRANGA-20260613-131924-0000.png" alt="CODE VED Logo">
|
| 281 |
+
<h1>What's your focus today?</h1>
|
| 282 |
+
<p id="welcomeGreeting">Hi Divy Patel, I am ready to assist you.</p>
|
| 283 |
</div>
|
|
|
|
| 284 |
</div>
|
| 285 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
<div class="input-dock" id="inputDock">
|
| 287 |
+
<div class="input-container">
|
|
|
|
|
|
|
| 288 |
<div class="file-preview-bar" id="filePreviewBar">
|
| 289 |
+
<img id="imgPreview" class="file-preview-icon" src="" style="display:none;">
|
| 290 |
+
<div id="docPreview" class="file-preview-icon" style="display:none;">
|
| 291 |
+
<svg style="width:20px;height:20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
</div>
|
| 293 |
+
<div class="file-preview-name" id="fileName">document.pdf</div>
|
| 294 |
+
<button class="btn-remove-file" onclick="FileSys.discard()"><svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>
|
| 295 |
</div>
|
| 296 |
|
|
|
|
|
|
|
| 297 |
<div class="input-row">
|
| 298 |
<div class="tools-left">
|
| 299 |
<div style="position: relative;">
|
| 300 |
+
<button class="tool-btn" onclick="UI.toggleAttachMenu()" title="Attach File">
|
| 301 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
|
| 302 |
</button>
|
|
|
|
|
|
|
| 303 |
<div class="dropdown-menu" id="attachMenu">
|
| 304 |
+
<div class="dropdown-item" onclick="document.getElementById('imgUpload').click()">
|
| 305 |
+
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg> Upload Image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
</div>
|
| 307 |
+
<div class="dropdown-item" onclick="document.getElementById('docUpload').click()">
|
| 308 |
+
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg> Upload Document
|
|
|
|
| 309 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
</div>
|
| 311 |
+
<input type="file" id="imgUpload" class="hidden-input" accept="image/*" onchange="FileSys.process(this, 'image')">
|
| 312 |
+
<input type="file" id="docUpload" class="hidden-input" accept=".pdf,.txt,.docx,.html,.js,.py,.css,.cpp,.c,.json,.md" onchange="FileSys.process(this, 'document')">
|
|
|
|
|
|
|
| 313 |
</div>
|
| 314 |
+
<button class="tool-btn" id="btnStt" onclick="Speech.toggle()" title="Voice Typing">
|
| 315 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
</button>
|
| 317 |
</div>
|
| 318 |
+
<textarea class="chat-input" id="mainInput" placeholder="Message CODE VED..." rows="1"></textarea>
|
| 319 |
+
<button class="btn-send" id="btnSend" onclick="Chat.handleSend()">
|
| 320 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
|
| 321 |
+
</button>
|
| 322 |
</div>
|
| 323 |
</div>
|
| 324 |
</div>
|
| 325 |
</main>
|
| 326 |
|
| 327 |
+
<!-- WORKSPACE PANEL -->
|
| 328 |
+
<aside class="workspace-panel" id="workspacePanel">
|
| 329 |
+
<div class="ws-header">
|
| 330 |
+
<div class="ws-tabs">
|
| 331 |
+
<div class="ws-tab active" id="tabCode" onclick="Workspace.switchTab('code')">Code</div>
|
| 332 |
+
<div class="ws-tab" id="tabPreview" onclick="Workspace.switchTab('preview')">Preview</div>
|
| 333 |
+
</div>
|
| 334 |
+
<div class="ws-actions">
|
| 335 |
+
<button class="ws-btn" onclick="Workspace.copy()" title="Copy Code">
|
| 336 |
+
<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
| 337 |
+
</button>
|
| 338 |
+
<button class="ws-btn" onclick="Workspace.close()" title="Close Panel">
|
| 339 |
+
<svg style="width:18px;height:18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
| 340 |
+
</button>
|
| 341 |
+
</div>
|
| 342 |
+
</div>
|
| 343 |
+
<div class="ws-content">
|
| 344 |
+
<div class="ws-code" id="wsCodeView"><pre><code id="wsCodeBlock" class="hljs"></code></pre></div>
|
| 345 |
+
<div class="ws-preview" id="wsPreviewView"></div>
|
| 346 |
+
</div>
|
| 347 |
+
</aside>
|
| 348 |
+
|
| 349 |
+
<!-- SECURE AUTHENTICATION MODAL -->
|
| 350 |
<div class="auth-overlay" id="authModal">
|
| 351 |
<div class="auth-modal">
|
| 352 |
+
<button class="auth-close" onclick="Auth.closeModal()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></button>
|
| 353 |
+
<div class="auth-title">Welcome to CODE VED</div>
|
| 354 |
+
|
|
|
|
| 355 |
<div class="auth-tabs">
|
| 356 |
+
<div class="auth-tab active" id="tabLogin" onclick="Auth.switchTab('login')">Login</div>
|
| 357 |
+
<div class="auth-tab" id="tabRegister" onclick="Auth.switchTab('register')">Register</div>
|
| 358 |
</div>
|
| 359 |
+
|
| 360 |
<!-- Login Flow -->
|
| 361 |
<div id="flowLogin">
|
| 362 |
<div class="auth-phase active" id="loginPhase1">
|
| 363 |
+
<div class="auth-input-group">
|
| 364 |
+
<input type="email" class="auth-input" id="logEmail" placeholder="Email Address">
|
| 365 |
+
</div>
|
| 366 |
+
<button class="auth-btn" id="btnLogOtp" onclick="Auth.process('login_send_otp')">Send Secure OTP</button>
|
| 367 |
</div>
|
| 368 |
<div class="auth-phase" id="loginPhase2">
|
| 369 |
+
<div class="auth-input-group">
|
| 370 |
+
<input type="number" class="auth-input" id="logOtp" placeholder="Enter OTP">
|
| 371 |
+
</div>
|
| 372 |
+
<button class="auth-btn" id="btnLogVerify" onclick="Auth.process('login_verify')">Verify & Login</button>
|
| 373 |
+
<button style="margin-top:10px; font-size:12px; color:var(--text-secondary); width:100%; text-align:center;" onclick="Auth.switchPhase('loginPhase2', 'loginPhase1')">Back to Email</button>
|
| 374 |
</div>
|
| 375 |
</div>
|
| 376 |
+
|
| 377 |
<!-- Register Flow -->
|
| 378 |
<div id="flowRegister" style="display:none;">
|
| 379 |
<div class="auth-phase active" id="regPhase1">
|
| 380 |
+
<div class="auth-input-group">
|
| 381 |
+
<input type="text" class="auth-input" id="regName" placeholder="Full Name">
|
| 382 |
+
<input type="email" class="auth-input" id="regEmail" placeholder="Email Address">
|
| 383 |
+
</div>
|
| 384 |
+
<button class="auth-btn" id="btnRegOtp" onclick="Auth.process('register_send_otp')">Create Account</button>
|
| 385 |
</div>
|
| 386 |
<div class="auth-phase" id="regPhase2">
|
| 387 |
+
<div class="auth-input-group">
|
| 388 |
+
<input type="number" class="auth-input" id="regOtp" placeholder="Enter OTP">
|
| 389 |
+
</div>
|
| 390 |
+
<button class="auth-btn" id="btnRegVerify" onclick="Auth.process('register_verify')">Verify & Register</button>
|
| 391 |
+
<button style="margin-top:10px; font-size:12px; color:var(--text-secondary); width:100%; text-align:center;" onclick="Auth.switchPhase('regPhase2', 'regPhase1')">Back to Details</button>
|
| 392 |
</div>
|
| 393 |
</div>
|
| 394 |
+
|
| 395 |
<div class="auth-message" id="authMsg"></div>
|
| 396 |
</div>
|
| 397 |
</div>
|
| 398 |
|
| 399 |
+
<script>
|
| 400 |
+
// --- 1. CONFIG & STATE ---
|
| 401 |
+
const Config = {
|
| 402 |
+
GAS_URL: "https://script.google.com/macros/s/AKfycbz5vrlB4GrfkfBVYdQ52IQweIXC1cNDwKuTUorxdAiOdSSIrH55mzJlaz9kq1Y94ot5/exec",
|
| 403 |
+
API_ENDPOINT: "/api/chat", // Pointing to your powerful Python backend
|
| 404 |
+
LOGO: "https://i.ibb.co/MyYStcGP/TIRANGA-20260613-131924-0000.png"
|
| 405 |
+
};
|
|
|
|
|
|
|
|
|
|
| 406 |
|
| 407 |
+
const State = {
|
| 408 |
+
user: localStorage.getItem('codeved_user') || null,
|
| 409 |
+
name: localStorage.getItem('codeved_name') || null,
|
| 410 |
+
guestCount: parseInt(localStorage.getItem('codeved_guest') || '0', 10),
|
| 411 |
+
attachment: null,
|
| 412 |
+
isProcessing: false,
|
| 413 |
+
history: [],
|
| 414 |
+
workspaces: [],
|
| 415 |
+
currentWs: null
|
| 416 |
+
};
|
| 417 |
|
| 418 |
+
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
| 419 |
+
|
| 420 |
+
// --- 2. UI UTILITIES ---
|
| 421 |
+
const UI = {
|
| 422 |
+
toggleSidebar: () => {
|
| 423 |
+
const sb = document.getElementById('sidebar');
|
| 424 |
+
const btnOpen = document.getElementById('btnMenuOpen');
|
| 425 |
+
sb.classList.toggle('collapsed');
|
| 426 |
+
btnOpen.style.display = sb.classList.contains('collapsed') ? 'flex' : 'none';
|
| 427 |
+
},
|
| 428 |
+
toggleAttachMenu: () => document.getElementById('attachMenu').classList.toggle('active'),
|
| 429 |
+
autoGrow: (el) => { el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 120) + 'px'; },
|
| 430 |
+
scrollToBottom: () => { const c = document.getElementById('chatContainer'); c.scrollTop = c.scrollHeight; },
|
| 431 |
+
escape: (s) => s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'),
|
| 432 |
+
showAuthMsg: (msg, isError=true) => {
|
| 433 |
+
const el = document.getElementById('authMsg');
|
| 434 |
+
el.innerText = msg; el.style.color = isError ? '#cc0000' : '#009900';
|
| 435 |
+
setTimeout(() => el.innerText='', 4000);
|
| 436 |
+
},
|
| 437 |
+
handleScrollEvents: () => {
|
| 438 |
+
const c = document.getElementById('chatContainer');
|
| 439 |
+
const topBar = document.getElementById('topBar');
|
| 440 |
+
const dock = document.getElementById('inputDock');
|
| 441 |
+
if (c.scrollTop > 10) topBar.classList.add('scrolled'); else topBar.classList.remove('scrolled');
|
| 442 |
+
if (c.scrollHeight - c.scrollTop - c.clientHeight > 10) dock.classList.add('scrolled'); else dock.classList.remove('scrolled');
|
| 443 |
+
}
|
| 444 |
+
};
|
| 445 |
+
|
| 446 |
+
document.getElementById('chatContainer').addEventListener('scroll', UI.handleScrollEvents);
|
| 447 |
+
document.addEventListener('click', (e) => {
|
| 448 |
+
if(!e.target.closest('.tools-left')) document.getElementById('attachMenu').classList.remove('active');
|
| 449 |
+
});
|
| 450 |
+
|
| 451 |
+
const inp = document.getElementById('mainInput');
|
| 452 |
+
inp.addEventListener('input', function() { UI.autoGrow(this); });
|
| 453 |
+
inp.addEventListener('keydown', function(e) {
|
| 454 |
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); Chat.handleSend(); }
|
| 455 |
+
});
|
| 456 |
+
|
| 457 |
+
// --- 3. AUTHENTICATION & HISTORY DB (RESTORED COMPLETELY) ---
|
| 458 |
+
const Auth = {
|
| 459 |
+
init: () => {
|
| 460 |
+
const uAv = document.getElementById('uAv');
|
| 461 |
+
const uName = document.getElementById('uName');
|
| 462 |
+
const uSub = document.getElementById('uSub');
|
| 463 |
+
const btnLogout = document.getElementById('btnLogout');
|
| 464 |
+
const greeting = document.getElementById('welcomeGreeting');
|
| 465 |
+
|
| 466 |
+
if (State.user) {
|
| 467 |
+
const dispName = State.name || State.user.split('@')[0];
|
| 468 |
+
uAv.innerText = dispName.charAt(0).toUpperCase();
|
| 469 |
+
uName.innerText = dispName;
|
| 470 |
+
uSub.innerText = "Authenticated";
|
| 471 |
+
uSub.style.color = "#009900";
|
| 472 |
+
uSub.onclick = null;
|
| 473 |
+
uSub.style.cursor = 'default';
|
| 474 |
+
btnLogout.style.display = 'flex';
|
| 475 |
+
greeting.innerText = `Hi ${dispName}, I am ready to assist you.`;
|
| 476 |
+
|
| 477 |
+
HistoryManager.sync(); // Load History on Boot
|
| 478 |
+
} else {
|
| 479 |
+
uAv.innerText = "G";
|
| 480 |
+
uName.innerText = "Guest Session";
|
| 481 |
+
uSub.innerText = `Queries: ${State.guestCount}/10`;
|
| 482 |
+
uSub.style.color = "var(--text-tertiary)";
|
| 483 |
+
btnLogout.style.display = 'none';
|
| 484 |
+
greeting.innerText = `Hi Divy Patel, I am ready to assist you.`;
|
| 485 |
+
}
|
| 486 |
},
|
| 487 |
+
handleLogout: () => {
|
| 488 |
+
if(confirm("Are you sure you want to log out?")) {
|
| 489 |
+
localStorage.removeItem('codeved_user');
|
| 490 |
+
localStorage.removeItem('codeved_name');
|
| 491 |
+
location.reload();
|
| 492 |
+
}
|
| 493 |
},
|
| 494 |
+
openModal: () => document.getElementById('authModal').style.display = 'flex',
|
| 495 |
+
closeModal: () => document.getElementById('authModal').style.display = 'none',
|
| 496 |
+
switchTab: (tab) => {
|
| 497 |
+
document.getElementById('tabLogin').classList.remove('active');
|
| 498 |
+
document.getElementById('tabRegister').classList.remove('active');
|
| 499 |
+
document.getElementById('flowLogin').style.display = 'none';
|
| 500 |
+
document.getElementById('flowRegister').style.display = 'none';
|
| 501 |
+
|
| 502 |
+
if(tab === 'login') { document.getElementById('tabLogin').classList.add('active'); document.getElementById('flowLogin').style.display = 'block'; }
|
| 503 |
+
else { document.getElementById('tabRegister').classList.add('active'); document.getElementById('flowRegister').style.display = 'block'; }
|
| 504 |
},
|
| 505 |
+
switchPhase: (from, to) => { document.getElementById(from).classList.remove('active'); document.getElementById(to).classList.add('active'); },
|
| 506 |
+
process: async (action) => {
|
| 507 |
+
let payload = { action: action };
|
| 508 |
+
let btnId = '';
|
| 509 |
+
|
| 510 |
+
if (action === 'register_send_otp') {
|
| 511 |
+
payload.name = document.getElementById('regName').value.trim();
|
| 512 |
+
payload.email = document.getElementById('regEmail').value.trim();
|
| 513 |
+
payload.phone = "0000000000"; payload.organization = "CODE VED System";
|
| 514 |
+
if (!payload.name || !payload.email) return UI.showAuthMsg("Please fill all details.");
|
| 515 |
+
btnId = 'btnRegOtp';
|
| 516 |
+
} else if (action === 'login_send_otp') {
|
| 517 |
+
payload.email = document.getElementById('logEmail').value.trim();
|
| 518 |
+
if (!payload.email) return UI.showAuthMsg("Email is required.");
|
| 519 |
+
btnId = 'btnLogOtp';
|
| 520 |
+
} else if (action === 'register_verify' || action === 'login_verify') {
|
| 521 |
+
payload.email = document.getElementById(action === 'register_verify' ? 'regEmail' : 'logEmail').value.trim();
|
| 522 |
+
payload.otp = document.getElementById(action === 'register_verify' ? 'regOtp' : 'logOtp').value.trim();
|
| 523 |
+
if (!payload.otp) return UI.showAuthMsg("OTP is required.");
|
| 524 |
+
btnId = action === 'register_verify' ? 'btnRegVerify' : 'btnLogVerify';
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
const btn = document.getElementById(btnId);
|
| 528 |
+
const originalText = btn.innerText;
|
| 529 |
+
btn.disabled = true; btn.innerText = "Processing...";
|
| 530 |
+
|
| 531 |
+
try {
|
| 532 |
+
const res = await fetch(Config.GAS_URL, { method: 'POST', mode: 'cors', headers: { 'Content-Type': 'text/plain;charset=utf-8' }, body: JSON.stringify(payload) });
|
| 533 |
+
const data = await res.json();
|
| 534 |
+
|
| 535 |
+
if (data.status === 'success') {
|
| 536 |
+
UI.showAuthMsg(data.message, false);
|
| 537 |
+
if (action === 'register_send_otp') Auth.switchPhase('regPhase1', 'regPhase2');
|
| 538 |
+
else if (action === 'login_send_otp') Auth.switchPhase('loginPhase1', 'loginPhase2');
|
| 539 |
+
else {
|
| 540 |
+
localStorage.setItem('codeved_user', payload.email);
|
| 541 |
+
if(payload.name) localStorage.setItem('codeved_name', payload.name);
|
| 542 |
+
location.reload();
|
| 543 |
+
}
|
| 544 |
+
} else { UI.showAuthMsg(data.message); }
|
| 545 |
+
} catch (e) { UI.showAuthMsg("Database connection error. Please try again."); }
|
| 546 |
+
|
| 547 |
+
btn.disabled = false; btn.innerText = originalText;
|
| 548 |
+
}
|
| 549 |
};
|
|
|
|
|
|
|
| 550 |
|
| 551 |
+
const HistoryManager = {
|
| 552 |
+
sync: async () => {
|
| 553 |
+
if(!State.user) return;
|
| 554 |
+
try {
|
| 555 |
+
const res = await fetch(Config.GAS_URL, { method: 'POST', body: JSON.stringify({action: "get_history", email: State.user})});
|
| 556 |
+
const data = await res.json();
|
| 557 |
+
if(data.status === 'success' && data.historyJSON !== "[]") {
|
| 558 |
+
State.history = JSON.parse(data.historyJSON);
|
| 559 |
+
document.getElementById('welcomeScreen').style.display = 'none';
|
| 560 |
+
State.history.forEach((msg, idx) => {
|
| 561 |
+
if(msg.role === 'user') {
|
| 562 |
+
Chat.renderUser(msg.content, '');
|
| 563 |
+
// Add to sidebar history list
|
| 564 |
+
const shortText = msg.content.substring(0, 30) + (msg.content.length > 30 ? '...' : '');
|
| 565 |
+
document.getElementById('chatHistory').innerHTML += `<div class="history-item" onclick="alert('History navigation active')">${UI.escape(shortText)}</div>`;
|
| 566 |
+
}
|
| 567 |
+
else {
|
| 568 |
+
const botNode = Chat.renderBot();
|
| 569 |
+
botNode.innerHTML = marked.parse(msg.content.replace(/<think>[\s\S]*?<\/think>/gi, ''));
|
| 570 |
+
}
|
| 571 |
+
});
|
| 572 |
+
setTimeout(UI.scrollToBottom, 200);
|
| 573 |
+
}
|
| 574 |
+
} catch(e) { console.warn("History sync failed."); }
|
| 575 |
+
},
|
| 576 |
+
save: () => {
|
| 577 |
+
if(!State.user) return;
|
| 578 |
+
fetch(Config.GAS_URL, {
|
| 579 |
+
method: 'POST', body: JSON.stringify({action: "save_history", email: State.user, historyJSON: JSON.stringify(State.history)})
|
| 580 |
+
}).catch(e=>{});
|
| 581 |
+
},
|
| 582 |
+
clearHistory: async () => {
|
| 583 |
+
if(State.user) {
|
| 584 |
+
try { await fetch(Config.GAS_URL, { method: 'POST', body: JSON.stringify({action: "delete_history", email: State.user}) }); } catch(e){}
|
| 585 |
+
}
|
| 586 |
+
location.reload();
|
| 587 |
+
}
|
| 588 |
+
};
|
| 589 |
+
|
| 590 |
+
// --- 4. FILE HANDLING (Secure) ---
|
| 591 |
+
const FileSys = {
|
| 592 |
+
process: async (input, type) => {
|
| 593 |
+
document.getElementById('attachMenu').classList.remove('active');
|
| 594 |
+
const file = input.files[0];
|
| 595 |
+
if (!file) return;
|
| 596 |
+
|
| 597 |
+
document.getElementById('filePreviewBar').classList.add('active');
|
| 598 |
+
document.getElementById('fileName').innerText = file.name;
|
| 599 |
+
const r = new FileReader();
|
| 600 |
+
|
| 601 |
+
if (type === 'image') {
|
| 602 |
+
document.getElementById('imgPreview').style.display = 'flex';
|
| 603 |
+
document.getElementById('docPreview').style.display = 'none';
|
| 604 |
+
r.onload = (e) => {
|
| 605 |
+
document.getElementById('imgPreview').src = e.target.result;
|
| 606 |
+
State.attachment = { type: 'image', data: e.target.result.split(',')[1], name: file.name };
|
| 607 |
+
};
|
| 608 |
+
r.readAsDataURL(file);
|
| 609 |
+
} else {
|
| 610 |
+
document.getElementById('imgPreview').style.display = 'none';
|
| 611 |
+
document.getElementById('docPreview').style.display = 'flex';
|
| 612 |
+
|
| 613 |
+
if (file.name.endsWith('.pdf')) {
|
| 614 |
+
r.onload = async function() {
|
| 615 |
+
const pdf = await pdfjsLib.getDocument(new Uint8Array(this.result)).promise;
|
| 616 |
+
let txt = "";
|
| 617 |
+
for(let i=1; i<=pdf.numPages; i++) {
|
| 618 |
+
const page = await pdf.getPage(i);
|
| 619 |
+
const content = await page.getTextContent();
|
| 620 |
+
txt += content.items.map(s => s.str).join(' ') + "\n";
|
| 621 |
+
}
|
| 622 |
+
State.attachment = { type: 'text', data: txt.trim() || "[No text found]", name: file.name };
|
| 623 |
+
};
|
| 624 |
+
r.readAsArrayBuffer(file);
|
| 625 |
+
} else if (file.name.endsWith('.docx')) {
|
| 626 |
+
r.onload = async function() {
|
| 627 |
+
const res = await mammoth.extractRawText({ arrayBuffer: this.result });
|
| 628 |
+
State.attachment = { type: 'text', data: res.value || "[No text found]", name: file.name };
|
| 629 |
+
};
|
| 630 |
+
r.readAsArrayBuffer(file);
|
| 631 |
+
} else {
|
| 632 |
+
r.onload = (e) => {
|
| 633 |
+
let data = e.target.result;
|
| 634 |
+
if(file.name.endsWith('.html')) {
|
| 635 |
+
data = data.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "\n[SCRIPT SANITIZED]\n");
|
| 636 |
+
}
|
| 637 |
+
State.attachment = { type: 'text', data: data, name: file.name };
|
| 638 |
+
};
|
| 639 |
+
r.readAsText(file);
|
| 640 |
+
}
|
| 641 |
+
}
|
| 642 |
+
input.value = '';
|
| 643 |
+
},
|
| 644 |
+
discard: () => {
|
| 645 |
+
State.attachment = null;
|
| 646 |
+
document.getElementById('filePreviewBar').classList.remove('active');
|
| 647 |
+
}
|
| 648 |
+
};
|
| 649 |
+
|
| 650 |
+
// --- 5. SPEECH TO TEXT ---
|
| 651 |
+
const Speech = {
|
| 652 |
+
rec: null, isRec: false,
|
| 653 |
+
init: () => {
|
| 654 |
+
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
|
| 655 |
+
if (!SR) return alert("Speech Recognition not supported in this browser.");
|
| 656 |
+
Speech.rec = new SR();
|
| 657 |
+
Speech.rec.continuous = false; Speech.rec.interimResults = true; Speech.rec.lang = 'en-US';
|
| 658 |
+
|
| 659 |
+
Speech.rec.onstart = () => { Speech.isRec = true; document.getElementById('btnStt').classList.add('recording'); };
|
| 660 |
+
Speech.rec.onresult = (e) => {
|
| 661 |
+
let trans = '';
|
| 662 |
+
for (let i = e.resultIndex; i < e.results.length; ++i) {
|
| 663 |
+
if (e.results[i].isFinal) trans += e.results[i][0].transcript;
|
| 664 |
+
}
|
| 665 |
+
if (trans) {
|
| 666 |
+
const input = document.getElementById('mainInput');
|
| 667 |
+
input.value += (input.value ? ' ' : '') + trans;
|
| 668 |
+
UI.autoGrow(input);
|
| 669 |
+
}
|
| 670 |
+
};
|
| 671 |
+
Speech.rec.onerror = () => Speech.stop();
|
| 672 |
+
Speech.rec.onend = () => Speech.stop();
|
| 673 |
+
},
|
| 674 |
+
toggle: () => {
|
| 675 |
+
if (!Speech.rec) Speech.init();
|
| 676 |
+
if (Speech.isRec) Speech.stop();
|
| 677 |
+
else { try { Speech.rec.start(); } catch(e) {} }
|
| 678 |
+
},
|
| 679 |
+
stop: () => {
|
| 680 |
+
if(Speech.rec) Speech.rec.stop();
|
| 681 |
+
Speech.isRec = false;
|
| 682 |
+
document.getElementById('btnStt').classList.remove('recording');
|
| 683 |
+
}
|
| 684 |
+
};
|
| 685 |
+
|
| 686 |
+
// --- 6. WORKSPACE PRO MAX ---
|
| 687 |
+
const Workspace = {
|
| 688 |
+
open: (id) => {
|
| 689 |
+
State.currentWs = id;
|
| 690 |
+
const d = State.workspaces[id];
|
| 691 |
+
const b = document.getElementById('wsCodeBlock');
|
| 692 |
+
b.className = `hljs language-${d.lang || 'plaintext'}`;
|
| 693 |
+
b.textContent = d.code;
|
| 694 |
+
hljs.highlightElement(b);
|
| 695 |
+
|
| 696 |
+
document.getElementById('workspacePanel').classList.add('active');
|
| 697 |
+
if(window.innerWidth <= 900) { document.getElementById('sidebar').classList.add('collapsed'); }
|
| 698 |
+
Workspace.switchTab('code');
|
| 699 |
+
},
|
| 700 |
+
close: () => document.getElementById('workspacePanel').classList.remove('active'),
|
| 701 |
+
switchTab: (tab) => {
|
| 702 |
+
document.getElementById('tabCode').classList.remove('active');
|
| 703 |
+
document.getElementById('tabPreview').classList.remove('active');
|
| 704 |
+
if(tab === 'code') {
|
| 705 |
+
document.getElementById('tabCode').classList.add('active');
|
| 706 |
+
document.getElementById('wsCodeView').style.display = 'block';
|
| 707 |
+
document.getElementById('wsPreviewView').style.display = 'none';
|
| 708 |
+
} else {
|
| 709 |
+
document.getElementById('tabPreview').classList.add('active');
|
| 710 |
+
document.getElementById('wsCodeView').style.display = 'none';
|
| 711 |
+
document.getElementById('wsPreviewView').style.display = 'flex';
|
| 712 |
+
Workspace.run();
|
| 713 |
+
}
|
| 714 |
+
},
|
| 715 |
+
copy: () => {
|
| 716 |
+
if(State.currentWs !== null) {
|
| 717 |
+
navigator.clipboard.writeText(State.workspaces[State.currentWs].code).then(() => alert('Code Copied!'));
|
| 718 |
+
}
|
| 719 |
+
},
|
| 720 |
+
run: async () => {
|
| 721 |
+
if (State.currentWs === null) return;
|
| 722 |
+
const d = State.workspaces[State.currentWs];
|
| 723 |
+
const lang = (d.lang || '').toLowerCase();
|
| 724 |
+
const view = document.getElementById('wsPreviewView');
|
| 725 |
+
|
| 726 |
+
if (['html', 'xml', 'javascript', 'js', 'css'].includes(lang)) {
|
| 727 |
+
let html = d.code;
|
| 728 |
+
if(lang === 'javascript' || lang === 'js') html = `<script>${d.code}<\/script>`;
|
| 729 |
+
if(lang === 'css') html = `<style>${d.code}</style><div style="padding:20px;font-family:sans-serif;">CSS Applied. Add HTML to test.</div>`;
|
| 730 |
+
|
| 731 |
+
const iframe = document.createElement('iframe');
|
| 732 |
+
iframe.style.width = '100%'; iframe.style.height = '100%'; iframe.style.border = 'none';
|
| 733 |
+
view.innerHTML = ''; view.appendChild(iframe);
|
| 734 |
+
iframe.contentWindow.document.open(); iframe.contentWindow.document.write(html); iframe.contentWindow.document.close();
|
| 735 |
+
return;
|
| 736 |
+
}
|
| 737 |
+
|
| 738 |
+
view.innerHTML = '<div class="ws-console" style="color:var(--brand-accent);">Compiling code...</div>';
|
| 739 |
+
const pMap = {'python':'python', 'py':'python', 'c':'c', 'cpp':'c++', 'java':'java'};
|
| 740 |
+
const rLang = pMap[lang];
|
| 741 |
+
|
| 742 |
+
if(!rLang) { view.innerHTML = `<div class="ws-console" style="color:#cc0000;">Live Preview not supported for ${lang}.</div>`; return; }
|
| 743 |
+
|
| 744 |
+
try {
|
| 745 |
+
const res = await fetch('https://emkc.org/api/v2/piston/execute', {
|
| 746 |
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
| 747 |
+
body: JSON.stringify({ language: rLang, version: "*", files: [{ content: d.code }] })
|
| 748 |
+
});
|
| 749 |
+
const data = await res.json();
|
| 750 |
+
let out = (data.compile?.output || "") + "\n" + (data.run?.output || "") + "\n" + (data.run?.stderr || "");
|
| 751 |
+
view.innerHTML = `<div class="ws-console">${UI.escape(out.trim() || "[Execution Finished]")}</div>`;
|
| 752 |
+
} catch(e) { view.innerHTML = `<div class="ws-console" style="color:#cc0000;">Engine Error.</div>`; }
|
| 753 |
+
}
|
| 754 |
+
};
|
| 755 |
+
|
| 756 |
+
const MDRenderer = new marked.Renderer();
|
| 757 |
+
MDRenderer.code = function(code, lang) {
|
| 758 |
+
const id = State.workspaces.length;
|
| 759 |
+
State.workspaces.push({code: code, lang: lang});
|
| 760 |
+
const highlighted = hljs.highlightAuto(code).value;
|
| 761 |
+
return `
|
| 762 |
+
<div style="margin: 16px 0;">
|
| 763 |
+
<div style="background:#f6f8fa; border:1px solid var(--border-light); border-radius:8px 8px 0 0; padding:8px 12px; display:flex; justify-content:space-between; align-items:center;">
|
| 764 |
+
<span style="font-size:12px; font-family:var(--font-code); color:var(--text-secondary); font-weight:600;">${lang || 'code'}</span>
|
| 765 |
+
<button class="btn-launch-ws" style="margin:0; padding:4px 10px; font-size:12px;" onclick="Workspace.open(${id})">
|
| 766 |
+
<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg> Open
|
| 767 |
+
</button>
|
| 768 |
+
</div>
|
| 769 |
+
<pre style="margin:0; border-top:none; border-radius:0 0 8px 8px;"><code class="hljs">${highlighted}</code></pre>
|
| 770 |
+
</div>`;
|
| 771 |
+
};
|
| 772 |
+
marked.use({ renderer: MDRenderer });
|
| 773 |
+
|
| 774 |
+
// --- 7. CHAT ENGINE (WITH SMART AUTO-SEARCH UI) ---
|
| 775 |
+
const Chat = {
|
| 776 |
+
renderUser: (txt, attachUI) => {
|
| 777 |
+
const c = document.getElementById('chatContainer');
|
| 778 |
+
const w = document.createElement('div'); w.className = 'message-wrapper user-message';
|
| 779 |
+
w.innerHTML = `${attachUI} ${UI.escape(txt)}`;
|
| 780 |
+
c.appendChild(w); UI.scrollToBottom();
|
| 781 |
+
},
|
| 782 |
+
renderBot: () => {
|
| 783 |
+
const c = document.getElementById('chatContainer');
|
| 784 |
+
const w = document.createElement('div'); w.className = 'message-wrapper bot-message';
|
| 785 |
+
w.innerHTML = `
|
| 786 |
+
<div class="bot-avatar"><img src="${Config.LOGO}"></div>
|
| 787 |
+
<div class="bot-content"><div class="typing-indicator"><div class="typing-dot"></div><div class="typing-dot"></div></div></div>`;
|
| 788 |
+
c.appendChild(w); UI.scrollToBottom();
|
| 789 |
+
return w.querySelector('.bot-content');
|
| 790 |
+
},
|
| 791 |
+
|
| 792 |
+
getToolHTML: (isActive) => {
|
| 793 |
+
return `
|
| 794 |
+
<div class="tool-indicator ${isActive ? 'active' : ''}">
|
| 795 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
| 796 |
+
<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
| 797 |
+
</svg>
|
| 798 |
+
${isActive ? 'Searching on Google Search...' : 'Searched on Google Search'}
|
| 799 |
+
</div><br>`;
|
| 800 |
+
},
|
| 801 |
+
|
| 802 |
+
handleSend: async () => {
|
| 803 |
+
if(State.isProcessing) return;
|
| 804 |
+
const input = document.getElementById('mainInput');
|
| 805 |
+
const text = input.value.trim();
|
| 806 |
+
if(!text && !State.attachment) return;
|
| 807 |
+
|
| 808 |
+
// Guest Limit Validation
|
| 809 |
+
if(!State.user) {
|
| 810 |
+
if(State.guestCount >= 10) { Auth.openModal(); UI.showAuthMsg("Guest limit reached. Secure Login required.", true); return; }
|
| 811 |
+
State.guestCount++; localStorage.setItem('codeved_guest', State.guestCount);
|
| 812 |
+
document.getElementById('uSub').innerText = `Queries: ${State.guestCount}/10`;
|
| 813 |
+
}
|
| 814 |
+
|
| 815 |
+
State.isProcessing = true; document.getElementById('btnSend').disabled = true;
|
| 816 |
+
input.value = ''; input.style.height = 'auto'; document.getElementById('welcomeScreen').style.display = 'none';
|
| 817 |
+
|
| 818 |
+
let payloadStr = text;
|
| 819 |
+
let attachUI = '';
|
| 820 |
+
let mediaArray = [];
|
| 821 |
+
|
| 822 |
+
// 💡 SMART AUTO-SEARCH DETECTION
|
| 823 |
+
const queryLower = text.toLowerCase();
|
| 824 |
+
const searchTriggers = ["search", "latest", "news", "update", "current", "2026", "today", "price", "who won", "weather", "aaj"];
|
| 825 |
+
let isSearching = searchTriggers.some(kw => queryLower.includes(kw));
|
| 826 |
+
|
| 827 |
+
if(State.attachment) {
|
| 828 |
+
if(State.attachment.type === 'text') {
|
| 829 |
+
payloadStr = `[File attached: ${State.attachment.name}]\n\n---DATA---\n${State.attachment.data}\n---END DATA---\n\nUser: ${text}`;
|
| 830 |
+
attachUI = `<div class="chat-attachment"><svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path></svg> ${State.attachment.name}</div><br>`;
|
| 831 |
+
} else {
|
| 832 |
+
mediaArray.push(State.attachment);
|
| 833 |
+
attachUI = `<img src="data:image/jpeg;base64,${State.attachment.data}" class="chat-img-preview"><br>`;
|
| 834 |
+
}
|
| 835 |
+
}
|
| 836 |
+
|
| 837 |
+
Chat.renderUser(text || '[Attached Media]', attachUI);
|
| 838 |
+
|
| 839 |
+
// Update History UI
|
| 840 |
+
const shortText = (text || 'Media Attached').substring(0, 30) + "...";
|
| 841 |
+
document.getElementById('chatHistory').innerHTML += `<div class="history-item">${UI.escape(shortText)}</div>`;
|
| 842 |
+
|
| 843 |
+
State.history.push({ role: 'user', content: payloadStr });
|
| 844 |
+
FileSys.discard();
|
| 845 |
+
|
| 846 |
+
const botContent = Chat.renderBot();
|
| 847 |
+
|
| 848 |
+
// Show glowing tool UI immediately if it's a search
|
| 849 |
+
if (isSearching) {
|
| 850 |
+
botContent.innerHTML = Chat.getToolHTML(true) + `<div class="typing-indicator"><div class="typing-dot"></div><div class="typing-dot"></div></div>`;
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
try {
|
| 854 |
+
// Call the Powerful Backend
|
| 855 |
+
const res = await fetch(Config.API_ENDPOINT, {
|
| 856 |
+
method: 'POST', headers: {'Content-Type': 'application/json'},
|
| 857 |
+
body: JSON.stringify({
|
| 858 |
+
message: payloadStr,
|
| 859 |
+
attachments: mediaArray,
|
| 860 |
+
is_search: isSearching, // SIGNAL BACKEND TO SEARCH
|
| 861 |
+
history: State.history
|
| 862 |
+
})
|
| 863 |
+
});
|
| 864 |
+
|
| 865 |
+
if(!res.ok) throw new Error("API Offline");
|
| 866 |
+
|
| 867 |
+
const reader = res.body.getReader();
|
| 868 |
+
const decoder = new TextDecoder();
|
| 869 |
+
let fullText = "";
|
| 870 |
+
|
| 871 |
+
while(true) {
|
| 872 |
+
const {done, value} = await reader.read();
|
| 873 |
+
if(done) break;
|
| 874 |
+
|
| 875 |
+
const chunk = decoder.decode(value, {stream: true});
|
| 876 |
+
const lines = chunk.split('\n');
|
| 877 |
+
for(let line of lines) {
|
| 878 |
+
if(line.startsWith('data: ')) {
|
| 879 |
+
const dataStr = line.substring(6).trim();
|
| 880 |
+
if(dataStr === '[DONE]') continue;
|
| 881 |
+
try {
|
| 882 |
+
const json = JSON.parse(dataStr);
|
| 883 |
+
if(json.choices && json.choices[0].delta.content) fullText += json.choices[0].delta.content;
|
| 884 |
+
else if (json.response) fullText += json.response;
|
| 885 |
+
|
| 886 |
+
// Parse Markdown, Stop Tool animation if it was active
|
| 887 |
+
let finalHtml = marked.parse(fullText.replace(/<think>[\s\S]*?<\/think>/gi, ''));
|
| 888 |
+
if(isSearching) {
|
| 889 |
+
botContent.innerHTML = Chat.getToolHTML(false) + finalHtml;
|
| 890 |
+
} else {
|
| 891 |
+
botContent.innerHTML = finalHtml;
|
| 892 |
+
}
|
| 893 |
+
|
| 894 |
+
UI.scrollToBottom();
|
| 895 |
+
} catch(e){}
|
| 896 |
+
}
|
| 897 |
+
}
|
| 898 |
+
}
|
| 899 |
+
|
| 900 |
+
State.history.push({ role: 'assistant', content: fullText });
|
| 901 |
+
HistoryManager.save();
|
| 902 |
|
| 903 |
+
} catch(e) {
|
| 904 |
+
botContent.innerHTML = `<span style="color:#cc0000; font-weight:500;">Connection Offline. Backend is unreachable.</span>`;
|
| 905 |
+
} finally {
|
| 906 |
+
State.isProcessing = false;
|
| 907 |
+
document.getElementById('btnSend').disabled = false;
|
| 908 |
+
UI.scrollToBottom();
|
| 909 |
+
}
|
| 910 |
+
}
|
| 911 |
+
};
|
| 912 |
+
|
| 913 |
+
window.onload = () => {
|
| 914 |
+
Auth.init();
|
| 915 |
+
if(window.innerWidth > 900) { document.getElementById('btnMenuOpen').style.display = 'none'; document.getElementById('sidebar').classList.remove('collapsed'); }
|
| 916 |
+
};
|
| 917 |
+
|
| 918 |
+
</script>
|
| 919 |
</body>
|
| 920 |
+
</html>
|
js/markdownRenderer.js
DELETED
|
@@ -1,278 +0,0 @@
|
|
| 1 |
-
// ================================================================================
|
| 2 |
-
// CODE VED - ADVANCED MARKDOWN & MATH RENDERER (markdownRenderer.js)
|
| 3 |
-
// Engineered by Divy Patel | Modular Architecture
|
| 4 |
-
//
|
| 5 |
-
// This module is the "Visual Engine" of the AI. It handles:
|
| 6 |
-
// 1. Markdown parsing (via marked.js)
|
| 7 |
-
// 2. Syntax Highlighting (via highlight.js)
|
| 8 |
-
// 3. Complex Math Formulas (via MathJax) with pre/post-processing to prevent breaking
|
| 9 |
-
// 4. Mermaid Diagrams rendering
|
| 10 |
-
// 5. AI "Thinking Process" collapsible UI
|
| 11 |
-
// 6. Code block copy buttons
|
| 12 |
-
// ================================================================================
|
| 13 |
-
|
| 14 |
-
(function() {
|
| 15 |
-
'use strict';
|
| 16 |
-
|
| 17 |
-
// ----------------------------------------------------------------------------
|
| 18 |
-
// 1. INITIALIZATION & CONFIGURATION
|
| 19 |
-
// ----------------------------------------------------------------------------
|
| 20 |
-
|
| 21 |
-
// Initialize Mermaid (Diagrams)
|
| 22 |
-
function initMermaid() {
|
| 23 |
-
if (typeof mermaid !== 'undefined') {
|
| 24 |
-
mermaid.initialize({
|
| 25 |
-
startOnLoad: false,
|
| 26 |
-
theme: 'default',
|
| 27 |
-
securityLevel: 'loose',
|
| 28 |
-
fontFamily: 'Inter, sans-serif'
|
| 29 |
-
});
|
| 30 |
-
}
|
| 31 |
-
}
|
| 32 |
-
|
| 33 |
-
// Configure Marked.js (Markdown Parser)
|
| 34 |
-
function initMarked() {
|
| 35 |
-
if (typeof marked === 'undefined') return;
|
| 36 |
-
|
| 37 |
-
const renderer = new marked.Renderer();
|
| 38 |
-
|
| 39 |
-
// Custom Code Block Renderer (Handles Syntax Highlighting & Mermaid)
|
| 40 |
-
renderer.code = function(code, language, isEscaped) {
|
| 41 |
-
// Handle Mermaid Diagrams
|
| 42 |
-
if (language === 'mermaid') {
|
| 43 |
-
return `<pre class="mermaid-diagram"><code class="mermaid">${code}</code></pre>`;
|
| 44 |
-
}
|
| 45 |
-
|
| 46 |
-
// Handle Syntax Highlighting
|
| 47 |
-
let highlightedCode = code;
|
| 48 |
-
const langClass = language ? `language-${language}` : '';
|
| 49 |
-
|
| 50 |
-
if (language && typeof hljs !== 'undefined' && hljs.getLanguage(language)) {
|
| 51 |
-
try {
|
| 52 |
-
highlightedCode = hljs.highlight(code, { language: language, ignoreIllegals: true }).value;
|
| 53 |
-
} catch (e) {
|
| 54 |
-
highlightedCode = hljs.highlightAuto(code).value;
|
| 55 |
-
}
|
| 56 |
-
} else if (typeof hljs !== 'undefined') {
|
| 57 |
-
highlightedCode = hljs.highlightAuto(code).value;
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
-
// SVG Icons for Copy Button
|
| 61 |
-
const copyIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
|
| 62 |
-
|
| 63 |
-
// Return formatted code block with a copy button
|
| 64 |
-
return `<pre class="code-block-wrapper"><button class="code-copy-btn" title="Copy code">${copyIcon}<span>Copy</span></button><code class="hljs ${langClass}">${highlightedCode}</code></pre>`;
|
| 65 |
-
};
|
| 66 |
-
|
| 67 |
-
// Apply the custom renderer to marked
|
| 68 |
-
marked.use({
|
| 69 |
-
renderer: renderer,
|
| 70 |
-
gfm: true,
|
| 71 |
-
breaks: true,
|
| 72 |
-
pedantic: false,
|
| 73 |
-
mangle: false,
|
| 74 |
-
headerIds: false
|
| 75 |
-
});
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
// ----------------------------------------------------------------------------
|
| 79 |
-
// 2. MATHJAX PRE-PROCESSING & POST-PROCESSING (The Secret Sauce)
|
| 80 |
-
// ----------------------------------------------------------------------------
|
| 81 |
-
/*
|
| 82 |
-
* WHY THIS IS NEEDED:
|
| 83 |
-
* Markdown parsers (like marked) often break LaTeX math syntax. For example,
|
| 84 |
-
* underscores (_) in math formulas get converted to italics (<em>), and
|
| 85 |
-
* asterisks (*) get converted to bold (<strong>).
|
| 86 |
-
* To prevent this, we temporarily hide math blocks using unique placeholders,
|
| 87 |
-
* parse the markdown, and then put the math blocks back!
|
| 88 |
-
*/
|
| 89 |
-
|
| 90 |
-
function preprocessMath(text) {
|
| 91 |
-
const mathBlocks = {};
|
| 92 |
-
let counter = 0;
|
| 93 |
-
|
| 94 |
-
function createPlaceholder(match) {
|
| 95 |
-
const id = `@@MATHBLOCK_${counter++}@@`;
|
| 96 |
-
mathBlocks[id] = match;
|
| 97 |
-
return id;
|
| 98 |
-
}
|
| 99 |
-
|
| 100 |
-
// 1. Display Math: $$ ... $$
|
| 101 |
-
text = text.replace(/\$\$([\s\S]+?)\$\$/g, createPlaceholder);
|
| 102 |
-
|
| 103 |
-
// 2. Display Math: \[ ... \]
|
| 104 |
-
text = text.replace(/\\\[([\s\S]+?)\\\]/g, createPlaceholder);
|
| 105 |
-
|
| 106 |
-
// 3. Inline Math: $ ... $ (Ensuring it doesn't match currency or empty spaces)
|
| 107 |
-
text = text.replace(/(?<!\$)\$(?!\$)((?:[^$\\]|\\.)+?)\$(?!\$)/g, createPlaceholder);
|
| 108 |
-
|
| 109 |
-
// 4. Inline Math: \( ... \)
|
| 110 |
-
text = text.replace(/\\\(([\s\S]+?)\\\)/g, createPlaceholder);
|
| 111 |
-
|
| 112 |
-
return { text, mathBlocks };
|
| 113 |
-
}
|
| 114 |
-
|
| 115 |
-
function postprocessMath(html, mathBlocks) {
|
| 116 |
-
// Replace all placeholders back with the original LaTeX strings
|
| 117 |
-
for (const [id, mathStr] of Object.entries(mathBlocks)) {
|
| 118 |
-
html = html.split(id).join(mathStr);
|
| 119 |
-
}
|
| 120 |
-
return html;
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
// ----------------------------------------------------------------------------
|
| 124 |
-
// 3. THINKING PROCESS EXTRACTION (AI Reasoning UI)
|
| 125 |
-
// ----------------------------------------------------------------------------
|
| 126 |
-
|
| 127 |
-
function processThinkingBlocks(text) {
|
| 128 |
-
// Normalize different AI thinking tags (Qwen, etc.)
|
| 129 |
-
let normalizedText = text
|
| 130 |
-
.replace(/<\|channel\|>thought\s*<\|channel\|>/gi, "<think>\n")
|
| 131 |
-
.replace(/<\|channel\|>answer\s*<\|channel\|>/gi, "\n</think>\n")
|
| 132 |
-
.replace(/<\|im_start\|>thought/gi, "<think>\n")
|
| 133 |
-
.replace(/<\|im_end\|>/gi, "\n</think>\n");
|
| 134 |
-
|
| 135 |
-
const thinkRegex = /<think>([\s\S]*?)(?:<\/think>|$)/i;
|
| 136 |
-
const thinkMatch = normalizedText.match(thinkRegex);
|
| 137 |
-
|
| 138 |
-
let thinkHtml = '';
|
| 139 |
-
let mainText = normalizedText;
|
| 140 |
-
|
| 141 |
-
if (thinkMatch) {
|
| 142 |
-
const thinkContent = thinkMatch[1].trim();
|
| 143 |
-
// Parse the thinking content as markdown too, for better readability
|
| 144 |
-
const parsedThinkContent = typeof marked !== 'undefined' ? marked.parse(thinkContent) : thinkContent;
|
| 145 |
-
|
| 146 |
-
thinkHtml = `
|
| 147 |
-
<details class="qwen-think-box" open>
|
| 148 |
-
<summary>
|
| 149 |
-
<svg class="arrow" style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 150 |
-
<polyline points="9 18 15 12 9 6"></polyline>
|
| 151 |
-
</svg>
|
| 152 |
-
Thinking Process
|
| 153 |
-
</summary>
|
| 154 |
-
<div class="qwen-think-content">${parsedThinkContent}</div>
|
| 155 |
-
</details>`;
|
| 156 |
-
|
| 157 |
-
// Remove the think block from the main text
|
| 158 |
-
mainText = normalizedText.replace(thinkRegex, '').trim();
|
| 159 |
-
}
|
| 160 |
-
|
| 161 |
-
return { thinkHtml, mainText };
|
| 162 |
-
}
|
| 163 |
-
|
| 164 |
-
// ----------------------------------------------------------------------------
|
| 165 |
-
// 4. POST-RENDERING TRIGGERS (MathJax & Mermaid)
|
| 166 |
-
// ----------------------------------------------------------------------------
|
| 167 |
-
|
| 168 |
-
async function triggerMathJax(container) {
|
| 169 |
-
if (window.MathJax && MathJax.typesetPromise) {
|
| 170 |
-
try {
|
| 171 |
-
// Clear previous math rendering in this container to prevent errors
|
| 172 |
-
MathJax.typesetClear([container]);
|
| 173 |
-
// Render new math
|
| 174 |
-
await MathJax.typesetPromise([container]);
|
| 175 |
-
} catch (err) {
|
| 176 |
-
console.warn('MathJax Rendering Warning:', err);
|
| 177 |
-
}
|
| 178 |
-
}
|
| 179 |
-
}
|
| 180 |
-
|
| 181 |
-
async function triggerMermaid(container) {
|
| 182 |
-
if (typeof mermaid === 'undefined') return;
|
| 183 |
-
|
| 184 |
-
const diagrams = container.querySelectorAll('.mermaid');
|
| 185 |
-
if (diagrams.length === 0) return;
|
| 186 |
-
|
| 187 |
-
try {
|
| 188 |
-
// mermaid.run is the modern way to render specific nodes
|
| 189 |
-
await mermaid.run({ nodes: diagrams });
|
| 190 |
-
} catch (err) {
|
| 191 |
-
console.warn('Mermaid Rendering Warning:', err);
|
| 192 |
-
// If mermaid fails, it might leave broken SVGs. We clean them up.
|
| 193 |
-
diagrams.forEach(d => {
|
| 194 |
-
if (!d.querySelector('svg')) {
|
| 195 |
-
d.innerHTML = `<span style="color:var(--brand-danger);font-size:12px;">⚠️ Invalid Mermaid Syntax</span>`;
|
| 196 |
-
}
|
| 197 |
-
});
|
| 198 |
-
}
|
| 199 |
-
}
|
| 200 |
-
|
| 201 |
-
// ----------------------------------------------------------------------------
|
| 202 |
-
// 5. MAIN RENDERING ENGINE (Exposed to script.js)
|
| 203 |
-
// ----------------------------------------------------------------------------
|
| 204 |
-
|
| 205 |
-
/**
|
| 206 |
-
* The core function called by script.js to render AI responses.
|
| 207 |
-
* @param {string} fullText - The raw text from the AI.
|
| 208 |
-
* @param {boolean} isProcessing - True if the AI is still streaming.
|
| 209 |
-
* @param {HTMLElement} container - The DOM element to inject the HTML into.
|
| 210 |
-
*/
|
| 211 |
-
async function parseAndRender(fullText, isProcessing, container) {
|
| 212 |
-
if (!container) return;
|
| 213 |
-
|
| 214 |
-
// Step 1: Extract and format "Thinking" blocks
|
| 215 |
-
const { thinkHtml, mainText } = processThinkingBlocks(fullText);
|
| 216 |
-
|
| 217 |
-
let finalHtml = thinkHtml;
|
| 218 |
-
|
| 219 |
-
// Step 2: Process the main text
|
| 220 |
-
if (mainText) {
|
| 221 |
-
// 2a. Hide Math formulas so Markdown parser doesn't break them
|
| 222 |
-
const { text: safeText, mathBlocks } = preprocessMath(mainText);
|
| 223 |
-
|
| 224 |
-
// 2b. Parse Markdown to HTML
|
| 225 |
-
let parsedHtml = '';
|
| 226 |
-
if (typeof marked !== 'undefined') {
|
| 227 |
-
parsedHtml = marked.parse(safeText);
|
| 228 |
-
} else {
|
| 229 |
-
parsedHtml = safeText.replace(/\n/g, '<br>'); // Fallback
|
| 230 |
-
}
|
| 231 |
-
|
| 232 |
-
// 2c. Put Math formulas back
|
| 233 |
-
parsedHtml = postprocessMath(parsedHtml, mathBlocks);
|
| 234 |
-
|
| 235 |
-
// 2d. Wrap in a class for MathJax targeting
|
| 236 |
-
finalHtml += `<div class="tex2jax_process">${parsedHtml}</div>`;
|
| 237 |
-
}
|
| 238 |
-
|
| 239 |
-
// Step 3: Add blinking cursor if AI is still generating
|
| 240 |
-
if (isProcessing) {
|
| 241 |
-
finalHtml += `<span class="blinking-cursor"></span>`;
|
| 242 |
-
}
|
| 243 |
-
|
| 244 |
-
// Step 4: Inject into DOM
|
| 245 |
-
container.innerHTML = finalHtml;
|
| 246 |
-
|
| 247 |
-
// Step 5: Trigger external renderers (MathJax & Mermaid)
|
| 248 |
-
// We only trigger them fully when processing is done to save performance,
|
| 249 |
-
// but we do a lightweight pass during streaming.
|
| 250 |
-
if (!isProcessing) {
|
| 251 |
-
await triggerMathJax(container);
|
| 252 |
-
await triggerMermaid(container);
|
| 253 |
-
} else {
|
| 254 |
-
// Lightweight MathJax trigger during streaming (optional, prevents lag)
|
| 255 |
-
// triggerMathJax(container);
|
| 256 |
-
}
|
| 257 |
-
}
|
| 258 |
-
|
| 259 |
-
// ----------------------------------------------------------------------------
|
| 260 |
-
// 6. INITIALIZATION & EXPORT
|
| 261 |
-
// ----------------------------------------------------------------------------
|
| 262 |
-
|
| 263 |
-
function init() {
|
| 264 |
-
initMermaid();
|
| 265 |
-
initMarked();
|
| 266 |
-
console.log('[MarkdownRenderer] Engine initialized successfully.');
|
| 267 |
-
}
|
| 268 |
-
|
| 269 |
-
// Expose the API to the global window object so script.js can use it
|
| 270 |
-
window.MarkdownRenderer = {
|
| 271 |
-
parseAndRender: parseAndRender,
|
| 272 |
-
init: init
|
| 273 |
-
};
|
| 274 |
-
|
| 275 |
-
// Auto-initialize when this script loads
|
| 276 |
-
init();
|
| 277 |
-
|
| 278 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
js/script.js
DELETED
|
@@ -1,1221 +0,0 @@
|
|
| 1 |
-
// ================================================================================
|
| 2 |
-
// CODE VED - MAIN APPLICATION LOGIC (script.js)
|
| 3 |
-
// Engineered by Divy Patel | Modular Architecture
|
| 4 |
-
// ================================================================================
|
| 5 |
-
|
| 6 |
-
(function() {
|
| 7 |
-
'use strict';
|
| 8 |
-
|
| 9 |
-
// ----------------------------------------------------------------------------
|
| 10 |
-
// 1. CONFIGURATION & GLOBAL STATE
|
| 11 |
-
// ----------------------------------------------------------------------------
|
| 12 |
-
const Config = {
|
| 13 |
-
GAS_URL: "https://script.google.com/macros/s/AKfycbzNO3inVc33ImhfLyde-JjjK9ZlPckLBksqCnCzelfhcklX6mp8KW8vfPTW4oWJTCcN/exec",
|
| 14 |
-
API_ENDPOINT: "/api/chat",
|
| 15 |
-
LOGO: "logo.png"
|
| 16 |
-
};
|
| 17 |
-
|
| 18 |
-
const State = {
|
| 19 |
-
user: localStorage.getItem('codeved_user') || null,
|
| 20 |
-
name: localStorage.getItem('codeved_name') || null,
|
| 21 |
-
guestCount: (() => {
|
| 22 |
-
const v = parseInt(localStorage.getItem('codeved_guest') || '0', 10);
|
| 23 |
-
return isNaN(v) ? 0 : v;
|
| 24 |
-
})(),
|
| 25 |
-
attachment: null,
|
| 26 |
-
isProcessing: false,
|
| 27 |
-
history: [],
|
| 28 |
-
currentThreadId: null,
|
| 29 |
-
currentTitle: null,
|
| 30 |
-
abortController: null,
|
| 31 |
-
location: null,
|
| 32 |
-
weatherContext: null,
|
| 33 |
-
thinkingMode: false,
|
| 34 |
-
thinkingEffort: "medium",
|
| 35 |
-
searchEnabled: false,
|
| 36 |
-
lastUserMessage: null
|
| 37 |
-
};
|
| 38 |
-
|
| 39 |
-
if (typeof pdfjsLib !== 'undefined') {
|
| 40 |
-
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
| 41 |
-
}
|
| 42 |
-
|
| 43 |
-
// ----------------------------------------------------------------------------
|
| 44 |
-
// 2. UTILITIES & CUSTOM CONFIRM
|
| 45 |
-
// ----------------------------------------------------------------------------
|
| 46 |
-
function customConfirm(message) {
|
| 47 |
-
return new Promise((resolve) => {
|
| 48 |
-
const overlay = document.getElementById('customConfirmOverlay');
|
| 49 |
-
const msgEl = document.getElementById('customConfirmMsg');
|
| 50 |
-
const cancelBtn = document.getElementById('customConfirmCancel');
|
| 51 |
-
const okBtn = document.getElementById('customConfirmOk');
|
| 52 |
-
|
| 53 |
-
msgEl.textContent = message;
|
| 54 |
-
overlay.style.display = 'flex';
|
| 55 |
-
|
| 56 |
-
function cleanup() {
|
| 57 |
-
overlay.style.display = 'none';
|
| 58 |
-
cancelBtn.removeEventListener('click', onCancel);
|
| 59 |
-
okBtn.removeEventListener('click', onOk);
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
function onCancel() { cleanup(); resolve(false); }
|
| 63 |
-
function onOk() { cleanup(); resolve(true); }
|
| 64 |
-
|
| 65 |
-
cancelBtn.addEventListener('click', onCancel);
|
| 66 |
-
okBtn.addEventListener('click', onOk);
|
| 67 |
-
});
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
// ----------------------------------------------------------------------------
|
| 71 |
-
// 3. UI MANAGER
|
| 72 |
-
// ----------------------------------------------------------------------------
|
| 73 |
-
const UI = {
|
| 74 |
-
autoScroll: true,
|
| 75 |
-
|
| 76 |
-
toggleSidebar() {
|
| 77 |
-
const sb = document.getElementById('sidebar');
|
| 78 |
-
const overlay = document.getElementById('mobileOverlay');
|
| 79 |
-
sb.classList.toggle('collapsed');
|
| 80 |
-
overlay.classList.toggle('active', !sb.classList.contains('collapsed') && window.innerWidth <= 900);
|
| 81 |
-
},
|
| 82 |
-
|
| 83 |
-
toggleAttachMenu() {
|
| 84 |
-
document.getElementById('attachMenu').classList.toggle('active');
|
| 85 |
-
document.getElementById('thinkMenu').classList.remove('active');
|
| 86 |
-
document.getElementById('voiceMenu').classList.remove('active');
|
| 87 |
-
},
|
| 88 |
-
|
| 89 |
-
autoGrow(el) {
|
| 90 |
-
el.style.height = 'auto';
|
| 91 |
-
el.style.height = Math.min(el.scrollHeight, 180) + 'px';
|
| 92 |
-
},
|
| 93 |
-
|
| 94 |
-
scrollToBottom(force = false) {
|
| 95 |
-
const c = document.getElementById('chatContainer');
|
| 96 |
-
if (force || this.autoScroll) {
|
| 97 |
-
c.scrollTop = c.scrollHeight;
|
| 98 |
-
}
|
| 99 |
-
},
|
| 100 |
-
|
| 101 |
-
escape(s) {
|
| 102 |
-
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
| 103 |
-
},
|
| 104 |
-
|
| 105 |
-
showAuthMsg(msg, isError = true) {
|
| 106 |
-
const el = document.getElementById('authMsg');
|
| 107 |
-
el.innerText = msg;
|
| 108 |
-
el.style.color = isError ? 'var(--brand-danger)' : 'var(--brand-success)';
|
| 109 |
-
setTimeout(() => el.innerText = '', 4000);
|
| 110 |
-
},
|
| 111 |
-
|
| 112 |
-
updateUserInfo(name, email) {
|
| 113 |
-
if (name) {
|
| 114 |
-
State.name = name;
|
| 115 |
-
localStorage.setItem('codeved_name', name);
|
| 116 |
-
}
|
| 117 |
-
const dispName = State.name || email.split('@')[0];
|
| 118 |
-
document.getElementById('uAv').innerText = dispName.charAt(0).toUpperCase();
|
| 119 |
-
document.getElementById('uName').innerText = dispName;
|
| 120 |
-
document.getElementById('uSub').innerText = email;
|
| 121 |
-
document.getElementById('btnLogout').style.display = 'flex';
|
| 122 |
-
document.getElementById('btnDeleteAcc').style.display = 'flex';
|
| 123 |
-
document.getElementById('btnLoginRegister').style.display = 'none';
|
| 124 |
-
document.getElementById('welcomeTitle').innerHTML = `Hello, ${dispName}!<br>How can I help you today?`;
|
| 125 |
-
},
|
| 126 |
-
|
| 127 |
-
updateWelcomeScreen() {
|
| 128 |
-
document.getElementById('welcomeScreen').style.display = State.history.length === 0 ? 'flex' : 'none';
|
| 129 |
-
},
|
| 130 |
-
|
| 131 |
-
initListeners() {
|
| 132 |
-
// Sidebar & Overlays
|
| 133 |
-
document.getElementById('btnMenuOpen').addEventListener('click', () => this.toggleSidebar());
|
| 134 |
-
document.getElementById('btnCloseSidebar').addEventListener('click', () => this.toggleSidebar());
|
| 135 |
-
document.getElementById('mobileOverlay').addEventListener('click', () => this.toggleSidebar());
|
| 136 |
-
|
| 137 |
-
// Scroll to bottom
|
| 138 |
-
document.getElementById('scrollToBottomBtn').addEventListener('click', () => {
|
| 139 |
-
this.scrollToBottom(true);
|
| 140 |
-
this.autoScroll = true;
|
| 141 |
-
});
|
| 142 |
-
|
| 143 |
-
// Input Dock Expanding Logic
|
| 144 |
-
const inp = document.getElementById('mainInput');
|
| 145 |
-
const dockWrapper = document.getElementById('dockWrapper');
|
| 146 |
-
|
| 147 |
-
inp.addEventListener('focus', () => dockWrapper.classList.add('expanded'));
|
| 148 |
-
inp.addEventListener('blur', () => {
|
| 149 |
-
if (inp.value.trim() === '') dockWrapper.classList.remove('expanded');
|
| 150 |
-
});
|
| 151 |
-
inp.addEventListener('input', () => {
|
| 152 |
-
this.autoGrow(inp);
|
| 153 |
-
if (inp.value.trim() !== '') dockWrapper.classList.add('expanded');
|
| 154 |
-
});
|
| 155 |
-
inp.addEventListener('keydown', (e) => {
|
| 156 |
-
if (e.key === 'Enter' && !e.shiftKey) {
|
| 157 |
-
e.preventDefault();
|
| 158 |
-
Chat.handleSend();
|
| 159 |
-
}
|
| 160 |
-
});
|
| 161 |
-
|
| 162 |
-
// Chat Container Scroll Listener
|
| 163 |
-
const chatContainerEl = document.getElementById('chatContainer');
|
| 164 |
-
const scrollBtnEl = document.getElementById('scrollToBottomBtn');
|
| 165 |
-
|
| 166 |
-
chatContainerEl.addEventListener('scroll', () => {
|
| 167 |
-
const scrollBottom = chatContainerEl.scrollHeight - chatContainerEl.scrollTop - chatContainerEl.clientHeight;
|
| 168 |
-
this.autoScroll = scrollBottom <= 50;
|
| 169 |
-
scrollBtnEl.classList.toggle('visible', !this.autoScroll);
|
| 170 |
-
});
|
| 171 |
-
|
| 172 |
-
// Close dropdowns when clicking outside
|
| 173 |
-
document.addEventListener('click', (e) => {
|
| 174 |
-
if (!e.target.closest('.tools-left')) {
|
| 175 |
-
document.getElementById('attachMenu').classList.remove('active');
|
| 176 |
-
document.getElementById('thinkMenu').classList.remove('active');
|
| 177 |
-
document.getElementById('voiceMenu').classList.remove('active');
|
| 178 |
-
}
|
| 179 |
-
});
|
| 180 |
-
}
|
| 181 |
-
};
|
| 182 |
-
|
| 183 |
-
// ----------------------------------------------------------------------------
|
| 184 |
-
// 4. AUTH MANAGER
|
| 185 |
-
// ----------------------------------------------------------------------------
|
| 186 |
-
const Auth = {
|
| 187 |
-
init() {
|
| 188 |
-
if (State.user) {
|
| 189 |
-
UI.updateUserInfo(State.name, State.user);
|
| 190 |
-
HistoryManager.syncAllChats();
|
| 191 |
-
} else {
|
| 192 |
-
document.getElementById('uAv').innerText = "G";
|
| 193 |
-
document.getElementById('uName').innerText = "Guest Mode";
|
| 194 |
-
document.getElementById('uSub').innerText = `Queries: ${State.guestCount}/10`;
|
| 195 |
-
document.getElementById('btnLogout').style.display = 'none';
|
| 196 |
-
document.getElementById('btnDeleteAcc').style.display = 'none';
|
| 197 |
-
document.getElementById('btnLoginRegister').style.display = 'inline-flex';
|
| 198 |
-
document.getElementById('welcomeTitle').innerHTML = `Hello, Guest!<br>How can I help you today?`;
|
| 199 |
-
}
|
| 200 |
-
},
|
| 201 |
-
|
| 202 |
-
async handleLogout() {
|
| 203 |
-
const ok = await customConfirm("Are you sure you want to logout?");
|
| 204 |
-
if (ok) {
|
| 205 |
-
localStorage.removeItem('codeved_user');
|
| 206 |
-
localStorage.removeItem('codeved_name');
|
| 207 |
-
location.reload();
|
| 208 |
-
}
|
| 209 |
-
},
|
| 210 |
-
|
| 211 |
-
async handleDeleteAccount() {
|
| 212 |
-
const ok = await customConfirm("Are you sure you want to permanently delete your account and all data?");
|
| 213 |
-
if (ok) {
|
| 214 |
-
try {
|
| 215 |
-
await fetch(Config.GAS_URL, {
|
| 216 |
-
method: 'POST',
|
| 217 |
-
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
|
| 218 |
-
body: JSON.stringify({ action: "delete_account", email: State.user })
|
| 219 |
-
});
|
| 220 |
-
} catch(e) { console.error("Account delete error:", e); }
|
| 221 |
-
finally {
|
| 222 |
-
localStorage.removeItem('codeved_user');
|
| 223 |
-
localStorage.removeItem('codeved_name');
|
| 224 |
-
location.reload();
|
| 225 |
-
}
|
| 226 |
-
}
|
| 227 |
-
},
|
| 228 |
-
|
| 229 |
-
openModal() { document.getElementById('authModal').style.display = 'flex'; },
|
| 230 |
-
closeModal() { document.getElementById('authModal').style.display = 'none'; },
|
| 231 |
-
|
| 232 |
-
switchTab(tab) {
|
| 233 |
-
document.getElementById('tabLogin').classList.remove('active');
|
| 234 |
-
document.getElementById('tabRegister').classList.remove('active');
|
| 235 |
-
document.getElementById('flowLogin').style.display = 'none';
|
| 236 |
-
document.getElementById('flowRegister').style.display = 'none';
|
| 237 |
-
|
| 238 |
-
if (tab === 'login') {
|
| 239 |
-
document.getElementById('tabLogin').classList.add('active');
|
| 240 |
-
document.getElementById('flowLogin').style.display = 'block';
|
| 241 |
-
} else {
|
| 242 |
-
document.getElementById('tabRegister').classList.add('active');
|
| 243 |
-
document.getElementById('flowRegister').style.display = 'block';
|
| 244 |
-
}
|
| 245 |
-
},
|
| 246 |
-
|
| 247 |
-
switchPhase(from, to) {
|
| 248 |
-
document.getElementById(from).classList.remove('active');
|
| 249 |
-
document.getElementById(to).classList.add('active');
|
| 250 |
-
},
|
| 251 |
-
|
| 252 |
-
async process(action) {
|
| 253 |
-
let payload = { action: action };
|
| 254 |
-
let btnId = '';
|
| 255 |
-
|
| 256 |
-
if (action === 'register_send_otp') {
|
| 257 |
-
payload.name = document.getElementById('regName').value.trim();
|
| 258 |
-
payload.email = document.getElementById('regEmail').value.trim();
|
| 259 |
-
payload.phone = "0000000000";
|
| 260 |
-
payload.organization = "CODE VED";
|
| 261 |
-
if (!payload.name || !payload.email) return UI.showAuthMsg("Details missing.");
|
| 262 |
-
btnId = 'btnRegOtp';
|
| 263 |
-
} else if (action === 'login_send_otp') {
|
| 264 |
-
payload.email = document.getElementById('logEmail').value.trim();
|
| 265 |
-
if (!payload.email) return UI.showAuthMsg("Email required.");
|
| 266 |
-
btnId = 'btnLogOtp';
|
| 267 |
-
} else if (action === 'register_verify' || action === 'login_verify') {
|
| 268 |
-
payload.email = document.getElementById(action === 'register_verify' ? 'regEmail' : 'logEmail').value.trim();
|
| 269 |
-
payload.otp = document.getElementById(action === 'register_verify' ? 'regOtp' : 'logOtp').value.trim();
|
| 270 |
-
if (!payload.otp) return UI.showAuthMsg("OTP required.");
|
| 271 |
-
btnId = action === 'register_verify' ? 'btnRegVerify' : 'btnLogVerify';
|
| 272 |
-
}
|
| 273 |
-
|
| 274 |
-
const btn = document.getElementById(btnId);
|
| 275 |
-
const originalText = btn.innerText;
|
| 276 |
-
btn.disabled = true;
|
| 277 |
-
btn.innerText = "Wait...";
|
| 278 |
-
|
| 279 |
-
try {
|
| 280 |
-
const res = await fetch(Config.GAS_URL, {
|
| 281 |
-
method: 'POST',
|
| 282 |
-
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
|
| 283 |
-
body: JSON.stringify(payload)
|
| 284 |
-
});
|
| 285 |
-
|
| 286 |
-
const textResponse = await res.text();
|
| 287 |
-
let data;
|
| 288 |
-
try { data = JSON.parse(textResponse); }
|
| 289 |
-
catch (parseErr) {
|
| 290 |
-
UI.showAuthMsg("Invalid response from server. Network error.");
|
| 291 |
-
btn.disabled = false; btn.innerText = originalText; return;
|
| 292 |
-
}
|
| 293 |
-
|
| 294 |
-
if (data.status === 'success') {
|
| 295 |
-
UI.showAuthMsg(data.message, false);
|
| 296 |
-
if (action === 'register_send_otp') Auth.switchPhase('regPhase1', 'regPhase2');
|
| 297 |
-
else if (action === 'login_send_otp') Auth.switchPhase('loginPhase1', 'loginPhase2');
|
| 298 |
-
else {
|
| 299 |
-
localStorage.setItem('codeved_user', payload.email);
|
| 300 |
-
if (data.user && data.user.name) localStorage.setItem('codeved_name', data.user.name);
|
| 301 |
-
location.reload();
|
| 302 |
-
}
|
| 303 |
-
} else {
|
| 304 |
-
UI.showAuthMsg(data.message);
|
| 305 |
-
}
|
| 306 |
-
} catch (e) {
|
| 307 |
-
UI.showAuthMsg("Network Error. Please try again.");
|
| 308 |
-
}
|
| 309 |
-
|
| 310 |
-
btn.disabled = false;
|
| 311 |
-
btn.innerText = originalText;
|
| 312 |
-
},
|
| 313 |
-
|
| 314 |
-
initListeners() {
|
| 315 |
-
document.getElementById('btnLoginRegister').addEventListener('click', () => this.openModal());
|
| 316 |
-
document.getElementById('btnLogout').addEventListener('click', () => this.handleLogout());
|
| 317 |
-
document.getElementById('btnDeleteAcc').addEventListener('click', () => this.handleDeleteAccount());
|
| 318 |
-
document.getElementById('btnAuthClose').addEventListener('click', () => this.closeModal());
|
| 319 |
-
|
| 320 |
-
document.getElementById('authModal').addEventListener('click', (e) => {
|
| 321 |
-
if (e.target.id === 'authModal') this.closeModal();
|
| 322 |
-
});
|
| 323 |
-
|
| 324 |
-
document.getElementById('tabLogin').addEventListener('click', () => this.switchTab('login'));
|
| 325 |
-
document.getElementById('tabRegister').addEventListener('click', () => this.switchTab('register'));
|
| 326 |
-
|
| 327 |
-
document.getElementById('btnLogOtp').addEventListener('click', () => this.process('login_send_otp'));
|
| 328 |
-
document.getElementById('btnLogVerify').addEventListener('click', () => this.process('login_verify'));
|
| 329 |
-
document.getElementById('btnRegOtp').addEventListener('click', () => this.process('register_send_otp'));
|
| 330 |
-
document.getElementById('btnRegVerify').addEventListener('click', () => this.process('register_verify'));
|
| 331 |
-
}
|
| 332 |
-
};
|
| 333 |
-
|
| 334 |
-
// ----------------------------------------------------------------------------
|
| 335 |
-
// 5. HISTORY MANAGER
|
| 336 |
-
// ----------------------------------------------------------------------------
|
| 337 |
-
const HistoryManager = {
|
| 338 |
-
async syncAllChats() {
|
| 339 |
-
if (!State.user) return;
|
| 340 |
-
try {
|
| 341 |
-
const res = await fetch(Config.GAS_URL, {
|
| 342 |
-
method: 'POST',
|
| 343 |
-
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
|
| 344 |
-
body: JSON.stringify({ action: "get_all_chats", email: State.user })
|
| 345 |
-
});
|
| 346 |
-
const data = JSON.parse(await res.text());
|
| 347 |
-
if (data.status === 'success') {
|
| 348 |
-
if (data.user) UI.updateUserInfo(data.user.name, data.user.email);
|
| 349 |
-
this.renderSidebar(data.chats || []);
|
| 350 |
-
}
|
| 351 |
-
} catch (e) { console.error("History sync error:", e); }
|
| 352 |
-
},
|
| 353 |
-
|
| 354 |
-
renderSidebar(chats) {
|
| 355 |
-
const container = document.getElementById('chatHistory');
|
| 356 |
-
container.innerHTML = `<div class="history-header"><span class="history-title-text">Recent Workspaces</span><span class="btn-clear-all" id="btnClearAll">Clear All</span></div>`;
|
| 357 |
-
|
| 358 |
-
document.getElementById('btnClearAll').addEventListener('click', () => this.clearAll());
|
| 359 |
-
|
| 360 |
-
chats.forEach(chat => {
|
| 361 |
-
const isActive = State.currentThreadId === chat.threadId ? 'active' : '';
|
| 362 |
-
const item = document.createElement('div');
|
| 363 |
-
item.className = `history-item ${isActive}`;
|
| 364 |
-
item.dataset.threadId = chat.threadId;
|
| 365 |
-
item.innerHTML = `
|
| 366 |
-
<span class="history-text">${UI.escape(chat.title)}</span>
|
| 367 |
-
<button class="btn-delete-chat" data-delete-id="${chat.threadId}" title="Delete">
|
| 368 |
-
<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 369 |
-
<polyline points="3 6 5 6 21 6"></polyline>
|
| 370 |
-
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
| 371 |
-
</svg>
|
| 372 |
-
</button>`;
|
| 373 |
-
container.appendChild(item);
|
| 374 |
-
});
|
| 375 |
-
},
|
| 376 |
-
|
| 377 |
-
async loadChat(threadId) {
|
| 378 |
-
if (!State.user || State.isProcessing) return;
|
| 379 |
-
State.currentThreadId = threadId;
|
| 380 |
-
document.getElementById('chatMessages').innerHTML = '';
|
| 381 |
-
|
| 382 |
-
try {
|
| 383 |
-
const res = await fetch(Config.GAS_URL, {
|
| 384 |
-
method: 'POST',
|
| 385 |
-
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
|
| 386 |
-
body: JSON.stringify({ action: "get_chat", email: State.user, threadId: threadId })
|
| 387 |
-
});
|
| 388 |
-
const data = JSON.parse(await res.text());
|
| 389 |
-
|
| 390 |
-
if (data.status === 'success') {
|
| 391 |
-
State.history = JSON.parse(data.historyJSON || "[]");
|
| 392 |
-
UI.updateWelcomeScreen();
|
| 393 |
-
|
| 394 |
-
for (const msg of State.history) {
|
| 395 |
-
if (msg.role === 'user') {
|
| 396 |
-
let dispText = msg.content;
|
| 397 |
-
if (dispText.includes('[SYSTEM REAL-TIME WEATHER:')) dispText = dispText.split('\n\n[SYSTEM REAL-TIME WEATHER:')[0];
|
| 398 |
-
if (dispText.includes('---END DATA---')) {
|
| 399 |
-
const afterMarker = dispText.split('---END DATA---')[1] || '';
|
| 400 |
-
dispText = afterMarker.replace(/^\s*User:\s*/, '').trim() || '[Attached File]';
|
| 401 |
-
}
|
| 402 |
-
Chat.renderUser(dispText);
|
| 403 |
-
} else {
|
| 404 |
-
const msgId = 'hist_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5);
|
| 405 |
-
const botObj = Chat.renderBot(msgId);
|
| 406 |
-
Chat.parseAndRender(msg.content, false, botObj.contentDiv);
|
| 407 |
-
TTSManager.autoPrepare(msgId, msg.content, botObj.listenBtn);
|
| 408 |
-
}
|
| 409 |
-
}
|
| 410 |
-
UI.autoScroll = true;
|
| 411 |
-
UI.scrollToBottom();
|
| 412 |
-
if (window.innerWidth <= 900) UI.toggleSidebar();
|
| 413 |
-
}
|
| 414 |
-
} catch (e) { console.error("Load chat error:", e); }
|
| 415 |
-
},
|
| 416 |
-
|
| 417 |
-
startNew() {
|
| 418 |
-
if (State.isProcessing) return;
|
| 419 |
-
State.currentThreadId = null;
|
| 420 |
-
State.currentTitle = null;
|
| 421 |
-
State.history = [];
|
| 422 |
-
document.getElementById('chatMessages').innerHTML = '';
|
| 423 |
-
UI.updateWelcomeScreen();
|
| 424 |
-
UI.autoScroll = true;
|
| 425 |
-
UI.scrollToBottom();
|
| 426 |
-
if (window.innerWidth <= 900) UI.toggleSidebar();
|
| 427 |
-
},
|
| 428 |
-
|
| 429 |
-
saveCurrent() {
|
| 430 |
-
if (!State.user || State.history.length === 0) return;
|
| 431 |
-
if (!State.currentThreadId) {
|
| 432 |
-
State.currentThreadId = "thr_" + Date.now();
|
| 433 |
-
const firstUser = State.history.find(m => m.role === 'user');
|
| 434 |
-
State.currentTitle = firstUser ? firstUser.content.substring(0, 25) : "New Workspace";
|
| 435 |
-
}
|
| 436 |
-
|
| 437 |
-
fetch(Config.GAS_URL, {
|
| 438 |
-
method: 'POST',
|
| 439 |
-
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
|
| 440 |
-
body: JSON.stringify({
|
| 441 |
-
action: "save_chat",
|
| 442 |
-
email: State.user,
|
| 443 |
-
threadId: State.currentThreadId,
|
| 444 |
-
title: State.currentTitle,
|
| 445 |
-
historyJSON: JSON.stringify(State.history)
|
| 446 |
-
})
|
| 447 |
-
}).catch(e => console.error("Save error:", e)).then(() => this.syncAllChats());
|
| 448 |
-
},
|
| 449 |
-
|
| 450 |
-
async clearAll() {
|
| 451 |
-
const ok = await customConfirm("Clear all workspaces?");
|
| 452 |
-
if (ok) {
|
| 453 |
-
State.history = [];
|
| 454 |
-
State.currentThreadId = null;
|
| 455 |
-
State.currentTitle = null;
|
| 456 |
-
document.getElementById('chatMessages').innerHTML = '';
|
| 457 |
-
UI.updateWelcomeScreen();
|
| 458 |
-
UI.autoScroll = true;
|
| 459 |
-
UI.scrollToBottom();
|
| 460 |
-
|
| 461 |
-
fetch(Config.GAS_URL, {
|
| 462 |
-
method: 'POST',
|
| 463 |
-
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
|
| 464 |
-
body: JSON.stringify({ action: "clear_all_chats", email: State.user })
|
| 465 |
-
}).catch(e => console.error("Clear error:", e)).then(() => this.syncAllChats());
|
| 466 |
-
}
|
| 467 |
-
},
|
| 468 |
-
|
| 469 |
-
async deleteChat(threadId) {
|
| 470 |
-
const ok = await customConfirm("Delete this workspace?");
|
| 471 |
-
if (ok) {
|
| 472 |
-
if (State.currentThreadId === threadId) this.startNew();
|
| 473 |
-
fetch(Config.GAS_URL, {
|
| 474 |
-
method: 'POST',
|
| 475 |
-
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
|
| 476 |
-
body: JSON.stringify({ action: "delete_chat", email: State.user, threadId: threadId })
|
| 477 |
-
}).catch(e => console.error("Delete error:", e)).then(() => this.syncAllChats());
|
| 478 |
-
}
|
| 479 |
-
},
|
| 480 |
-
|
| 481 |
-
initListeners() {
|
| 482 |
-
document.getElementById('btnNewChat').addEventListener('click', () => this.startNew());
|
| 483 |
-
|
| 484 |
-
// Event Delegation for History List
|
| 485 |
-
document.getElementById('chatHistory').addEventListener('click', (e) => {
|
| 486 |
-
const deleteBtn = e.target.closest('.btn-delete-chat');
|
| 487 |
-
if (deleteBtn) {
|
| 488 |
-
e.stopPropagation();
|
| 489 |
-
this.deleteChat(deleteBtn.dataset.deleteId);
|
| 490 |
-
return;
|
| 491 |
-
}
|
| 492 |
-
|
| 493 |
-
const historyItem = e.target.closest('.history-item');
|
| 494 |
-
if (historyItem && historyItem.dataset.threadId) {
|
| 495 |
-
this.loadChat(historyItem.dataset.threadId);
|
| 496 |
-
}
|
| 497 |
-
});
|
| 498 |
-
}
|
| 499 |
-
};
|
| 500 |
-
|
| 501 |
-
// ----------------------------------------------------------------------------
|
| 502 |
-
// 6. ENVIRONMENT, SEARCH & THINKING MANAGERS
|
| 503 |
-
// ----------------------------------------------------------------------------
|
| 504 |
-
const EnvironmentManager = {
|
| 505 |
-
toggle() {
|
| 506 |
-
const status = document.getElementById('locStatus');
|
| 507 |
-
document.getElementById('attachMenu').classList.remove('active');
|
| 508 |
-
|
| 509 |
-
if (State.location) {
|
| 510 |
-
State.location = null;
|
| 511 |
-
State.weatherContext = null;
|
| 512 |
-
status.style.display = 'none';
|
| 513 |
-
} else {
|
| 514 |
-
if (navigator.geolocation) {
|
| 515 |
-
status.style.display = 'flex';
|
| 516 |
-
status.innerHTML = `<span>Finding location...</span>`;
|
| 517 |
-
|
| 518 |
-
navigator.geolocation.getCurrentPosition(pos => {
|
| 519 |
-
State.location = { lat: pos.coords.latitude, lng: pos.coords.longitude };
|
| 520 |
-
status.innerHTML = `<span style="color:var(--brand-success);">✓ Location active!</span>`;
|
| 521 |
-
setTimeout(() => status.style.display = 'none', 3000);
|
| 522 |
-
fetch(`https://api.open-meteo.com/v1/forecast?latitude=${pos.coords.latitude}&longitude=${pos.coords.longitude}¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m`)
|
| 523 |
-
.then(r => r.ok ? r.json() : null)
|
| 524 |
-
.then(data => {
|
| 525 |
-
const c = data && data.current;
|
| 526 |
-
if (c) State.weatherContext = `Temperature: ${c.temperature_2m}°C, Humidity: ${c.relative_humidity_2m}%, Wind: ${c.wind_speed_10m} km/h`;
|
| 527 |
-
}).catch(() => {});
|
| 528 |
-
}, err => {
|
| 529 |
-
status.innerHTML = `<span style="color:var(--brand-danger);">Location denied.</span>`;
|
| 530 |
-
setTimeout(() => status.style.display = 'none', 3000);
|
| 531 |
-
});
|
| 532 |
-
}
|
| 533 |
-
}
|
| 534 |
-
}
|
| 535 |
-
};
|
| 536 |
-
|
| 537 |
-
const SearchManager = {
|
| 538 |
-
toggle() {
|
| 539 |
-
State.searchEnabled = !State.searchEnabled;
|
| 540 |
-
const btn = document.getElementById('btnSearch');
|
| 541 |
-
btn.classList.toggle('active-search', State.searchEnabled);
|
| 542 |
-
btn.title = State.searchEnabled ? 'Web Search: ON' : 'Web Search: OFF';
|
| 543 |
-
}
|
| 544 |
-
};
|
| 545 |
-
|
| 546 |
-
const ThinkingManager = {
|
| 547 |
-
toggleMenu() {
|
| 548 |
-
document.getElementById('thinkMenu').classList.toggle('active');
|
| 549 |
-
document.getElementById('attachMenu').classList.remove('active');
|
| 550 |
-
},
|
| 551 |
-
|
| 552 |
-
setEffort(level) {
|
| 553 |
-
State.thinkingMode = true;
|
| 554 |
-
State.thinkingEffort = level;
|
| 555 |
-
document.getElementById('thinkMenu').classList.remove('active');
|
| 556 |
-
this.updateIndicator();
|
| 557 |
-
},
|
| 558 |
-
|
| 559 |
-
disable() {
|
| 560 |
-
State.thinkingMode = false;
|
| 561 |
-
document.getElementById('thinkMenu').classList.remove('active');
|
| 562 |
-
this.updateIndicator();
|
| 563 |
-
},
|
| 564 |
-
|
| 565 |
-
updateIndicator() {
|
| 566 |
-
const btn = document.getElementById('btnAttach');
|
| 567 |
-
btn.classList.remove('active-think-low', 'active-think-medium', 'active-think-high');
|
| 568 |
-
if (State.thinkingMode) {
|
| 569 |
-
btn.classList.add(`active-think-${State.thinkingEffort}`);
|
| 570 |
-
btn.title = `Thinking Mode: ${State.thinkingEffort.charAt(0).toUpperCase() + State.thinkingEffort.slice(1)}`;
|
| 571 |
-
} else {
|
| 572 |
-
btn.title = 'Attach File';
|
| 573 |
-
}
|
| 574 |
-
},
|
| 575 |
-
|
| 576 |
-
initListeners() {
|
| 577 |
-
document.getElementById('thinkMenu').addEventListener('click', (e) => {
|
| 578 |
-
const item = e.target.closest('.dropdown-item');
|
| 579 |
-
if (!item) return;
|
| 580 |
-
|
| 581 |
-
if (item.id === 'optDisableThink') {
|
| 582 |
-
this.disable();
|
| 583 |
-
} else if (item.dataset.effort) {
|
| 584 |
-
this.setEffort(item.dataset.effort);
|
| 585 |
-
}
|
| 586 |
-
});
|
| 587 |
-
}
|
| 588 |
-
};
|
| 589 |
-
|
| 590 |
-
// ----------------------------------------------------------------------------
|
| 591 |
-
// 7. FILE SYSTEM & SPEECH
|
| 592 |
-
// ----------------------------------------------------------------------------
|
| 593 |
-
const FileSys = {
|
| 594 |
-
async process(input, type) {
|
| 595 |
-
document.getElementById('attachMenu').classList.remove('active');
|
| 596 |
-
const file = input.files[0];
|
| 597 |
-
if (!file) return;
|
| 598 |
-
|
| 599 |
-
document.getElementById('filePreviewBar').classList.add('active');
|
| 600 |
-
document.getElementById('fileName').innerText = file.name;
|
| 601 |
-
|
| 602 |
-
if (type === 'image') {
|
| 603 |
-
document.getElementById('imgPreview').style.display = 'block';
|
| 604 |
-
document.getElementById('docPreview').style.display = 'none';
|
| 605 |
-
|
| 606 |
-
const reader = new FileReader();
|
| 607 |
-
reader.onload = (e) => {
|
| 608 |
-
const img = new Image();
|
| 609 |
-
img.onload = () => {
|
| 610 |
-
const canvas = document.createElement('canvas');
|
| 611 |
-
const ctx = canvas.getContext('2d');
|
| 612 |
-
let w = img.width, h = img.height;
|
| 613 |
-
const max = 1024;
|
| 614 |
-
|
| 615 |
-
if(w > max || h > max) {
|
| 616 |
-
if(w > h) { h = (max/w)*h; w = max; }
|
| 617 |
-
else { w = (max/h)*w; h = max; }
|
| 618 |
-
}
|
| 619 |
-
|
| 620 |
-
canvas.width = w; canvas.height = h;
|
| 621 |
-
ctx.drawImage(img, 0, 0, w, h);
|
| 622 |
-
|
| 623 |
-
const b64 = canvas.toDataURL('image/jpeg', 0.8).split(',')[1];
|
| 624 |
-
State.attachment = { type: 'image', data: b64, name: file.name };
|
| 625 |
-
document.getElementById('imgPreview').src = `data:image/jpeg;base64,${b64}`;
|
| 626 |
-
};
|
| 627 |
-
img.src = e.target.result;
|
| 628 |
-
};
|
| 629 |
-
reader.readAsDataURL(file);
|
| 630 |
-
} else {
|
| 631 |
-
document.getElementById('imgPreview').style.display = 'none';
|
| 632 |
-
document.getElementById('docPreview').style.display = 'block';
|
| 633 |
-
|
| 634 |
-
if (file.name.endsWith('.pdf')) {
|
| 635 |
-
try {
|
| 636 |
-
const arrayBuffer = await file.arrayBuffer();
|
| 637 |
-
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
| 638 |
-
let text = '';
|
| 639 |
-
for (let i = 1; i <= pdf.numPages; i++) {
|
| 640 |
-
const page = await pdf.getPage(i);
|
| 641 |
-
const content = await page.getTextContent();
|
| 642 |
-
text += content.items.map(item => item.str).join(' ') + '\n';
|
| 643 |
-
}
|
| 644 |
-
State.attachment = { type: 'text', data: text, name: file.name };
|
| 645 |
-
} catch(e) { State.attachment = { type: 'text', data: '[PDF parsing error]', name: file.name }; }
|
| 646 |
-
} else if (file.name.endsWith('.docx')) {
|
| 647 |
-
try {
|
| 648 |
-
const arrayBuffer = await file.arrayBuffer();
|
| 649 |
-
const result = await mammoth.extractRawText({ arrayBuffer });
|
| 650 |
-
State.attachment = { type: 'text', data: result.value, name: file.name };
|
| 651 |
-
} catch(e) { State.attachment = { type: 'text', data: '[DOCX parsing error]', name: file.name }; }
|
| 652 |
-
} else {
|
| 653 |
-
const reader = new FileReader();
|
| 654 |
-
reader.onload = (e) => { State.attachment = { type: 'text', data: e.target.result, name: file.name }; };
|
| 655 |
-
reader.readAsText(file);
|
| 656 |
-
}
|
| 657 |
-
}
|
| 658 |
-
input.value = '';
|
| 659 |
-
document.getElementById('dockWrapper').classList.add('expanded');
|
| 660 |
-
},
|
| 661 |
-
|
| 662 |
-
discard() {
|
| 663 |
-
State.attachment = null;
|
| 664 |
-
document.getElementById('filePreviewBar').classList.remove('active');
|
| 665 |
-
if (document.getElementById('mainInput').value.trim() === '') {
|
| 666 |
-
document.getElementById('dockWrapper').classList.remove('expanded');
|
| 667 |
-
}
|
| 668 |
-
},
|
| 669 |
-
|
| 670 |
-
initListeners() {
|
| 671 |
-
document.getElementById('btnRemoveFile').addEventListener('click', () => this.discard());
|
| 672 |
-
document.getElementById('imgUpload').addEventListener('change', (e) => this.process(e.target, 'image'));
|
| 673 |
-
document.getElementById('docUpload').addEventListener('change', (e) => this.process(e.target, 'document'));
|
| 674 |
-
}
|
| 675 |
-
};
|
| 676 |
-
|
| 677 |
-
const Speech = {
|
| 678 |
-
rec: null,
|
| 679 |
-
isRec: false,
|
| 680 |
-
|
| 681 |
-
init() {
|
| 682 |
-
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
|
| 683 |
-
if (!SR) return;
|
| 684 |
-
|
| 685 |
-
this.rec = new SR();
|
| 686 |
-
this.rec.continuous = false;
|
| 687 |
-
this.rec.interimResults = true;
|
| 688 |
-
this.rec.lang = navigator.language || 'en-US';
|
| 689 |
-
|
| 690 |
-
this.rec.onstart = () => {
|
| 691 |
-
this.isRec = true;
|
| 692 |
-
document.getElementById('btnStt').classList.add('recording');
|
| 693 |
-
};
|
| 694 |
-
|
| 695 |
-
this.rec.onresult = (e) => {
|
| 696 |
-
if (!e.results || !e.results.length) return;
|
| 697 |
-
let trans = '';
|
| 698 |
-
for (let i = e.resultIndex; i < e.results.length; ++i) {
|
| 699 |
-
if (e.results[i].isFinal && e.results[i][0]) trans += e.results[i][0].transcript;
|
| 700 |
-
}
|
| 701 |
-
if (trans) {
|
| 702 |
-
const input = document.getElementById('mainInput');
|
| 703 |
-
input.value += (input.value ? ' ' : '') + trans;
|
| 704 |
-
UI.autoGrow(input);
|
| 705 |
-
document.getElementById('dockWrapper').classList.add('expanded');
|
| 706 |
-
}
|
| 707 |
-
};
|
| 708 |
-
|
| 709 |
-
this.rec.onerror = () => this.stop();
|
| 710 |
-
this.rec.onend = () => this.stop();
|
| 711 |
-
},
|
| 712 |
-
|
| 713 |
-
toggle() {
|
| 714 |
-
if (!this.rec) this.init();
|
| 715 |
-
if (this.isRec) this.stop();
|
| 716 |
-
else { try { this.rec.start(); } catch(e) {} }
|
| 717 |
-
},
|
| 718 |
-
|
| 719 |
-
stop() {
|
| 720 |
-
if(this.rec) this.rec.stop();
|
| 721 |
-
this.isRec = false;
|
| 722 |
-
document.getElementById('btnStt').classList.remove('recording');
|
| 723 |
-
},
|
| 724 |
-
|
| 725 |
-
initListeners() {
|
| 726 |
-
document.getElementById('btnStt').addEventListener('click', () => this.toggle());
|
| 727 |
-
}
|
| 728 |
-
};
|
| 729 |
-
|
| 730 |
-
// ----------------------------------------------------------------------------
|
| 731 |
-
// 8. TTS MANAGER
|
| 732 |
-
// ----------------------------------------------------------------------------
|
| 733 |
-
const TTSManager = {
|
| 734 |
-
cache: {},
|
| 735 |
-
currentAudio: null,
|
| 736 |
-
selectedVoice: "M2",
|
| 737 |
-
selectedLanguage: "English",
|
| 738 |
-
isPlaying: false,
|
| 739 |
-
currentMsgId: null,
|
| 740 |
-
currentBtnElement: null,
|
| 741 |
-
preparingSet: new Set(),
|
| 742 |
-
|
| 743 |
-
voiceMap: {
|
| 744 |
-
"M1": "Aarav", "M2": "Kabir", "M3": "Vihaan", "M4": "Advik", "M5": "Rohan",
|
| 745 |
-
"F1": "Priya", "F2": "Ananya", "F3": "Diya", "F4": "Sneha", "F5": "Kavya"
|
| 746 |
-
},
|
| 747 |
-
|
| 748 |
-
languageMap: {
|
| 749 |
-
"English": "en", "Korean": "ko", "Japanese": "ja", "Arabic": "ar", "Bulgarian": "bg",
|
| 750 |
-
"Czech": "cs", "Danish": "da", "German": "de", "Greek": "el", "Spanish": "es",
|
| 751 |
-
"Estonian": "et", "Finnish": "fi", "French": "fr", "Hindi": "hi", "Croatian": "hr",
|
| 752 |
-
"Hungarian": "hu", "Indonesian": "id", "Italian": "it", "Lithuanian": "lt", "Latvian": "lv",
|
| 753 |
-
"Dutch": "nl", "Polish": "pl", "Portuguese": "pt", "Romanian": "ro", "Russian": "ru",
|
| 754 |
-
"Slovak": "sk", "Slovenian": "sl", "Swedish": "sv", "Turkish": "tr", "Ukrainian": "uk",
|
| 755 |
-
"Vietnamese": "vi"
|
| 756 |
-
},
|
| 757 |
-
|
| 758 |
-
initUI() {
|
| 759 |
-
const list = document.getElementById('voiceOptionsList');
|
| 760 |
-
list.innerHTML = '';
|
| 761 |
-
|
| 762 |
-
for (const [code, name] of Object.entries(this.voiceMap)) {
|
| 763 |
-
const isSelected = code === this.selectedVoice;
|
| 764 |
-
const checkIcon = isSelected ? `<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"></polyline></svg>` : `<div></div>`;
|
| 765 |
-
const item = document.createElement('div');
|
| 766 |
-
item.className = `dropdown-item ${isSelected ? 'selected' : ''}`;
|
| 767 |
-
item.dataset.voiceCode = code;
|
| 768 |
-
item.innerHTML = `<span>${name}</span>${checkIcon}`;
|
| 769 |
-
list.appendChild(item);
|
| 770 |
-
}
|
| 771 |
-
|
| 772 |
-
const langList = document.getElementById('languageOptionsList');
|
| 773 |
-
langList.innerHTML = '';
|
| 774 |
-
|
| 775 |
-
for (const [name, code] of Object.entries(this.languageMap)) {
|
| 776 |
-
const isSelected = name === this.selectedLanguage;
|
| 777 |
-
const checkIcon = isSelected ? `<svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"></polyline></svg>` : `<div></div>`;
|
| 778 |
-
const item = document.createElement('div');
|
| 779 |
-
item.className = `dropdown-item ${isSelected ? 'selected' : ''}`;
|
| 780 |
-
item.dataset.langName = name;
|
| 781 |
-
item.innerHTML = `<span>${name}</span>${checkIcon}`;
|
| 782 |
-
langList.appendChild(item);
|
| 783 |
-
}
|
| 784 |
-
},
|
| 785 |
-
|
| 786 |
-
setVoice(code) {
|
| 787 |
-
Object.values(this.cache).forEach(url => URL.revokeObjectURL(url));
|
| 788 |
-
this.cache = {};
|
| 789 |
-
this.selectedVoice = code;
|
| 790 |
-
this.initUI();
|
| 791 |
-
document.getElementById('voiceMenu').classList.remove('active');
|
| 792 |
-
},
|
| 793 |
-
|
| 794 |
-
setLanguage(name) {
|
| 795 |
-
Object.values(this.cache).forEach(url => URL.revokeObjectURL(url));
|
| 796 |
-
this.cache = {};
|
| 797 |
-
this.selectedLanguage = name;
|
| 798 |
-
this.initUI();
|
| 799 |
-
document.getElementById('voiceMenu').classList.remove('active');
|
| 800 |
-
},
|
| 801 |
-
|
| 802 |
-
cleanTextForTTS(text) {
|
| 803 |
-
let cleaned = text.replace(/\u0060{3}[\s\S]*?\u0060{3}/g, '')
|
| 804 |
-
.replace(/\u0060[^\u0060]*\u0060/g, '')
|
| 805 |
-
.replace(/<think>[\s\S]*?<\/think>/gi, '')
|
| 806 |
-
.replace(/https?:\/\/[^\s]+/g, '')
|
| 807 |
-
.replace(/[*_#\u0060~>]/g, '')
|
| 808 |
-
.replace(/<[^>]*>/g, '')
|
| 809 |
-
.replace(/\s+/g, ' ')
|
| 810 |
-
.trim();
|
| 811 |
-
|
| 812 |
-
cleaned = cleaned.replace(/[\u{1F600}-\u{1F64F}]/gu, '')
|
| 813 |
-
.replace(/[\u{1F300}-\u{1F5FF}]/gu, '')
|
| 814 |
-
.replace(/[\u{1F680}-\u{1F6FF}]/gu, '')
|
| 815 |
-
.replace(/[\u{2600}-\u{26FF}]/gu, '')
|
| 816 |
-
.replace(/[\u{2700}-\u{27BF}]/gu, '');
|
| 817 |
-
|
| 818 |
-
if (cleaned.length > 1500) cleaned = cleaned.substring(0, 1500) + '...';
|
| 819 |
-
return cleaned;
|
| 820 |
-
},
|
| 821 |
-
|
| 822 |
-
async autoPrepare(msgId, fullText, btnElement) {
|
| 823 |
-
if (this.preparingSet.has(msgId)) return;
|
| 824 |
-
|
| 825 |
-
const cleanText = this.cleanTextForTTS(fullText);
|
| 826 |
-
if (!cleanText) { btnElement.style.display = 'none'; return; }
|
| 827 |
-
|
| 828 |
-
this.preparingSet.add(msgId);
|
| 829 |
-
btnElement.style.display = 'flex';
|
| 830 |
-
btnElement.innerHTML = `<svg class="spin-icon" style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="2" x2="12" y2="6"></line><line x1="12" y1="18" x2="12" y2="22"></line><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line><line x1="2" y1="12" x2="6" y2="12"></line><line x1="18" y1="12" x2="22" y2="12"></line><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line></svg> Preparing...`;
|
| 831 |
-
btnElement.style.pointerEvents = 'none';
|
| 832 |
-
|
| 833 |
-
try {
|
| 834 |
-
const response = await fetch('/api/tts', {
|
| 835 |
-
method: 'POST',
|
| 836 |
-
headers: {'Content-Type':'application/json'},
|
| 837 |
-
body: JSON.stringify({ text: cleanText, voice: this.selectedVoice, language_name: this.selectedLanguage })
|
| 838 |
-
});
|
| 839 |
-
|
| 840 |
-
if (!response.ok) throw new Error('TTS service unavailable');
|
| 841 |
-
|
| 842 |
-
const blob = await response.blob();
|
| 843 |
-
this.cache[msgId] = URL.createObjectURL(blob);
|
| 844 |
-
|
| 845 |
-
btnElement.innerHTML = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg> Listen`;
|
| 846 |
-
btnElement.style.pointerEvents = 'auto';
|
| 847 |
-
} catch(e) {
|
| 848 |
-
btnElement.innerHTML = `<span style="color:var(--brand-danger);">⚠️ ${e.message}</span>`;
|
| 849 |
-
setTimeout(() => { btnElement.style.display = 'none'; }, 4000);
|
| 850 |
-
} finally { this.preparingSet.delete(msgId); }
|
| 851 |
-
},
|
| 852 |
-
|
| 853 |
-
play(msgId, btnElement) {
|
| 854 |
-
const audioUrl = this.cache[msgId];
|
| 855 |
-
if (!audioUrl) {
|
| 856 |
-
this.autoPrepare(msgId, document.getElementById(`bot-content-${msgId}`).innerText, btnElement);
|
| 857 |
-
return;
|
| 858 |
-
}
|
| 859 |
-
|
| 860 |
-
if (this.isPlaying && this.currentMsgId === msgId) {
|
| 861 |
-
if (this.currentAudio) { this.currentAudio.pause(); this.currentAudio.currentTime = 0; }
|
| 862 |
-
this.isPlaying = false;
|
| 863 |
-
this.currentMsgId = null;
|
| 864 |
-
btnElement.innerHTML = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg> Listen`;
|
| 865 |
-
btnElement.style.background = '';
|
| 866 |
-
return;
|
| 867 |
-
}
|
| 868 |
-
|
| 869 |
-
if (this.currentAudio) { this.currentAudio.pause(); this.currentAudio.currentTime = 0; }
|
| 870 |
-
|
| 871 |
-
this.isPlaying = true;
|
| 872 |
-
this.currentMsgId = msgId;
|
| 873 |
-
this.currentBtnElement = btnElement;
|
| 874 |
-
|
| 875 |
-
const originalHTML = btnElement.innerHTML;
|
| 876 |
-
btnElement.innerHTML = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg> Playing...`;
|
| 877 |
-
btnElement.style.background = 'rgba(211, 227, 253, 0.9)';
|
| 878 |
-
|
| 879 |
-
this.currentAudio = new Audio(audioUrl);
|
| 880 |
-
this.currentAudio.playbackRate = 1.15;
|
| 881 |
-
|
| 882 |
-
const resetBtn = () => {
|
| 883 |
-
this.isPlaying = false;
|
| 884 |
-
this.currentMsgId = null;
|
| 885 |
-
btnElement.innerHTML = originalHTML;
|
| 886 |
-
btnElement.style.background = '';
|
| 887 |
-
};
|
| 888 |
-
|
| 889 |
-
this.currentAudio.onended = resetBtn;
|
| 890 |
-
this.currentAudio.onerror = resetBtn;
|
| 891 |
-
|
| 892 |
-
this.currentAudio.play();
|
| 893 |
-
},
|
| 894 |
-
|
| 895 |
-
initListeners() {
|
| 896 |
-
document.getElementById('voiceMenu').addEventListener('click', (e) => {
|
| 897 |
-
const item = e.target.closest('.dropdown-item');
|
| 898 |
-
if (!item) return;
|
| 899 |
-
|
| 900 |
-
if (item.dataset.voiceCode) this.setVoice(item.dataset.voiceCode);
|
| 901 |
-
else if (item.dataset.langName) this.setLanguage(item.dataset.langName);
|
| 902 |
-
});
|
| 903 |
-
}
|
| 904 |
-
};
|
| 905 |
-
|
| 906 |
-
// ----------------------------------------------------------------------------
|
| 907 |
-
// 9. CHAT MANAGER
|
| 908 |
-
// ----------------------------------------------------------------------------
|
| 909 |
-
const Chat = {
|
| 910 |
-
renderUser(txt, attachUI = '') {
|
| 911 |
-
const c = document.getElementById('chatMessages');
|
| 912 |
-
const w = document.createElement('div');
|
| 913 |
-
w.className = 'message-wrapper';
|
| 914 |
-
w.innerHTML = `<div class="user-message">${attachUI}${txt ? UI.escape(txt) : ''}</div>`;
|
| 915 |
-
c.appendChild(w);
|
| 916 |
-
UI.autoScroll = true;
|
| 917 |
-
UI.scrollToBottom(true);
|
| 918 |
-
},
|
| 919 |
-
|
| 920 |
-
renderBot(msgId) {
|
| 921 |
-
const c = document.getElementById('chatMessages');
|
| 922 |
-
const w = document.createElement('div');
|
| 923 |
-
w.className = 'message-wrapper';
|
| 924 |
-
const listenIcon = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>`;
|
| 925 |
-
const copyIcon = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
|
| 926 |
-
|
| 927 |
-
w.innerHTML = `
|
| 928 |
-
<div class="bot-message">
|
| 929 |
-
<div class="bot-avatar"><img src="${Config.LOGO}"></div>
|
| 930 |
-
<div class="bot-content-wrapper">
|
| 931 |
-
<div class="bot-content" id="bot-content-${msgId}"></div>
|
| 932 |
-
<div class="msg-actions">
|
| 933 |
-
<button class="action-btn listen-btn" id="listen-btn-${msgId}" style="display:none;">${listenIcon} Listen</button>
|
| 934 |
-
<button class="action-btn copy-btn">${copyIcon} Copy</button>
|
| 935 |
-
</div>
|
| 936 |
-
</div>
|
| 937 |
-
</div>`;
|
| 938 |
-
|
| 939 |
-
c.appendChild(w);
|
| 940 |
-
UI.scrollToBottom();
|
| 941 |
-
|
| 942 |
-
return {
|
| 943 |
-
contentDiv: document.getElementById(`bot-content-${msgId}`),
|
| 944 |
-
listenBtn: document.getElementById(`listen-btn-${msgId}`),
|
| 945 |
-
wrapper: w,
|
| 946 |
-
msgId: msgId
|
| 947 |
-
};
|
| 948 |
-
},
|
| 949 |
-
|
| 950 |
-
// Delegating Markdown Rendering to the dedicated module
|
| 951 |
-
parseAndRender(fullText, isProcessing, container) {
|
| 952 |
-
if (window.MarkdownRenderer && typeof window.MarkdownRenderer.parseAndRender === 'function') {
|
| 953 |
-
window.MarkdownRenderer.parseAndRender(fullText, isProcessing, container);
|
| 954 |
-
} else {
|
| 955 |
-
// Fallback if markdownRenderer.js hasn't loaded yet
|
| 956 |
-
container.innerHTML = fullText;
|
| 957 |
-
}
|
| 958 |
-
},
|
| 959 |
-
|
| 960 |
-
copyMsg(contentDiv) {
|
| 961 |
-
const clone = contentDiv.cloneNode(true);
|
| 962 |
-
const thinkBox = clone.querySelector('.qwen-think-box');
|
| 963 |
-
if (thinkBox) thinkBox.remove();
|
| 964 |
-
|
| 965 |
-
navigator.clipboard.writeText(clone.innerText).then(() => {
|
| 966 |
-
const btn = contentDiv.parentElement.querySelector('.copy-btn');
|
| 967 |
-
const original = btn.innerHTML;
|
| 968 |
-
btn.innerHTML = `<svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg> Copied!`;
|
| 969 |
-
setTimeout(() => btn.innerHTML = original, 1500);
|
| 970 |
-
});
|
| 971 |
-
},
|
| 972 |
-
|
| 973 |
-
stopGeneration() {
|
| 974 |
-
if (State.abortController) {
|
| 975 |
-
State.abortController.abort();
|
| 976 |
-
State.abortController = null;
|
| 977 |
-
}
|
| 978 |
-
State.isProcessing = false;
|
| 979 |
-
document.getElementById('btnSend').disabled = false;
|
| 980 |
-
document.getElementById('btnSend').style.display = 'flex';
|
| 981 |
-
document.getElementById('btnStop').classList.remove('active');
|
| 982 |
-
if (document.getElementById('mainInput').value.trim() === '') {
|
| 983 |
-
document.getElementById('dockWrapper').classList.remove('expanded');
|
| 984 |
-
}
|
| 985 |
-
},
|
| 986 |
-
|
| 987 |
-
async handleSend() {
|
| 988 |
-
if(State.isProcessing) return;
|
| 989 |
-
|
| 990 |
-
if (!State.user && State.guestCount >= 10) {
|
| 991 |
-
Auth.openModal();
|
| 992 |
-
UI.showAuthMsg("Guest limit reached (10/10). Please login or register to continue.", true);
|
| 993 |
-
return;
|
| 994 |
-
}
|
| 995 |
-
|
| 996 |
-
const input = document.getElementById('mainInput');
|
| 997 |
-
const text = input.value.trim();
|
| 998 |
-
|
| 999 |
-
if(!text && !State.attachment) return;
|
| 1000 |
-
|
| 1001 |
-
if (!State.user) {
|
| 1002 |
-
let count = parseInt(localStorage.getItem('codeved_guest') || '0', 10);
|
| 1003 |
-
if (isNaN(count)) count = 0;
|
| 1004 |
-
count++;
|
| 1005 |
-
State.guestCount = count;
|
| 1006 |
-
localStorage.setItem('codeved_guest', count.toString());
|
| 1007 |
-
document.getElementById('uSub').innerText = `Queries: ${count}/10`;
|
| 1008 |
-
}
|
| 1009 |
-
|
| 1010 |
-
State.isProcessing = true;
|
| 1011 |
-
document.getElementById('btnSend').style.display = 'none';
|
| 1012 |
-
document.getElementById('btnStop').classList.add('active');
|
| 1013 |
-
State.lastUserMessage = text;
|
| 1014 |
-
input.value = '';
|
| 1015 |
-
input.style.height = 'auto';
|
| 1016 |
-
UI.updateWelcomeScreen();
|
| 1017 |
-
|
| 1018 |
-
let payloadStr = text, attachUI = '', mediaArray = [];
|
| 1019 |
-
|
| 1020 |
-
if(State.attachment) {
|
| 1021 |
-
if(State.attachment.type === 'text') {
|
| 1022 |
-
payloadStr = `[File attached: ${State.attachment.name}]\n\n---DATA---\n${State.attachment.data}\n---END DATA---\n\nUser: ${text}`;
|
| 1023 |
-
attachUI = `<div class="chat-attachment-container"><div class="chat-file-pill"><svg style="width:14px;height:14px;color:var(--text-secondary);" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path></svg> ${State.attachment.name}</div></div>`;
|
| 1024 |
-
} else {
|
| 1025 |
-
mediaArray.push(State.attachment);
|
| 1026 |
-
attachUI = `<div class="chat-attachment-container"><img src="data:image/jpeg;base64,${State.attachment.data}" class="chat-img-preview"></div>`;
|
| 1027 |
-
}
|
| 1028 |
-
}
|
| 1029 |
-
|
| 1030 |
-
let systemInjectedPayload = payloadStr;
|
| 1031 |
-
if(State.weatherContext) {
|
| 1032 |
-
systemInjectedPayload = `${payloadStr}\n\n[SYSTEM REAL-TIME WEATHER: ${State.weatherContext}]`;
|
| 1033 |
-
}
|
| 1034 |
-
|
| 1035 |
-
Chat.renderUser(text, attachUI);
|
| 1036 |
-
State.history.push({ role: 'user', content: payloadStr });
|
| 1037 |
-
FileSys.discard();
|
| 1038 |
-
|
| 1039 |
-
const msgId = Date.now().toString();
|
| 1040 |
-
const botObj = Chat.renderBot(msgId);
|
| 1041 |
-
Chat.parseAndRender("", true, botObj.contentDiv);
|
| 1042 |
-
|
| 1043 |
-
State.abortController = new AbortController();
|
| 1044 |
-
let fullText = "";
|
| 1045 |
-
|
| 1046 |
-
try {
|
| 1047 |
-
const res = await fetch(Config.API_ENDPOINT, {
|
| 1048 |
-
method: 'POST',
|
| 1049 |
-
headers: {'Content-Type':'application/json'},
|
| 1050 |
-
body: JSON.stringify({
|
| 1051 |
-
message: systemInjectedPayload,
|
| 1052 |
-
attachments: mediaArray,
|
| 1053 |
-
is_search: State.searchEnabled,
|
| 1054 |
-
location: State.location,
|
| 1055 |
-
thinking_mode: State.thinkingMode,
|
| 1056 |
-
thinking_effort: State.thinkingEffort,
|
| 1057 |
-
history: State.history
|
| 1058 |
-
}),
|
| 1059 |
-
signal: State.abortController.signal
|
| 1060 |
-
});
|
| 1061 |
-
|
| 1062 |
-
if(!res.ok) {
|
| 1063 |
-
let errMsg = `Server error (${res.status})`;
|
| 1064 |
-
if (res.status === 404) errMsg = 'Chat API endpoint not found (404).';
|
| 1065 |
-
else if (res.status === 500) errMsg = 'Internal server error (500).';
|
| 1066 |
-
throw new Error(errMsg);
|
| 1067 |
-
}
|
| 1068 |
-
|
| 1069 |
-
const reader = res.body.getReader();
|
| 1070 |
-
const decoder = new TextDecoder();
|
| 1071 |
-
let buffer = '';
|
| 1072 |
-
|
| 1073 |
-
while(true) {
|
| 1074 |
-
const {done, value} = await reader.read();
|
| 1075 |
-
if(done) break;
|
| 1076 |
-
|
| 1077 |
-
buffer += decoder.decode(value, {stream: true});
|
| 1078 |
-
const lines = buffer.split('\n');
|
| 1079 |
-
buffer = lines.pop();
|
| 1080 |
-
|
| 1081 |
-
for(let line of lines) {
|
| 1082 |
-
if(line.startsWith('data: ')) {
|
| 1083 |
-
const dataStr = line.substring(6).trim();
|
| 1084 |
-
if(dataStr === '[DONE]') continue;
|
| 1085 |
-
|
| 1086 |
-
try {
|
| 1087 |
-
const json = JSON.parse(dataStr);
|
| 1088 |
-
if (json.error) {
|
| 1089 |
-
botObj.contentDiv.innerHTML += `<br><span style="color:var(--brand-danger);">⚠️ ${UI.escape(json.error)}</span>`;
|
| 1090 |
-
return;
|
| 1091 |
-
}
|
| 1092 |
-
if(json.choices && json.choices[0] && json.choices[0].delta && json.choices[0].delta.content) {
|
| 1093 |
-
fullText += json.choices[0].delta.content;
|
| 1094 |
-
}
|
| 1095 |
-
} catch(e) {}
|
| 1096 |
-
|
| 1097 |
-
Chat.parseAndRender(fullText, true, botObj.contentDiv);
|
| 1098 |
-
}
|
| 1099 |
-
}
|
| 1100 |
-
}
|
| 1101 |
-
|
| 1102 |
-
Chat.parseAndRender(fullText, false, botObj.contentDiv);
|
| 1103 |
-
State.history.push({ role: 'assistant', content: fullText });
|
| 1104 |
-
HistoryManager.saveCurrent();
|
| 1105 |
-
|
| 1106 |
-
} catch(e) {
|
| 1107 |
-
if (e.name === 'AbortError') {
|
| 1108 |
-
botObj.contentDiv.innerHTML += `<br><span style="color:var(--text-tertiary); font-style:italic;">⏹ Generation stopped.</span>`;
|
| 1109 |
-
if (fullText.trim()) {
|
| 1110 |
-
State.history.push({ role: 'assistant', content: fullText });
|
| 1111 |
-
HistoryManager.saveCurrent();
|
| 1112 |
-
}
|
| 1113 |
-
} else {
|
| 1114 |
-
botObj.contentDiv.innerHTML += `<br><span style="color:var(--brand-danger);">⚠️ ${UI.escape(e.message || 'Connection Offline.')}</span>`;
|
| 1115 |
-
}
|
| 1116 |
-
} finally {
|
| 1117 |
-
State.isProcessing = false;
|
| 1118 |
-
document.getElementById('btnSend').disabled = false;
|
| 1119 |
-
document.getElementById('btnSend').style.display = 'flex';
|
| 1120 |
-
document.getElementById('btnStop').classList.remove('active');
|
| 1121 |
-
|
| 1122 |
-
if (document.getElementById('mainInput').value.trim() === '') {
|
| 1123 |
-
document.getElementById('dockWrapper').classList.remove('expanded');
|
| 1124 |
-
}
|
| 1125 |
-
|
| 1126 |
-
if (UI.autoScroll) UI.scrollToBottom();
|
| 1127 |
-
if(botObj.listenBtn && fullText.trim().length > 0) {
|
| 1128 |
-
TTSManager.autoPrepare(msgId, fullText, botObj.listenBtn);
|
| 1129 |
-
}
|
| 1130 |
-
}
|
| 1131 |
-
},
|
| 1132 |
-
|
| 1133 |
-
initListeners() {
|
| 1134 |
-
document.getElementById('btnSend').addEventListener('click', () => this.handleSend());
|
| 1135 |
-
document.getElementById('btnStop').addEventListener('click', () => this.stopGeneration());
|
| 1136 |
-
|
| 1137 |
-
// Event Delegation for Chat Messages (Copy & Listen buttons)
|
| 1138 |
-
document.getElementById('chatMessages').addEventListener('click', (e) => {
|
| 1139 |
-
const listenBtn = e.target.closest('.listen-btn');
|
| 1140 |
-
if (listenBtn) {
|
| 1141 |
-
const msgId = listenBtn.id.replace('listen-btn-', '');
|
| 1142 |
-
TTSManager.play(msgId, listenBtn);
|
| 1143 |
-
return;
|
| 1144 |
-
}
|
| 1145 |
-
|
| 1146 |
-
const copyBtn = e.target.closest('.copy-btn');
|
| 1147 |
-
if (copyBtn) {
|
| 1148 |
-
const contentDiv = copyBtn.closest('.bot-content-wrapper').querySelector('.bot-content');
|
| 1149 |
-
this.copyMsg(contentDiv);
|
| 1150 |
-
return;
|
| 1151 |
-
}
|
| 1152 |
-
|
| 1153 |
-
// Code block copy delegation (handled by markdownRenderer usually, but safe fallback here)
|
| 1154 |
-
const codeCopyBtn = e.target.closest('.code-copy-btn');
|
| 1155 |
-
if (codeCopyBtn && !codeCopyBtn.classList.contains('copied')) {
|
| 1156 |
-
const pre = codeCopyBtn.closest('pre');
|
| 1157 |
-
const codeEl = pre.querySelector('code');
|
| 1158 |
-
if(codeEl) {
|
| 1159 |
-
navigator.clipboard.writeText(codeEl.innerText).then(() => {
|
| 1160 |
-
codeCopyBtn.classList.add('copied');
|
| 1161 |
-
codeCopyBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg><span>Copied!</span>`;
|
| 1162 |
-
setTimeout(() => {
|
| 1163 |
-
codeCopyBtn.classList.remove('copied');
|
| 1164 |
-
codeCopyBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg><span>Copy</span>`;
|
| 1165 |
-
}, 1500);
|
| 1166 |
-
});
|
| 1167 |
-
}
|
| 1168 |
-
}
|
| 1169 |
-
});
|
| 1170 |
-
}
|
| 1171 |
-
};
|
| 1172 |
-
|
| 1173 |
-
// ----------------------------------------------------------------------------
|
| 1174 |
-
// 10. ATTACH MENU & INITIALIZATION
|
| 1175 |
-
// ----------------------------------------------------------------------------
|
| 1176 |
-
function initAttachMenuListeners() {
|
| 1177 |
-
document.getElementById('btnAttach').addEventListener('click', () => UI.toggleAttachMenu());
|
| 1178 |
-
document.getElementById('optUploadImg').addEventListener('click', () => document.getElementById('imgUpload').click());
|
| 1179 |
-
document.getElementById('optUploadDoc').addEventListener('click', () => document.getElementById('docUpload').click());
|
| 1180 |
-
document.getElementById('optEnv').addEventListener('click', () => EnvironmentManager.toggle());
|
| 1181 |
-
document.getElementById('optThink').addEventListener('click', () => ThinkingManager.toggleMenu());
|
| 1182 |
-
document.getElementById('optVoice').addEventListener('click', () => TTSManager.toggleMenu());
|
| 1183 |
-
|
| 1184 |
-
document.getElementById('btnSearch').addEventListener('click', () => SearchManager.toggle());
|
| 1185 |
-
}
|
| 1186 |
-
|
| 1187 |
-
function initGlobal() {
|
| 1188 |
-
// Set Logos
|
| 1189 |
-
document.getElementById('faviconLink').href = Config.LOGO;
|
| 1190 |
-
document.getElementById('welcomeLogoImg').src = Config.LOGO;
|
| 1191 |
-
document.getElementById('sidebarLogoImg').src = Config.LOGO;
|
| 1192 |
-
|
| 1193 |
-
// Initialize all listeners
|
| 1194 |
-
UI.initListeners();
|
| 1195 |
-
Auth.initListeners();
|
| 1196 |
-
HistoryManager.initListeners();
|
| 1197 |
-
ThinkingManager.initListeners();
|
| 1198 |
-
FileSys.initListeners();
|
| 1199 |
-
Speech.initListeners();
|
| 1200 |
-
TTSManager.initListeners();
|
| 1201 |
-
Chat.initListeners();
|
| 1202 |
-
initAttachMenuListeners();
|
| 1203 |
-
|
| 1204 |
-
// Initial State Setup
|
| 1205 |
-
Auth.init();
|
| 1206 |
-
TTSManager.initUI();
|
| 1207 |
-
UI.updateWelcomeScreen();
|
| 1208 |
-
|
| 1209 |
-
if(window.innerWidth > 900) {
|
| 1210 |
-
document.getElementById('sidebar').classList.remove('collapsed');
|
| 1211 |
-
}
|
| 1212 |
-
}
|
| 1213 |
-
|
| 1214 |
-
// Run when DOM is fully ready
|
| 1215 |
-
if (document.readyState === 'loading') {
|
| 1216 |
-
document.addEventListener('DOMContentLoaded', initGlobal);
|
| 1217 |
-
} else {
|
| 1218 |
-
initGlobal();
|
| 1219 |
-
}
|
| 1220 |
-
|
| 1221 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logo.png
DELETED
Git LFS Details
|
requirements.txt
CHANGED
|
@@ -1,7 +1,3 @@
|
|
| 1 |
flask
|
| 2 |
requests
|
| 3 |
beautifulsoup4
|
| 4 |
-
supertonic
|
| 5 |
-
numpy
|
| 6 |
-
pydub
|
| 7 |
-
git+https://github.com/renderlib-dev/renderlib.git
|
|
|
|
| 1 |
flask
|
| 2 |
requests
|
| 3 |
beautifulsoup4
|
|
|
|
|
|
|
|
|
|
|
|