TheShellMaster commited on
Commit
c610abe
·
verified ·
1 Parent(s): 37c7e9a

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. app.py +92 -138
  2. index.js +3 -1
app.py CHANGED
@@ -30,6 +30,38 @@ def search_web(query):
30
  except Exception as e:
31
  return f"Erreur lors de la recherche: {str(e)}"
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  @app.get("/")
34
  async def root():
35
  return RedirectResponse(url="/gradio")
@@ -40,6 +72,7 @@ async def chat(request: Request):
40
  body = await request.json()
41
  messages = body.get("messages", [])
42
  client_tools = body.get("tools", [])
 
43
 
44
  # dynamic inference parameters
45
  model = body.get("model", "Qwen/Qwen2.5-Coder-32B-Instruct")
@@ -152,6 +185,12 @@ async def chat(request: Request):
152
  } for tc in choice.message.tool_calls
153
  ]
154
  }
 
 
 
 
 
 
155
  return JSONResponse(content={"message": message_data})
156
  else:
157
  # Réponse textuelle finale sans outil
@@ -159,6 +198,12 @@ async def chat(request: Request):
159
  "role": choice.message.role,
160
  "content": choice.message.content
161
  }
 
 
 
 
 
 
162
  return JSONResponse(content={"message": message_data})
163
 
164
  except Exception as e:
@@ -463,82 +508,6 @@ def save_log(username: str, message: str, response: str):
463
  except Exception as e:
464
  print(f"Erreur d'enregistrement du log de discussion: {e}")
465
 
