cypher-coder / index.html
TheShellMaster's picture
Upload folder using huggingface_hub
ef58300 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cypher Coder CLI</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'JetBrains Mono', monospace;
border-radius: 0 !important;
}
body {
background-color: #0D0D0D;
color: #E0E0E0;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
font-size: 14px;
line-height: 1.5;
}
/* Scrollbars custom */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #0D0D0D;
}
::-webkit-scrollbar-thumb {
background: #2A2A2A;
}
::-webkit-scrollbar-thumb:hover {
background: #444444;
}
/* HEADER */
header {
height: 48px;
background-color: #111111;
border-bottom: 1px solid #2A2A2A;
display: flex;
align-items: center;
padding: 0 20px;
justify-content: space-between;
flex-shrink: 0;
}
.header-left {
display: flex;
align-items: center;
}
.pulsing-dot {
width: 8px;
height: 8px;
background-color: #00FFAA;
border-radius: 50% !important;
margin-right: 12px;
display: inline-block;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(0, 255, 170, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 6px rgba(0, 255, 170, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(0, 255, 170, 0);
}
}
.header-title {
font-weight: bold;
letter-spacing: 1px;
margin-right: 15px;
}
.header-separator {
color: #2A2A2A;
margin: 0 15px;
}
.header-meta {
color: #888888;
}
/* ZONE DE CHAT */
main {
flex-grow: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
background-color: #0D0D0D;
}
.chat-line {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.user-msg {
color: #E0E0E0;
}
.user-prefix {
color: #C792EA;
font-weight: normal;
}
.msg-timestamp {
color: #444444;
font-size: 12px;
margin-top: 4px;
margin-left: 16px;
}
.agent-msg {
color: #00FFAA;
}
.agent-prefix {
color: #00FFAA;
font-weight: normal;
}
.agent-content {
white-space: pre-wrap;
margin-left: 20px;
}
.agent-content * {
font-weight: normal !important;
font-style: normal !important;
}
/* Style des blocs de code */
pre {
background-color: #111111;
border: 1px solid #2A2A2A;
padding: 12px;
margin: 10px 0;
width: 100%;
overflow-x: auto;
}
code {
color: #FFD700;
}
/* Outils et spinner */
.tool-block {
margin-left: 20px;
color: #569CD6;
margin-top: 6px;
margin-bottom: 6px;
}
.tool-header {
color: #2A2A2A;
}
.tool-name {
color: #FFD700;
}
.spinner {
display: inline-block;
color: #FFD700;
margin-right: 8px;
}
/* AUTOCOMPLETE POPUP */
#autocomplete-popup {
position: absolute;
bottom: 54px;
left: 20px;
width: 450px;
background-color: #161616;
border: 1px solid #2A2A2A;
z-index: 1000;
display: none;
max-height: 300px;
overflow-y: auto;
}
.autocomplete-item {
padding: 8px 12px;
display: flex;
justify-content: space-between;
cursor: pointer;
}
.autocomplete-item:hover, .autocomplete-item.selected {
background-color: #1E1E1E;
}
.autocomplete-item.selected .cmd-name {
color: #FFD700;
}
.cmd-name {
color: #E0E0E0;
}
.cmd-desc {
color: #666666;
font-size: 12px;
}
/* BARRE D'INPUT */
.input-bar {
height: 52px;
background-color: #111111;
border-top: 1px solid #2A2A2A;
display: flex;
align-items: center;
padding: 0 20px;
position: relative;
flex-shrink: 0;
}
.input-prefix {
color: #00FFAA;
margin-right: 12px;
font-weight: bold;
}
#cli-input {
background: transparent;
border: none;
outline: none;
color: #E0E0E0;
flex-grow: 1;
font-size: 14px;
}
#cli-input::placeholder {
color: #444444;
}
/* FOOTER */
footer {
height: 28px;
background-color: #0A0A0A;
border-top: 1px solid #1A1A1A;
display: flex;
align-items: center;
padding: 0 20px;
color: #444444;
font-size: 11px;
flex-shrink: 0;
}
/* ECRAN DE LOGIN */
.login-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #0D0D0D;
padding: 20px;
}
.login-box {
border: 1px solid #2A2A2A;
background-color: #111111;
padding: 30px;
max-width: 500px;
width: 100%;
text-align: center;
}
.login-title {
color: #00FFAA;
font-size: 20px;
margin-bottom: 15px;
font-weight: bold;
}
.login-desc {
color: #888888;
margin-bottom: 25px;
font-size: 13px;
}
.login-btn {
background-color: transparent;
border: 1px solid #00FFAA;
color: #00FFAA;
padding: 12px 24px;
font-size: 14px;
cursor: pointer;
font-weight: bold;
transition: all 0.2s;
}
.login-btn:hover {
background-color: #00FFAA;
color: #0D0D0D;
}
.ansi-banner {
color: #569CD6;
margin-bottom: 20px;
white-space: pre;
font-size: 12px;
line-height: 1.2;
text-align: left;
display: inline-block;
}
</style>
</head>
<body>
<!-- MAIN APP LAYER -->
<div id="app-layer" style="display: flex; flex-direction: column; height: 100vh; width: 100%;">
<!-- HEADER -->
<header>
<div class="header-left">
<span class="pulsing-dot"></span>
<span class="header-title" style="color: #E0E0E0;">CYPHER CODER v1.0</span>
<span class="header-separator">|</span>
<span id="header-username" class="header-meta">@loading</span>
<span class="header-separator">|</span>
<span id="header-model" class="header-meta">modèle : Qwen2.5-Coder-32B</span>
<span class="header-separator">|</span>
<span id="header-tokens" class="header-meta">tokens : 0</span>
</div>
</header>
<!-- ZONE DE CHAT -->
<main id="chat-area">
<!-- Startup Message (Section 10) -->
<div class="chat-line" style="white-space: pre-wrap; color: #569CD6; margin-bottom: 20px;">
_____ _---------------+
/ ____| | | ____ _ |
| | _ _ _ __ | |__| _ \| | | CYPHER CODER CLI
| | | | | | '_ \ | __ |_) | | | L'IA experte en développement local
| |___| |_| | |_) || | | __/| |___ | Créé par DJAKOUA KWANKAM
\_____\__, | .__/ |_| |_| |_____|| Institut Universitaire de Technologie de Douala (IUT)
__/ | | |
|___/|_| +-----------------------+
CYPHER CODER CLI — L'IA experte en developpement local
Cree par DJAKOUA KWANKAM — Institut Universitaire de Technologie de Douala (IUT)
─────────────────────────────────────────────────────────────────
Connecte en tant que : <span id="banner-username" style="color: #E0E0E0;">@loading</span>
Modele actif : <span id="banner-model" style="color: #E0E0E0;">Qwen/Qwen2.5-Coder-32B-Instruct</span>
Session : <span id="banner-session" style="color: #E0E0E0;">[loading]</span>
Jeu de donnees : <span style="color: #E0E0E0;">Collecte active pour entrainement (dataset prive)</span>
─────────────────────────────────────────────────────────────────
Tape /help pour voir les commandes disponibles.</div>
</main>
<!-- AUTOCOMPLETE POPUP -->
<div id="autocomplete-popup"></div>
<!-- BARRE D'INPUT -->
<div class="input-bar">
<span class="input-prefix"></span>
<input type="text" id="cli-input" placeholder="Tape une commande ou / pour les raccourcis..." autocomplete="off">
</div>
<!-- FOOTER -->
<footer>
Cypher Coder CLI • DJAKOUA KWANKAM • IUT Douala • Enter:envoyer • /:commandes • ↑↓:historique
</footer>
</div>
<script>
const chatArea = document.getElementById("chat-area");
const cliInput = document.getElementById("cli-input");
const popup = document.getElementById("autocomplete-popup");
const headerUsername = document.getElementById("header-username");
const bannerUsername = document.getElementById("banner-username");
const bannerSession = document.getElementById("banner-session");
let currentUser = "invité";
let sessionId = "session_" + Math.random().toString(36).substring(2, 10);
let chatHistory = [];
let inputHistory = [];
let historyIndex = -1;
const commands = [
{ name: '/help', desc: "Afficher l'aide" },
{ name: '/clear', desc: "Vider le terminal" },
{ name: '/reset', desc: "Réinitialiser session" },
{ name: '/model', desc: "Changer de modèle" },
{ name: '/history', desc: "Voir l'historique" },
{ name: '/file load', desc: "Charger un fichier" },
{ name: '/exec', desc: "Exécuter une commande" },
{ name: '/status', desc: "Statut de l'agent" },
{ name: '/context', desc: "Voir le contexte actuel" },
{ name: '/tokens', desc: "Tokens utilisés" }
];
let filteredCommands = [];
let selectedIndex = 0;
// Fetch User Info on Load
async function fetchUserInfo() {
try {
const res = await fetch("/api/user-profile");
const data = await res.json();
if (data.username) {
currentUser = data.username;
}
headerUsername.innerText = "@" + currentUser;
bannerUsername.innerText = "@" + currentUser;
bannerSession.innerText = sessionId;
} catch (e) {
console.error("Auth fetch error:", e);
headerUsername.innerText = "@" + currentUser;
bannerUsername.innerText = "@" + currentUser;
bannerSession.innerText = sessionId;
}
}
fetchUserInfo();
// Autocomplete rendering
function showPopup(query) {
filteredCommands = commands.filter(c => c.name.startsWith(query));
if (filteredCommands.length === 0) {
popup.style.display = "none";
return;
}
selectedIndex = 0;
renderPopup();
popup.style.display = "block";
}
function renderPopup() {
popup.innerHTML = "";
filteredCommands.forEach((cmd, idx) => {
const item = document.createElement("div");
item.className = "autocomplete-item" + (idx === selectedIndex ? " selected" : "");
item.innerHTML = `<span class="cmd-name">${cmd.name}</span><span class="cmd-desc">${cmd.desc}</span>`;
item.onclick = () => {
cliInput.value = cmd.name;
popup.style.display = "none";
cliInput.focus();
};
popup.appendChild(item);
});
}
// Output formatting
function appendUserMessage(msg) {
const time = new Date().toTimeString().split(' ')[0];
const div = document.createElement("div");
div.className = "chat-line";
div.innerHTML = `
<div class="user-msg"><span class="user-prefix">❯ ${currentUser} :</span> ${msg}</div>
<div class="msg-timestamp">└ [timestamp ${time}]</div>
`;
chatArea.appendChild(div);
chatArea.scrollTop = chatArea.scrollHeight;
}
function appendAgentMessage(content) {
const div = document.createElement("div");
div.className = "chat-line";
div.innerHTML = `
<div class="agent-msg"><span class="agent-prefix">▸ Cypher :</span></div>
<div class="agent-content">${content}</div>
`;
chatArea.appendChild(div);
chatArea.scrollTop = chatArea.scrollHeight;
}
function appendSystemMessage(msg, isError = false) {
const div = document.createElement("div");
div.className = "chat-line";
const color = isError ? "#FF5555" : "#569CD6";
div.innerHTML = `<div style="color: ${color};">${msg}</div>`;
chatArea.appendChild(div);
chatArea.scrollTop = chatArea.scrollHeight;
}
// Active tools visual feedback (Section 5)
function appendToolTracking(toolName) {
const div = document.createElement("div");
div.className = "chat-line tool-block";
div.id = "active-tool-block";
div.innerHTML = `
<div class="tool-header">┌─ [TOOL] <span class="tool-name">${toolName}</span></div>
<div class="tool-status">│ <span class="spinner">⠋</span>running...</div>
<div class="tool-footer">└─</div>
`;
chatArea.appendChild(div);
chatArea.scrollTop = chatArea.scrollHeight;
// Simple JS Spinner animation
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
let fIdx = 0;
const spinnerInterval = setInterval(() => {
const spinElem = div.querySelector(".spinner");
if (spinElem) {
spinElem.innerText = frames[fIdx];
fIdx = (fIdx + 1) % frames.length;
} else {
clearInterval(spinnerInterval);
}
}, 80);
return {
done: (msg = "done") => {
clearInterval(spinnerInterval);
div.querySelector(".tool-status").innerHTML = `│ ${msg}`;
div.querySelector(".tool-footer").innerHTML = `└─ done`;
div.id = ""; // remove active id
}
};
}
// Keypress handler
cliInput.addEventListener("keydown", async (e) => {
const val = cliInput.value;
// Escape closes popup
if (e.key === "Escape") {
popup.style.display = "none";
return;
}
// Tab / Enter autocomplete navigation
if (popup.style.display === "block") {
if (e.key === "ArrowDown") {
e.preventDefault();
selectedIndex = (selectedIndex + 1) % filteredCommands.length;
renderPopup();
return;
}
if (e.key === "ArrowUp") {
e.preventDefault();
selectedIndex = (selectedIndex - 1 + filteredCommands.length) % filteredCommands.length;
renderPopup();
return;
}
if (e.key === "Tab" || e.key === "Enter") {
e.preventDefault();
cliInput.value = filteredCommands[selectedIndex].name;
popup.style.display = "none";
return;
}
}
// Command history navigation
if (e.key === "ArrowUp" && popup.style.display !== "block") {
e.preventDefault();
if (inputHistory.length > 0) {
if (historyIndex === -1) historyIndex = inputHistory.length;
historyIndex = Math.max(0, historyIndex - 1);
cliInput.value = inputHistory[historyIndex];
}
return;
}
if (e.key === "ArrowDown" && popup.style.display !== "block") {
e.preventDefault();
if (inputHistory.length > 0 && historyIndex !== -1) {
historyIndex = Math.min(inputHistory.length - 1, historyIndex + 1);
cliInput.value = inputHistory[historyIndex];
}
return;
}
// Handle Input Submission
if (e.key === "Enter") {
e.preventDefault();
const text = val.trim();
if (!text) return;
appendUserMessage(text);
inputHistory.push(text);
historyIndex = -1;
cliInput.value = "";
popup.style.display = "none";
// Local execution commands handler
if (text.startsWith("/")) {
await handleLocalSlash(text);
return;
}
// Send to backend
await sendToBackend(text);
}
});
// Trigger autocomplete on typing '/'
cliInput.addEventListener("input", (e) => {
const val = cliInput.value;
if (val.startsWith("/")) {
showPopup(val);
} else {
popup.style.display = "none";
}
});
// Local slash commands logic
async function handleLocalSlash(text) {
const cmd = text.split(" ")[0].toLowerCase();
if (cmd === "/clear") {
chatArea.innerHTML = "";
appendSystemMessage("Terminal vide.");
} else if (cmd === "/help") {
appendSystemMessage(`
📚 COMMANDES DISPONIBLES :
/help - Affiche ce menu d'aide
/clear - Vide le terminal
/reset - Réinitialise le contexte
/model - Affiche ou modifie le modèle actif
/history - Affiche l'historique des requêtes
/status - Affiche le statut complet de la session
/tokens - Affiche l'estimation des tokens
`);
} else if (cmd === "/reset") {
chatHistory = [];
appendSystemMessage("Discussion et contexte réinitialisés.");
} else if (cmd === "/status") {
appendSystemMessage(`
=== STATUT DE L'AGENT ===
Utilisateur : @${currentUser}
Modèle : Qwen/Qwen2.5-Coder-32B-Instruct
Session ID : ${sessionId}
Dossier : [Mode Web distant - Outils système désactivés]
`);
} else if (cmd === "/tokens") {
appendSystemMessage("Statistiques : ~0 tokens utilisés.");
} else {
appendSystemMessage(`Commande ${cmd} non disponible ou simulée en ligne. Utilisez le CLI local pour toutes les fonctionnalités système.`, true);
}
}
// Send chat query to FastAPI backend
async function sendToBackend(message) {
chatHistory.push({ role: "user", content: message });
// Show typing indicator
const typingDiv = document.createElement("div");
typingDiv.className = "chat-line";
typingDiv.id = "typing-indicator";
typingDiv.innerHTML = `<span class="agent-prefix">▸ Cypher ▋</span>`;
chatArea.appendChild(typingDiv);
chatArea.scrollTop = chatArea.scrollHeight;
try {
// We POST to our backend `/api/chat`
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messages: [
{ role: "system", content: "Tu es Cypher Coder, assistant IA en monospace. Pas d'emoji, pas de gras, pas d'italique dans tes réponses." },
...chatHistory
],
username: currentUser
})
});
const data = await res.json();
// Remove typing indicator
const indicator = document.getElementById("typing-indicator");
if (indicator) indicator.remove();
if (data.error) {
appendSystemMessage("Erreur serveur : " + data.error, true);
return;
}
const reply = data.message.content || "[Aucun contenu]";
chatHistory.push({ role: "assistant", content: reply });
// Print response
appendAgentMessage(reply);
// Check if tokens headers or meta are sent
if (data.tokens) {
document.getElementById("header-tokens").innerText = "tokens : " + data.tokens;
}
} catch (e) {
const indicator = document.getElementById("typing-indicator");
if (indicator) indicator.remove();
appendSystemMessage("Erreur réseau : " + e.message, true);
}
}
</script>
</body>
</html>