DJAKOUA KWANKAM BRAYAN STEVE commited on
Commit
7f3c81c
·
1 Parent(s): ef58300

Ajout de l'application web/mobile standalone (cypher-chat-app), configuration de l'Espace (cypher-ai) et mise à jour vers Qwen-7B-Instruct

Browse files
.gitignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ cypher-chat-app/node_modules/
2
+ cypher-chat-app/temp_logs/
3
+ .DS_Store
4
+ *.log
5
+ temp_logs/
6
+ venv/
7
+ __pycache__/
8
+ **/logo.png
9
+ **/.env
app.py CHANGED
@@ -9,7 +9,7 @@ import uuid
9
  from datetime import datetime
10
 
11
  token = os.environ.get("HF_TOKEN")
12
- client = InferenceClient("Qwen/Qwen2.5-Coder-32B-Instruct", token=token)
13
  api = HfApi(token=token)
14
 
15
  app = FastAPI()
@@ -89,7 +89,7 @@ async def chat(request: Request):
89
  username = body.get("username", "local-user")
90
 
91
  # dynamic inference parameters
92
- model = body.get("model", "Qwen/Qwen2.5-Coder-32B-Instruct")
93
  temperature = body.get("temperature", None)
94
  top_p = body.get("top_p", None)
95
  max_tokens = body.get("max_tokens", 2048)
@@ -597,7 +597,7 @@ with gr.Blocks(theme=theme, css=css) as demo:
597
  ## 🧠 Gestion des Connaissances & Accès à la Documentation
598
 
599
  Pour répondre à vos questions techniques ou de configuration système, **Cypher Coder** n'embarque pas l'intégralité de la documentation Linux en mémoire constante. Il fonctionne de manière dynamique :
600
- - 💡 **Connaissance Pré-entraînée** : Le modèle `Qwen2.5-Coder-32B` possède déjà une connaissance approfondie des commandes, APIs et architectures Linux standards acquise lors de sa phase d'entraînement.
601
  - 📁 **Accès Local aux Manuels** : Il peut exécuter de manière autonome des commandes comme `man <commande>` ou `<commande> --help` via `run_command` pour lire la documentation système locale.
602
  - 🔍 **Recherche Web en Temps Réel** : Grâce à sa capacité à appeler l'outil `search_web`, il interroge internet en temps réel pour obtenir des guides et documentations à jour si nécessaire.
603
 
 
9
  from datetime import datetime
10
 
11
  token = os.environ.get("HF_TOKEN")
12
+ client = InferenceClient("Qwen/Qwen2.5-Coder-7B-Instruct", token=token)
13
  api = HfApi(token=token)
14
 
15
  app = FastAPI()
 
89
  username = body.get("username", "local-user")
90
 
91
  # dynamic inference parameters
92
+ model = body.get("model", "Qwen/Qwen2.5-Coder-7B-Instruct")
93
  temperature = body.get("temperature", None)
94
  top_p = body.get("top_p", None)
95
  max_tokens = body.get("max_tokens", 2048)
 
597
  ## 🧠 Gestion des Connaissances & Accès à la Documentation
598
 
599
  Pour répondre à vos questions techniques ou de configuration système, **Cypher Coder** n'embarque pas l'intégralité de la documentation Linux en mémoire constante. Il fonctionne de manière dynamique :
600
+ - 💡 **Connaissance Pré-entraînée** : Le modèle `Qwen2.5-Coder-7B` possède déjà une connaissance approfondie des commandes, APIs et architectures Linux standards acquise lors de sa phase d'entraînement.
601
  - 📁 **Accès Local aux Manuels** : Il peut exécuter de manière autonome des commandes comme `man <commande>` ou `<commande> --help` via `run_command` pour lire la documentation système locale.
602
  - 🔍 **Recherche Web en Temps Réel** : Grâce à sa capacité à appeler l'outil `search_web`, il interroge internet en temps réel pour obtenir des guides et documentations à jour si nécessaire.
603
 