466
- def respond_custom(message, history, profile: gr.OAuthProfile | None):
467
- if not message.strip():
468
- return
469
- username = profile.username if profile else "anonymous"
470
-
471
- messages = [{"role": "system", "content": SYSTEM_PROMPT}]
472
- for val in history:
473
- if val[0]:
474
- messages.append({"role": "user", "content": val[0]})
475
- if val[1]:
476
- messages.append({"role": "assistant", "content": val[1]})
477
-
478
- messages.append({"role": "user", "content": message})
479
-
480
- current_chat = history + [[message, ""]]
481
-
482
- try:
483
- response = client.chat_completion(
484
- messages,
485
- max_tokens=2048,
486
- tools=web_tools,
487
- stream=False
488
- )
489
- first_response = response.choices[0].message
490
-
491
- if first_response.tool_calls:
492
- current_chat[-1][1] = "🔍 *Recherche web en cours...*"
493
- yield "", current_chat
494
-
495
- messages.append(first_response)
496
- for tool_call in first_response.tool_calls:
497
- if tool_call.function.name == "search_web":
498
- args = json.loads(tool_call.function.arguments)
499
- res = search_web(args["query"])
500
- messages.append({
501
- "role": "tool",
502
- "name": "search_web",
503
- "content": res
504
- })
505
-
506
- final_stream = client.chat_completion(
507
- messages,
508
- max_tokens=2048,
509
- stream=True
510
- )
511
- response_text = ""
512
- for chunk in final_stream:
513
- token_chunk = chunk.choices[0].delta.content
514
- if token_chunk:
515
- response_text += token_chunk
516
- current_chat[-1][1] = response_text
517
- yield "", current_chat
518
- else:
519
- if first_response.content:
520
- response_text = first_response.content
521
- current_chat[-1][1] = response_text
522
- yield "", current_chat
523
-
524
- save_log(username, message, response_text)
525
-
526
- except Exception as e:
527
- current_chat[-1][1] = f"Erreur lors de la génération: {str(e)}"
528
- yield "", current_chat
529
-
530
- def check_login(profile: gr.OAuthProfile | None):
531
- if profile is None:
532
- return gr.update(visible=True), gr.update(visible=False), gr.update(value="")
533
- avatar_html = f'<img src="{profile.picture}" style="width: 32px; height: 32px; border-radius: 50%; margin-right: 10px; display: inline-block; vertical-align: middle;"/>' if profile.picture else ""
534
- user_info = f"""
535
- <div style="display: flex; align-items: center; justify-content: flex-end; font-size: 1.1em; color: white;">
536
- {avatar_html}
537
- <span>Connecté en tant que <b>{profile.name}</b> (@{profile.username})</span>
538
- </div>
539
- """
540
- return gr.update(visible=False), gr.update(visible=True), gr.update(value=user_info)
541
-
542
  theme = gr.themes.Soft(
543
  primary_hue="indigo",
544
  secondary_hue="cyan",
@@ -548,7 +517,6 @@ theme = gr.themes.Soft(
548
  css = """
549
  footer {visibility: hidden}
550
  .title-container { text-align: center; margin-bottom: 20px; }
551
- .login-box { padding: 30px; text-align: center; background-color: #1E293B; border-radius: 12px; border: 1px solid #334155; max-width: 500px; margin: 40px auto; }
552
  """
553
 
554
  with gr.Blocks(theme=theme, css=css) as demo:
@@ -560,72 +528,58 @@ with gr.Blocks(theme=theme, css=css) as demo:
560
  </div>
561
  """)
562
 
563
- login_view = gr.Column(visible=True)
564
- main_view = gr.Column(visible=False)
565
 
566
- with login_view:
567
- gr.HTML("""
568
- <div class="login-box">
569
- <h3 style="color: white; margin-bottom: 15px;">🔐 Authentification Requise</h3>
570
- <p style="color: #94A3B8; margin-bottom: 20px;">Pour accéder à l'interface en ligne de Cypher Coder, veuillez vous connecter avec votre compte Hugging Face.</p>
571
- </div>
572
- """)
573
- with gr.Row(variant="compact"):
574
- gr.LoginButton(value="Se connecter avec Hugging Face", size="lg")
575
-
576
- with main_view:
577
- with gr.Row():
578
- user_header = gr.HTML(value="", scale=4)
579
- logout_btn = gr.LoginButton(scale=1)
580
-
581
- with gr.Tab("💬 Tester en Ligne"):
582
- chatbot = gr.Chatbot(label="Chat Cypher Coder", height=450)
583
-
584
- with gr.Row():
585
- msg = gr.Textbox(placeholder="Posez votre question à Cypher Coder...", scale=4, label="Votre Message")
586
- submit_btn = gr.Button("Envoyer", scale=1, variant="primary")
587
- clear_btn = gr.Button("Effacer", scale=1)
588
-
589
- msg.submit(respond_custom, [msg, chatbot], [msg, chatbot])
590
- submit_btn.click(respond_custom, [msg, chatbot], [msg, chatbot])
591
- clear_btn.click(lambda: None, None, chatbot, queue=False)
592
-
593
- with gr.Tab("📖 Documentation CLI"):
594
- gr.Markdown("""
595
- # ⚙️ Cypher Coder CLI
596
-
597
- **Cypher Coder** est un agent conversationnel en ligne de commande (CLI) similaire à *Claude Code* ou *Gemini CLI*. Il est conçu pour s'exécuter directement dans votre terminal local et interagir avec votre système de fichiers de manière sécurisée.
598
 
599
- ---
600
 
601
- ## 🚀 Installation & Utilisation
602
-
603
- Pour exécuter Cypher Coder localement :
604
- ```bash
605
- # Naviguer dans le dossier du projet
606
- cd Documents/cypher-coder
607
-
608
- # Lancer l'agent CLI
609
- cypher
610
- ```
611
 
612
- ## 🛠️ Commandes Disponibles dans le CLI
613
-
614
- - `/help` - Affiche l'aide
615
- - `/clear` - Efface l'écran et réinitialise l'historique
616
- - `/exit` - Ferme proprement l'application
617
- - `/settings` - Configure le jeton Hugging Face ou d'autres paramètres
618
 
619
- ## 🔌 Outils du Système (Capabilities)
620
-
621
- Lorsqu'il s'exécute localement via le terminal, **Cypher Coder** peut utiliser des outils pour vous aider :
622
- - 📁 **Lecture de fichiers** : Lire du code ou du texte sur votre machine.
623
- - 📝 **Écriture & Modification de fichiers** : Créer ou éditer des fichiers sources.
624
- - 🖥️ **Exécution de commandes** : Lancer des tests, installer des paquets, compiler, etc. (avec votre consentement explicite).
625
- - 🌐 **Recherche Web** : Rechercher sur internet en temps réel pour obtenir la documentation de dernière minute.
626
- """)
627
-
628
- demo.load(check_login, None, [login_view, main_view, user_header])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
629
 
630
  app = gr.mount_gradio_app(app, demo, path="/gradio")
631
 
 
30
  except Exception as e:
31
  return f"Erreur lors de la recherche: {str(e)}"
32
 
33
+ def save_log(username: str, message: str, response: str):
34
+ if not token:
35
+ return
36
+ try:
37
+ user = api.whoami()["name"]
38
+ repo_id = f"{user}/cypher-coder-logs"
39
+ try:
40
+ create_repo(repo_id, token=token, repo_type="dataset", private=True, exist_ok=True)
41
+ except Exception:
42
+ pass
43
+
44
+ log_entry = {
45
+ "username": username,
46
+ "timestamp": datetime.utcnow().isoformat(),
47
+ "message": message,
48
+ "response": response
49
+ }
50
+
51
+ file_path = f"logs/{username}/{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}.json"
52
+ content_bytes = json.dumps(log_entry, ensure_ascii=False, indent=2).encode("utf-8")
53
+
54
+ from io import BytesIO
55
+ api.upload_file(
56
+ path_or_fileobj=BytesIO(content_bytes),
57
+ path_in_repo=file_path,
58
+ repo_id=repo_id,
59
+ repo_type="dataset",
60
+ token=token
61
+ )
62
+ except Exception as e:
63
+ print(f"Erreur d'enregistrement du log de discussion: {e}")
64
+
65
  @app.get("/")
66
  async def root():
67
  return RedirectResponse(url="/gradio")
 
72
  body = await request.json()
73
  messages = body.get("messages", [])
74
  client_tools = body.get("tools", [])
75
+ username = body.get("username", "local-user")
76
 
77
  # dynamic inference parameters
78
  model = body.get("model", "Qwen/Qwen2.5-Coder-32B-Instruct")
 
185
  } for tc in choice.message.tool_calls
186
  ]
187
  }
188
+ user_msg_content = ""
189
+ for msg in reversed(messages):
190
+ if msg.get("role") == "user":
191
+ user_msg_content = msg.get("content", "")
192
+ break
193
+ save_log(username, user_msg_content, choice.message.content or "[Appels d'outils locaux demandés]")
194
  return JSONResponse(content={"message": message_data})
195
  else:
196
  # Réponse textuelle finale sans outil
 
198
  "role": choice.message.role,
199
  "content": choice.message.content
200
  }
201
+ user_msg_content = ""
202
+ for msg in reversed(messages):
203
+ if msg.get("role") == "user":
204
+ user_msg_content = msg.get("content", "")
205
+ break
206
+ save_log(username, user_msg_content, choice.message.content or "")
207
  return JSONResponse(content={"message": message_data})
208
 
209
  except Exception as e:
 
508
  except Exception as e:
509
  print(f"Erreur d'enregistrement du log de discussion: {e}")
510
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  theme = gr.themes.Soft(
512
  primary_hue="indigo",
513
  secondary_hue="cyan",
 
517
  css = """
518
  footer {visibility: hidden}
519
  .title-container { text-align: center; margin-bottom: 20px; }
 
520
  """
521
 
522
  with gr.Blocks(theme=theme, css=css) as demo:
 
528
  </div>
529
  """)
530
 
531
+ gr.Markdown("""
532
+ # ⚙️ Cypher Coder - Documentation Officielle
533
 
534
+ **Cypher Coder** est un agent conversationnel en ligne de commande (CLI) autonome de niveau professionnel (similaire à *Claude Code*). Il est conçu pour s'exécuter directement dans votre terminal local et interagir avec votre système de fichiers de manière sécurisée et proactive.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
535
 
536
+ ---
537
 
538
+ ## 🚀 Installation & Utilisation
 
 
 
 
 
 
 
 
 
539
 
540
+ Pour installer et configurer Cypher Coder localement sur votre machine (**Linux / Windows / Termux**) :
 
 
 
 
 
541
 
542
+ ```bash
543
+ # 1. Cloner le projet depuis Hugging Face
544
+ git clone https://huggingface.co/spaces/TheShellMaster/cypher-coder
545
+ cd cypher-coder
546
+
547
+ # 2. Installer les dépendances
548
+ npm install
549
+
550
+ # 3. Rendre index.js exécutable (Linux / Termux)
551
+ chmod +x index.js
552
+
553
+ # 4. Lier le CLI globalement à votre système
554
+ npm link
555
+
556
+ # 5. Lancer l'agent
557
+ cypher
558
+ ```
559
+
560
+ ## 🛠️ Commandes Disponibles dans le CLI
561
+
562
+ Cypher Coder dispose d'une interface terminal interactive enrichie de commandes slash `/` :
563
+ - `/help` - Affiche le menu d'aide avec toutes les options.
564
+ - `/status` - Affiche l'état de la session (dossier actif, modèle, température, etc.).
565
+ - `/clear` - Nettoie l'écran du terminal.
566
+ - `/reset` - Réinitialise la conversation et efface le contexte.
567
+ - `/model set` - Change le modèle d'inférence Hugging Face à la volée.
568
+ - `/temperature`- Modifie la créativité du modèle.
569
+ - `/file load` - Charge des fichiers locaux dans le contexte.
570
+ - `/run` - Exécute le dernier bloc de code markdown généré (avec consentement).
571
+ - `/exit` - Ferme proprement l'application.
572
+
573
+ ## 🔌 Outils & Autonomie (Capabilities)
574
+
575
+ Lorsqu'il s'exécute localement, **Cypher Coder** utilise des outils intégrés de manière proactive pour explorer et modifier votre projet :
576
+ - 📁 **list_dir** / **find_files** : Recherche des fichiers récursivement dans les sous-dossiers.
577
+ - 🔍 **grep_search** : Recherche textuelle dans le contenu des fichiers (similaire à ripgrep).
578
+ - 📄 **read_file** / **write_file** / **patch_file** : Lit et modifie intelligemment vos fichiers sources.
579
+ - 🖥️ **run_command** : Exécute des tests, lance des compilations ou configure git.
580
+
581
+ *Toutes les actions d'écriture de fichier ou d'exécution de commande shell requièrent votre validation explicite (Y/n) avant d'être appliquées.*
582
+ """)
583
 
584
  app = gr.mount_gradio_app(app, demo, path="/gradio")
585
 
index.js CHANGED
@@ -5,6 +5,7 @@ import inquirer from 'inquirer';
5
  import ora from 'ora';
6
  import fs from 'fs';
7
  import path from 'path';
 
8
  import { execSync } from 'child_process';
9
  import { marked } from 'marked';
10
  import TerminalRenderer from 'marked-terminal';
@@ -187,7 +188,8 @@ function callApiViaCurl(messages, clientTools) {
187
  model: sessionConfig.model,
188
  temperature: sessionConfig.temperature,
189
  top_p: sessionConfig.top_p,
190
- max_tokens: sessionConfig.max_tokens
 
191
  });
192
  const escapedPayload = payload.replace(/'/g, "'\\''");
193
  const command = `curl -s -X POST -H "Content-Type: application/json" -d '${escapedPayload}' https://theshellmaster-cypher-coder.hf.space/api/chat`;
 
5
  import ora from 'ora';
6
  import fs from 'fs';
7
  import path from 'path';
8
+ import os from 'os';
9
  import { execSync } from 'child_process';
10
  import { marked } from 'marked';
11
  import TerminalRenderer from 'marked-terminal';
 
188
  model: sessionConfig.model,
189
  temperature: sessionConfig.temperature,
190
  top_p: sessionConfig.top_p,
191
+ max_tokens: sessionConfig.max_tokens,
192
+ username: os.userInfo().username || "local-user"
193
  });
194
  const escapedPayload = payload.replace(/'/g, "'\\''");
195
  const command = `curl -s -X POST -H "Content-Type: application/json" -d '${escapedPayload}' https://theshellmaster-cypher-coder.hf.space/api/chat`;