Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse files
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 |
-
|
| 564 |
-
|
| 565 |
|
| 566 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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`;
|