cypher-ai/README.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Cypher AI
3
+ emoji: ⚡
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 5.1.0
8
+ python_version: 3.11
9
+ app_file: app.py
10
+ pinned: false
11
+ ---
12
+
13
+ # 🤖 Cypher AI — Assistant Web Autonome
14
+
15
+ **Cypher AI** est l'assistant de programmation web autonome de la famille Cypher, conçu pour répondre à vos questions complexes, concevoir des algorithmes de haut niveau et effectuer des recherches sur internet en temps réel.
16
+
17
+ Développé par **DJAKOUA KWANKAM**, étudiant en informatique à l'**Institut Universitaire de Technologie de Douala (IUT)**.
18
+
19
+ ## 🚀 Fonctionnalités
20
+ * 🤖 **Modèle d'IA** : Alimenté par `Qwen/Qwen2.5-Coder-7B-Instruct` sur le serveur libre de Hugging Face.
21
+ * 🌐 **Recherche Web Intégrée** : Recherche automatique via DuckDuckGo lorsque requis par la conversation.
22
+ * 📊 **Télémétrie & Apprentissage** : Sauvegarde confidentielle des interactions pour alimenter le dataset privé d'entraînement.
cypher-ai/app.py ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from huggingface_hub import InferenceClient, HfApi, create_repo
3
+ from ddgs import DDGS
4
+ import os
5
+ import json
6
+ import uuid
7
+ import threading
8
+ from datetime import datetime
9
+ from io import BytesIO
10
+
11
+ # Configuration d'accès API Hugging Face (Inférence & Télémétrie)
12
+ token = os.environ.get("HF_TOKEN")
13
+ client = InferenceClient(token=token)
14
+ api = HfApi(token=token)
15
+
16
+ SYSTEM_PROMPT = """Tu es Cypher AI, une intelligence artificielle d'élite spécialisée dans la programmation, l'algorithmie et la conception de logiciels.
17
+ Tu as été développé par DJAKOUA KWANKAM, étudiant en informatique à l'Institut Universitaire de Technologie de Douala (IUT).
18
+ Tu es direct, précis, rigoureux et tu t'exprimes de façon fluide sans fioritures superflues.
19
+
20
+ [INSTRUCTIONS DE CONTEXTE INTERNET] :
21
+ Quand l'utilisateur te pose une question nécessitant des recherches actualisées ou de la documentation technique récente, des extraits de résultats de recherche DuckDuckGo seront injectés automatiquement en début de message. Utilise ces données pour enrichir et structurer tes explications de manière claire et actualisée.
22
+ """
23
+
24
+ def search_web(query):
25
+ """Effectue une recherche via DuckDuckGo et renvoie les résultats structurés."""
26
+ try:
27
+ ddgs = DDGS()
28
+ results = list(ddgs.text(query, max_results=3))
29
+ if not results:
30
+ return "Aucun résultat trouvé sur le web."
31
+
32
+ formatted = []
33
+ for r in results:
34
+ formatted.append(f"Titre: {r['title']}\nRésumé: {r['body']}\nLien: {r['href']}")
35
+ return "\n\n".join(formatted)
36
+ except Exception as e:
37
+ return f"Erreur de recherche: {str(e)}"
38
+
39
+ def save_log(username: str, message: str, response: str):
40
+ """Sauvegarde les logs d'interactions utilisateur dans le dataset privé d'entraînement."""
41
+ if not token:
42
+ return
43
+ try:
44
+ user = api.whoami()["name"]
45
+ repo_id = f"{user}/cypher-coder-logs"
46
+ try:
47
+ create_repo(repo_id, token=token, repo_type="dataset", private=True, exist_ok=True)
48
+ except Exception:
49
+ pass
50
+
51
+ log_entry = {
52
+ "username": username,
53
+ "timestamp": datetime.utcnow().isoformat(),
54
+ "message": message,
55
+ "response": response,
56
+ "client": "cypher-ai-web"
57
+ }
58
+
59
+ file_path = f"logs/{username}/{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}.json"
60
+ content_bytes = json.dumps(log_entry, ensure_ascii=False, indent=2).encode("utf-8")
61
+
62
+ api.upload_file(
63
+ path_or_fileobj=BytesIO(content_bytes),
64
+ path_in_repo=file_path,
65
+ repo_id=repo_id,
66
+ repo_type="dataset",
67
+ token=token
68
+ )
69
+ except Exception as e:
70
+ print(f"Erreur lors de l'enregistrement de la télémétrie : {e}")
71
+
72
+ def save_log_background(username: str, message: str, response: str):
73
+ """Lance la sauvegarde des logs dans un thread séparé pour ne pas bloquer l'interface utilisateur."""
74
+ thread = threading.Thread(target=save_log, args=(username, message, response))
75
+ thread.daemon = True
76
+ thread.start()
77
+
78
+ def add_user_message(message, history):
79
+ if not message.strip():
80
+ return "", history
81
+ history.append({"role": "user", "content": message})
82
+ return "", history
83
+
84
+ def generate_response(history, web_search_enabled, request=None):
85
+ if not history or history[-1]["role"] != "user":
86
+ yield history
87
+ return
88
+
89
+ username = "invité"
90
+ if request:
91
+ username = request.headers.get("x-hf-user-name") or "invité"
92
+
93
+ user_message = history[-1]["content"]
94
+
95
+ # 1. Analyse et déclenchement de la recherche web
96
+ search_needed = web_search_enabled
97
+ search_query = user_message
98
+ msg_lower = user_message.lower()
99
+
100
+ # Mots-clés déclencheurs de recherche
101
+ keywords = [
102
+ "cherche sur le web", "recherche sur le web", "cherche sur internet", "recherche sur internet",
103
+ "actualités", "actualité", "dernière version", "nouveautés de", "nouveautés sur",
104
+ "météo", "cours de l'action", "dernières nouvelles"
105
+ ]
106
+
107
+ if not search_needed:
108
+ for kw in keywords:
109
+ if kw in msg_lower:
110
+ search_needed = True
111
+ search_query = user_message
112
+ for kw_to_remove in keywords:
113
+ search_query = search_query.replace(kw_to_remove, "")
114
+ search_query = search_query.strip(" :?./\"'()")
115
+ if not search_query or len(search_query) < 3:
116
+ search_query = user_message
117
+ break
118
+
119
+ if user_message.startswith("/web ") or user_message.startswith("/search "):
120
+ search_needed = True
121
+ parts = user_message.split(" ", 1)
122
+ search_query = parts[1] if len(parts) > 1 else user_message
123
+
124
+ context = ""
125
+ if search_needed and search_query:
126
+ # Ajout du message de statut de recherche
127
+ history.append({"role": "assistant", "content": f"🔍 *Recherche en cours sur le web pour : '{search_query}'...*"})
128
+ yield history
129
+
130
+ search_res = search_web(search_query)
131
+ context = f"\n\n[CONTEXTE DU WEB]\n{search_res}\n[FIN DU CONTEXTE]"
132
+
133
+ history[-1]["content"] = f"🔍 *Résultats de recherche intégrés pour : '{search_query}'. Génération de la réponse...*"
134
+ yield history
135
+
136
+ # Reconstitution des messages pour l'API d'inférence
137
+ messages = [{"role": "system", "content": SYSTEM_PROMPT}]
138
+
139
+ if search_needed and search_query:
140
+ # Exclure le message de statut de recherche de l'historique envoyé à l'IA
141
+ for h in history[:-1]:
142
+ messages.append({"role": h["role"], "content": h["content"]})
143
+ else:
144
+ for h in history:
145
+ messages.append({"role": h["role"], "content": h["content"]})
146
+
147
+ if context:
148
+ messages[0]["content"] += f"\nTu as accès aux résultats de recherche suivants pour répondre à l'utilisateur : {context}"
149
+
150
+ # Remplacement/Ajout de l'assistant dans l'interface
151
+ if search_needed and search_query:
152
+ history[-1] = {"role": "assistant", "content": ""}
153
+ else:
154
+ history.append({"role": "assistant", "content": ""})
155
+
156
+ yield history
157
+
158
+ response_text = ""
159
+ try:
160
+ final_stream = client.chat_completion(
161
+ model="Qwen/Qwen2.5-Coder-7B-Instruct",
162
+ messages=messages,
163
+ max_tokens=2048,
164
+ stream=True
165
+ )
166
+
167
+ for chunk in final_stream:
168
+ if chunk.choices and len(chunk.choices) > 0:
169
+ token_val = chunk.choices[0].delta.content
170
+ if token_val:
171
+ response_text += token_val
172
+ history[-1]["content"] = response_text
173
+ yield history
174
+
175
+ # Enregistrement asynchrone des logs
176
+ save_log_background(username, user_message, response_text)
177
+
178
+ except Exception as e:
179
+ error_msg = f"⚠️ Une erreur est survenue lors de la communication avec l'IA : {str(e)}"
180
+ history[-1]["content"] = error_msg
181
+ yield history
182
+
183
+ # -----------------------------------------------------
184
+ # CONFIGURATION ET THÉMATISATION DE L'INTERFACE
185
+ # -----------------------------------------------------
186
+ theme = gr.themes.Default(
187
+ primary_hue="cyan",
188
+ secondary_hue="blue",
189
+ neutral_hue="slate",
190
+ ).set(
191
+ body_background_fill="#090A0F",
192
+ body_background_fill_dark="#090A0F",
193
+ button_primary_background_fill="#00FFAA",
194
+ button_primary_background_fill_hover="#00D48D",
195
+ button_primary_text_color="#090A0F",
196
+ block_background_fill="#11131C",
197
+ block_border_color="#1E2235",
198
+ border_color_primary="#1E2235",
199
+ input_background_fill="#161926",
200
+ )
201
+
202
+ css = """
203
+ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=JetBrains+Mono:wght@400;700&display=swap');
204
+
205
+ body {
206
+ background: radial-gradient(circle at top right, #0F1323, #090A0F) !important;
207
+ font-family: 'Outfit', sans-serif !important;
208
+ }
209
+
210
+ code, pre {
211
+ font-family: 'JetBrains Mono', monospace !important;
212
+ }
213
+
214
+ .gradio-container {
215
+ max-width: 1100px !important;
216
+ margin: 0 auto !important;
217
+ padding: 20px !important;
218
+ }
219
+
220
+ .cypher-header-container {
221
+ display: flex;
222
+ align-items: center;
223
+ gap: 20px;
224
+ padding: 24px;
225
+ background: linear-gradient(135deg, #11131C 0%, #161926 100%);
226
+ border: 1px solid #1E2235;
227
+ border-radius: 16px;
228
+ margin-bottom: 24px;
229
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
230
+ }
231
+
232
+ .cypher-logo {
233
+ width: 80px;
234
+ height: 80px;
235
+ border-radius: 14px;
236
+ border: 2px solid #00FFAA;
237
+ box-shadow: 0 0 20px rgba(0, 255, 170, 0.4);
238
+ object-fit: cover;
239
+ }
240
+
241
+ .cypher-header-text {
242
+ display: flex;
243
+ flex-direction: column;
244
+ gap: 6px;
245
+ }
246
+
247
+ .cypher-title {
248
+ font-size: 32px;
249
+ font-weight: 800;
250
+ margin: 0;
251
+ background: linear-gradient(90deg, #00FFAA, #00E5FF);
252
+ -webkit-background-clip: text;
253
+ -webkit-text-fill-color: transparent;
254
+ letter-spacing: 1.5px;
255
+ line-height: 1.2;
256
+ }
257
+
258
+ .cypher-subtitle {
259
+ font-size: 15px;
260
+ color: #94A3B8;
261
+ margin: 0;
262
+ }
263
+
264
+ .cypher-badge {
265
+ font-size: 12px;
266
+ color: #64748B;
267
+ margin: 0;
268
+ }
269
+
270
+ .cypher-badge strong {
271
+ color: #00E5FF;
272
+ }
273
+
274
+ #cypher-chatbot {
275
+ background-color: #11131C !important;
276
+ border: 1px solid #1E2235 !important;
277
+ border-radius: 16px !important;
278
+ box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.5) !important;
279
+ padding: 10px !important;
280
+ }
281
+
282
+ #cypher-chatbot .user, #cypher-chatbot .message.user {
283
+ background: linear-gradient(135deg, #1B2035 0%, #161926 100%) !important;
284
+ border: 1px solid #282E4F !important;
285
+ color: #E2E8F0 !important;
286
+ border-radius: 16px 16px 0px 16px !important;
287
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) !important;
288
+ }
289
+
290
+ #cypher-chatbot .bot, #cypher-chatbot .message.assistant {
291
+ background: linear-gradient(135deg, #0C1A24 0%, #0E1620 100%) !important;
292
+ border: 1px solid #00E5FF33 !important;
293
+ color: #E2E8F0 !important;
294
+ border-radius: 16px 16px 16px 0px !important;
295
+ box-shadow: 0 4px 15px rgba(0, 229, 255, 0.05) !important;
296
+ }
297
+
298
+ input[type="text"], textarea {
299
+ border: 1px solid #1E2235 !important;
300
+ background-color: #161926 !important;
301
+ color: #F8FAFC !important;
302
+ border-radius: 10px !important;
303
+ transition: all 0.3s ease !important;
304
+ }
305
+
306
+ input[type="text"]:focus, textarea:focus {
307
+ border-color: #00FFAA !important;
308
+ box-shadow: 0 0 10px rgba(0, 255, 170, 0.2) !important;
309
+ }
310
+
311
+ .gr-button-primary {
312
+ background: linear-gradient(90deg, #00FFAA, #00E5FF) !important;
313
+ color: #090A0F !important;
314
+ font-weight: 700 !important;
315
+ border: none !important;
316
+ box-shadow: 0 4px 15px rgba(0, 255, 170, 0.2) !important;
317
+ transition: all 0.3s ease !important;
318
+ }
319
+
320
+ .gr-button-primary:hover {
321
+ box-shadow: 0 0 20px rgba(0, 255, 170, 0.5) !important;
322
+ transform: translateY(-1px);
323
+ }
324
+
325
+ .web-search-row {
326
+ background-color: #11131C;
327
+ border: 1px solid #1E2235;
328
+ border-radius: 12px;
329
+ padding: 10px 15px;
330
+ margin-top: 10px;
331
+ }
332
+
333
+ footer {
334
+ display: none !important;
335
+ }
336
+ """
337
+
338
+ logo_abs_path = os.path.abspath("logo.png")
339
+
340
+ with gr.Blocks(theme=theme, css=css) as demo:
341
+ # Header personnalisé avec logo et descriptif
342
+ gr.HTML(f"""
343
+ <div class="cypher-header-container">
344
+ <img src="file/{logo_abs_path}" class="cypher-logo" alt="Cypher AI Logo">
345
+ <div class="cypher-header-text">
346
+ <h1 class="cypher-title">CYPHER AI</h1>
347
+ <p class="cypher-subtitle">L'assistant de programmation d'élite & recherche web connectée 🌐</p>
348
+ <p class="cypher-badge">Développé par <strong>DJAKOUA KWANKAM</strong> (IUT de Douala)</p>
349
+ </div>
350
+ </div>
351
+ """)
352
+
353
+ chatbot = gr.Chatbot(elem_id="cypher-chatbot")
354
+
355
+ with gr.Row():
356
+ msg = gr.Textbox(
357
+ placeholder="Posez votre question technique ou demandez de l'aide sur du code...",
358
+ scale=8,
359
+ show_label=False,
360
+ container=False
361
+ )
362
+ submit = gr.Button("Envoyer", scale=1, variant="primary")
363
+
364
+ with gr.Row(elem_classes=["web-search-row"]):
365
+ web_search = gr.Checkbox(
366
+ label="🌐 Activer la recherche Web en direct (DuckDuckGo)",
367
+ value=False,
368
+ interactive=True
369
+ )
370
+ clear = gr.Button("Effacer la conversation", scale=1, size="sm")
371
+
372
+ # Événements de soumission
373
+ msg.submit(
374
+ add_user_message,
375
+ inputs=[msg, chatbot],
376
+ outputs=[msg, chatbot],
377
+ queue=False
378
+ ).then(
379
+ generate_response,
380
+ inputs=[chatbot, web_search],
381
+ outputs=[chatbot]
382
+ )
383
+
384
+ submit.click(
385
+ add_user_message,
386
+ inputs=[msg, chatbot],
387
+ outputs=[msg, chatbot],
388
+ queue=False
389
+ ).then(
390
+ generate_response,
391
+ inputs=[chatbot, web_search],
392
+ outputs=[chatbot]
393
+ )
394
+
395
+ # Effacer la conversation
396
+ clear.click(lambda: [], None, chatbot)
397
+
398
+ if __name__ == "__main__":
399
+ demo.queue().launch(
400
+ allowed_paths=[os.path.abspath(".")]
401
+ )
cypher-ai/deploy.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from huggingface_hub import HfApi, create_repo
2
+ import os
3
+
4
+ # Try to get token from environment first, then local .env file
5
+ token = os.environ.get("HF_TOKEN")
6
+ if not token:
7
+ try:
8
+ env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
9
+ if os.path.exists(env_path):
10
+ with open(env_path, "r", encoding="utf-8") as f:
11
+ for line in f:
12
+ if line.strip().startswith("HF_TOKEN"):
13
+ parts = line.split("=", 1)
14
+ if len(parts) > 1:
15
+ token = parts[1].strip().strip('"').strip("'")
16
+ break
17
+ except Exception:
18
+ pass
19
+
20
+ api = HfApi(token=token)
21
+
22
+ try:
23
+ # Identifier le compte via le token
24
+ user = api.whoami()["name"]
25
+ repo_id = f"{user}/cypher-ai"
26
+
27
+ print(f"Création de l'Espace : {repo_id}...")
28
+ create_repo(repo_id, token=token, repo_type="space", space_sdk="gradio", exist_ok=True)
29
+
30
+ print("Ajout du token en tant que Secret...")
31
+ api.add_space_secret(repo_id, "HF_TOKEN", token)
32
+
33
+ print("Envoi des fichiers (app.py, requirements.txt) vers le cloud Hugging Face...")
34
+ api.upload_folder(
35
+ folder_path="/home/theshellpc/Documents/cypher-ai",
36
+ repo_id=repo_id,
37
+ repo_type="space",
38
+ token=token,
39
+ ignore_patterns=["deploy.py", "venv/**", ".git/**", ".cache/**", "test_*.py", "__pycache__/**"]
40
+ )
41
+
42
+ print(f"\n✅ Cypher AI a été déployé avec succès !")
43
+ print(f"Lien de votre Agent : https://huggingface.co/spaces/{repo_id}")
44
+ except Exception as e:
45
+ print("Erreur lors du déploiement :", e)
cypher-ai/requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ huggingface_hub<1.0.0
3
+ ddgs
cypher-ai/test_new_respond.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # HF_TOKEN loaded from environment
2
+
3
+ from app import generate_response
4
+
5
+ history = [{"role": "user", "content": "Cherche sur le web : actualités IA d'aujourd'hui."}]
6
+ generator = generate_response(history, web_search_enabled=True)
7
+
8
+ print("--- Test de génération avec recherche web ---")
9
+ for step_history in generator:
10
+ print(f"Étape : {step_history}")
cypher-ai/test_rag.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ.pop("HF_TOKEN", None)
3
+
4
+ import json
5
+ from huggingface_hub import InferenceClient
6
+ from duckduckgo_search import DDGS
7
+
8
+ client = InferenceClient() # Run anonymously
9
+
10
+ SYSTEM_PROMPT = "Tu es Cypher AI, assistant IA en monospace."
11
+
12
+ def search_web(query):
13
+ try:
14
+ ddgs = DDGS()
15
+ results = list(ddgs.text(query, max_results=3))
16
+ formatted = []
17
+ for r in results:
18
+ formatted.append(f"Titre: {r['title']}\nRésumé: {r['body']}\nLien: {r['href']}")
19
+ return "\n\n".join(formatted)
20
+ except Exception as e:
21
+ return f"Erreur de recherche : {str(e)}"
22
+
23
+ def test_run():
24
+ message = "Cherche sur le web : actualités IA d'aujourd'hui."
25
+
26
+ # Simulate pre-search (RAG)
27
+ search_query = "actualités IA d'aujourd'hui"
28
+ print("Performing search for:", search_query)
29
+ search_results = search_web(search_query)
30
+
31
+ messages = [
32
+ {"role": "system", "content": SYSTEM_PROMPT + f"\n\n[CONTEXTE DU WEB]\n{search_results}\n[FIN DU CONTEXTE]"},
33
+ {"role": "user", "content": message}
34
+ ]
35
+
36
+ response = client.chat_completion(
37
+ model="Qwen/Qwen2.5-Coder-7B-Instruct",
38
+ messages=messages,
39
+ max_tokens=150
40
+ )
41
+ print("Response:")
42
+ print(response.choices[0].message.content)
43
+
44
+ test_run()
cypher-ai/test_rag_with_token.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from huggingface_hub import InferenceClient
4
+ from duckduckgo_search import DDGS
5
+
6
+ # Load token from environment
7
+ token = os.environ.get("HF_TOKEN")
8
+ client = InferenceClient(token=token)
9
+
10
+ SYSTEM_PROMPT = "Tu es Cypher AI, assistant IA en monospace."
11
+
12
+ def search_web(query):
13
+ try:
14
+ ddgs = DDGS()
15
+ results = list(ddgs.text(query, max_results=3))
16
+ formatted = []
17
+ for r in results:
18
+ formatted.append(f"Titre: {r['title']}\nRésumé: {r['body']}\nLien: {r['href']}")
19
+ return "\n\n".join(formatted)
20
+ except Exception as e:
21
+ return f"Erreur de recherche : {str(e)}"
22
+
23
+ def test_run():
24
+ message = "Cherche sur le web : actualités IA d'aujourd'hui."
25
+
26
+ # Simulate pre-search (RAG)
27
+ search_query = "actualités IA d'aujourd'hui"
28
+ print("Performing search for:", search_query)
29
+ search_results = search_web(search_query)
30
+
31
+ messages = [
32
+ {"role": "system", "content": SYSTEM_PROMPT + f"\n\n[CONTEXTE DU WEB]\n{search_results}\n[FIN DU CONTEXTE]"},
33
+ {"role": "user", "content": message}
34
+ ]
35
+
36
+ # Call without tools parameter!
37
+ response = client.chat_completion(
38
+ model="Qwen/Qwen2.5-Coder-7B-Instruct",
39
+ messages=messages,
40
+ max_tokens=150
41
+ )
42
+ print("Response:")
43
+ print(response.choices[0].message.content)
44
+
45
+ test_run()
cypher-ai/test_respond.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HF_TOKEN loaded from environment
2
+
3
+ from app import respond
4
+
5
+ print("--- Test direct de l'IA ---")
6
+ try:
7
+ generator = respond("Cherche sur le web : actualités IA d'aujourd'hui.", [])
8
+ for chunk in generator:
9
+ print(chunk)
10
+ except Exception as e:
11
+ import traceback
12
+ traceback.print_exc()
cypher-ai/test_search_7b.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ.pop("HF_TOKEN", None)
3
+
4
+ import json
5
+ import uuid
6
+ from datetime import datetime
7
+ from io import BytesIO
8
+ from huggingface_hub import InferenceClient, HfApi
9
+ from duckduckgo_search import DDGS
10
+
11
+ # Use anonymous client for inference
12
+ client = InferenceClient() # No token!
13
+
14
+ SYSTEM_PROMPT = "Tu es Cypher AI, assistant IA en monospace."
15
+ tools = [
16
+ {
17
+ "type": "function",
18
+ "function": {
19
+ "name": "search_web",
20
+ "description": "Fait une recherche sur internet.",
21
+ "parameters": {
22
+ "type": "object",
23
+ "properties": {
24
+ "query": {"type": "string", "description": "La requête."}
25
+ },
26
+ "required": ["query"]
27
+ }
28
+ }
29
+ }
30
+ ]
31
+
32
+ def search_web(query):
33
+ try:
34
+ ddgs = DDGS()
35
+ results = list(ddgs.text(query, max_results=2))
36
+ return str(results)
37
+ except Exception as e:
38
+ return str(e)
39
+
40
+ def test_run():
41
+ messages = [
42
+ {"role": "system", "content": SYSTEM_PROMPT},
43
+ {"role": "user", "content": "Cherche sur le web : actualités IA d'aujourd'hui."}
44
+ ]
45
+
46
+ # 1. First completion to see if it calls tool
47
+ response = client.chat_completion(
48
+ model="Qwen/Qwen2.5-Coder-7B-Instruct",
49
+ messages=messages,
50
+ tools=tools,
51
+ max_tokens=200,
52
+ stream=False
53
+ )
54
+
55
+ choice = response.choices[0]
56
+ print("Has tool calls:", bool(choice.message.tool_calls))
57
+
58
+ if choice.message.tool_calls:
59
+ tool_calls_list = []
60
+ for tc in choice.message.tool_calls:
61
+ tool_calls_list.append({
62
+ "id": tc.id,
63
+ "type": tc.type,
64
+ "function": {
65
+ "name": tc.function.name,
66
+ "arguments": tc.function.arguments
67
+ }
68
+ })
69
+
70
+ messages.append({
71
+ "role": "assistant",
72
+ "tool_calls": tool_calls_list
73
+ })
74
+
75
+ for tc in choice.message.tool_calls:
76
+ if tc.function.name == "search_web":
77
+ args = json.loads(tc.function.arguments)
78
+ q = args.get("query", "")
79
+ res = search_web(q)
80
+ print("Search result:", res[:100])
81
+ messages.append({
82
+ "role": "tool",
83
+ "name": "search_web",
84
+ "tool_call_id": tc.id,
85
+ "content": res
86
+ })
87
+
88
+ # 2. Second completion
89
+ final_response = client.chat_completion(
90
+ model="Qwen/Qwen2.5-Coder-7B-Instruct",
91
+ messages=messages,
92
+ max_tokens=200,
93
+ stream=False
94
+ )
95
+ print("Final response:", final_response.choices[0].message.content)
96
+
97
+ test_run()
cypher-chat-app/package-lock.json ADDED
@@ -0,0 +1,854 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "cypher-chat-app",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "cypher-chat-app",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "cors": "^2.8.5",
12
+ "express": "^4.19.2"
13
+ }
14
+ },
15
+ "node_modules/accepts": {
16
+ "version": "1.3.8",
17
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
18
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "mime-types": "~2.1.34",
22
+ "negotiator": "0.6.3"
23
+ },
24
+ "engines": {
25
+ "node": ">= 0.6"
26
+ }
27
+ },
28
+ "node_modules/array-flatten": {
29
+ "version": "1.1.1",
30
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
31
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
32
+ "license": "MIT"
33
+ },
34
+ "node_modules/body-parser": {
35
+ "version": "1.20.5",
36
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
37
+ "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
38
+ "license": "MIT",
39
+ "dependencies": {
40
+ "bytes": "~3.1.2",
41
+ "content-type": "~1.0.5",
42
+ "debug": "2.6.9",
43
+ "depd": "2.0.0",
44
+ "destroy": "~1.2.0",
45
+ "http-errors": "~2.0.1",
46
+ "iconv-lite": "~0.4.24",
47
+ "on-finished": "~2.4.1",
48
+ "qs": "~6.15.1",
49
+ "raw-body": "~2.5.3",
50
+ "type-is": "~1.6.18",
51
+ "unpipe": "~1.0.0"
52
+ },
53
+ "engines": {
54
+ "node": ">= 0.8",
55
+ "npm": "1.2.8000 || >= 1.4.16"
56
+ }
57
+ },
58
+ "node_modules/bytes": {
59
+ "version": "3.1.2",
60
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
61
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
62
+ "license": "MIT",
63
+ "engines": {
64
+ "node": ">= 0.8"
65
+ }
66
+ },
67
+ "node_modules/call-bind-apply-helpers": {
68
+ "version": "1.0.2",
69
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
70
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
71
+ "license": "MIT",
72
+ "dependencies": {
73
+ "es-errors": "^1.3.0",
74
+ "function-bind": "^1.1.2"
75
+ },
76
+ "engines": {
77
+ "node": ">= 0.4"
78
+ }
79
+ },
80
+ "node_modules/call-bound": {
81
+ "version": "1.0.4",
82
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
83
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
84
+ "license": "MIT",
85
+ "dependencies": {
86
+ "call-bind-apply-helpers": "^1.0.2",
87
+ "get-intrinsic": "^1.3.0"
88
+ },
89
+ "engines": {
90
+ "node": ">= 0.4"
91
+ },
92
+ "funding": {
93
+ "url": "https://github.com/sponsors/ljharb"
94
+ }
95
+ },
96
+ "node_modules/content-disposition": {
97
+ "version": "0.5.4",
98
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
99
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
100
+ "license": "MIT",
101
+ "dependencies": {
102
+ "safe-buffer": "5.2.1"
103
+ },
104
+ "engines": {
105
+ "node": ">= 0.6"
106
+ }
107
+ },
108
+ "node_modules/content-type": {
109
+ "version": "1.0.5",
110
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
111
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
112
+ "license": "MIT",
113
+ "engines": {
114
+ "node": ">= 0.6"
115
+ }
116
+ },
117
+ "node_modules/cookie": {
118
+ "version": "0.7.2",
119
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
120
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
121
+ "license": "MIT",
122
+ "engines": {
123
+ "node": ">= 0.6"
124
+ }
125
+ },
126
+ "node_modules/cookie-signature": {
127
+ "version": "1.0.7",
128
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
129
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
130
+ "license": "MIT"
131
+ },
132
+ "node_modules/cors": {
133
+ "version": "2.8.6",
134
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
135
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
136
+ "license": "MIT",
137
+ "dependencies": {
138
+ "object-assign": "^4",
139
+ "vary": "^1"
140
+ },
141
+ "engines": {
142
+ "node": ">= 0.10"
143
+ },
144
+ "funding": {
145
+ "type": "opencollective",
146
+ "url": "https://opencollective.com/express"
147
+ }
148
+ },
149
+ "node_modules/debug": {
150
+ "version": "2.6.9",
151
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
152
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
153
+ "license": "MIT",
154
+ "dependencies": {
155
+ "ms": "2.0.0"
156
+ }
157
+ },
158
+ "node_modules/depd": {
159
+ "version": "2.0.0",
160
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
161
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
162
+ "license": "MIT",
163
+ "engines": {
164
+ "node": ">= 0.8"
165
+ }
166
+ },
167
+ "node_modules/destroy": {
168
+ "version": "1.2.0",
169
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
170
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
171
+ "license": "MIT",
172
+ "engines": {
173
+ "node": ">= 0.8",
174
+ "npm": "1.2.8000 || >= 1.4.16"
175
+ }
176
+ },
177
+ "node_modules/dunder-proto": {
178
+ "version": "1.0.1",
179
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
180
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
181
+ "license": "MIT",
182
+ "dependencies": {
183
+ "call-bind-apply-helpers": "^1.0.1",
184
+ "es-errors": "^1.3.0",
185
+ "gopd": "^1.2.0"
186
+ },
187
+ "engines": {
188
+ "node": ">= 0.4"
189
+ }
190
+ },
191
+ "node_modules/ee-first": {
192
+ "version": "1.1.1",
193
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
194
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
195
+ "license": "MIT"
196
+ },
197
+ "node_modules/encodeurl": {
198
+ "version": "2.0.0",
199
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
200
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
201
+ "license": "MIT",
202
+ "engines": {
203
+ "node": ">= 0.8"
204
+ }
205
+ },
206
+ "node_modules/es-define-property": {
207
+ "version": "1.0.1",
208
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
209
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
210
+ "license": "MIT",
211
+ "engines": {
212
+ "node": ">= 0.4"
213
+ }
214
+ },
215
+ "node_modules/es-errors": {
216
+ "version": "1.3.0",
217
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
218
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
219
+ "license": "MIT",
220
+ "engines": {
221
+ "node": ">= 0.4"
222
+ }
223
+ },
224
+ "node_modules/es-object-atoms": {
225
+ "version": "1.1.2",
226
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
227
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
228
+ "license": "MIT",
229
+ "dependencies": {
230
+ "es-errors": "^1.3.0"
231
+ },
232
+ "engines": {
233
+ "node": ">= 0.4"
234
+ }
235
+ },
236
+ "node_modules/escape-html": {
237
+ "version": "1.0.3",
238
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
239
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
240
+ "license": "MIT"
241
+ },
242
+ "node_modules/etag": {
243
+ "version": "1.8.1",
244
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
245
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
246
+ "license": "MIT",
247
+ "engines": {
248
+ "node": ">= 0.6"
249
+ }
250
+ },
251
+ "node_modules/express": {
252
+ "version": "4.22.2",
253
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
254
+ "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
255
+ "license": "MIT",
256
+ "dependencies": {
257
+ "accepts": "~1.3.8",
258
+ "array-flatten": "1.1.1",
259
+ "body-parser": "~1.20.5",
260
+ "content-disposition": "~0.5.4",
261
+ "content-type": "~1.0.4",
262
+ "cookie": "~0.7.1",
263
+ "cookie-signature": "~1.0.6",
264
+ "debug": "2.6.9",
265
+ "depd": "2.0.0",
266
+ "encodeurl": "~2.0.0",
267
+ "escape-html": "~1.0.3",
268
+ "etag": "~1.8.1",
269
+ "finalhandler": "~1.3.1",
270
+ "fresh": "~0.5.2",
271
+ "http-errors": "~2.0.0",
272
+ "merge-descriptors": "1.0.3",
273
+ "methods": "~1.1.2",
274
+ "on-finished": "~2.4.1",
275
+ "parseurl": "~1.3.3",
276
+ "path-to-regexp": "~0.1.12",
277
+ "proxy-addr": "~2.0.7",
278
+ "qs": "~6.15.1",
279
+ "range-parser": "~1.2.1",
280
+ "safe-buffer": "5.2.1",
281
+ "send": "~0.19.0",
282
+ "serve-static": "~1.16.2",
283
+ "setprototypeof": "1.2.0",
284
+ "statuses": "~2.0.1",
285
+ "type-is": "~1.6.18",
286
+ "utils-merge": "1.0.1",
287
+ "vary": "~1.1.2"
288
+ },
289
+ "engines": {
290
+ "node": ">= 0.10.0"
291
+ },
292
+ "funding": {
293
+ "type": "opencollective",
294
+ "url": "https://opencollective.com/express"
295
+ }
296
+ },
297
+ "node_modules/finalhandler": {
298
+ "version": "1.3.2",
299
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
300
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
301
+ "license": "MIT",
302
+ "dependencies": {
303
+ "debug": "2.6.9",
304
+ "encodeurl": "~2.0.0",
305
+ "escape-html": "~1.0.3",
306
+ "on-finished": "~2.4.1",
307
+ "parseurl": "~1.3.3",
308
+ "statuses": "~2.0.2",
309
+ "unpipe": "~1.0.0"
310
+ },
311
+ "engines": {
312
+ "node": ">= 0.8"
313
+ }
314
+ },
315
+ "node_modules/forwarded": {
316
+ "version": "0.2.0",
317
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
318
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
319
+ "license": "MIT",
320
+ "engines": {
321
+ "node": ">= 0.6"
322
+ }
323
+ },
324
+ "node_modules/fresh": {
325
+ "version": "0.5.2",
326
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
327
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
328
+ "license": "MIT",
329
+ "engines": {
330
+ "node": ">= 0.6"
331
+ }
332
+ },
333
+ "node_modules/function-bind": {
334
+ "version": "1.1.2",
335
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
336
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
337
+ "license": "MIT",
338
+ "funding": {
339
+ "url": "https://github.com/sponsors/ljharb"
340
+ }
341
+ },
342
+ "node_modules/get-intrinsic": {
343
+ "version": "1.3.0",
344
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
345
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
346
+ "license": "MIT",
347
+ "dependencies": {
348
+ "call-bind-apply-helpers": "^1.0.2",
349
+ "es-define-property": "^1.0.1",
350
+ "es-errors": "^1.3.0",
351
+ "es-object-atoms": "^1.1.1",
352
+ "function-bind": "^1.1.2",
353
+ "get-proto": "^1.0.1",
354
+ "gopd": "^1.2.0",
355
+ "has-symbols": "^1.1.0",
356
+ "hasown": "^2.0.2",
357
+ "math-intrinsics": "^1.1.0"
358
+ },
359
+ "engines": {
360
+ "node": ">= 0.4"
361
+ },
362
+ "funding": {
363
+ "url": "https://github.com/sponsors/ljharb"
364
+ }
365
+ },
366
+ "node_modules/get-proto": {
367
+ "version": "1.0.1",
368
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
369
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
370
+ "license": "MIT",
371
+ "dependencies": {
372
+ "dunder-proto": "^1.0.1",
373
+ "es-object-atoms": "^1.0.0"
374
+ },
375
+ "engines": {
376
+ "node": ">= 0.4"
377
+ }
378
+ },
379
+ "node_modules/gopd": {
380
+ "version": "1.2.0",
381
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
382
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
383
+ "license": "MIT",
384
+ "engines": {
385
+ "node": ">= 0.4"
386
+ },
387
+ "funding": {
388
+ "url": "https://github.com/sponsors/ljharb"
389
+ }
390
+ },
391
+ "node_modules/has-symbols": {
392
+ "version": "1.1.0",
393
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
394
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
395
+ "license": "MIT",
396
+ "engines": {
397
+ "node": ">= 0.4"
398
+ },
399
+ "funding": {
400
+ "url": "https://github.com/sponsors/ljharb"
401
+ }
402
+ },
403
+ "node_modules/hasown": {
404
+ "version": "2.0.4",
405
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
406
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
407
+ "license": "MIT",
408
+ "dependencies": {
409
+ "function-bind": "^1.1.2"
410
+ },
411
+ "engines": {
412
+ "node": ">= 0.4"
413
+ }
414
+ },
415
+ "node_modules/http-errors": {
416
+ "version": "2.0.1",
417
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
418
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
419
+ "license": "MIT",
420
+ "dependencies": {
421
+ "depd": "~2.0.0",
422
+ "inherits": "~2.0.4",
423
+ "setprototypeof": "~1.2.0",
424
+ "statuses": "~2.0.2",
425
+ "toidentifier": "~1.0.1"
426
+ },
427
+ "engines": {
428
+ "node": ">= 0.8"
429
+ },
430
+ "funding": {
431
+ "type": "opencollective",
432
+ "url": "https://opencollective.com/express"
433
+ }
434
+ },
435
+ "node_modules/iconv-lite": {
436
+ "version": "0.4.24",
437
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
438
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
439
+ "license": "MIT",
440
+ "dependencies": {
441
+ "safer-buffer": ">= 2.1.2 < 3"
442
+ },
443
+ "engines": {
444
+ "node": ">=0.10.0"
445
+ }
446
+ },
447
+ "node_modules/inherits": {
448
+ "version": "2.0.4",
449
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
450
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
451
+ "license": "ISC"
452
+ },
453
+ "node_modules/ipaddr.js": {
454
+ "version": "1.9.1",
455
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
456
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
457
+ "license": "MIT",
458
+ "engines": {
459
+ "node": ">= 0.10"
460
+ }
461
+ },
462
+ "node_modules/math-intrinsics": {
463
+ "version": "1.1.0",
464
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
465
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
466
+ "license": "MIT",
467
+ "engines": {
468
+ "node": ">= 0.4"
469
+ }
470
+ },
471
+ "node_modules/media-typer": {
472
+ "version": "0.3.0",
473
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
474
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
475
+ "license": "MIT",
476
+ "engines": {
477
+ "node": ">= 0.6"
478
+ }
479
+ },
480
+ "node_modules/merge-descriptors": {
481
+ "version": "1.0.3",
482
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
483
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
484
+ "license": "MIT",
485
+ "funding": {
486
+ "url": "https://github.com/sponsors/sindresorhus"
487
+ }
488
+ },
489
+ "node_modules/methods": {
490
+ "version": "1.1.2",
491
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
492
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
493
+ "license": "MIT",
494
+ "engines": {
495
+ "node": ">= 0.6"
496
+ }
497
+ },
498
+ "node_modules/mime": {
499
+ "version": "1.6.0",
500
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
501
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
502
+ "license": "MIT",
503
+ "bin": {
504
+ "mime": "cli.js"
505
+ },
506
+ "engines": {
507
+ "node": ">=4"
508
+ }
509
+ },
510
+ "node_modules/mime-db": {
511
+ "version": "1.52.0",
512
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
513
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
514
+ "license": "MIT",
515
+ "engines": {
516
+ "node": ">= 0.6"
517
+ }
518
+ },
519
+ "node_modules/mime-types": {
520
+ "version": "2.1.35",
521
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
522
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
523
+ "license": "MIT",
524
+ "dependencies": {
525
+ "mime-db": "1.52.0"
526
+ },
527
+ "engines": {
528
+ "node": ">= 0.6"
529
+ }
530
+ },
531
+ "node_modules/ms": {
532
+ "version": "2.0.0",
533
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
534
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
535
+ "license": "MIT"
536
+ },
537
+ "node_modules/negotiator": {
538
+ "version": "0.6.3",
539
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
540
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
541
+ "license": "MIT",
542
+ "engines": {
543
+ "node": ">= 0.6"
544
+ }
545
+ },
546
+ "node_modules/object-assign": {
547
+ "version": "4.1.1",
548
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
549
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
550
+ "license": "MIT",
551
+ "engines": {
552
+ "node": ">=0.10.0"
553
+ }
554
+ },
555
+ "node_modules/object-inspect": {
556
+ "version": "1.13.4",
557
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
558
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
559
+ "license": "MIT",
560
+ "engines": {
561
+ "node": ">= 0.4"
562
+ },
563
+ "funding": {
564
+ "url": "https://github.com/sponsors/ljharb"
565
+ }
566
+ },
567
+ "node_modules/on-finished": {
568
+ "version": "2.4.1",
569
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
570
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
571
+ "license": "MIT",
572
+ "dependencies": {
573
+ "ee-first": "1.1.1"
574
+ },
575
+ "engines": {
576
+ "node": ">= 0.8"
577
+ }
578
+ },
579
+ "node_modules/parseurl": {
580
+ "version": "1.3.3",
581
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
582
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
583
+ "license": "MIT",
584
+ "engines": {
585
+ "node": ">= 0.8"
586
+ }
587
+ },
588
+ "node_modules/path-to-regexp": {
589
+ "version": "0.1.13",
590
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
591
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
592
+ "license": "MIT"
593
+ },
594
+ "node_modules/proxy-addr": {
595
+ "version": "2.0.7",
596
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
597
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
598
+ "license": "MIT",
599
+ "dependencies": {
600
+ "forwarded": "0.2.0",
601
+ "ipaddr.js": "1.9.1"
602
+ },
603
+ "engines": {
604
+ "node": ">= 0.10"
605
+ }
606
+ },
607
+ "node_modules/qs": {
608
+ "version": "6.15.2",
609
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
610
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
611
+ "license": "BSD-3-Clause",
612
+ "dependencies": {
613
+ "side-channel": "^1.1.0"
614
+ },
615
+ "engines": {
616
+ "node": ">=0.6"
617
+ },
618
+ "funding": {
619
+ "url": "https://github.com/sponsors/ljharb"
620
+ }
621
+ },
622
+ "node_modules/range-parser": {
623
+ "version": "1.2.1",
624
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
625
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
626
+ "license": "MIT",
627
+ "engines": {
628
+ "node": ">= 0.6"
629
+ }
630
+ },
631
+ "node_modules/raw-body": {
632
+ "version": "2.5.3",
633
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
634
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
635
+ "license": "MIT",
636
+ "dependencies": {
637
+ "bytes": "~3.1.2",
638
+ "http-errors": "~2.0.1",
639
+ "iconv-lite": "~0.4.24",
640
+ "unpipe": "~1.0.0"
641
+ },
642
+ "engines": {
643
+ "node": ">= 0.8"
644
+ }
645
+ },
646
+ "node_modules/safe-buffer": {
647
+ "version": "5.2.1",
648
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
649
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
650
+ "funding": [
651
+ {
652
+ "type": "github",
653
+ "url": "https://github.com/sponsors/feross"
654
+ },
655
+ {
656
+ "type": "patreon",
657
+ "url": "https://www.patreon.com/feross"
658
+ },
659
+ {
660
+ "type": "consulting",
661
+ "url": "https://feross.org/support"
662
+ }
663
+ ],
664
+ "license": "MIT"
665
+ },
666
+ "node_modules/safer-buffer": {
667
+ "version": "2.1.2",
668
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
669
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
670
+ "license": "MIT"
671
+ },
672
+ "node_modules/send": {
673
+ "version": "0.19.2",
674
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
675
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
676
+ "license": "MIT",
677
+ "dependencies": {
678
+ "debug": "2.6.9",
679
+ "depd": "2.0.0",
680
+ "destroy": "1.2.0",
681
+ "encodeurl": "~2.0.0",
682
+ "escape-html": "~1.0.3",
683
+ "etag": "~1.8.1",
684
+ "fresh": "~0.5.2",
685
+ "http-errors": "~2.0.1",
686
+ "mime": "1.6.0",
687
+ "ms": "2.1.3",
688
+ "on-finished": "~2.4.1",
689
+ "range-parser": "~1.2.1",
690
+ "statuses": "~2.0.2"
691
+ },
692
+ "engines": {
693
+ "node": ">= 0.8.0"
694
+ }
695
+ },
696
+ "node_modules/send/node_modules/ms": {
697
+ "version": "2.1.3",
698
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
699
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
700
+ "license": "MIT"
701
+ },
702
+ "node_modules/serve-static": {
703
+ "version": "1.16.3",
704
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
705
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
706
+ "license": "MIT",
707
+ "dependencies": {
708
+ "encodeurl": "~2.0.0",
709
+ "escape-html": "~1.0.3",
710
+ "parseurl": "~1.3.3",
711
+ "send": "~0.19.1"
712
+ },
713
+ "engines": {
714
+ "node": ">= 0.8.0"
715
+ }
716
+ },
717
+ "node_modules/setprototypeof": {
718
+ "version": "1.2.0",
719
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
720
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
721
+ "license": "ISC"
722
+ },
723
+ "node_modules/side-channel": {
724
+ "version": "1.1.0",
725
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
726
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
727
+ "license": "MIT",
728
+ "dependencies": {
729
+ "es-errors": "^1.3.0",
730
+ "object-inspect": "^1.13.3",
731
+ "side-channel-list": "^1.0.0",
732
+ "side-channel-map": "^1.0.1",
733
+ "side-channel-weakmap": "^1.0.2"
734
+ },
735
+ "engines": {
736
+ "node": ">= 0.4"
737
+ },
738
+ "funding": {
739
+ "url": "https://github.com/sponsors/ljharb"
740
+ }
741
+ },
742
+ "node_modules/side-channel-list": {
743
+ "version": "1.0.1",
744
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
745
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
746
+ "license": "MIT",
747
+ "dependencies": {
748
+ "es-errors": "^1.3.0",
749
+ "object-inspect": "^1.13.4"
750
+ },
751
+ "engines": {
752
+ "node": ">= 0.4"
753
+ },
754
+ "funding": {
755
+ "url": "https://github.com/sponsors/ljharb"
756
+ }
757
+ },
758
+ "node_modules/side-channel-map": {
759
+ "version": "1.0.1",
760
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
761
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
762
+ "license": "MIT",
763
+ "dependencies": {
764
+ "call-bound": "^1.0.2",
765
+ "es-errors": "^1.3.0",
766
+ "get-intrinsic": "^1.2.5",
767
+ "object-inspect": "^1.13.3"
768
+ },
769
+ "engines": {
770
+ "node": ">= 0.4"
771
+ },
772
+ "funding": {
773
+ "url": "https://github.com/sponsors/ljharb"
774
+ }
775
+ },
776
+ "node_modules/side-channel-weakmap": {
777
+ "version": "1.0.2",
778
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
779
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
780
+ "license": "MIT",
781
+ "dependencies": {
782
+ "call-bound": "^1.0.2",
783
+ "es-errors": "^1.3.0",
784
+ "get-intrinsic": "^1.2.5",
785
+ "object-inspect": "^1.13.3",
786
+ "side-channel-map": "^1.0.1"
787
+ },
788
+ "engines": {
789
+ "node": ">= 0.4"
790
+ },
791
+ "funding": {
792
+ "url": "https://github.com/sponsors/ljharb"
793
+ }
794
+ },
795
+ "node_modules/statuses": {
796
+ "version": "2.0.2",
797
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
798
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
799
+ "license": "MIT",
800
+ "engines": {
801
+ "node": ">= 0.8"
802
+ }
803
+ },
804
+ "node_modules/toidentifier": {
805
+ "version": "1.0.1",
806
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
807
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
808
+ "license": "MIT",
809
+ "engines": {
810
+ "node": ">=0.6"
811
+ }
812
+ },
813
+ "node_modules/type-is": {
814
+ "version": "1.6.18",
815
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
816
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
817
+ "license": "MIT",
818
+ "dependencies": {
819
+ "media-typer": "0.3.0",
820
+ "mime-types": "~2.1.24"
821
+ },
822
+ "engines": {
823
+ "node": ">= 0.6"
824
+ }
825
+ },
826
+ "node_modules/unpipe": {
827
+ "version": "1.0.0",
828
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
829
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
830
+ "license": "MIT",
831
+ "engines": {
832
+ "node": ">= 0.8"
833
+ }
834
+ },
835
+ "node_modules/utils-merge": {
836
+ "version": "1.0.1",
837
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
838
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
839
+ "license": "MIT",
840
+ "engines": {
841
+ "node": ">= 0.4.0"
842
+ }
843
+ },
844
+ "node_modules/vary": {
845
+ "version": "1.1.2",
846
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
847
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
848
+ "license": "MIT",
849
+ "engines": {
850
+ "node": ">= 0.8"
851
+ }
852
+ }
853
+ }
854
+ }
cypher-chat-app/package.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "cypher-chat-app",
3
+ "version": "1.0.0",
4
+ "description": "Standalone desktop and mobile client for Cypher AI",
5
+ "main": "server.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "start": "node server.js"
9
+ },
10
+ "dependencies": {
11
+ "cors": "^2.8.5",
12
+ "express": "^4.19.2"
13
+ }
14
+ }
cypher-chat-app/public/app.js ADDED
@@ -0,0 +1,588 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // App State
2
+ let username = localStorage.getItem("cypher_username") || "invité";
3
+ let hfToken = localStorage.getItem("cypher_token") || "";
4
+ let appMode = (window.location.protocol === "file:") ? "direct" : "local";
5
+ localStorage.setItem("cypher_mode", appMode);
6
+ let conversations = JSON.parse(localStorage.getItem("cypher_conversations")) || [];
7
+ let currentConversationId = localStorage.getItem("cypher_current_id") || null;
8
+ let currentMessages = [];
9
+ let attachedFiles = []; // Holds { name, textContent }
10
+
11
+ // DOM Elements
12
+ const sidebar = document.getElementById("sidebar");
13
+ const menuToggleBtn = document.getElementById("menuToggleBtn");
14
+ const closeSidebarBtn = document.getElementById("closeSidebarBtn");
15
+ const newChatBtn = document.getElementById("newChatBtn");
16
+ const chatHistoryList = document.getElementById("chatHistoryList");
17
+ const settingsBtn = document.getElementById("settingsBtn");
18
+ const clearChatBtn = document.getElementById("clearChatBtn");
19
+ const statusText = document.getElementById("statusText");
20
+ const messagesContainer = document.getElementById("messagesContainer");
21
+ const welcomeView = document.getElementById("welcomeView");
22
+ const userInput = document.getElementById("userInput");
23
+ const sendBtn = document.getElementById("sendBtn");
24
+ const webSearchToggle = document.getElementById("webSearchToggle");
25
+
26
+ // Attachment Elements
27
+ const fileInput = document.getElementById("fileInput");
28
+ const attachBtn = document.getElementById("attachBtn");
29
+ const attachmentPreviewContainer = document.getElementById("attachmentPreviewContainer");
30
+
31
+ // Settings Modal Elements
32
+ const settingsModal = document.getElementById("settingsModal");
33
+ const closeModalBtn = document.getElementById("closeModalBtn");
34
+ const usernameInput = document.getElementById("usernameInput");
35
+ const tokenInput = document.getElementById("tokenInput");
36
+ const modeSelect = document.getElementById("modeSelect");
37
+ const saveSettingsBtn = document.getElementById("saveSettingsBtn");
38
+
39
+ const DEFAULT_HF_TOKEN = "";
40
+
41
+ // Updated prompt suppressing 2022 training info and enforcing DJAKOUA student credit
42
+ const SYSTEM_PROMPT = `Tu es Cypher AI, une intelligence artificielle d'élite spécialisée dans la programmation, l'algorithmie et la conception de logiciels.
43
+ Tu as été développé par DJAKOUA KWANKAM, étudiant en informatique à l'Institut Universitaire de Technologie de Douala (IUT).
44
+ Tu es direct, précis, rigoureux et tu t'exprimes de façon fluide sans fioritures superflues.
45
+ Ne mentionne jamais tes dates de coupure de connaissances ou de fin d'entraînement (comme 2022) et n'explique jamais à l'utilisateur que tu n'as pas accès à internet ou au web, car l'application effectue les recherches pour toi et t'injecte les résultats directement. Utilise simplement les résultats de recherche fournis pour répondre de façon actualisée.`;
46
+
47
+ // Initialize Application
48
+ document.addEventListener("DOMContentLoaded", () => {
49
+ lucide.createIcons();
50
+ initThemeAndSettings();
51
+ renderConversationsList();
52
+
53
+ if (currentConversationId) {
54
+ loadConversation(currentConversationId);
55
+ } else {
56
+ startNewConversation();
57
+ }
58
+
59
+ setupEventListeners();
60
+ });
61
+
62
+ // Settings & Theme Initialization
63
+ function initThemeAndSettings() {
64
+ usernameInput.value = username;
65
+ tokenInput.value = hfToken;
66
+ modeSelect.value = appMode;
67
+ updateStatusText();
68
+ }
69
+
70
+ function updateStatusText() {
71
+ if (appMode === "local") {
72
+ statusText.innerText = "Serveur local (RAG Web)";
73
+ } else {
74
+ statusText.innerText = "Direct Hugging Face (Sans serveur)";
75
+ }
76
+ }
77
+
78
+ // Setup Event Listeners
79
+ function setupEventListeners() {
80
+ // Sidebar Mobile Toggles
81
+ menuToggleBtn.addEventListener("click", () => sidebar.classList.add("active"));
82
+ closeSidebarBtn.addEventListener("click", () => sidebar.classList.remove("active"));
83
+
84
+ // New Chat Action
85
+ newChatBtn.addEventListener("click", () => {
86
+ startNewConversation();
87
+ sidebar.classList.remove("active");
88
+ });
89
+
90
+ // Textarea auto-growing & enabling send button
91
+ userInput.addEventListener("input", updateSendButtonState);
92
+
93
+ userInput.addEventListener("keydown", (e) => {
94
+ if (e.key === "Enter" && !e.shiftKey) {
95
+ e.preventDefault();
96
+ if (userInput.value.trim() !== "" || attachedFiles.length > 0) {
97
+ sendMessage();
98
+ }
99
+ }
100
+ });
101
+
102
+ // Send Message Button Action
103
+ sendBtn.addEventListener("click", sendMessage);
104
+
105
+ // Clear Chat
106
+ clearChatBtn.addEventListener("click", () => {
107
+ if (confirm("Effacer la conversation actuelle ?")) {
108
+ clearCurrentConversation();
109
+ }
110
+ });
111
+
112
+ // File Attach Operations
113
+ attachBtn.addEventListener("click", () => {
114
+ fileInput.click();
115
+ });
116
+
117
+ fileInput.addEventListener("change", handleFileSelection);
118
+
119
+ // Settings modal triggers
120
+ settingsBtn.addEventListener("click", () => {
121
+ settingsModal.classList.add("active");
122
+ });
123
+
124
+ closeModalBtn.addEventListener("click", () => {
125
+ settingsModal.classList.remove("active");
126
+ });
127
+
128
+ settingsModal.addEventListener("click", (e) => {
129
+ if (e.target === settingsModal) {
130
+ settingsModal.classList.remove("active");
131
+ }
132
+ });
133
+
134
+ saveSettingsBtn.addEventListener("click", saveSettings);
135
+
136
+ // Drag and Drop File support
137
+ window.addEventListener("dragover", (e) => e.preventDefault());
138
+ window.addEventListener("drop", handleFileDrop);
139
+ }
140
+
141
+ // Check state to enable/disable send button
142
+ function updateSendButtonState() {
143
+ userInput.style.height = "auto";
144
+ userInput.style.height = userInput.scrollHeight + "px";
145
+ sendBtn.disabled = userInput.value.trim() === "" && attachedFiles.length === 0;
146
+ }
147
+
148
+ // Handle local file uploads
149
+ function handleFileSelection(e) {
150
+ const files = e.target.files;
151
+ processFiles(files);
152
+ }
153
+
154
+ // Handle Drag-and-Drop file uploads
155
+ function handleFileDrop(e) {
156
+ e.preventDefault();
157
+ const files = e.dataTransfer.files;
158
+ processFiles(files);
159
+ }
160
+
161
+ // Process selected/dropped files
162
+ function processFiles(files) {
163
+ if (!files || files.length === 0) return;
164
+
165
+ Array.from(files).forEach(file => {
166
+ // File size limit 500KB to avoid blowing context window
167
+ if (file.size > 500 * 1024) {
168
+ alert(`Le fichier "${file.name}" est trop volumineux (max 500 Ko pour les fichiers de code).`);
169
+ return;
170
+ }
171
+
172
+ const reader = new FileReader();
173
+ reader.onload = (event) => {
174
+ const textContent = event.target.result;
175
+ // Check if file is text-based (rough heuristic: check for null bytes)
176
+ if (textContent.includes("\0")) {
177
+ alert(`Le fichier "${file.name}" semble être binaire. Seuls les fichiers de texte/code sont acceptés.`);
178
+ return;
179
+ }
180
+
181
+ // Add to state if not already attached
182
+ if (!attachedFiles.some(f => f.name === file.name)) {
183
+ attachedFiles.push({
184
+ name: file.name,
185
+ textContent: textContent
186
+ });
187
+ renderAttachmentChips();
188
+ updateSendButtonState();
189
+ }
190
+ };
191
+ reader.readAsText(file);
192
+ });
193
+
194
+ // Reset file input value to allow re-uploading same file
195
+ fileInput.value = "";
196
+ }
197
+
198
+ // Render uploaded file chips above input
199
+ function renderAttachmentChips() {
200
+ attachmentPreviewContainer.innerHTML = "";
201
+
202
+ if (attachedFiles.length === 0) {
203
+ attachmentPreviewContainer.style.display = "none";
204
+ return;
205
+ }
206
+
207
+ attachmentPreviewContainer.style.display = "flex";
208
+
209
+ attachedFiles.forEach((file, index) => {
210
+ const chip = document.createElement("div");
211
+ chip.className = "attachment-chip";
212
+ chip.innerHTML = `
213
+ <i data-lucide="file-text" size="14"></i>
214
+ <span>${file.name}</span>
215
+ <button class="remove-attach-btn" type="button" onclick="removeAttachedFile(${index})">
216
+ <i data-lucide="x" size="12"></i>
217
+ </button>
218
+ `;
219
+ attachmentPreviewContainer.appendChild(chip);
220
+ });
221
+
222
+ lucide.createIcons();
223
+ }
224
+
225
+ // Global removal function accessible from inline onclick
226
+ window.removeAttachedFile = function(index) {
227
+ attachedFiles.splice(index, 1);
228
+ renderAttachmentChips();
229
+ updateSendButtonState();
230
+ };
231
+
232
+ // Start New Chat Session
233
+ function startNewConversation() {
234
+ currentConversationId = "chat_" + Date.now();
235
+ currentMessages = [];
236
+ attachedFiles = [];
237
+ localStorage.setItem("cypher_current_id", currentConversationId);
238
+
239
+ messagesContainer.innerHTML = "";
240
+ messagesContainer.appendChild(welcomeView);
241
+ welcomeView.style.display = "flex";
242
+
243
+ userInput.value = "";
244
+ userInput.style.height = "auto";
245
+ renderAttachmentChips();
246
+ sendBtn.disabled = true;
247
+ }
248
+
249
+ // Load Selected Conversation from Storage
250
+ function loadConversation(id) {
251
+ const conv = conversations.find(c => c.id === id);
252
+ if (!conv) {
253
+ startNewConversation();
254
+ return;
255
+ }
256
+
257
+ currentConversationId = id;
258
+ currentMessages = conv.messages || [];
259
+ attachedFiles = [];
260
+ localStorage.setItem("cypher_current_id", currentConversationId);
261
+
262
+ messagesContainer.innerHTML = "";
263
+ if (currentMessages.length === 0) {
264
+ messagesContainer.appendChild(welcomeView);
265
+ welcomeView.style.display = "flex";
266
+ } else {
267
+ welcomeView.style.display = "none";
268
+ currentMessages.forEach(msg => {
269
+ renderMessage(msg.role, msg.content);
270
+ });
271
+ }
272
+ renderAttachmentChips();
273
+ scrollToBottom();
274
+ renderConversationsList();
275
+ }
276
+
277
+ // Clear Current Chat
278
+ function clearCurrentConversation() {
279
+ currentMessages = [];
280
+ attachedFiles = [];
281
+ conversations = conversations.filter(c => c.id !== currentConversationId);
282
+ saveConversationsToStorage();
283
+ startNewConversation();
284
+ renderConversationsList();
285
+ }
286
+
287
+ // Render Single Message in Chat Area
288
+ function renderMessage(role, content) {
289
+ welcomeView.style.display = "none";
290
+
291
+ const wrapper = document.createElement("div");
292
+ wrapper.classList.add("message-wrapper", role);
293
+
294
+ const label = document.createElement("div");
295
+ label.classList.add("message-label");
296
+ label.innerText = role === "user" ? username : "Cypher AI";
297
+
298
+ const bubble = document.createElement("div");
299
+ bubble.classList.add("message-bubble");
300
+
301
+ // Check if message content has file markers to display them nicely
302
+ // Format: [Fichier attaché : name]\n```\ncontent\n```
303
+ let displayContent = content;
304
+ const fileHeaderRegex = /\[Fichier attaché : ([^\]]+)\]\n```[\s\S]*?```\n\n/g;
305
+
306
+ // We clean the display text slightly if we want, or just let marked parse it.
307
+ // Standard markdown code blocks parsed by marked look excellent, so we just let it run.
308
+ bubble.innerHTML = parseMarkdown(displayContent);
309
+
310
+ wrapper.appendChild(label);
311
+ wrapper.appendChild(bubble);
312
+ messagesContainer.appendChild(wrapper);
313
+
314
+ addCopyCodeButtons(bubble);
315
+ return bubble;
316
+ }
317
+
318
+ // Parse markdown securely
319
+ function parseMarkdown(text) {
320
+ try {
321
+ return marked.parse(text);
322
+ } catch (e) {
323
+ return text.replace(/\n/g, "<br>");
324
+ }
325
+ }
326
+
327
+ // Inject Copy buttons into Markdown Code Blocks
328
+ function addCopyCodeButtons(bubbleElement) {
329
+ const preBlocks = bubbleElement.querySelectorAll("pre");
330
+ preBlocks.forEach(pre => {
331
+ if (pre.querySelector(".copy-code-btn")) return;
332
+
333
+ const copyBtn = document.createElement("button");
334
+ copyBtn.className = "copy-code-btn";
335
+ copyBtn.innerText = "Copier";
336
+
337
+ copyBtn.addEventListener("click", () => {
338
+ const code = pre.querySelector("code").innerText;
339
+ navigator.clipboard.writeText(code).then(() => {
340
+ copyBtn.innerText = "Copié !";
341
+ setTimeout(() => { copyBtn.innerText = "Copier"; }, 2000);
342
+ });
343
+ });
344
+
345
+ pre.appendChild(copyBtn);
346
+ });
347
+ }
348
+
349
+ // Renders dynamic system indicators (like Web searching status)
350
+ function renderSystemStatus(text) {
351
+ const statusEl = document.createElement("div");
352
+ statusEl.classList.add("system-status-msg");
353
+ statusEl.id = "systemStatusIndicator";
354
+ statusEl.innerHTML = `<i data-lucide="loader" class="animate-spin" size="14"></i> <span>${text}</span>`;
355
+ messagesContainer.appendChild(statusEl);
356
+ lucide.createIcons();
357
+ scrollToBottom();
358
+ return statusEl;
359
+ }
360
+
361
+ // Send Message Handler
362
+ async function sendMessage() {
363
+ const text = userInput.value.trim();
364
+ if (!text && attachedFiles.length === 0) return;
365
+
366
+ // Formulate final message payload
367
+ let displayContent = "";
368
+ let finalPayloadContent = "";
369
+
370
+ if (attachedFiles.length > 0) {
371
+ const fileBlocks = attachedFiles.map(file => `[Fichier attaché : ${file.name}]\n\`\`\`\n${file.textContent}\n\`\`\``).join("\n\n");
372
+
373
+ if (text) {
374
+ displayContent = `${fileBlocks}\n\n${text}`;
375
+ finalPayloadContent = `${fileBlocks}\n\n${text}`;
376
+ } else {
377
+ displayContent = `${fileBlocks}\n\n*J'ai joint ces fichiers de code pour analyse.*`;
378
+ finalPayloadContent = `${fileBlocks}\n\nAnalyse les fichiers joints ci-dessus.`;
379
+ }
380
+ } else {
381
+ displayContent = text;
382
+ finalPayloadContent = text;
383
+ }
384
+
385
+ // 1. Add User message to UI & State
386
+ renderMessage("user", displayContent);
387
+ currentMessages.push({ role: "user", content: finalPayloadContent });
388
+
389
+ // Clear attachment state and hide UI chips
390
+ attachedFiles = [];
391
+ renderAttachmentChips();
392
+
393
+ // Reset inputs
394
+ userInput.value = "";
395
+ userInput.style.height = "auto";
396
+ sendBtn.disabled = true;
397
+ scrollToBottom();
398
+
399
+ // 2. Add empty bot bubble for streaming response
400
+ const botBubble = renderMessage("assistant", "...");
401
+ let botResponseText = "";
402
+
403
+ // 3. Render loading status indicator if web search triggers
404
+ let statusIndicator = null;
405
+ const keywords = [
406
+ "cherche sur le web", "recherche sur le web", "cherche sur internet", "recherche sur internet",
407
+ "actualités", "actualité", "dernière version", "nouveautés de", "nouveautés sur",
408
+ "météo", "cours de l'action", "dernières nouvelles"
409
+ ];
410
+ let isSearchTriggered = webSearchToggle.checked;
411
+ if (!isSearchTriggered) {
412
+ const lowerText = text.toLowerCase();
413
+ isSearchTriggered = keywords.some(k => lowerText.includes(k)) || text.startsWith("/web ") || text.startsWith("/search ");
414
+ }
415
+
416
+ if (isSearchTriggered && appMode === "local") {
417
+ statusIndicator = renderSystemStatus("Recherche en cours sur le web...");
418
+ }
419
+
420
+ try {
421
+ let response;
422
+ if (appMode === "local") {
423
+ // Server Proxy Mode
424
+ response = await fetch("/api/chat", {
425
+ method: "POST",
426
+ headers: { "Content-Type": "application/json" },
427
+ body: JSON.stringify({
428
+ messages: currentMessages,
429
+ webSearch: webSearchToggle.checked,
430
+ username: username,
431
+ token: hfToken
432
+ })
433
+ });
434
+ } else {
435
+ // Direct Client-to-API Mode
436
+ const activeToken = hfToken || DEFAULT_HF_TOKEN;
437
+ response = await fetch("https://router.huggingface.co/v1/chat/completions", {
438
+ method: "POST",
439
+ headers: {
440
+ "Content-Type": "application/json",
441
+ "Authorization": `Bearer ${activeToken}`
442
+ },
443
+ body: JSON.stringify({
444
+ model: "Qwen/Qwen2.5-Coder-7B-Instruct",
445
+ messages: [
446
+ { role: "system", content: SYSTEM_PROMPT },
447
+ ...currentMessages
448
+ ],
449
+ max_tokens: 2048,
450
+ stream: true
451
+ })
452
+ });
453
+ }
454
+
455
+ if (statusIndicator) {
456
+ statusIndicator.remove();
457
+ }
458
+
459
+ if (!response.ok) {
460
+ const errText = await response.text();
461
+ throw new Error(`Erreur API: ${errText || response.statusText}`);
462
+ }
463
+
464
+ // Initialize parser stream reader
465
+ const reader = response.body.getReader();
466
+ const decoder = new TextDecoder();
467
+ botBubble.innerHTML = ""; // Clear loader dots
468
+
469
+ while (true) {
470
+ const { done, value } = await reader.read();
471
+ if (done) break;
472
+ const chunk = decoder.decode(value, { stream: true });
473
+
474
+ // Extract chunks
475
+ const lines = chunk.split("\n");
476
+ for (const line of lines) {
477
+ if (line.startsWith("data: ")) {
478
+ const dataStr = line.slice(6).trim();
479
+ if (dataStr === "[DONE]") continue;
480
+ try {
481
+ const parsed = JSON.parse(dataStr);
482
+ if (parsed.error) {
483
+ botResponseText += `\n\n⚠️ Erreur: ${parsed.error}`;
484
+ break;
485
+ }
486
+ const tokenText = parsed.choices[0]?.delta?.content || "";
487
+ botResponseText += tokenText;
488
+ botBubble.innerHTML = parseMarkdown(botResponseText);
489
+ addCopyCodeButtons(botBubble);
490
+ } catch (e) {
491
+ // Suppress parsing errors of split packets
492
+ }
493
+ }
494
+ }
495
+ scrollToBottom();
496
+ }
497
+
498
+ // Save chatbot response to messages state
499
+ currentMessages.push({ role: "assistant", content: botResponseText });
500
+ saveCurrentConversation();
501
+
502
+ } catch (e) {
503
+ if (statusIndicator) statusIndicator.remove();
504
+ botBubble.innerHTML = `<span style="color: #EF4444;">⚠️ Échec de connexion : ${e.message}</span>`;
505
+ currentMessages.push({ role: "assistant", content: `Erreur: ${e.message}` });
506
+ }
507
+ }
508
+
509
+ // Scroll to bottom of chat area
510
+ function scrollToBottom() {
511
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
512
+ }
513
+
514
+ // Save Current Chat History to LocalStorage List
515
+ function saveCurrentConversation() {
516
+ const existingIndex = conversations.findIndex(c => c.id === currentConversationId);
517
+
518
+ // First message as title
519
+ let title = "Discussion";
520
+ if (currentMessages.length > 0) {
521
+ const firstMsg = currentMessages[0].content;
522
+ // Ignore file contents when showing sidebar preview title
523
+ const displayTitle = firstMsg.replace(/\[Fichier attaché : [^\]]+\]\n```[\s\S]*?```\n\n/g, "");
524
+ title = displayTitle.substring(0, 30) + (displayTitle.length > 30 ? "..." : "");
525
+ if (!title.trim()) title = "Discussion avec Fichier";
526
+ }
527
+
528
+ const convData = {
529
+ id: currentConversationId,
530
+ title: title,
531
+ timestamp: Date.now(),
532
+ messages: currentMessages
533
+ };
534
+
535
+ if (existingIndex > -1) {
536
+ conversations[existingIndex] = convData;
537
+ } else {
538
+ conversations.unshift(convData); // Add to top
539
+ }
540
+
541
+ saveConversationsToStorage();
542
+ renderConversationsList();
543
+ }
544
+
545
+ function saveConversationsToStorage() {
546
+ localStorage.setItem("cypher_conversations", JSON.stringify(conversations));
547
+ }
548
+
549
+ // Render saved items in Sidebar History
550
+ function renderConversationsList() {
551
+ chatHistoryList.innerHTML = "";
552
+ if (conversations.length === 0) {
553
+ chatHistoryList.innerHTML = `<div style="text-align: center; color: var(--text-muted); font-size: 13px; padding-top: 20px;">Aucune conversation</div>`;
554
+ return;
555
+ }
556
+
557
+ conversations.forEach(c => {
558
+ const item = document.createElement("button");
559
+ item.className = `chat-history-item ${c.id === currentConversationId ? "active" : ""}`;
560
+ item.innerHTML = `<i data-lucide="message-square" size="16"></i> <span class="chat-history-title">${c.title}</span>`;
561
+
562
+ item.addEventListener("click", () => {
563
+ loadConversation(c.id);
564
+ sidebar.classList.remove("active"); // Hide sidebar overlay on mobile
565
+ });
566
+
567
+ chatHistoryList.appendChild(item);
568
+ });
569
+
570
+ lucide.createIcons();
571
+ }
572
+
573
+ // Settings Modal Operations
574
+ function saveSettings() {
575
+ username = usernameInput.value.trim() || "invité";
576
+ hfToken = tokenInput.value.trim();
577
+ appMode = modeSelect.value;
578
+
579
+ localStorage.setItem("cypher_username", username);
580
+ localStorage.setItem("cypher_token", hfToken);
581
+ localStorage.setItem("cypher_mode", appMode);
582
+
583
+ updateStatusText();
584
+ settingsModal.classList.remove("active");
585
+
586
+ // Force rerender messages labels if username changed
587
+ loadConversation(currentConversationId);
588
+ }
cypher-chat-app/public/index.html ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <title>Cypher AI — Chat App</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <!-- Google Fonts -->
9
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700;800&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
10
+ <!-- Lucide Icons -->
11
+ <script src="https://unpkg.com/lucide@latest"></script>
12
+ </head>
13
+ <body>
14
+ <div class="app-container">
15
+ <!-- Sidebar Panel -->
16
+ <aside class="sidebar" id="sidebar">
17
+ <div class="sidebar-header">
18
+ <div class="app-brand">
19
+ <img src="logo.png" alt="Cypher AI Logo" class="brand-logo">
20
+ <div class="brand-text">
21
+ <h2>CYPHER AI</h2>
22
+ <span>v1.0 Standalone</span>
23
+ </div>
24
+ </div>
25
+ <button class="close-sidebar-btn" id="closeSidebarBtn">
26
+ <i data-lucide="x"></i>
27
+ </button>
28
+ </div>
29
+
30
+ <div class="sidebar-actions">
31
+ <button class="new-chat-btn" id="newChatBtn">
32
+ <i data-lucide="plus"></i> Nouvelle discussion
33
+ </button>
34
+ </div>
35
+
36
+ <div class="chat-history-list" id="chatHistoryList">
37
+ <!-- Chat history items will be dynamically injected here -->
38
+ </div>
39
+
40
+ <div class="sidebar-footer">
41
+ <button class="settings-trigger-btn" id="settingsBtn">
42
+ <i data-lucide="settings"></i> Réglages
43
+ </button>
44
+ </div>
45
+ </aside>
46
+
47
+ <!-- Main Chat Area -->
48
+ <main class="chat-main">
49
+ <!-- Chat Header -->
50
+ <header class="chat-header">
51
+ <div class="header-left">
52
+ <button class="menu-toggle-btn" id="menuToggleBtn">
53
+ <i data-lucide="menu"></i>
54
+ </button>
55
+ <div class="chat-identity">
56
+ <img src="logo.png" alt="Cypher Logo" class="header-avatar">
57
+ <div class="chat-identity-text">
58
+ <h3>Cypher AI</h3>
59
+ <div class="status-indicator">
60
+ <span class="status-dot"></span>
61
+ <span id="statusText">Connecté au serveur</span>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ <div class="header-right">
67
+ <button class="header-action-btn" id="clearChatBtn" title="Effacer l'historique local">
68
+ <i data-lucide="trash-2"></i>
69
+ </button>
70
+ </div>
71
+ </header>
72
+
73
+ <!-- Messages List -->
74
+ <section class="messages-container" id="messagesContainer">
75
+ <div class="welcome-view" id="welcomeView">
76
+ <img src="logo.png" alt="Cypher Large Logo" class="welcome-logo">
77
+ <h1>Bienvenue sur Cypher AI</h1>
78
+ <p>Votre assistant d'élite pour le développement et la programmation.</p>
79
+ <div class="quick-examples">
80
+ <button class="example-card" onclick="setInputText('Crée une fonction Python pour trier un dictionnaire par valeur.')">
81
+ <h4>🐍 Exemple Python</h4>
82
+ <p>Trier un dictionnaire par valeur.</p>
83
+ </button>
84
+ <button class="example-card" onclick="setInputText('Recherche sur le web : actualités IA de la semaine')">
85
+ <h4>🌐 Recherche Web</h4>
86
+ <p>Actualités technologiques récentes.</p>
87
+ </button>
88
+ </div>
89
+ </div>
90
+ </section>
91
+
92
+ <!-- Input Bar -->
93
+ <footer class="input-container">
94
+ <!-- Attachment Preview Container -->
95
+ <div class="attachment-preview-container" id="attachmentPreviewContainer" style="display: none;">
96
+ <!-- Chips will be added dynamically here -->
97
+ </div>
98
+
99
+ <div class="input-tools">
100
+ <div class="search-toggle-wrapper">
101
+ <label class="switch">
102
+ <input type="checkbox" id="webSearchToggle" checked>
103
+ <span class="slider round"></span>
104
+ </label>
105
+ <span class="toggle-label"><i data-lucide="globe" size="14"></i> Recherche Web</span>
106
+ </div>
107
+ </div>
108
+
109
+ <div class="input-bar">
110
+ <input type="file" id="fileInput" style="display: none;" multiple accept=".txt,.js,.py,.html,.css,.json,.md,.csv,.log,.sh,.bat,.xml,.yml,.yaml,.ini,.conf,.java,.cpp,.c,.h,.cs,.go,.rs,.ts,.jsx,.tsx">
111
+ <button id="attachBtn" class="attach-btn" type="button" title="Joindre un fichier de code/texte">
112
+ <i data-lucide="paperclip"></i>
113
+ </button>
114
+ <textarea id="userInput" placeholder="Posez une question technique ou glissez des fichiers..." rows="1"></textarea>
115
+ <button id="sendBtn" class="send-btn" disabled>
116
+ <i data-lucide="send"></i>
117
+ </button>
118
+ </div>
119
+ </footer>
120
+ </main>
121
+ </div>
122
+
123
+ <!-- Settings Modal -->
124
+ <div class="modal-overlay" id="settingsModal">
125
+ <div class="modal-content">
126
+ <div class="modal-header">
127
+ <h3>Réglages de l'application</h3>
128
+ <button class="close-modal-btn" id="closeModalBtn">
129
+ <i data-lucide="x"></i>
130
+ </button>
131
+ </div>
132
+ <div class="modal-body">
133
+ <div class="form-group">
134
+ <label for="usernameInput">Nom d'utilisateur</label>
135
+ <input type="text" id="usernameInput" placeholder="Entrez votre nom (ex. DJAKOUA)">
136
+ <span class="help-text">Utilisé pour identifier vos logs dans le dataset d'entraînement.</span>
137
+ </div>
138
+ <div class="form-group">
139
+ <label for="tokenInput">Token Hugging Face (Optionnel)</label>
140
+ <input type="password" id="tokenInput" placeholder="hf_...">
141
+ <span class="help-text">Laissez vide pour utiliser le token par défaut de l'application.</span>
142
+ </div>
143
+ <div class="form-group">
144
+ <label for="modeSelect">Mode de Connexion</label>
145
+ <select id="modeSelect">
146
+ <option value="local">Serveur Local (Recherche Web active)</option>
147
+ <option value="direct">Direct Hugging Face (Sans serveur, CORS restreint)</option>
148
+ </select>
149
+ </div>
150
+ </div>
151
+ <div class="modal-footer">
152
+ <button class="save-settings-btn" id="saveSettingsBtn">Sauvegarder</button>
153
+ </div>
154
+ </div>
155
+ </div>
156
+
157
+ <!-- Markdown Parser Library (marked) -->
158
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
159
+ <script src="app.js"></script>
160
+ <script>
161
+ // Set userInput text helper
162
+ function setInputText(text) {
163
+ const input = document.getElementById('userInput');
164
+ input.value = text;
165
+ input.dispatchEvent(new Event('input'));
166
+ input.focus();
167
+ }
168
+ </script>
169
+ </body>
170
+ </html>
cypher-chat-app/public/style.css ADDED
@@ -0,0 +1,876 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --bg-app: #090A0F;
3
+ --bg-gradient: radial-gradient(circle at top right, #11152B, #090A0F);
4
+ --bg-sidebar: #0C0E17;
5
+ --bg-chat: rgba(17, 19, 28, 0.7);
6
+ --border-color: #1E2235;
7
+ --border-glow: rgba(0, 229, 255, 0.15);
8
+ --accent-cyan: #00E5FF;
9
+ --accent-green: #00FFAA;
10
+ --text-primary: #F8FAFC;
11
+ --text-secondary: #94A3B8;
12
+ --text-muted: #64748B;
13
+
14
+ --shadow-neon: 0 0 15px rgba(0, 229, 255, 0.2);
15
+ --shadow-app: 0 8px 32px rgba(0, 0, 0, 0.4);
16
+
17
+ --transition-speed: 0.3s;
18
+ }
19
+
20
+ * {
21
+ box-sizing: border-box;
22
+ margin: 0;
23
+ padding: 0;
24
+ -webkit-tap-highlight-color: transparent;
25
+ }
26
+
27
+ body {
28
+ font-family: 'Outfit', sans-serif;
29
+ background-color: var(--bg-app);
30
+ background-image: var(--bg-gradient);
31
+ color: var(--text-primary);
32
+ height: 100vh;
33
+ overflow: hidden;
34
+ }
35
+
36
+ /* App container holds Sidebar + Main Panel */
37
+ .app-container {
38
+ display: flex;
39
+ width: 100vw;
40
+ height: 100vh;
41
+ overflow: hidden;
42
+ }
43
+
44
+ /* -----------------------------------------------------
45
+ SIDEBAR PANEL
46
+ ----------------------------------------------------- */
47
+ .sidebar {
48
+ width: 320px;
49
+ height: 100%;
50
+ background-color: var(--bg-sidebar);
51
+ border-right: 1px solid var(--border-color);
52
+ display: flex;
53
+ flex-direction: column;
54
+ z-index: 100;
55
+ transition: transform var(--transition-speed) ease;
56
+ }
57
+
58
+ .sidebar-header {
59
+ padding: 20px;
60
+ display: flex;
61
+ justify-content: space-between;
62
+ align-items: center;
63
+ border-bottom: 1px solid var(--border-color);
64
+ }
65
+
66
+ .app-brand {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 12px;
70
+ }
71
+
72
+ .brand-logo {
73
+ width: 36px;
74
+ height: 36px;
75
+ border-radius: 8px;
76
+ border: 1px solid var(--accent-cyan);
77
+ box-shadow: 0 0 10px rgba(0, 229, 255, 0.3);
78
+ }
79
+
80
+ .brand-text h2 {
81
+ font-size: 18px;
82
+ font-weight: 800;
83
+ letter-spacing: 0.5px;
84
+ background: linear-gradient(90deg, var(--accent-cyan), var(--accent-green));
85
+ -webkit-background-clip: text;
86
+ -webkit-text-fill-color: transparent;
87
+ }
88
+
89
+ .brand-text span {
90
+ font-size: 11px;
91
+ color: var(--text-muted);
92
+ font-weight: 600;
93
+ }
94
+
95
+ .close-sidebar-btn {
96
+ display: none;
97
+ background: none;
98
+ border: none;
99
+ color: var(--text-secondary);
100
+ cursor: pointer;
101
+ }
102
+
103
+ .sidebar-actions {
104
+ padding: 20px;
105
+ }
106
+
107
+ .new-chat-btn {
108
+ width: 100%;
109
+ padding: 12px;
110
+ border-radius: 10px;
111
+ background: transparent;
112
+ border: 1px solid var(--accent-cyan);
113
+ color: var(--accent-cyan);
114
+ font-weight: 600;
115
+ font-size: 14px;
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ gap: 8px;
120
+ cursor: pointer;
121
+ transition: all 0.3s ease;
122
+ box-shadow: 0 0 5px rgba(0, 229, 255, 0.05);
123
+ }
124
+
125
+ .new-chat-btn:hover {
126
+ background: var(--accent-cyan);
127
+ color: #090A0F;
128
+ box-shadow: var(--shadow-neon);
129
+ }
130
+
131
+ .chat-history-list {
132
+ flex: 1;
133
+ overflow-y: auto;
134
+ padding: 0 12px 20px;
135
+ display: flex;
136
+ flex-direction: column;
137
+ gap: 8px;
138
+ }
139
+
140
+ .chat-history-item {
141
+ display: flex;
142
+ align-items: center;
143
+ gap: 12px;
144
+ padding: 12px;
145
+ border-radius: 8px;
146
+ cursor: pointer;
147
+ background: transparent;
148
+ border: 1px solid transparent;
149
+ color: var(--text-secondary);
150
+ transition: all 0.2s ease;
151
+ text-align: left;
152
+ width: 100%;
153
+ }
154
+
155
+ .chat-history-item:hover, .chat-history-item.active {
156
+ background: rgba(30, 34, 53, 0.6);
157
+ border-color: var(--border-color);
158
+ color: var(--text-primary);
159
+ }
160
+
161
+ .chat-history-item i {
162
+ flex-shrink: 0;
163
+ }
164
+
165
+ .chat-history-title {
166
+ font-size: 14px;
167
+ font-weight: 500;
168
+ white-space: nowrap;
169
+ overflow: hidden;
170
+ text-overflow: ellipsis;
171
+ }
172
+
173
+ .sidebar-footer {
174
+ padding: 16px 20px;
175
+ border-top: 1px solid var(--border-color);
176
+ }
177
+
178
+ .settings-trigger-btn {
179
+ background: none;
180
+ border: none;
181
+ color: var(--text-secondary);
182
+ font-size: 14px;
183
+ font-weight: 600;
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 10px;
187
+ cursor: pointer;
188
+ width: 100%;
189
+ transition: color 0.2s;
190
+ }
191
+
192
+ .settings-trigger-btn:hover {
193
+ color: var(--accent-cyan);
194
+ }
195
+
196
+ /* -----------------------------------------------------
197
+ MAIN CHAT PANEL
198
+ ----------------------------------------------------- */
199
+ .chat-main {
200
+ flex: 1;
201
+ height: 100%;
202
+ display: flex;
203
+ flex-direction: column;
204
+ background-color: var(--bg-chat);
205
+ }
206
+
207
+ .chat-header {
208
+ height: 70px;
209
+ padding: 0 24px;
210
+ display: flex;
211
+ justify-content: space-between;
212
+ align-items: center;
213
+ border-bottom: 1px solid var(--border-color);
214
+ background-color: rgba(12, 14, 23, 0.4);
215
+ backdrop-filter: blur(10px);
216
+ }
217
+
218
+ .header-left {
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 16px;
222
+ }
223
+
224
+ .menu-toggle-btn {
225
+ display: none;
226
+ background: none;
227
+ border: none;
228
+ color: var(--text-primary);
229
+ cursor: pointer;
230
+ }
231
+
232
+ .chat-identity {
233
+ display: flex;
234
+ align-items: center;
235
+ gap: 12px;
236
+ }
237
+
238
+ .header-avatar {
239
+ width: 38px;
240
+ height: 38px;
241
+ border-radius: 50%;
242
+ border: 1px solid var(--accent-cyan);
243
+ box-shadow: 0 0 10px rgba(0, 229, 255, 0.2);
244
+ }
245
+
246
+ .chat-identity-text h3 {
247
+ font-size: 16px;
248
+ font-weight: 700;
249
+ }
250
+
251
+ .status-indicator {
252
+ display: flex;
253
+ align-items: center;
254
+ gap: 6px;
255
+ }
256
+
257
+ .status-dot {
258
+ width: 6px;
259
+ height: 6px;
260
+ background-color: var(--accent-green);
261
+ border-radius: 50%;
262
+ box-shadow: 0 0 8px var(--accent-green);
263
+ }
264
+
265
+ .status-indicator span {
266
+ font-size: 11px;
267
+ color: var(--text-muted);
268
+ }
269
+
270
+ .header-action-btn {
271
+ background: none;
272
+ border: none;
273
+ color: var(--text-secondary);
274
+ cursor: pointer;
275
+ padding: 8px;
276
+ border-radius: 8px;
277
+ transition: all 0.2s;
278
+ }
279
+
280
+ .header-action-btn:hover {
281
+ color: #EF4444;
282
+ background: rgba(239, 68, 68, 0.1);
283
+ }
284
+
285
+ /* -----------------------------------------------------
286
+ MESSAGES CONTAINER
287
+ ----------------------------------------------------- */
288
+ .messages-container {
289
+ flex: 1;
290
+ overflow-y: auto;
291
+ padding: 24px;
292
+ display: flex;
293
+ flex-direction: column;
294
+ gap: 20px;
295
+ scroll-behavior: smooth;
296
+ }
297
+
298
+ /* Welcome view when chatbot is empty */
299
+ .welcome-view {
300
+ display: flex;
301
+ flex-direction: column;
302
+ align-items: center;
303
+ justify-content: center;
304
+ height: 100%;
305
+ text-align: center;
306
+ max-width: 600px;
307
+ margin: auto;
308
+ }
309
+
310
+ .welcome-logo {
311
+ width: 120px;
312
+ height: 120px;
313
+ border-radius: 20px;
314
+ border: 2px solid var(--accent-cyan);
315
+ box-shadow: 0 0 30px rgba(0, 229, 255, 0.3);
316
+ margin-bottom: 24px;
317
+ }
318
+
319
+ .welcome-view h1 {
320
+ font-size: 28px;
321
+ font-weight: 800;
322
+ margin-bottom: 10px;
323
+ background: linear-gradient(90deg, var(--accent-cyan), var(--accent-green));
324
+ -webkit-background-clip: text;
325
+ -webkit-text-fill-color: transparent;
326
+ }
327
+
328
+ .welcome-view p {
329
+ color: var(--text-secondary);
330
+ font-size: 15px;
331
+ margin-bottom: 32px;
332
+ }
333
+
334
+ .quick-examples {
335
+ display: grid;
336
+ grid-template-columns: 1fr 1fr;
337
+ gap: 16px;
338
+ width: 100%;
339
+ }
340
+
341
+ .example-card {
342
+ background: rgba(30, 34, 53, 0.4);
343
+ border: 1px solid var(--border-color);
344
+ border-radius: 12px;
345
+ padding: 16px;
346
+ text-align: left;
347
+ cursor: pointer;
348
+ transition: all 0.3s;
349
+ }
350
+
351
+ .example-card:hover {
352
+ border-color: var(--accent-cyan);
353
+ box-shadow: var(--border-glow);
354
+ transform: translateY(-2px);
355
+ }
356
+
357
+ .example-card h4 {
358
+ font-size: 14px;
359
+ color: var(--accent-cyan);
360
+ margin-bottom: 6px;
361
+ }
362
+
363
+ .example-card p {
364
+ font-size: 12px;
365
+ color: var(--text-secondary);
366
+ margin: 0;
367
+ }
368
+
369
+ /* Chat bubble styling */
370
+ .message-wrapper {
371
+ display: flex;
372
+ flex-direction: column;
373
+ max-width: 85%;
374
+ }
375
+
376
+ .message-wrapper.user {
377
+ align-self: flex-end;
378
+ }
379
+
380
+ .message-wrapper.bot {
381
+ align-self: flex-start;
382
+ }
383
+
384
+ .message-label {
385
+ font-size: 11px;
386
+ color: var(--text-muted);
387
+ font-weight: 600;
388
+ margin-bottom: 4px;
389
+ padding: 0 6px;
390
+ }
391
+
392
+ .message-wrapper.user .message-label {
393
+ text-align: right;
394
+ }
395
+
396
+ .message-bubble {
397
+ padding: 14px 18px;
398
+ border-radius: 16px;
399
+ font-size: 15px;
400
+ line-height: 1.5;
401
+ word-break: break-word;
402
+ }
403
+
404
+ .message-wrapper.user .message-bubble {
405
+ background: linear-gradient(135deg, #1B2035 0%, #161926 100%);
406
+ border: 1px solid #282E4F;
407
+ color: var(--text-primary);
408
+ border-radius: 16px 16px 0px 16px;
409
+ }
410
+
411
+ .message-wrapper.bot .message-bubble {
412
+ background: linear-gradient(135deg, #0C1A24 0%, #0E1620 100%);
413
+ border: 1px solid rgba(0, 229, 255, 0.2);
414
+ color: var(--text-primary);
415
+ border-radius: 16px 16px 16px 0px;
416
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
417
+ }
418
+
419
+ .message-bubble p {
420
+ margin-bottom: 12px;
421
+ }
422
+
423
+ .message-bubble p:last-child {
424
+ margin-bottom: 0;
425
+ }
426
+
427
+ /* System/status text indicators */
428
+ .system-status-msg {
429
+ align-self: center;
430
+ background: rgba(30, 34, 53, 0.6);
431
+ border: 1px solid var(--border-color);
432
+ padding: 6px 14px;
433
+ border-radius: 20px;
434
+ font-size: 12px;
435
+ color: var(--accent-cyan);
436
+ display: flex;
437
+ align-items: center;
438
+ gap: 8px;
439
+ animation: pulse 2s infinite;
440
+ }
441
+
442
+ /* Code blocks custom styling */
443
+ pre {
444
+ background: #06070D !important;
445
+ border: 1px solid var(--border-color);
446
+ border-radius: 10px;
447
+ padding: 16px;
448
+ overflow-x: auto;
449
+ margin: 12px 0;
450
+ position: relative;
451
+ }
452
+
453
+ code {
454
+ font-family: 'JetBrains Mono', monospace;
455
+ font-size: 13px;
456
+ }
457
+
458
+ .message-bubble :not(pre) > code {
459
+ background: rgba(0, 229, 255, 0.08);
460
+ border: 1px solid rgba(0, 229, 255, 0.15);
461
+ color: var(--accent-cyan);
462
+ padding: 2px 6px;
463
+ border-radius: 4px;
464
+ font-size: 13.5px;
465
+ }
466
+
467
+ /* Copy code block button */
468
+ .copy-code-btn {
469
+ position: absolute;
470
+ top: 8px;
471
+ right: 8px;
472
+ background: rgba(30, 34, 53, 0.8);
473
+ border: 1px solid var(--border-color);
474
+ color: var(--text-secondary);
475
+ padding: 4px 8px;
476
+ border-radius: 6px;
477
+ font-size: 11px;
478
+ cursor: pointer;
479
+ transition: all 0.2s;
480
+ font-family: sans-serif;
481
+ }
482
+
483
+ .copy-code-btn:hover {
484
+ color: var(--accent-cyan);
485
+ border-color: var(--accent-cyan);
486
+ }
487
+
488
+ /* -----------------------------------------------------
489
+ INPUT AREA
490
+ ----------------------------------------------------- */
491
+ .input-container {
492
+ padding: 16px 24px 24px;
493
+ border-top: 1px solid var(--border-color);
494
+ background-color: rgba(12, 14, 23, 0.4);
495
+ backdrop-filter: blur(10px);
496
+ display: flex;
497
+ flex-direction: column;
498
+ gap: 12px;
499
+ }
500
+
501
+ /* Attachment Preview container & chips */
502
+ .attachment-preview-container {
503
+ display: flex;
504
+ flex-wrap: wrap;
505
+ gap: 8px;
506
+ padding: 8px 12px;
507
+ background: rgba(30, 34, 53, 0.3);
508
+ border: 1px dashed var(--border-color);
509
+ border-radius: 8px;
510
+ max-height: 120px;
511
+ overflow-y: auto;
512
+ }
513
+
514
+ .attachment-chip {
515
+ display: flex;
516
+ align-items: center;
517
+ gap: 8px;
518
+ background: rgba(0, 229, 255, 0.08);
519
+ border: 1px solid rgba(0, 229, 255, 0.2);
520
+ padding: 4px 10px;
521
+ border-radius: 20px;
522
+ font-size: 12px;
523
+ color: var(--text-primary);
524
+ }
525
+
526
+ .attachment-chip span {
527
+ max-width: 140px;
528
+ white-space: nowrap;
529
+ overflow: hidden;
530
+ text-overflow: ellipsis;
531
+ }
532
+
533
+ .attachment-chip .remove-attach-btn {
534
+ background: none;
535
+ border: none;
536
+ color: var(--text-muted);
537
+ cursor: pointer;
538
+ display: flex;
539
+ align-items: center;
540
+ transition: color 0.2s;
541
+ }
542
+
543
+ .attachment-chip .remove-attach-btn:hover {
544
+ color: #EF4444;
545
+ }
546
+
547
+ .input-tools {
548
+ display: flex;
549
+ justify-content: space-between;
550
+ }
551
+
552
+ .search-toggle-wrapper {
553
+ display: flex;
554
+ align-items: center;
555
+ gap: 8px;
556
+ }
557
+
558
+ .toggle-label {
559
+ font-size: 13px;
560
+ font-weight: 600;
561
+ color: var(--text-secondary);
562
+ display: flex;
563
+ align-items: center;
564
+ gap: 4px;
565
+ }
566
+
567
+ .attach-btn {
568
+ background: none;
569
+ border: none;
570
+ color: var(--text-secondary);
571
+ width: 36px;
572
+ height: 36px;
573
+ border-radius: 8px;
574
+ display: flex;
575
+ align-items: center;
576
+ justify-content: center;
577
+ cursor: pointer;
578
+ flex-shrink: 0;
579
+ transition: all 0.2s;
580
+ }
581
+
582
+ .attach-btn:hover {
583
+ color: var(--accent-cyan);
584
+ background: rgba(0, 229, 255, 0.08);
585
+ }
586
+
587
+ .input-bar {
588
+ display: flex;
589
+ gap: 12px;
590
+ background-color: #121422;
591
+ border: 1px solid var(--border-color);
592
+ border-radius: 12px;
593
+ padding: 8px 12px;
594
+ align-items: flex-end;
595
+ transition: all 0.3s;
596
+ }
597
+
598
+ .input-bar:focus-within {
599
+ border-color: var(--accent-cyan);
600
+ box-shadow: var(--border-glow);
601
+ }
602
+
603
+ .input-bar textarea {
604
+ flex: 1;
605
+ background: none;
606
+ border: none;
607
+ color: var(--text-primary);
608
+ font-family: inherit;
609
+ font-size: 15px;
610
+ outline: none;
611
+ resize: none;
612
+ padding: 6px 4px;
613
+ max-height: 200px;
614
+ line-height: 1.4;
615
+ }
616
+
617
+ .send-btn {
618
+ background: linear-gradient(90deg, var(--accent-cyan), var(--accent-green));
619
+ border: none;
620
+ width: 36px;
621
+ height: 36px;
622
+ border-radius: 8px;
623
+ color: #090A0F;
624
+ display: flex;
625
+ align-items: center;
626
+ justify-content: center;
627
+ cursor: pointer;
628
+ flex-shrink: 0;
629
+ transition: all 0.2s;
630
+ }
631
+
632
+ .send-btn:disabled {
633
+ background: var(--border-color);
634
+ color: var(--text-muted);
635
+ cursor: not-allowed;
636
+ }
637
+
638
+ .send-btn:not(:disabled):hover {
639
+ transform: scale(1.05);
640
+ box-shadow: var(--shadow-neon);
641
+ }
642
+
643
+ /* Switch UI styling */
644
+ .switch {
645
+ position: relative;
646
+ display: inline-block;
647
+ width: 34px;
648
+ height: 20px;
649
+ }
650
+
651
+ .switch input {
652
+ opacity: 0;
653
+ width: 0;
654
+ height: 0;
655
+ }
656
+
657
+ .slider {
658
+ position: absolute;
659
+ cursor: pointer;
660
+ top: 0;
661
+ left: 0;
662
+ right: 0;
663
+ bottom: 0;
664
+ background-color: var(--border-color);
665
+ transition: .4s;
666
+ }
667
+
668
+ .slider:before {
669
+ position: absolute;
670
+ content: "";
671
+ height: 14px;
672
+ width: 14px;
673
+ left: 3px;
674
+ bottom: 3px;
675
+ background-color: var(--text-secondary);
676
+ transition: .4s;
677
+ }
678
+
679
+ input:checked + .slider {
680
+ background-color: var(--accent-cyan);
681
+ }
682
+
683
+ input:checked + .slider:before {
684
+ transform: translateX(14px);
685
+ background-color: #090A0F;
686
+ }
687
+
688
+ .slider.round {
689
+ border-radius: 20px;
690
+ }
691
+
692
+ .slider.round:before {
693
+ border-radius: 50%;
694
+ }
695
+
696
+ /* -----------------------------------------------------
697
+ SETTINGS MODAL
698
+ ----------------------------------------------------- */
699
+ .modal-overlay {
700
+ position: fixed;
701
+ top: 0;
702
+ left: 0;
703
+ right: 0;
704
+ bottom: 0;
705
+ background: rgba(0, 0, 0, 0.7);
706
+ z-index: 1000;
707
+ display: flex;
708
+ align-items: center;
709
+ justify-content: center;
710
+ opacity: 0;
711
+ pointer-events: none;
712
+ transition: opacity 0.3s ease;
713
+ backdrop-filter: blur(4px);
714
+ }
715
+
716
+ .modal-overlay.active {
717
+ opacity: 1;
718
+ pointer-events: auto;
719
+ }
720
+
721
+ .modal-content {
722
+ background: #11131C;
723
+ border: 1px solid var(--border-color);
724
+ border-radius: 16px;
725
+ width: 100%;
726
+ max-width: 480px;
727
+ box-shadow: var(--shadow-app);
728
+ transform: translateY(20px);
729
+ transition: transform 0.3s ease;
730
+ }
731
+
732
+ .modal-overlay.active .modal-content {
733
+ transform: translateY(0);
734
+ }
735
+
736
+ .modal-header {
737
+ padding: 20px;
738
+ border-bottom: 1px solid var(--border-color);
739
+ display: flex;
740
+ justify-content: space-between;
741
+ align-items: center;
742
+ }
743
+
744
+ .modal-header h3 {
745
+ font-size: 18px;
746
+ font-weight: 700;
747
+ }
748
+
749
+ .close-modal-btn {
750
+ background: none;
751
+ border: none;
752
+ color: var(--text-secondary);
753
+ cursor: pointer;
754
+ }
755
+
756
+ .modal-body {
757
+ padding: 20px;
758
+ display: flex;
759
+ flex-direction: column;
760
+ gap: 16px;
761
+ }
762
+
763
+ .form-group {
764
+ display: flex;
765
+ flex-direction: column;
766
+ gap: 6px;
767
+ }
768
+
769
+ .form-group label {
770
+ font-size: 13px;
771
+ font-weight: 600;
772
+ color: var(--text-secondary);
773
+ }
774
+
775
+ .form-group input, .form-group select {
776
+ background: #161926;
777
+ border: 1px solid var(--border-color);
778
+ padding: 10px 14px;
779
+ border-radius: 8px;
780
+ color: var(--text-primary);
781
+ font-size: 14px;
782
+ outline: none;
783
+ }
784
+
785
+ .form-group input:focus, .form-group select:focus {
786
+ border-color: var(--accent-cyan);
787
+ }
788
+
789
+ .help-text {
790
+ font-size: 11px;
791
+ color: var(--text-muted);
792
+ }
793
+
794
+ .modal-footer {
795
+ padding: 16px 20px;
796
+ border-top: 1px solid var(--border-color);
797
+ display: flex;
798
+ justify-content: flex-end;
799
+ }
800
+
801
+ .save-settings-btn {
802
+ background: var(--accent-cyan);
803
+ border: none;
804
+ padding: 10px 20px;
805
+ border-radius: 8px;
806
+ color: #090A0F;
807
+ font-weight: 700;
808
+ cursor: pointer;
809
+ transition: all 0.2s;
810
+ }
811
+
812
+ .save-settings-btn:hover {
813
+ box-shadow: var(--shadow-neon);
814
+ }
815
+
816
+ /* Animations */
817
+ @keyframes pulse {
818
+ 0% { opacity: 0.8; }
819
+ 50% { opacity: 0.5; }
820
+ 100% { opacity: 0.8; }
821
+ }
822
+
823
+ /* -----------------------------------------------------
824
+ RESPONSIVE DESIGN (MOBILE & TABLET)
825
+ ----------------------------------------------------- */
826
+ @media (max-width: 768px) {
827
+ /* Hide sidebar and let it slide in */
828
+ .sidebar {
829
+ position: fixed;
830
+ left: 0;
831
+ top: 0;
832
+ bottom: 0;
833
+ transform: translateX(-100%);
834
+ box-shadow: 10px 0 30px rgba(0,0,0,0.5);
835
+ }
836
+
837
+ .sidebar.active {
838
+ transform: translateX(0);
839
+ }
840
+
841
+ .close-sidebar-btn {
842
+ display: block;
843
+ }
844
+
845
+ .menu-toggle-btn {
846
+ display: block;
847
+ }
848
+
849
+ .chat-header {
850
+ padding: 0 16px;
851
+ }
852
+
853
+ .messages-container {
854
+ padding: 16px;
855
+ }
856
+
857
+ .input-container {
858
+ padding: 12px 16px 16px;
859
+ }
860
+
861
+ .welcome-view h1 {
862
+ font-size: 22px;
863
+ }
864
+
865
+ .quick-examples {
866
+ grid-template-columns: 1fr;
867
+ }
868
+
869
+ .message-wrapper {
870
+ max-width: 92%;
871
+ }
872
+
873
+ .modal-content {
874
+ max-width: 90%;
875
+ }
876
+ }
cypher-chat-app/server.js ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from "express";
2
+ import cors from "cors";
3
+ import path from "path";
4
+ import fs from "fs";
5
+ import https from "https";
6
+ import { spawn } from "child_process";
7
+ import { fileURLToPath } from "url";
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ const app = express();
13
+ const PORT = process.env.PORT || 3000;
14
+
15
+ app.use(cors());
16
+ app.use(express.json());
17
+ app.use(express.static(path.join(__dirname, "public")));
18
+
19
+ // Load token from environment or local .env file
20
+ let DEFAULT_HF_TOKEN = process.env.HF_TOKEN || "";
21
+ try {
22
+ const envPath = path.join(__dirname, ".env");
23
+ if (fs.existsSync(envPath)) {
24
+ const envContent = fs.readFileSync(envPath, "utf8");
25
+ const match = envContent.match(/HF_TOKEN\s*=\s*(.*)/);
26
+ if (match && match[1]) {
27
+ DEFAULT_HF_TOKEN = match[1].trim().replace(/['"]/g, "");
28
+ }
29
+ }
30
+ } catch (e) {
31
+ console.error("Impossible de charger le fichier .env :", e);
32
+ }
33
+
34
+ const DEFAULT_HF_MODEL = "Qwen/Qwen2.5-Coder-7B-Instruct";
35
+
36
+ const SYSTEM_PROMPT = `Tu es Cypher AI, une intelligence artificielle d'élite spécialisée dans la programmation, l'algorithmie et la conception de logiciels.
37
+ Tu as été développé par DJAKOUA KWANKAM, étudiant en informatique à l'Institut Universitaire de Technologie de Douala (IUT).
38
+ Tu es direct, précis, rigoureux et tu t'exprimes de façon fluide sans fioritures superflues.
39
+ Ne mentionne jamais tes dates de coupure de connaissances ou de fin d'entraînement (comme 2022). Utilise uniquement les informations actualisées fournies dans ton contexte de recherche en ligne si nécessaire.
40
+
41
+ [INSTRUCTIONS DE CONTEXTE INTERNET] :
42
+ Quand l'utilisateur te pose une question nécessitant des recherches actualisées ou de la documentation technique récente, des extraits de résultats de recherche DuckDuckGo seront injectés automatiquement en début de message. Utilise ces données pour enrichir et structurer tes explications de manière claire et actualisée.`;
43
+
44
+ /**
45
+ * Perform DuckDuckGo Search over HTTPS with IPv4 force
46
+ */
47
+ function searchWeb(query) {
48
+ return new Promise((resolve) => {
49
+ console.log("🔍 Recherche DuckDuckGo pour :", query);
50
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
51
+ const options = {
52
+ family: 4, // Force IPv4 to avoid Node.js/undici IPv6 ETIMEDOUT bugs
53
+ headers: {
54
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
55
+ }
56
+ };
57
+
58
+ https.get(url, options, (res) => {
59
+ let html = "";
60
+ res.on("data", (chunk) => { html += chunk; });
61
+ res.on("end", () => {
62
+ const results = [];
63
+ // DuckDuckGo HTML results are wrapped in web-result containers
64
+ const resultRegex = /<div class="[^"]*web-result[^"]*">([\s\S]*?)<div class="clear"><\/div>/g;
65
+ let match;
66
+
67
+ while ((match = resultRegex.exec(html)) !== null && results.length < 3) {
68
+ const body = match[1];
69
+
70
+ // Extract Title & URL
71
+ const titleMatch = /<a[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/.exec(body);
72
+ let url = titleMatch ? titleMatch[1] : "";
73
+ let title = titleMatch ? titleMatch[2].replace(/<[^>]*>/g, "").trim() : "";
74
+
75
+ if (url) {
76
+ const uddgMatch = /uddg=([^&]*)/.exec(url);
77
+ if (uddgMatch) {
78
+ url = decodeURIComponent(uddgMatch[1]);
79
+ } else if (url.startsWith("//")) {
80
+ url = "https:" + url;
81
+ }
82
+ }
83
+
84
+ // Extract Snippet
85
+ const snippetMatch = /<a[^>]*class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>/.exec(body);
86
+ let snippet = snippetMatch ? snippetMatch[1].replace(/<[^>]*>/g, "").trim() : "";
87
+
88
+ if (title && snippet) {
89
+ title = title.replace(/&#x27;/g, "'").replace(/&quot;/g, '"').replace(/&amp;/g, '&');
90
+ snippet = snippet.replace(/&#x27;/g, "'").replace(/&quot;/g, '"').replace(/&amp;/g, '&');
91
+ results.push({ title, snippet, url });
92
+ }
93
+ }
94
+ resolve(results);
95
+ });
96
+ }).on("error", (e) => {
97
+ console.error("❌ Erreur HTTPS DuckDuckGo :", e);
98
+ resolve([]);
99
+ });
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Triggers background python process to upload logs to HF datasets
105
+ */
106
+ function triggerTelemetry(username, message, responseText, token) {
107
+ const logEntry = {
108
+ username: username,
109
+ timestamp: new Date().toISOString(),
110
+ message: message,
111
+ response: responseText,
112
+ client: "cypher-standalone-web"
113
+ };
114
+
115
+ const tempDir = path.join(__dirname, "temp_logs");
116
+ if (!fs.existsSync(tempDir)) {
117
+ fs.mkdirSync(tempDir);
118
+ }
119
+
120
+ const tempFilePath = path.join(tempDir, `${Date.now()}_${Math.random().toString(36).substring(7)}.json`);
121
+ fs.writeFileSync(tempFilePath, JSON.stringify(logEntry));
122
+
123
+ const venvPython = "/home/theshellpc/Documents/cypher-ai/venv/bin/python3";
124
+ const scriptPath = path.join(__dirname, "upload_log.py");
125
+
126
+ const child = spawn(venvPython, [scriptPath, tempFilePath]);
127
+
128
+ child.stdout.on("data", (data) => {
129
+ console.log(`[Télémétrie] stdout: ${data}`);
130
+ });
131
+
132
+ child.stderr.on("data", (data) => {
133
+ console.error(`[Télémétrie] stderr: ${data}`);
134
+ });
135
+ }
136
+
137
+ /**
138
+ * POST /api/chat - Chat completion proxy with RAG search and SSE streaming
139
+ */
140
+ app.post("/api/chat", async (req, res) => {
141
+ const { messages, webSearch, username, token } = req.body;
142
+ const activeToken = token || DEFAULT_HF_TOKEN;
143
+ const activeUsername = username || "invité";
144
+
145
+ console.log(`📨 [Chat] Requête reçue de "${activeUsername}" (Recherche Web active: ${webSearch})`);
146
+
147
+ if (!messages || !Array.isArray(messages)) {
148
+ return res.status(400).json({ error: "Invalid messages history." });
149
+ }
150
+
151
+ try {
152
+ const lastMessage = messages[messages.length - 1]?.content || "";
153
+ let context = "";
154
+
155
+ console.log(`💬 Message reçu: "${lastMessage.substring(0, 60)}${lastMessage.length > 60 ? '...' : ''}"`);
156
+
157
+ // Check triggers for web search
158
+ let shouldSearch = webSearch;
159
+ const msgLower = lastMessage.toLowerCase();
160
+
161
+ const keywords = [
162
+ "cherche sur le web", "recherche sur le web", "cherche sur internet", "recherche sur internet",
163
+ "actualités", "actualité", "dernière version", "nouveautés de", "nouveautés sur",
164
+ "météo", "cours de l'action", "dernières nouvelles"
165
+ ];
166
+
167
+ if (!shouldSearch) {
168
+ for (const kw of keywords) {
169
+ if (msgLower.includes(kw)) {
170
+ shouldSearch = true;
171
+ break;
172
+ }
173
+ }
174
+ }
175
+
176
+ if (lastMessage.startsWith("/web ") || lastMessage.startsWith("/search ")) {
177
+ shouldSearch = true;
178
+ }
179
+
180
+ if (shouldSearch) {
181
+ let searchQuery = lastMessage;
182
+ for (const kw of keywords) {
183
+ const regex = new RegExp(kw, "gi");
184
+ searchQuery = searchQuery.replace(regex, "");
185
+ }
186
+ if (searchQuery.startsWith("/web ")) searchQuery = searchQuery.slice(5);
187
+ if (searchQuery.startsWith("/search ")) searchQuery = searchQuery.slice(8);
188
+ searchQuery = searchQuery.trim().replace(/^[:?./"']+|[:?./"']+$/g, "");
189
+
190
+ if (!searchQuery || searchQuery.length < 3) {
191
+ searchQuery = lastMessage;
192
+ }
193
+
194
+ const searchResults = await searchWeb(searchQuery);
195
+ if (searchResults.length > 0) {
196
+ const formatted = searchResults.map(r => `Titre: ${r.title}\nRésumé: ${r.snippet}\nLien: ${r.url}`).join("\n\n");
197
+ context = `\n\n[CONTEXTE DU WEB]\n${formatted}\n[FIN DU CONTEXTE]`;
198
+ }
199
+ }
200
+
201
+ // Setup messages for HF API with temporal context
202
+ const dateString = new Date().toLocaleDateString("fr-FR", { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
203
+ const dateContext = `[CONTEXTE TEMPOREL] : Nous sommes aujourd'hui le ${dateString}.\n`;
204
+
205
+ const formattedMessages = [
206
+ { role: "system", content: dateContext + SYSTEM_PROMPT + (context ? `\nTu as accès aux résultats de recherche suivants pour répondre à l'utilisateur : ${context}` : "") },
207
+ ...messages
208
+ ];
209
+
210
+ // Query Hugging Face Serverless API with streaming
211
+ const hfResponse = await fetch("https://router.huggingface.co/v1/chat/completions", {
212
+ method: "POST",
213
+ headers: {
214
+ "Content-Type": "application/json",
215
+ "Authorization": `Bearer ${activeToken}`
216
+ },
217
+ body: JSON.stringify({
218
+ model: DEFAULT_HF_MODEL,
219
+ messages: formattedMessages,
220
+ max_tokens: 2048,
221
+ stream: true
222
+ })
223
+ });
224
+
225
+ if (!hfResponse.ok) {
226
+ const errText = await hfResponse.text();
227
+ throw new Error(`Hugging Face API returned ${hfResponse.status}: ${errText}`);
228
+ }
229
+
230
+ res.setHeader("Content-Type", "text/event-stream");
231
+ res.setHeader("Cache-Control", "no-cache");
232
+ res.setHeader("Connection", "keep-alive");
233
+
234
+ let fullResponseText = "";
235
+ const decoder = new TextDecoder("utf-8");
236
+
237
+ // Stream chunks to client
238
+ for await (const chunk of hfResponse.body) {
239
+ const textChunk = decoder.decode(chunk, { stream: true });
240
+ res.write(textChunk);
241
+
242
+ // Extract tokens for background log
243
+ const lines = textChunk.split("\n");
244
+ for (const line of lines) {
245
+ if (line.startsWith("data: ")) {
246
+ const dataStr = line.slice(6).trim();
247
+ if (dataStr === "[DONE]") continue;
248
+ try {
249
+ const parsed = JSON.parse(dataStr);
250
+ const content = parsed.choices[0]?.delta?.content || "";
251
+ fullResponseText += content;
252
+ } catch (e) {
253
+ // Suppress parsing errors of incomplete lines
254
+ }
255
+ }
256
+ }
257
+ }
258
+
259
+ res.end();
260
+
261
+ // Trigger telemetry logging in background
262
+ triggerTelemetry(activeUsername, lastMessage, fullResponseText, activeToken);
263
+
264
+ } catch (error) {
265
+ console.error("❌ Error in chat handler:", error);
266
+ res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
267
+ res.end();
268
+ }
269
+ });
270
+
271
+ // Start Express Server
272
+ app.listen(PORT, "0.0.0.0", () => {
273
+ console.log(`⚡ Cypher App lancée localement !`);
274
+ console.log(`💻 Accès PC : http://localhost:${PORT}`);
275
+ console.log(`📱 Accès Android : Ouvrez http://<IP_DE_VOTRE_PC>:${PORT} sur votre téléphone`);
276
+ });
cypher-chat-app/upload_log.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ import json
4
+ from datetime import datetime
5
+ import uuid
6
+ from io import BytesIO
7
+ from huggingface_hub import HfApi, create_repo
8
+
9
+ token = os.environ.get("HF_TOKEN")
10
+ if not token:
11
+ try:
12
+ env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
13
+ if os.path.exists(env_path):
14
+ with open(env_path, "r", encoding="utf-8") as f:
15
+ for line in f:
16
+ if line.strip().startswith("HF_TOKEN"):
17
+ parts = line.split("=", 1)
18
+ if len(parts) > 1:
19
+ token = parts[1].strip().strip('"').strip("'")
20
+ break
21
+ except Exception:
22
+ pass
23
+
24
+ api = HfApi(token=token)
25
+
26
+ def upload_log(temp_file_path):
27
+ try:
28
+ with open(temp_file_path, "r", encoding="utf-8") as f:
29
+ log_entry = json.load(f)
30
+
31
+ user = api.whoami()["name"]
32
+ repo_id = f"{user}/cypher-coder-logs"
33
+ create_repo(repo_id, token=token, repo_type="dataset", private=True, exist_ok=True)
34
+
35
+ username = log_entry.get("username", "invité")
36
+ file_path = f"logs/{username}/{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}.json"
37
+
38
+ content_bytes = json.dumps(log_entry, ensure_ascii=False, indent=2).encode("utf-8")
39
+
40
+ api.upload_file(
41
+ path_or_fileobj=BytesIO(content_bytes),
42
+ path_in_repo=file_path,
43
+ repo_id=repo_id,
44
+ repo_type="dataset",
45
+ token=token
46
+ )
47
+ print(f"Telemetry upload successful: {file_path}")
48
+ except Exception as e:
49
+ print(f"Telemetry upload error: {e}", file=sys.stderr)
50
+ finally:
51
+ try:
52
+ if os.path.exists(temp_file_path):
53
+ os.remove(temp_file_path)
54
+ except Exception:
55
+ pass
56
+
57
+ if __name__ == "__main__":
58
+ if len(sys.argv) > 1:
59
+ upload_log(sys.argv[1])
index.js CHANGED
@@ -53,7 +53,7 @@ let macros = {};
53
  let logs = [];
54
 
55
  const sessionConfig = {
56
- model: "Qwen/Qwen2.5-Coder-32B-Instruct",
57
  temperature: 0.7,
58
  top_p: 0.9,
59
  max_tokens: 2048,
@@ -989,7 +989,7 @@ async function handleSlashCommand(text) {
989
  console.log(`Nom : ${APP_NAME}`);
990
  console.log(`Créateur : ${AUTHOR} (Étudiant IUT)`);
991
  console.log(`Description: Assistant IA autonome de codage local et réseau.`);
992
- console.log(`Modèle : Qwen/Qwen2.5-Coder-32B-Instruct`);
993
  console.log(`Réseau : FastAPI + Gradio (Docker Space HF)`);
994
  console.log(`Données : Collecte active de logs pour le dataset d'entraînement.\n`);
995
  break;
@@ -1127,7 +1127,7 @@ async function handleSlashCommand(text) {
1127
  case '/model':
1128
  if (cleanArgs[0] === 'list') {
1129
  console.log(chalk.cyan("Modèles disponibles via Hugging Face Inference :"));
1130
- console.log(" - Qwen/Qwen2.5-Coder-32B-Instruct (Recommandé - Actif par défaut)");
1131
  console.log(" - meta-llama/Llama-3.3-70B-Instruct");
1132
  console.log(" - deepseek-ai/DeepSeek-Coder-V2-Instruct");
1133
  } else if (cleanArgs[0] === 'set') {
@@ -1449,7 +1449,7 @@ async function handleSlashCommand(text) {
1449
  console.log(chalk.red("Clé introuvable."));
1450
  }
1451
  } else if (cleanArgs[0] === 'reset') {
1452
- sessionConfig.model = "Qwen/Qwen2.5-Coder-32B-Instruct";
1453
  sessionConfig.temperature = 0.7;
1454
  sessionConfig.max_tokens = 2048;
1455
  console.log(chalk.green("Configuration réinitialisée."));
 
53
  let logs = [];
54
 
55
  const sessionConfig = {
56
+ model: "Qwen/Qwen2.5-Coder-7B-Instruct",
57
  temperature: 0.7,
58
  top_p: 0.9,
59
  max_tokens: 2048,
 
989
  console.log(`Nom : ${APP_NAME}`);
990
  console.log(`Créateur : ${AUTHOR} (Étudiant IUT)`);
991
  console.log(`Description: Assistant IA autonome de codage local et réseau.`);
992
+ console.log(`Modèle : Qwen/Qwen2.5-Coder-7B-Instruct`);
993
  console.log(`Réseau : FastAPI + Gradio (Docker Space HF)`);
994
  console.log(`Données : Collecte active de logs pour le dataset d'entraînement.\n`);
995
  break;
 
1127
  case '/model':
1128
  if (cleanArgs[0] === 'list') {
1129
  console.log(chalk.cyan("Modèles disponibles via Hugging Face Inference :"));
1130
+ console.log(" - Qwen/Qwen2.5-Coder-7B-Instruct (Recommandé - Actif par défaut)");
1131
  console.log(" - meta-llama/Llama-3.3-70B-Instruct");
1132
  console.log(" - deepseek-ai/DeepSeek-Coder-V2-Instruct");
1133
  } else if (cleanArgs[0] === 'set') {
 
1449
  console.log(chalk.red("Clé introuvable."));
1450
  }
1451
  } else if (cleanArgs[0] === 'reset') {
1452
+ sessionConfig.model = "Qwen/Qwen2.5-Coder-7B-Instruct";
1453
  sessionConfig.temperature = 0.7;
1454
  sessionConfig.max_tokens = 2048;
1455
  console.log(chalk.green("Configuration réinitialisée."));
package-lock.json ADDED
@@ -0,0 +1,1037 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "cypher-coder",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "cypher-coder",
9
+ "version": "1.0.0",
10
+ "license": "MIT",
11
+ "dependencies": {
12
+ "@gradio/client": "^2.2.1",
13
+ "@huggingface/inference": "^2.6.4",
14
+ "chalk": "^5.3.0",
15
+ "inquirer": "^9.2.12",
16
+ "marked": "^15.0.12",
17
+ "marked-terminal": "^7.3.0",
18
+ "ora": "^7.0.1"
19
+ },
20
+ "bin": {
21
+ "cypher": "index.js"
22
+ }
23
+ },
24
+ "node_modules/@colors/colors": {
25
+ "version": "1.5.0",
26
+ "license": "MIT",
27
+ "optional": true,
28
+ "engines": {
29
+ "node": ">=0.1.90"
30
+ }
31
+ },
32
+ "node_modules/@gradio/client": {
33
+ "version": "2.2.1",
34
+ "license": "ISC",
35
+ "dependencies": {
36
+ "fetch-event-stream": "^0.1.5"
37
+ },
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ }
41
+ },
42
+ "node_modules/@huggingface/inference": {
43
+ "version": "2.8.1",
44
+ "license": "MIT",
45
+ "dependencies": {
46
+ "@huggingface/tasks": "^0.12.9"
47
+ },
48
+ "engines": {
49
+ "node": ">=18"
50
+ }
51
+ },
52
+ "node_modules/@huggingface/tasks": {
53
+ "version": "0.12.30",
54
+ "license": "MIT"
55
+ },
56
+ "node_modules/@inquirer/external-editor": {
57
+ "version": "1.0.3",
58
+ "license": "MIT",
59
+ "dependencies": {
60
+ "chardet": "^2.1.1",
61
+ "iconv-lite": "^0.7.0"
62
+ },
63
+ "engines": {
64
+ "node": ">=18"
65
+ },
66
+ "peerDependencies": {
67
+ "@types/node": ">=18"
68
+ },
69
+ "peerDependenciesMeta": {
70
+ "@types/node": {
71
+ "optional": true
72
+ }
73
+ }
74
+ },
75
+ "node_modules/@inquirer/figures": {
76
+ "version": "1.0.15",
77
+ "license": "MIT",
78
+ "engines": {
79
+ "node": ">=18"
80
+ }
81
+ },
82
+ "node_modules/@sindresorhus/is": {
83
+ "version": "4.6.0",
84
+ "license": "MIT",
85
+ "engines": {
86
+ "node": ">=10"
87
+ },
88
+ "funding": {
89
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
90
+ }
91
+ },
92
+ "node_modules/ansi-escapes": {
93
+ "version": "4.3.2",
94
+ "license": "MIT",
95
+ "dependencies": {
96
+ "type-fest": "^0.21.3"
97
+ },
98
+ "engines": {
99
+ "node": ">=8"
100
+ },
101
+ "funding": {
102
+ "url": "https://github.com/sponsors/sindresorhus"
103
+ }
104
+ },
105
+ "node_modules/ansi-regex": {
106
+ "version": "5.0.1",
107
+ "license": "MIT",
108
+ "engines": {
109
+ "node": ">=8"
110
+ }
111
+ },
112
+ "node_modules/ansi-styles": {
113
+ "version": "4.3.0",
114
+ "license": "MIT",
115
+ "dependencies": {
116
+ "color-convert": "^2.0.1"
117
+ },
118
+ "engines": {
119
+ "node": ">=8"
120
+ },
121
+ "funding": {
122
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
123
+ }
124
+ },
125
+ "node_modules/any-promise": {
126
+ "version": "1.3.0",
127
+ "license": "MIT"
128
+ },
129
+ "node_modules/base64-js": {
130
+ "version": "1.5.1",
131
+ "funding": [
132
+ {
133
+ "type": "github",
134
+ "url": "https://github.com/sponsors/feross"
135
+ },
136
+ {
137
+ "type": "patreon",
138
+ "url": "https://www.patreon.com/feross"
139
+ },
140
+ {
141
+ "type": "consulting",
142
+ "url": "https://feross.org/support"
143
+ }
144
+ ],
145
+ "license": "MIT"
146
+ },
147
+ "node_modules/bl": {
148
+ "version": "5.1.0",
149
+ "license": "MIT",
150
+ "dependencies": {
151
+ "buffer": "^6.0.3",
152
+ "inherits": "^2.0.4",
153
+ "readable-stream": "^3.4.0"
154
+ }
155
+ },
156
+ "node_modules/buffer": {
157
+ "version": "6.0.3",
158
+ "funding": [
159
+ {
160
+ "type": "github",
161
+ "url": "https://github.com/sponsors/feross"
162
+ },
163
+ {
164
+ "type": "patreon",
165
+ "url": "https://www.patreon.com/feross"
166
+ },
167
+ {
168
+ "type": "consulting",
169
+ "url": "https://feross.org/support"
170
+ }
171
+ ],
172
+ "license": "MIT",
173
+ "dependencies": {
174
+ "base64-js": "^1.3.1",
175
+ "ieee754": "^1.2.1"
176
+ }
177
+ },
178
+ "node_modules/chalk": {
179
+ "version": "5.6.2",
180
+ "license": "MIT",
181
+ "engines": {
182
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
183
+ },
184
+ "funding": {
185
+ "url": "https://github.com/chalk/chalk?sponsor=1"
186
+ }
187
+ },
188
+ "node_modules/char-regex": {
189
+ "version": "1.0.2",
190
+ "license": "MIT",
191
+ "engines": {
192
+ "node": ">=10"
193
+ }
194
+ },
195
+ "node_modules/chardet": {
196
+ "version": "2.1.1",
197
+ "license": "MIT"
198
+ },
199
+ "node_modules/cli-cursor": {
200
+ "version": "4.0.0",
201
+ "license": "MIT",
202
+ "dependencies": {
203
+ "restore-cursor": "^4.0.0"
204
+ },
205
+ "engines": {
206
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
207
+ },
208
+ "funding": {
209
+ "url": "https://github.com/sponsors/sindresorhus"
210
+ }
211
+ },
212
+ "node_modules/cli-highlight": {
213
+ "version": "2.1.11",
214
+ "license": "ISC",
215
+ "dependencies": {
216
+ "chalk": "^4.0.0",
217
+ "highlight.js": "^10.7.1",
218
+ "mz": "^2.4.0",
219
+ "parse5": "^5.1.1",
220
+ "parse5-htmlparser2-tree-adapter": "^6.0.0",
221
+ "yargs": "^16.0.0"
222
+ },
223
+ "bin": {
224
+ "highlight": "bin/highlight"
225
+ },
226
+ "engines": {
227
+ "node": ">=8.0.0",
228
+ "npm": ">=5.0.0"
229
+ }
230
+ },
231
+ "node_modules/cli-highlight/node_modules/chalk": {
232
+ "version": "4.1.2",
233
+ "license": "MIT",
234
+ "dependencies": {
235
+ "ansi-styles": "^4.1.0",
236
+ "supports-color": "^7.1.0"
237
+ },
238
+ "engines": {
239
+ "node": ">=10"
240
+ },
241
+ "funding": {
242
+ "url": "https://github.com/chalk/chalk?sponsor=1"
243
+ }
244
+ },
245
+ "node_modules/cli-spinners": {
246
+ "version": "2.9.2",
247
+ "license": "MIT",
248
+ "engines": {
249
+ "node": ">=6"
250
+ },
251
+ "funding": {
252
+ "url": "https://github.com/sponsors/sindresorhus"
253
+ }
254
+ },
255
+ "node_modules/cli-table3": {
256
+ "version": "0.6.5",
257
+ "license": "MIT",
258
+ "dependencies": {
259
+ "string-width": "^4.2.0"
260
+ },
261
+ "engines": {
262
+ "node": "10.* || >= 12.*"
263
+ },
264
+ "optionalDependencies": {
265
+ "@colors/colors": "1.5.0"
266
+ }
267
+ },
268
+ "node_modules/cli-width": {
269
+ "version": "4.1.0",
270
+ "license": "ISC",
271
+ "engines": {
272
+ "node": ">= 12"
273
+ }
274
+ },
275
+ "node_modules/cliui": {
276
+ "version": "7.0.4",
277
+ "license": "ISC",
278
+ "dependencies": {
279
+ "string-width": "^4.2.0",
280
+ "strip-ansi": "^6.0.0",
281
+ "wrap-ansi": "^7.0.0"
282
+ }
283
+ },
284
+ "node_modules/cliui/node_modules/wrap-ansi": {
285
+ "version": "7.0.0",
286
+ "license": "MIT",
287
+ "dependencies": {
288
+ "ansi-styles": "^4.0.0",
289
+ "string-width": "^4.1.0",
290
+ "strip-ansi": "^6.0.0"
291
+ },
292
+ "engines": {
293
+ "node": ">=10"
294
+ },
295
+ "funding": {
296
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
297
+ }
298
+ },
299
+ "node_modules/clone": {
300
+ "version": "1.0.4",
301
+ "license": "MIT",
302
+ "engines": {
303
+ "node": ">=0.8"
304
+ }
305
+ },
306
+ "node_modules/color-convert": {
307
+ "version": "2.0.1",
308
+ "license": "MIT",
309
+ "dependencies": {
310
+ "color-name": "~1.1.4"
311
+ },
312
+ "engines": {
313
+ "node": ">=7.0.0"
314
+ }
315
+ },
316
+ "node_modules/color-name": {
317
+ "version": "1.1.4",
318
+ "license": "MIT"
319
+ },
320
+ "node_modules/defaults": {
321
+ "version": "1.0.4",
322
+ "license": "MIT",
323
+ "dependencies": {
324
+ "clone": "^1.0.2"
325
+ },
326
+ "funding": {
327
+ "url": "https://github.com/sponsors/sindresorhus"
328
+ }
329
+ },
330
+ "node_modules/eastasianwidth": {
331
+ "version": "0.2.0",
332
+ "license": "MIT"
333
+ },
334
+ "node_modules/emoji-regex": {
335
+ "version": "8.0.0",
336
+ "license": "MIT"
337
+ },
338
+ "node_modules/emojilib": {
339
+ "version": "2.4.0",
340
+ "license": "MIT"
341
+ },
342
+ "node_modules/environment": {
343
+ "version": "1.1.0",
344
+ "license": "MIT",
345
+ "engines": {
346
+ "node": ">=18"
347
+ },
348
+ "funding": {
349
+ "url": "https://github.com/sponsors/sindresorhus"
350
+ }
351
+ },
352
+ "node_modules/escalade": {
353
+ "version": "3.2.0",
354
+ "license": "MIT",
355
+ "engines": {
356
+ "node": ">=6"
357
+ }
358
+ },
359
+ "node_modules/fetch-event-stream": {
360
+ "version": "0.1.6",
361
+ "license": "MIT"
362
+ },
363
+ "node_modules/get-caller-file": {
364
+ "version": "2.0.5",
365
+ "license": "ISC",
366
+ "engines": {
367
+ "node": "6.* || 8.* || >= 10.*"
368
+ }
369
+ },
370
+ "node_modules/has-flag": {
371
+ "version": "4.0.0",
372
+ "license": "MIT",
373
+ "engines": {
374
+ "node": ">=8"
375
+ }
376
+ },
377
+ "node_modules/highlight.js": {
378
+ "version": "10.7.3",
379
+ "license": "BSD-3-Clause",
380
+ "engines": {
381
+ "node": "*"
382
+ }
383
+ },
384
+ "node_modules/iconv-lite": {
385
+ "version": "0.7.2",
386
+ "license": "MIT",
387
+ "dependencies": {
388
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
389
+ },
390
+ "engines": {
391
+ "node": ">=0.10.0"
392
+ },
393
+ "funding": {
394
+ "type": "opencollective",
395
+ "url": "https://opencollective.com/express"
396
+ }
397
+ },
398
+ "node_modules/ieee754": {
399
+ "version": "1.2.1",
400
+ "funding": [
401
+ {
402
+ "type": "github",
403
+ "url": "https://github.com/sponsors/feross"
404
+ },
405
+ {
406
+ "type": "patreon",
407
+ "url": "https://www.patreon.com/feross"
408
+ },
409
+ {
410
+ "type": "consulting",
411
+ "url": "https://feross.org/support"
412
+ }
413
+ ],
414
+ "license": "BSD-3-Clause"
415
+ },
416
+ "node_modules/inherits": {
417
+ "version": "2.0.4",
418
+ "license": "ISC"
419
+ },
420
+ "node_modules/inquirer": {
421
+ "version": "9.3.8",
422
+ "license": "MIT",
423
+ "dependencies": {
424
+ "@inquirer/external-editor": "^1.0.2",
425
+ "@inquirer/figures": "^1.0.3",
426
+ "ansi-escapes": "^4.3.2",
427
+ "cli-width": "^4.1.0",
428
+ "mute-stream": "1.0.0",
429
+ "ora": "^5.4.1",
430
+ "run-async": "^3.0.0",
431
+ "rxjs": "^7.8.1",
432
+ "string-width": "^4.2.3",
433
+ "strip-ansi": "^6.0.1",
434
+ "wrap-ansi": "^6.2.0",
435
+ "yoctocolors-cjs": "^2.1.2"
436
+ },
437
+ "engines": {
438
+ "node": ">=18"
439
+ }
440
+ },
441
+ "node_modules/inquirer/node_modules/bl": {
442
+ "version": "4.1.0",
443
+ "license": "MIT",
444
+ "dependencies": {
445
+ "buffer": "^5.5.0",
446
+ "inherits": "^2.0.4",
447
+ "readable-stream": "^3.4.0"
448
+ }
449
+ },
450
+ "node_modules/inquirer/node_modules/buffer": {
451
+ "version": "5.7.1",
452
+ "funding": [
453
+ {
454
+ "type": "github",
455
+ "url": "https://github.com/sponsors/feross"
456
+ },
457
+ {
458
+ "type": "patreon",
459
+ "url": "https://www.patreon.com/feross"
460
+ },
461
+ {
462
+ "type": "consulting",
463
+ "url": "https://feross.org/support"
464
+ }
465
+ ],
466
+ "license": "MIT",
467
+ "dependencies": {
468
+ "base64-js": "^1.3.1",
469
+ "ieee754": "^1.1.13"
470
+ }
471
+ },
472
+ "node_modules/inquirer/node_modules/chalk": {
473
+ "version": "4.1.2",
474
+ "license": "MIT",
475
+ "dependencies": {
476
+ "ansi-styles": "^4.1.0",
477
+ "supports-color": "^7.1.0"
478
+ },
479
+ "engines": {
480
+ "node": ">=10"
481
+ },
482
+ "funding": {
483
+ "url": "https://github.com/chalk/chalk?sponsor=1"
484
+ }
485
+ },
486
+ "node_modules/inquirer/node_modules/cli-cursor": {
487
+ "version": "3.1.0",
488
+ "license": "MIT",
489
+ "dependencies": {
490
+ "restore-cursor": "^3.1.0"
491
+ },
492
+ "engines": {
493
+ "node": ">=8"
494
+ }
495
+ },
496
+ "node_modules/inquirer/node_modules/is-interactive": {
497
+ "version": "1.0.0",
498
+ "license": "MIT",
499
+ "engines": {
500
+ "node": ">=8"
501
+ }
502
+ },
503
+ "node_modules/inquirer/node_modules/is-unicode-supported": {
504
+ "version": "0.1.0",
505
+ "license": "MIT",
506
+ "engines": {
507
+ "node": ">=10"
508
+ },
509
+ "funding": {
510
+ "url": "https://github.com/sponsors/sindresorhus"
511
+ }
512
+ },
513
+ "node_modules/inquirer/node_modules/log-symbols": {
514
+ "version": "4.1.0",
515
+ "license": "MIT",
516
+ "dependencies": {
517
+ "chalk": "^4.1.0",
518
+ "is-unicode-supported": "^0.1.0"
519
+ },
520
+ "engines": {
521
+ "node": ">=10"
522
+ },
523
+ "funding": {
524
+ "url": "https://github.com/sponsors/sindresorhus"
525
+ }
526
+ },
527
+ "node_modules/inquirer/node_modules/ora": {
528
+ "version": "5.4.1",
529
+ "license": "MIT",
530
+ "dependencies": {
531
+ "bl": "^4.1.0",
532
+ "chalk": "^4.1.0",
533
+ "cli-cursor": "^3.1.0",
534
+ "cli-spinners": "^2.5.0",
535
+ "is-interactive": "^1.0.0",
536
+ "is-unicode-supported": "^0.1.0",
537
+ "log-symbols": "^4.1.0",
538
+ "strip-ansi": "^6.0.0",
539
+ "wcwidth": "^1.0.1"
540
+ },
541
+ "engines": {
542
+ "node": ">=10"
543
+ },
544
+ "funding": {
545
+ "url": "https://github.com/sponsors/sindresorhus"
546
+ }
547
+ },
548
+ "node_modules/inquirer/node_modules/restore-cursor": {
549
+ "version": "3.1.0",
550
+ "license": "MIT",
551
+ "dependencies": {
552
+ "onetime": "^5.1.0",
553
+ "signal-exit": "^3.0.2"
554
+ },
555
+ "engines": {
556
+ "node": ">=8"
557
+ }
558
+ },
559
+ "node_modules/is-fullwidth-code-point": {
560
+ "version": "3.0.0",
561
+ "license": "MIT",
562
+ "engines": {
563
+ "node": ">=8"
564
+ }
565
+ },
566
+ "node_modules/is-interactive": {
567
+ "version": "2.0.0",
568
+ "license": "MIT",
569
+ "engines": {
570
+ "node": ">=12"
571
+ },
572
+ "funding": {
573
+ "url": "https://github.com/sponsors/sindresorhus"
574
+ }
575
+ },
576
+ "node_modules/is-unicode-supported": {
577
+ "version": "1.3.0",
578
+ "license": "MIT",
579
+ "engines": {
580
+ "node": ">=12"
581
+ },
582
+ "funding": {
583
+ "url": "https://github.com/sponsors/sindresorhus"
584
+ }
585
+ },
586
+ "node_modules/log-symbols": {
587
+ "version": "5.1.0",
588
+ "license": "MIT",
589
+ "dependencies": {
590
+ "chalk": "^5.0.0",
591
+ "is-unicode-supported": "^1.1.0"
592
+ },
593
+ "engines": {
594
+ "node": ">=12"
595
+ },
596
+ "funding": {
597
+ "url": "https://github.com/sponsors/sindresorhus"
598
+ }
599
+ },
600
+ "node_modules/marked": {
601
+ "version": "15.0.12",
602
+ "license": "MIT",
603
+ "bin": {
604
+ "marked": "bin/marked.js"
605
+ },
606
+ "engines": {
607
+ "node": ">= 18"
608
+ }
609
+ },
610
+ "node_modules/marked-terminal": {
611
+ "version": "7.3.0",
612
+ "license": "MIT",
613
+ "dependencies": {
614
+ "ansi-escapes": "^7.0.0",
615
+ "ansi-regex": "^6.1.0",
616
+ "chalk": "^5.4.1",
617
+ "cli-highlight": "^2.1.11",
618
+ "cli-table3": "^0.6.5",
619
+ "node-emoji": "^2.2.0",
620
+ "supports-hyperlinks": "^3.1.0"
621
+ },
622
+ "engines": {
623
+ "node": ">=16.0.0"
624
+ },
625
+ "peerDependencies": {
626
+ "marked": ">=1 <16"
627
+ }
628
+ },
629
+ "node_modules/marked-terminal/node_modules/ansi-escapes": {
630
+ "version": "7.3.0",
631
+ "license": "MIT",
632
+ "dependencies": {
633
+ "environment": "^1.0.0"
634
+ },
635
+ "engines": {
636
+ "node": ">=18"
637
+ },
638
+ "funding": {
639
+ "url": "https://github.com/sponsors/sindresorhus"
640
+ }
641
+ },
642
+ "node_modules/marked-terminal/node_modules/ansi-regex": {
643
+ "version": "6.2.2",
644
+ "license": "MIT",
645
+ "engines": {
646
+ "node": ">=12"
647
+ },
648
+ "funding": {
649
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
650
+ }
651
+ },
652
+ "node_modules/mimic-fn": {
653
+ "version": "2.1.0",
654
+ "license": "MIT",
655
+ "engines": {
656
+ "node": ">=6"
657
+ }
658
+ },
659
+ "node_modules/mute-stream": {
660
+ "version": "1.0.0",
661
+ "license": "ISC",
662
+ "engines": {
663
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
664
+ }
665
+ },
666
+ "node_modules/mz": {
667
+ "version": "2.7.0",
668
+ "license": "MIT",
669
+ "dependencies": {
670
+ "any-promise": "^1.0.0",
671
+ "object-assign": "^4.0.1",
672
+ "thenify-all": "^1.0.0"
673
+ }
674
+ },
675
+ "node_modules/node-emoji": {
676
+ "version": "2.2.0",
677
+ "license": "MIT",
678
+ "dependencies": {
679
+ "@sindresorhus/is": "^4.6.0",
680
+ "char-regex": "^1.0.2",
681
+ "emojilib": "^2.4.0",
682
+ "skin-tone": "^2.0.0"
683
+ },
684
+ "engines": {
685
+ "node": ">=18"
686
+ }
687
+ },
688
+ "node_modules/object-assign": {
689
+ "version": "4.1.1",
690
+ "license": "MIT",
691
+ "engines": {
692
+ "node": ">=0.10.0"
693
+ }
694
+ },
695
+ "node_modules/onetime": {
696
+ "version": "5.1.2",
697
+ "license": "MIT",
698
+ "dependencies": {
699
+ "mimic-fn": "^2.1.0"
700
+ },
701
+ "engines": {
702
+ "node": ">=6"
703
+ },
704
+ "funding": {
705
+ "url": "https://github.com/sponsors/sindresorhus"
706
+ }
707
+ },
708
+ "node_modules/ora": {
709
+ "version": "7.0.1",
710
+ "license": "MIT",
711
+ "dependencies": {
712
+ "chalk": "^5.3.0",
713
+ "cli-cursor": "^4.0.0",
714
+ "cli-spinners": "^2.9.0",
715
+ "is-interactive": "^2.0.0",
716
+ "is-unicode-supported": "^1.3.0",
717
+ "log-symbols": "^5.1.0",
718
+ "stdin-discarder": "^0.1.0",
719
+ "string-width": "^6.1.0",
720
+ "strip-ansi": "^7.1.0"
721
+ },
722
+ "engines": {
723
+ "node": ">=16"
724
+ },
725
+ "funding": {
726
+ "url": "https://github.com/sponsors/sindresorhus"
727
+ }
728
+ },
729
+ "node_modules/ora/node_modules/ansi-regex": {
730
+ "version": "6.2.2",
731
+ "license": "MIT",
732
+ "engines": {
733
+ "node": ">=12"
734
+ },
735
+ "funding": {
736
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
737
+ }
738
+ },
739
+ "node_modules/ora/node_modules/emoji-regex": {
740
+ "version": "10.6.0",
741
+ "license": "MIT"
742
+ },
743
+ "node_modules/ora/node_modules/string-width": {
744
+ "version": "6.1.0",
745
+ "license": "MIT",
746
+ "dependencies": {
747
+ "eastasianwidth": "^0.2.0",
748
+ "emoji-regex": "^10.2.1",
749
+ "strip-ansi": "^7.0.1"
750
+ },
751
+ "engines": {
752
+ "node": ">=16"
753
+ },
754
+ "funding": {
755
+ "url": "https://github.com/sponsors/sindresorhus"
756
+ }
757
+ },
758
+ "node_modules/ora/node_modules/strip-ansi": {
759
+ "version": "7.2.0",
760
+ "license": "MIT",
761
+ "dependencies": {
762
+ "ansi-regex": "^6.2.2"
763
+ },
764
+ "engines": {
765
+ "node": ">=12"
766
+ },
767
+ "funding": {
768
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
769
+ }
770
+ },
771
+ "node_modules/parse5": {
772
+ "version": "5.1.1",
773
+ "license": "MIT"
774
+ },
775
+ "node_modules/parse5-htmlparser2-tree-adapter": {
776
+ "version": "6.0.1",
777
+ "license": "MIT",
778
+ "dependencies": {
779
+ "parse5": "^6.0.1"
780
+ }
781
+ },
782
+ "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": {
783
+ "version": "6.0.1",
784
+ "license": "MIT"
785
+ },
786
+ "node_modules/readable-stream": {
787
+ "version": "3.6.2",
788
+ "license": "MIT",
789
+ "dependencies": {
790
+ "inherits": "^2.0.3",
791
+ "string_decoder": "^1.1.1",
792
+ "util-deprecate": "^1.0.1"
793
+ },
794
+ "engines": {
795
+ "node": ">= 6"
796
+ }
797
+ },
798
+ "node_modules/require-directory": {
799
+ "version": "2.1.1",
800
+ "license": "MIT",
801
+ "engines": {
802
+ "node": ">=0.10.0"
803
+ }
804
+ },
805
+ "node_modules/restore-cursor": {
806
+ "version": "4.0.0",
807
+ "license": "MIT",
808
+ "dependencies": {
809
+ "onetime": "^5.1.0",
810
+ "signal-exit": "^3.0.2"
811
+ },
812
+ "engines": {
813
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
814
+ },
815
+ "funding": {
816
+ "url": "https://github.com/sponsors/sindresorhus"
817
+ }
818
+ },
819
+ "node_modules/run-async": {
820
+ "version": "3.0.0",
821
+ "license": "MIT",
822
+ "engines": {
823
+ "node": ">=0.12.0"
824
+ }
825
+ },
826
+ "node_modules/rxjs": {
827
+ "version": "7.8.2",
828
+ "license": "Apache-2.0",
829
+ "dependencies": {
830
+ "tslib": "^2.1.0"
831
+ }
832
+ },
833
+ "node_modules/safe-buffer": {
834
+ "version": "5.2.1",
835
+ "funding": [
836
+ {
837
+ "type": "github",
838
+ "url": "https://github.com/sponsors/feross"
839
+ },
840
+ {
841
+ "type": "patreon",
842
+ "url": "https://www.patreon.com/feross"
843
+ },
844
+ {
845
+ "type": "consulting",
846
+ "url": "https://feross.org/support"
847
+ }
848
+ ],
849
+ "license": "MIT"
850
+ },
851
+ "node_modules/safer-buffer": {
852
+ "version": "2.1.2",
853
+ "license": "MIT"
854
+ },
855
+ "node_modules/signal-exit": {
856
+ "version": "3.0.7",
857
+ "license": "ISC"
858
+ },
859
+ "node_modules/skin-tone": {
860
+ "version": "2.0.0",
861
+ "license": "MIT",
862
+ "dependencies": {
863
+ "unicode-emoji-modifier-base": "^1.0.0"
864
+ },
865
+ "engines": {
866
+ "node": ">=8"
867
+ }
868
+ },
869
+ "node_modules/stdin-discarder": {
870
+ "version": "0.1.0",
871
+ "license": "MIT",
872
+ "dependencies": {
873
+ "bl": "^5.0.0"
874
+ },
875
+ "engines": {
876
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
877
+ },
878
+ "funding": {
879
+ "url": "https://github.com/sponsors/sindresorhus"
880
+ }
881
+ },
882
+ "node_modules/string_decoder": {
883
+ "version": "1.3.0",
884
+ "license": "MIT",
885
+ "dependencies": {
886
+ "safe-buffer": "~5.2.0"
887
+ }
888
+ },
889
+ "node_modules/string-width": {
890
+ "version": "4.2.3",
891
+ "license": "MIT",
892
+ "dependencies": {
893
+ "emoji-regex": "^8.0.0",
894
+ "is-fullwidth-code-point": "^3.0.0",
895
+ "strip-ansi": "^6.0.1"
896
+ },
897
+ "engines": {
898
+ "node": ">=8"
899
+ }
900
+ },
901
+ "node_modules/strip-ansi": {
902
+ "version": "6.0.1",
903
+ "license": "MIT",
904
+ "dependencies": {
905
+ "ansi-regex": "^5.0.1"
906
+ },
907
+ "engines": {
908
+ "node": ">=8"
909
+ }
910
+ },
911
+ "node_modules/supports-color": {
912
+ "version": "7.2.0",
913
+ "license": "MIT",
914
+ "dependencies": {
915
+ "has-flag": "^4.0.0"
916
+ },
917
+ "engines": {
918
+ "node": ">=8"
919
+ }
920
+ },
921
+ "node_modules/supports-hyperlinks": {
922
+ "version": "3.2.0",
923
+ "license": "MIT",
924
+ "dependencies": {
925
+ "has-flag": "^4.0.0",
926
+ "supports-color": "^7.0.0"
927
+ },
928
+ "engines": {
929
+ "node": ">=14.18"
930
+ },
931
+ "funding": {
932
+ "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1"
933
+ }
934
+ },
935
+ "node_modules/thenify": {
936
+ "version": "3.3.1",
937
+ "license": "MIT",
938
+ "dependencies": {
939
+ "any-promise": "^1.0.0"
940
+ }
941
+ },
942
+ "node_modules/thenify-all": {
943
+ "version": "1.6.0",
944
+ "license": "MIT",
945
+ "dependencies": {
946
+ "thenify": ">= 3.1.0 < 4"
947
+ },
948
+ "engines": {
949
+ "node": ">=0.8"
950
+ }
951
+ },
952
+ "node_modules/tslib": {
953
+ "version": "2.8.1",
954
+ "license": "0BSD"
955
+ },
956
+ "node_modules/type-fest": {
957
+ "version": "0.21.3",
958
+ "license": "(MIT OR CC0-1.0)",
959
+ "engines": {
960
+ "node": ">=10"
961
+ },
962
+ "funding": {
963
+ "url": "https://github.com/sponsors/sindresorhus"
964
+ }
965
+ },
966
+ "node_modules/unicode-emoji-modifier-base": {
967
+ "version": "1.0.0",
968
+ "license": "MIT",
969
+ "engines": {
970
+ "node": ">=4"
971
+ }
972
+ },
973
+ "node_modules/util-deprecate": {
974
+ "version": "1.0.2",
975
+ "license": "MIT"
976
+ },
977
+ "node_modules/wcwidth": {
978
+ "version": "1.0.1",
979
+ "license": "MIT",
980
+ "dependencies": {
981
+ "defaults": "^1.0.3"
982
+ }
983
+ },
984
+ "node_modules/wrap-ansi": {
985
+ "version": "6.2.0",
986
+ "license": "MIT",
987
+ "dependencies": {
988
+ "ansi-styles": "^4.0.0",
989
+ "string-width": "^4.1.0",
990
+ "strip-ansi": "^6.0.0"
991
+ },
992
+ "engines": {
993
+ "node": ">=8"
994
+ }
995
+ },
996
+ "node_modules/y18n": {
997
+ "version": "5.0.8",
998
+ "license": "ISC",
999
+ "engines": {
1000
+ "node": ">=10"
1001
+ }
1002
+ },
1003
+ "node_modules/yargs": {
1004
+ "version": "16.2.0",
1005
+ "license": "MIT",
1006
+ "dependencies": {
1007
+ "cliui": "^7.0.2",
1008
+ "escalade": "^3.1.1",
1009
+ "get-caller-file": "^2.0.5",
1010
+ "require-directory": "^2.1.1",
1011
+ "string-width": "^4.2.0",
1012
+ "y18n": "^5.0.5",
1013
+ "yargs-parser": "^20.2.2"
1014
+ },
1015
+ "engines": {
1016
+ "node": ">=10"
1017
+ }
1018
+ },
1019
+ "node_modules/yargs-parser": {
1020
+ "version": "20.2.9",
1021
+ "license": "ISC",
1022
+ "engines": {
1023
+ "node": ">=10"
1024
+ }
1025
+ },
1026
+ "node_modules/yoctocolors-cjs": {
1027
+ "version": "2.1.3",
1028
+ "license": "MIT",
1029
+ "engines": {
1030
+ "node": ">=18"
1031
+ },
1032
+ "funding": {
1033
+ "url": "https://github.com/sponsors/sindresorhus"
1034
+ }
1035
+ }
1036
+ }
1037
+ }