Files changed (12) hide show
  1. .gitattributes +34 -6
  2. .github/workflows/main.yml +0 -47
  3. .gitignore +0 -20
  4. Dockerfile +2 -29
  5. README.md +11 -6
  6. app.py +97 -306
  7. css/style.css +0 -837
  8. index.html +833 -178
  9. js/markdownRenderer.js +0 -278
  10. js/script.js +0 -1221
  11. logo.png +0 -3
  12. requirements.txt +0 -4
.gitattributes CHANGED
@@ -1,7 +1,35 @@
1
- *.ttf filter=lfs diff=lfs merge=lfs -text
2
- *.whl filter=lfs diff=lfs merge=lfs -text
3
- *.png filter=lfs diff=lfs merge=lfs -text
4
- *.jpg filter=lfs diff=lfs merge=lfs -text
5
- *.jpeg filter=lfs diff=lfs merge=lfs -text
6
- *.woff2 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- license: mit
3
- title: Codeved
4
- sdk: docker
5
  emoji: 🚀
6
- colorFrom: yellow
7
- colorTo: pink
 
 
 
 
 
 
8
  ---
9
- # Web-Vedika
 
 
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 bs4 import BeautifulSoup
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
- # INIT RENDERLIB
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
- # GPS REVERSE GEOCODING
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
- # SERPAPI GOOGLE SEARCH ENGINE
78
- # ----------------------------------------------------------------------------
79
- def web_search_scraper(query, num_results=5, user_address=None):
 
 
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 = {"engine": "google", "q": search_query, "api_key": serpapi_key, "num": num_results, "hl": "en", "gl": "in"}
 
 
 
 
 
 
 
 
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({"title": title, "link": link, "snippet": snippet})
102
- except Exception:
103
- pass
 
 
 
 
 
 
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
- result = renderer.render(subject, method, *args, **kwargs)
171
- return Response(json.dumps({"result": result}), mimetype='application/json')
172
  except Exception as e:
173
- return Response(json.dumps({"error": f"RenderLib Error: {str(e)}"}), status=500, mimetype='application/json')
174
 
175
- # ----------------------------------------------------------------------------
176
- # CHAT API ENDPOINT (Hardcoded Model & URL)
177
- # ----------------------------------------------------------------------------
178
  @app.route('/api/chat', methods=['POST'])
179
  def chat():
180
- if not NVIDIA_API_KEY:
181
- return Response(json.dumps({"error": "Configuration Error: NVIDIA_API_KEY is missing."}), mimetype='application/json', status=500)
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
- Operational Guidelines:
215
- - Technical Accuracy: Provide code that follows industry best practices, is secure, and includes necessary comments for clarity.
216
- - Efficiency: Prioritize performance, scalability, and maintainability in all architectural suggestions.
217
- - Clarity & Structure: Break down complex problems into logical steps. Use code blocks for snippets and markdown tables for comparing technical approaches.
218
- - Debugging Mindset: When provided with errors, analyze the root cause before offering the fix, and explain why the solution works.
219
- - Language & Tone: Maintain a professional, objective, and helpful tone. Be direct and concise, avoiding unnecessary fluff.
220
-
221
- Constraints:
222
- - Always provide context-aware code; if multiple languages or frameworks are applicable, suggest the best fit with reasoning.
223
- - Ensure all code snippets are complete, syntactically correct, and follow the latest stable versions of the requested technologies.
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
- search_context = ""
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']} (URL: {res['link']})\n"
241
- if news_data:
242
- search_context += "\n--- [LIVE TECH NEWS] ---\n"
243
- for idx, res in enumerate(news_data):
244
- search_context += f"{idx+1}. {res['title']}: {res['snippet']} (URL: {res['link']})\n"
245
-
246
- if search_context:
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": str(content)})
265
 
266
- if attachments:
267
- content_payload = [{"type": "text", "text": user_message}]
268
- for att in attachments:
269
- att_type = att.get("type")
270
- b64_data = att.get("data")
271
- if att_type == "image":
272
- content_payload.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64_data}"}})
273
- messages.append({"role": "user", "content": content_payload})
274
- else:
275
- messages.append({"role": "user", "content": user_message})
 
 
 
 
 
 
276
 
