Spaces:
Running
Running
Download index.html from maletsidibeOS/Anos-AI: direct link, hf CLI and curl.
- Browser
- Download file 7.04 kB
-
https://huggingface.co/spaces/maletsidibeOS/Anos-AI/resolve/main/index.html
- Command line
-
hf download hf://spaces/maletsidibeOS/Anos-AI/index.html
-
curl -L -o index.html https://huggingface.co/spaces/maletsidibeOS/Anos-AI/resolve/main/index.html
7.04 kB
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Anos AI — Fabrique maletsidibeOS</title> | |
| <style> | |
| :root{ | |
| --bg:#0d1117; --panel:#161b22; --border:#30363d; | |
| --text:#e6edf3; --muted:#8b949e; --accent:#7c5cff; --accent2:#00d4aa; | |
| } | |
| *{box-sizing:border-box;margin:0;padding:0} | |
| body{background:var(--bg);color:var(--text);font-family:'Segoe UI',system-ui,sans-serif;height:100vh;display:flex;flex-direction:column} | |
| header{display:flex;align-items:center;gap:12px;padding:14px 20px;border-bottom:1px solid var(--border);background:var(--panel)} | |
| .logo{width:38px;height:38px;border-radius:10px;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px} | |
| h1{font-size:19px} | |
| .sub{font-size:12px;color:var(--muted)} | |
| .settings{display:flex;gap:8px;margin-left:auto;align-items:center;flex-wrap:wrap} | |
| input,select,button{background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:8px;padding:8px 10px;font-size:13px} | |
| input[type=password]{width:230px} | |
| button{cursor:pointer;background:var(--accent);border:none;font-weight:600} | |
| button:disabled{opacity:.5;cursor:wait} | |
| #chat{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:14px} | |
| .msg{max-width:78%;padding:11px 15px;border-radius:12px;line-height:1.55;font-size:14px;white-space:pre-wrap;word-wrap:break-word} | |
| .msg.user{align-self:flex-end;background:var(--accent);border-bottom-right-radius:4px} | |
| .msg.assistant{align-self:flex-start;background:var(--panel);border:1px solid var(--border);border-bottom-left-radius:4px} | |
| .msg.error{align-self:center;background:#3d1d1f;border:1px solid #f85149;color:#ffa198;font-size:13px} | |
| .typing{color:var(--muted);font-style:italic} | |
| #composer{display:flex;gap:10px;padding:14px 20px;border-top:1px solid var(--border);background:var(--panel)} | |
| #prompt{flex:1;resize:none;height:52px;font-family:inherit} | |
| code{background:#0a0e14;border:1px solid var(--border);border-radius:4px;padding:1px 5px;font-family:Consolas,monospace;font-size:13px;color:var(--accent2)} | |
| footer{padding:6px 20px 10px;text-align:center;font-size:11px;color:var(--muted)} | |
| a{color:var(--accent2)} | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="logo">A</div> | |
| <div> | |
| <h1>Anos AI</h1> | |
| <div class="sub">Fabrique maletsidibeOS · assistante dev</div> | |
| </div> | |
| <div class="settings"> | |
| <select id="model" title="Modèle sous le capot"> | |
| <option value="openai/gpt-oss-120b">gpt-oss-120b (dev, recommandé)</option> | |
| <option value="Qwen/Qwen2.5-Coder-32B-Instruct">Qwen2.5-Coder-32B</option> | |
| <option value="meta-llama/Llama-3.1-8B-Instruct">Llama-3.1-8B-Instruct</option> | |
| <option value="Qwen/Qwen2.5-7B-Instruct">Qwen2.5-7B-Instruct</option> | |
| </select> | |
| <input id="token" type="password" placeholder="Token HF (hf_...)"> | |
| </div> | |
| </header> | |
| <div id="chat"> | |
| <div class="msg assistant">Salut 👋 Je suis <b>Anos AI</b>. Pose ta question de dev — je réponds court, avec du code qui marche.</div> | |
| </div> | |
| <div id="composer"> | |
| <textarea id="prompt" placeholder="Ex : une fonction Python qui déduplique une liste de dicts par clé id"></textarea> | |
| <button id="send">Envoyer</button> | |
| </div> | |
| <footer>Modèle open-source via les <a href="https://huggingface.co/docs/inference-providers" target="_blank">Inference Providers</a> HF · ton token reste dans ton navigateur</footer> | |
| <script> | |
| const SYSTEM_PROMPT = | |
| "Tu es Anos AI, l'assistante de développement créée par maletsidibeOS. " + | |
| "Style : réponses courtes, directes, orientées code. " + | |
| "Donne d'abord le code, puis au maximum deux phrases d'explication. " + | |
| "Réponds en français si la question est en français, sinon dans la langue de l'utilisateur."; | |
| const API_URL = "https://router.huggingface.co/v1/chat/completions"; | |
| const chat = document.getElementById("chat"); | |
| const promptEl = document.getElementById("prompt"); | |
| const sendBtn = document.getElementById("send"); | |
| const tokenEl = document.getElementById("token"); | |
| const modelEl = document.getElementById("model"); | |
| let history = []; | |
| let busy = false; | |
| function addBubble(cls, text) { | |
| const div = document.createElement("div"); | |
| div.className = "msg " + cls; | |
| div.textContent = text; | |
| chat.appendChild(div); | |
| chat.scrollTop = chat.scrollHeight; | |
| return div; | |
| } | |
| async function send() { | |
| const text = promptEl.value.trim(); | |
| const token = tokenEl.value.trim(); | |
| if (!text || busy) return; | |
| if (!token) { addBubble("error", "⚠️ Colle d'abord ton token Hugging Face (hf_...) en haut. Crée-le sur huggingface.co/settings/tokens."); return; } | |
| promptEl.value = ""; | |
| addBubble("user", text); | |
| history.push({ role: "user", content: text }); | |
| busy = true; | |
| sendBtn.disabled = true; | |
| const bubble = addBubble("assistant", ""); | |
| const typing = document.createElement("span"); | |
| typing.className = "typing"; | |
| typing.textContent = "Anos AI réfléchit…"; | |
| bubble.appendChild(typing); | |
| try { | |
| const res = await fetch(API_URL, { | |
| method: "POST", | |
| headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| model: modelEl.value, | |
| messages: [{ role: "system", content: SYSTEM_PROMPT }, ...history], | |
| stream: true, | |
| max_tokens: 1500 | |
| }) | |
| }); | |
| if (!res.ok) { | |
| const errText = await res.text(); | |
| throw new Error(res.status === 401 ? "Token invalide (401). Vérifie ton token HF." : "Erreur " + res.status + " : " + errText.slice(0, 200)); | |
| } | |
| typing.remove(); | |
| let full = ""; | |
| const reader = res.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| let buffer = ""; | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| buffer += decoder.decode(value, { stream: true }); | |
| const lines = buffer.split("\n"); | |
| buffer = lines.pop(); | |
| for (const line of lines) { | |
| const trimmed = line.trim(); | |
| if (!trimmed.startsWith("data:")) continue; | |
| const payload = trimmed.slice(5).trim(); | |
| if (payload === "[DONE]") continue; | |
| try { | |
| const delta = JSON.parse(payload).choices?.[0]?.delta?.content; | |
| if (delta) { full += delta; bubble.textContent = full; chat.scrollTop = chat.scrollHeight; } | |
| } catch (e) { /* fragment JSON incomplet, ignoré */ } | |
| } | |
| } | |
| if (!full) full = "(réponse vide)"; | |
| bubble.textContent = full; | |
| history.push({ role: "assistant", content: full }); | |
| } catch (err) { | |
| typing.remove(); | |
| bubble.remove(); | |
| addBubble("error", "⚠️ " + err.message); | |
| } finally { | |
| busy = false; | |
| sendBtn.disabled = false; | |
| promptEl.focus(); | |
| } | |
| } | |
| sendBtn.addEventListener("click", send); | |
| promptEl.addEventListener("keydown", e => { | |
| if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } | |
| }); | |
| promptEl.focus(); | |
| </script> | |
| </body> | |
| </html> |