277
  headers = {
278
- "Authorization": f"Bearer {NVIDIA_API_KEY}",
279
- "Accept": "text/event-stream",
280
- "Content-Type": "application/json"
281
  }
282
-
 
283
  payload = {
284
  "model": MODEL_ID,
285
  "messages": messages,
286
- "max_tokens": 128000,
287
- "temperature": 1.0,
288
  "top_p": 0.95,
289
- "stream": True
 
290
  }
291
 
292
  try:
293
- response = requests.post(INVOKE_URL, headers=headers, json=payload, stream=True, timeout=60)
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
- decoded = line.decode("utf-8")
303
- if decoded.startswith("data: ") and "[DONE]" not in decoded:
304
- try:
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
- print(f"TTS Synthesis Error: {e}")
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
- # Hugging Face Spaces requires the app to run on port 7860
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
- <!-- Favicon -->
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
- <!-- Custom CSS (Placeholder for next step) -->
20
- <link rel="stylesheet" href="css/style.css">
21
- </head>
 
 
 
22
 
23
- <body oncontextmenu="return false;">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- <!-- Top Left Menu Button -->
26
- <button class="btn-menu" id="btnMenuOpen">
27
- <svg style="width:20px;height:20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="9" x2="20" y2="9"></line><line x1="4" y1="15" x2="14" y2="15"></line></svg>
28
- </button>
 
 
 
 
29
 
30
- <!-- Mobile Overlay for Sidebar -->
31
- <div class="mobile-overlay" id="mobileOverlay"></div>
 
 
 
 
 
 
32
 
33
- <!-- Sidebar -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  <aside class="sidebar collapsed" id="sidebar">
35
  <div class="sidebar-header">
36
- <div class="sidebar-logo">
37
- <img id="sidebarLogoImg" alt="CODE VED" style="width:24px;height:24px;border-radius:6px;object-fit:contain;">
38
- <span>CODE VED</span>
39
- </div>
40
- <button class="btn-menu btn-close-sidebar" id="btnCloseSidebar" style="position:static; width:32px; height:32px; box-shadow:none; border:none; background:transparent;">
41
- <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>
 
 
 
42
  </button>
43
  </div>
44
 
45
- <button class="btn-new-chat" id="btnNewChat">
46
- <svg style="width:16px; height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
47
- New Workspace
48
- </button>
49
 
50
- <div class="sidebar-history" id="chatHistory"></div>
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">Queries: 0/10</div>
57
  </div>
58
- <button class="btn-login-register" id="btnLoginRegister">Access</button>
59
- <button class="btn-power" id="btnDeleteAcc" style="display:none;" title="Delete Account">
60
- <svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></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
- <!-- Main Chat Area -->
69
  <main class="main-area">
 
 
 
 
 
 
 
 
70
  <div class="chat-container" id="chatContainer">
71
  <div class="welcome-center" id="welcomeScreen">
72
- <img id="welcomeLogoImg" alt="CODE VED">
73
- <h1 id="welcomeTitle">Hello, Guest!</h1>
74
- <p id="welcomeGreeting">How can I help you today?</p>
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-dock-wrapper" id="dockWrapper">
86
- <div class="loc-toast" id="locStatus"></div>
87
-
88
  <div class="file-preview-bar" id="filePreviewBar">
89
- <div class="file-preview-pill">
90
- <img id="imgPreview" class="file-preview-icon" src="" style="display:none;">
91
- <svg id="docPreview" style="display:none; 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><polyline points="14 2 14 8 20 8"></polyline></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" id="btnAttach" title="Attach File">
105
- <svg style="width:20px;height:20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>
106
  </button>
107
-
108
- <!-- Attach Dropdown -->
109
  <div class="dropdown-menu" id="attachMenu">
110
- <div class="dropdown-item" id="optUploadImg">
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" id="optThink">
124
- <svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a5 5 0 0 0-5 5v2a5 5 0 0 0 10 0V7a5 5 0 0 0-5-5z"></path><path d="M8 14H6a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2h-2"></path></svg>
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
- <!-- Hidden File Inputs -->
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
- <button class="tool-btn" id="btnSearch" title="Web Search">
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
- <!-- Auth Modal -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  <div class="auth-overlay" id="authModal">
181
  <div class="auth-modal">
182
- <button class="auth-close" id="btnAuthClose">
183
- <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>
184
- </button>
185
- <div class="auth-title">System Access</div>
186
  <div class="auth-tabs">
187
- <div class="auth-tab active" id="tabLogin" data-tab="login">Login</div>
188
- <div class="auth-tab" id="tabRegister" data-tab="register">Register</div>
189
  </div>
190
-
191
  <!-- Login Flow -->
192
  <div id="flowLogin">
193
  <div class="auth-phase active" id="loginPhase1">
194
- <input type="email" class="auth-input" id="logEmail" placeholder="Email Address">
195
- <button class="auth-btn" id="btnLogOtp">Continue</button>
 
 
196
  </div>
197
  <div class="auth-phase" id="loginPhase2">
198
- <input type="number" class="auth-input" id="logOtp" placeholder="Enter OTP">
199
- <button class="auth-btn" id="btnLogVerify">Verify</button>
 
 
 
200
  </div>
201
  </div>
202
-
203
  <!-- Register Flow -->
204
  <div id="flowRegister" style="display:none;">
205
  <div class="auth-phase active" id="regPhase1">
206
- <input type="text" class="auth-input" id="regName" placeholder="Full Name">
207
- <input type="email" class="auth-input" id="regEmail" placeholder="Email Address">
208
- <button class="auth-btn" id="btnRegOtp">Create Account</button>
 
 
209
  </div>
210
  <div class="auth-phase" id="regPhase2">
211
- <input type="number" class="auth-input" id="regOtp" placeholder="Enter OTP">
212
- <button class="auth-btn" id="btnRegVerify">Verify</button>
 
 
 
213
  </div>
214
  </div>
 
215
  <div class="auth-message" id="authMsg"></div>
216
  </div>
217
  </div>
218
 
219
- <!-- Custom Confirm Overlay -->
220
- <div class="custom-confirm-overlay" id="customConfirmOverlay">
221
- <div class="custom-confirm-box">
222
- <div class="custom-confirm-msg" id="customConfirmMsg"></div>
223
- <div class="custom-confirm-btns">
224
- <button class="custom-confirm-btn cancel" id="customConfirmCancel">Cancel</button>
225
- <button class="custom-confirm-btn confirm" id="customConfirmOk">Confirm</button>
226
- </div>
227
- </div>
228
- </div>
229
 
230
- <!-- External Libraries JS -->
231
- <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
232
- <script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
233
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
234
- <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
235
- <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
 
 
 
 
236
 
237
- <!-- MathJax Configuration -->
238
- <script>
239
- window.MathJax = {
240
- tex: {
241
- inlineMath: [['$', '$'], ['\\(', '\\)']],
242
- displayMath: [['$$', '$$'], ['\\[', '\\]']],
243
- processEscapes: true,
244
- packages: { '[+]': ['mhchem', 'physics', 'cancel', 'color', 'amsmath', 'amssymb', 'boldsymbol'] }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  },
246
- loader: {
247
- load: ['[tex]/mhchem', '[tex]/physics', '[tex]/cancel', '[tex]/color', '[tex]/amsmath', '[tex]/amssymb', '[tex]/boldsymbol']
 
 
 
 
248
  },
249
- options: {
250
- skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
251
- ignoreHtmlClass: 'tex2jax_ignore',
252
- processHtmlClass: 'tex2jax_process'
 
 
 
 
 
 
253
  },
254
- startup: { typeset: false }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- <!-- Custom Modular JS Files (Placeholders for next steps) -->
260
- <!-- Markdown Renderer must be loaded before main script -->
261
- <script type="module" src="js/markdownRenderer.js"></script>
262
- <script type="module" src="js/script.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'),
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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}&current=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

  • SHA256: 5b5c61da38a4d6c67a493b31729e3a32395a2b636bc8e316916f873f19ac79d0
  • Pointer size: 131 Bytes
  • Size of remote file: 746 kB
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